veza/apps/web/src/features/player/store/playerStore.ts

251 lines
6.7 KiB
TypeScript
Raw Normal View History

/**
* Store Zustand pour gérer l'état du player
* FE-TYPE-011: Fully typed store
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { Track, PlayerState, PlayerControls } from '../types';
// FE-TYPE-011: Fully typed store interface
export interface PlayerStore extends PlayerState, PlayerControls {
setCurrentTime: (time: number) => void;
setDuration: (duration: number) => void;
removeFromQueue: (index: number) => void;
reorderQueue: (from: number, to: number) => void;
}
export const usePlayerStore = create<PlayerStore>()(
persist(
(set, get) => ({
// État initial
currentTrack: null,
isPlaying: false,
currentTime: 0,
duration: 0,
volume: 100,
muted: false,
queue: [],
currentIndex: -1,
repeat: 'off',
shuffle: false,
// Actions de contrôle
play: (track?: Track) => {
if (!track) {
// Si pas de track fourni, reprendre la lecture
set({ isPlaying: true });
return;
}
const { queue } = get();
const trackIndex = queue.findIndex((t) => t.id === track.id);
if (trackIndex >= 0) {
// Track déjà dans la queue
set({
currentTrack: track,
currentIndex: trackIndex,
isPlaying: true,
currentTime: 0,
});
} else {
// Nouvelle track, l'ajouter à la queue
const newQueue = [...queue, track];
set({
currentTrack: track,
currentIndex: newQueue.length - 1,
queue: newQueue,
isPlaying: true,
currentTime: 0,
});
}
},
pause: () => {
set({ isPlaying: false });
},
resume: () => {
set({ isPlaying: true });
},
stop: () => {
set({ isPlaying: false, currentTime: 0 });
},
next: () => {
const { queue, currentIndex, repeat, shuffle } = get();
if (queue.length === 0) return;
let nextIndex = currentIndex;
if (shuffle) {
// Mode shuffle: choisir une track aléatoire
nextIndex = Math.floor(Math.random() * queue.length);
} else if (currentIndex < queue.length - 1) {
// Track suivante dans la queue
nextIndex = currentIndex + 1;
} else if (repeat === 'playlist') {
// Répéter la playlist: revenir au début
nextIndex = 0;
} else {
// Pas de track suivante
return;
}
if (nextIndex !== currentIndex && nextIndex < queue.length) {
set({
currentIndex: nextIndex,
currentTrack: queue[nextIndex],
currentTime: 0,
isPlaying: true,
});
}
},
previous: () => {
const { queue, currentIndex } = get();
if (queue.length === 0 || currentIndex <= 0) return;
const prevIndex = currentIndex - 1;
set({
currentIndex: prevIndex,
currentTrack: queue[prevIndex],
currentTime: 0,
isPlaying: true,
});
},
seek: (time: number) => {
const { duration } = get();
const clampedTime = Math.max(0, Math.min(time, duration || 0));
set({ currentTime: clampedTime });
},
setCurrentTime: (time: number) => {
const { duration } = get();
const clampedTime = Math.max(0, Math.min(time, duration || 0));
set({ currentTime: clampedTime });
},
setDuration: (duration: number) => {
set({ duration: Math.max(0, duration) });
},
setVolume: (volume: number) => {
const clampedVolume = Math.max(0, Math.min(100, volume));
set({ volume: clampedVolume });
},
toggleMute: () => {
set({ muted: !get().muted });
},
toggleShuffle: () => {
set({ shuffle: !get().shuffle });
},
setRepeat: (mode: 'off' | 'track' | 'playlist') => {
set({ repeat: mode });
},
// Queue management
addToQueue: (tracks: Track[]) => {
const { queue } = get();
const newQueue = [...queue, ...tracks];
set({ queue: newQueue });
},
removeFromQueue: (index: number) => {
const { queue, currentIndex } = get();
if (index < 0 || index >= queue.length) return;
const newQueue = queue.filter((_, i) => i !== index);
// Ajuster l'index courant si nécessaire
let newIndex = currentIndex;
if (index < currentIndex) {
newIndex = currentIndex - 1;
} else if (index === currentIndex) {
// Si on supprime la track courante, passer à la suivante ou arrêter
if (newQueue.length > 0) {
newIndex = Math.min(newIndex, newQueue.length - 1);
set({
queue: newQueue,
currentIndex: newIndex,
currentTrack: newQueue[newIndex] || null,
isPlaying: newQueue[newIndex] ? get().isPlaying : false,
});
return;
} else {
newIndex = -1;
set({
queue: newQueue,
currentIndex: newIndex,
currentTrack: null,
isPlaying: false,
});
return;
}
}
set({ queue: newQueue, currentIndex: newIndex });
},
reorderQueue: (from: number, to: number) => {
const { queue, currentIndex } = get();
if (
from < 0 ||
from >= queue.length ||
to < 0 ||
to >= queue.length ||
from === to
) {
return;
}
const newQueue = [...queue];
const [removed] = newQueue.splice(from, 1);
newQueue.splice(to, 0, removed);
// Ajuster l'index courant
let newIndex = currentIndex;
if (from === currentIndex) {
newIndex = to;
} else if (from < currentIndex && to >= currentIndex) {
newIndex = currentIndex - 1;
} else if (from > currentIndex && to <= currentIndex) {
newIndex = currentIndex + 1;
}
set({ queue: newQueue, currentIndex: newIndex });
},
clearQueue: () => {
set({
queue: [],
currentIndex: -1,
currentTrack: null,
isPlaying: false,
currentTime: 0,
});
},
}),
{
name: 'player-storage',
partialize: (state) => ({
volume: state.volume,
muted: state.muted,
repeat: state.repeat,
shuffle: state.shuffle,
queue: state.queue,
currentIndex: state.currentIndex,
currentTrack: state.currentTrack,
}),
2025-12-13 02:34:34 +00:00
},
),
);