tunzo-player
Version:
A music playback service for Angular and Ionic apps with native audio control support.
189 lines (152 loc) • 4.42 kB
text/typescript
import { BehaviorSubject } from 'rxjs';
export class Player {
private static audio = new Audio();
private static currentSong: any = null;
private static currentIndex = 0;
private static isPlaying = false;
private static currentTime = 0;
private static duration = 0;
private static isShuffle = true;
private static queue: any[] = [];
static queue$ = new BehaviorSubject<any[]>([]);
private static playlist: any[] = [];
private static selectedQuality = 3;
/** Initialize with playlist and quality */
static initialize(playlist: any[], quality = 3) {
this.playlist = playlist;
this.selectedQuality = quality;
}
/** Call this once on user gesture to unlock audio in WebView */
static unlockAudio() {
this.audio.src = '';
this.audio.load();
this.audio.play().catch(() => { });
}
static play(song: any, index: number = 0) {
if (!song || !song.downloadUrl) return;
this.currentSong = song;
this.currentIndex = index;
let url = song.downloadUrl[this.selectedQuality]?.url || '';
// 🚀 Auto-convert http → https
if (url.startsWith('http://')) {
url = url.replace('http://', 'https://');
}
this.audio.src = url;
this.audio.load(); // Ensure audio is loaded before play
this.audio.play().then(() => {
this.isPlaying = true;
}).catch((err) => {
this.isPlaying = false;
console.warn('Audio play failed:', err);
});
// Set duration
this.audio.onloadedmetadata = () => {
this.duration = this.audio.duration;
};
// Set current time
this.audio.ontimeupdate = () => {
this.currentTime = this.audio.currentTime;
};
// Auto-play next song
this.audio.onended = () => {
this.autoNext();
};
// Catch errors
this.audio.onerror = (e) => {
console.error('Audio error:', this.audio.error, e);
};
}
static pause() {
this.audio.pause();
this.isPlaying = false;
}
static resume() {
this.audio.play();
this.isPlaying = true;
}
static togglePlayPause() {
if (this.isPlaying) {
this.pause();
} else {
this.resume();
}
}
static next() {
if (this.queue.length > 0) {
const nextQueued = this.queue.shift();
this.queue$.next([...this.queue]);
const index = this.playlist.findIndex(s => s.id === nextQueued.id);
this.play(nextQueued, index);
} else if (this.isShuffle) {
this.playRandom();
} else if (this.currentIndex < this.playlist.length - 1) {
this.play(this.playlist[this.currentIndex + 1], this.currentIndex + 1);
}
}
static prev() {
if (this.currentIndex > 0) {
this.play(this.playlist[this.currentIndex - 1], this.currentIndex - 1);
}
}
static seek(seconds: number) {
this.audio.currentTime = seconds;
}
static autoNext() {
this.next();
}
static playRandom() {
if (this.playlist.length <= 1) return;
let randomIndex;
do {
randomIndex = Math.floor(Math.random() * this.playlist.length);
} while (randomIndex === this.currentIndex);
this.play(this.playlist[randomIndex], randomIndex);
}
static toggleShuffle() {
this.isShuffle = !this.isShuffle;
}
static getShuffleStatus(): boolean {
return this.isShuffle;
}
static addToQueue(song: any) {
if (!this.queue.some(q => q.id === song.id)) {
this.queue.push(song);
this.queue$.next([...this.queue]);
}
}
static removeFromQueue(index: number) {
this.queue.splice(index, 1);
this.queue$.next([...this.queue]);
}
static reorderQueue(from: number, to: number) {
const item = this.queue.splice(from, 1)[0];
this.queue.splice(to, 0, item);
this.queue$.next([...this.queue]);
}
static getCurrentTime(): number {
return this.currentTime;
}
static getDuration(): number {
return this.duration;
}
static formatTime(time: number): string {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
}
static isPlayingSong(): boolean {
return this.isPlaying;
}
static getCurrentSong(): any {
return this.currentSong;
}
static setQuality(index: number) {
this.selectedQuality = index;
}
static getQueue(): any[] {
return this.queue;
}
static getPlaylist(): any[] {
return this.playlist;
}
}