import { writable, get } from "svelte/store"; import { API, withToken } from "../utils/api.js"; const INITIAL_STATE = { currentTrack: null, currentIndex: -1, queue: [], isPlaying: false, currentTime: 0, duration: 0, volume: 0.8, isMuted: false }; const { subscribe, set, update } = writable({ ...INITIAL_STATE }); let audio = null; let previousVolume = INITIAL_STATE.volume; function ensureAudio() { if (audio || typeof Audio === "undefined") return; audio = new Audio(); audio.preload = "metadata"; audio.volume = INITIAL_STATE.volume; audio.addEventListener("timeupdate", () => { update((state) => ({ ...state, currentTime: audio.currentTime || 0, duration: Number.isFinite(audio.duration) ? audio.duration : state.duration })); }); audio.addEventListener("loadedmetadata", () => { update((state) => ({ ...state, duration: Number.isFinite(audio.duration) ? audio.duration : 0 })); }); audio.addEventListener("play", () => { update((state) => ({ ...state, isPlaying: true })); }); audio.addEventListener("pause", () => { update((state) => ({ ...state, isPlaying: false })); }); audio.addEventListener("ended", () => { playNext(); }); } function buildStreamURL(item) { const base = `${API}/stream/${item.infoHash}?index=${item.fileIndex || 0}`; return withToken(base); } function setQueue(items = []) { update((state) => ({ ...state, queue: Array.isArray(items) ? items : [] })); } function playTrack(item, index, items = null) { if (!item) return; ensureAudio(); if (items) setQueue(items); const state = get({ subscribe }); const nextIndex = Number.isFinite(index) && index >= 0 ? index : state.queue.findIndex((entry) => entry.id === item.id); update((prev) => ({ ...prev, currentTrack: item, currentIndex: nextIndex, currentTime: 0, duration: item.mediaInfo?.format?.duration || 0 })); if (!audio) return; audio.src = buildStreamURL(item); audio .play() .catch(() => update((prev) => ({ ...prev, isPlaying: false }))); } function playByIndex(index) { ensureAudio(); const state = get({ subscribe }); if (!state.queue.length || index < 0 || index >= state.queue.length) return; const item = state.queue[index]; update((prev) => ({ ...prev, currentTrack: item, currentIndex: index, currentTime: 0, duration: item.mediaInfo?.format?.duration || 0 })); if (!audio) return; audio.src = buildStreamURL(item); audio .play() .catch(() => update((prev) => ({ ...prev, isPlaying: false }))); } function togglePlay() { ensureAudio(); const state = get({ subscribe }); if (!audio || !state.currentTrack) return; if (state.isPlaying) { audio.pause(); } else { audio .play() .catch(() => update((prev) => ({ ...prev, isPlaying: false }))); } } function playNext() { const state = get({ subscribe }); if (!state.queue.length) return; const nextIndex = state.currentIndex >= 0 ? (state.currentIndex + 1) % state.queue.length : 0; playByIndex(nextIndex); } function playPrevious() { const state = get({ subscribe }); if (!state.queue.length) return; const prevIndex = state.currentIndex > 0 ? state.currentIndex - 1 : state.queue.length - 1; playByIndex(prevIndex); } function seekToPercent(percent) { ensureAudio(); if (!audio) return; const state = get({ subscribe }); if (!state.duration) return; const nextTime = (Number(percent) / 100) * state.duration; audio.currentTime = nextTime; update((prev) => ({ ...prev, currentTime: nextTime })); } function setVolume(value) { ensureAudio(); const volume = Math.min(Math.max(Number(value) || 0, 0), 1); if (audio) audio.volume = volume; update((prev) => ({ ...prev, volume, isMuted: volume === 0 })); } function toggleMute() { ensureAudio(); const state = get({ subscribe }); if (!audio) return; if (state.isMuted || state.volume === 0) { const nextVolume = previousVolume || 0.8; audio.volume = nextVolume; update((prev) => ({ ...prev, volume: nextVolume, isMuted: false })); } else { previousVolume = state.volume; audio.volume = 0; update((prev) => ({ ...prev, volume: 0, isMuted: true })); } } function stopPlayback() { if (audio) { audio.pause(); audio.src = ""; } set({ ...INITIAL_STATE }); } export const musicPlayer = { subscribe }; export { setQueue, playTrack, togglePlay, playNext, playPrevious, seekToPercent, setVolume, toggleMute, stopPlayback };