feat(music): mini player ekle
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.
This commit is contained in:
203
client/src/stores/musicPlayerStore.js
Normal file
203
client/src/stores/musicPlayerStore.js
Normal file
@@ -0,0 +1,203 @@
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user