initial state: muzick music player + recommendation engine

This commit is contained in:
kami
2026-07-14 01:35:52 +04:00
commit 737bf19fd1
196 changed files with 32431 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
import { create } from 'zustand';
import type { Track } from '../types';
export type RepeatMode = 'none' | 'all' | 'one';
interface PlaybackState {
currentTrack: Track | null;
queue: Track[];
isPlaying: boolean;
position: number;
duration: number;
volume: number;
shuffle: boolean;
repeat: RepeatMode;
setQueue: (queue: Track[]) => void;
playTrack: (track: Track) => void;
play: () => void;
pause: () => void;
next: () => void;
prev: () => void;
setPosition: (position: number) => void;
setDuration: (duration: number) => void;
setVolume: (volume: number) => void;
setCurrentTrack: (track: Track | null) => void;
toggleShuffle: () => void;
cycleRepeat: () => void;
}
export const usePlaybackStore = create<PlaybackState>((set, get) => ({
currentTrack: null,
queue: [],
isPlaying: false,
position: 0,
duration: 0,
volume: 1,
shuffle: false,
repeat: 'none',
setQueue: (queue) => set({ queue }),
playTrack: (track) =>
set({
currentTrack: track,
isPlaying: true,
position: 0,
duration: track.duration ?? 0,
}),
play: () => set({ isPlaying: true }),
pause: () => set({ isPlaying: false }),
next: () => {
const { queue, currentTrack, shuffle, repeat } = get();
if (queue.length === 0) return;
const idx = currentTrack ? queue.findIndex((t) => t.id === currentTrack.id) : -1;
// Repeat one: replay current track
if (repeat === 'one' && currentTrack) {
set({ position: 0, isPlaying: true });
return;
}
if (shuffle) {
// Shuffle: pick a random track from the remaining queue (excluding current)
const remaining = queue.filter((t) => t.id !== currentTrack?.id);
if (remaining.length === 0) {
if (repeat === 'all') {
const pick = queue[Math.floor(Math.random() * queue.length)];
set({ currentTrack: pick, position: 0, duration: pick.duration ?? 0, isPlaying: true });
}
return;
}
const pick = remaining[Math.floor(Math.random() * remaining.length)];
set({ currentTrack: pick, position: 0, duration: pick.duration ?? 0, isPlaying: true });
return;
}
// Sequential
const nextTrack = idx >= 0 ? queue[idx + 1] : null;
if (nextTrack) {
// Trim played tracks from queue to prevent unbounded growth (Vibe prefetch leak)
const trimmed = queue.slice(idx + 1);
set({ queue: trimmed, currentTrack: nextTrack, position: 0, duration: nextTrack.duration ?? 0, isPlaying: true });
} else if (repeat === 'all') {
const first = queue[0];
if (first) {
set({ currentTrack: first, position: 0, duration: first.duration ?? 0, isPlaying: true });
}
}
},
prev: () => {
const { queue, currentTrack } = get();
if (queue.length === 0) return;
const idx = currentTrack ? queue.findIndex((t) => t.id === currentTrack.id) : -1;
const prevTrack = idx > 0 ? queue[idx - 1] : null;
if (prevTrack) {
set({ currentTrack: prevTrack, position: 0, duration: prevTrack.duration ?? 0, isPlaying: true });
}
},
setPosition: (position) => set({ position }),
setDuration: (duration) => set({ duration }),
setVolume: (volume) => set({ volume }),
setCurrentTrack: (currentTrack) => set({ currentTrack }),
toggleShuffle: () => set((state) => ({ shuffle: !state.shuffle })),
cycleRepeat: () =>
set((state) => {
const modes: RepeatMode[] = ['none', 'all', 'one'];
const next = modes[(modes.indexOf(state.repeat) + 1) % modes.length];
return { repeat: next };
}),
}));