Müzik çalar durumunu yönetmek için global store oluştur. Özel bir mini player bileşeni ile çalma listesi ve kontrolleri ekle. Müzik çaların uygulama genelinde kalıcı olmasını sağla.
204 lines
4.6 KiB
JavaScript
204 lines
4.6 KiB
JavaScript
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
|
|
};
|