// ~/store/player.ts import { defineStore } from 'pinia' import type { Track } from '~/../types/types' import { useDataStore } from '~/store/data' export const usePlayerStore = defineStore('player', { state: () => ({ currentTrack: null as Track | null, position: 0, audio: null as HTMLAudioElement | null, isPaused: true, progressionLast: 0 }), actions: { attachAudio(el: HTMLAudioElement) { this.audio = el // attach listeners if not already attached (idempotent enough for our use) this.audio.addEventListener('play', () => { this.isPaused = false }) this.audio.addEventListener('playing', () => { this.isPaused = false }) this.audio.addEventListener('pause', () => { this.isPaused = true }) this.audio.addEventListener('ended', () => { this.isPaused = true const track = this.currentTrack if (!track) return const dataStore = useDataStore() if (track.compilationId.length === 6) { const next = dataStore.getNextPlaylistTrack(track) if (next && next.compilationId === track.compilationId) { this.playTrack(next) } } else { console.log('ended') } }) }, async playCompilation(compilationId: string) { if (this.currentTrack?.compilationId === compilationId) { this.togglePlay() } else { const dataStore = useDataStore() const first = (compilationId.length === 6) ? dataStore.getFirstTrackOfPlaylist(compilationId) : dataStore.getFirstTrackOfCompilation(compilationId) if (first) { await this.playTrack(first) } } }, async playTrack(track: Track) { // mettre à jour la piste courante uniquement après avoir géré le toggle if (this.currentTrack?.id === track.id) { this.togglePlay() } else { this.currentTrack = track if (!this.audio) { // fallback: create an audio element and attach listeners this.attachAudio(new Audio()) } // Interrompre toute lecture en cours avant de charger une nouvelle source const audio = this.audio as HTMLAudioElement try { audio.pause() } catch (_) {} // on entre en phase de chargement this.isPaused = true audio.src = track.url audio.load() // lancer la lecture (seek si nécessaire une fois les metadata chargées) try { const wantedStart = track.start ?? 0 // Attendre que les metadata soient prêtes pour pouvoir positionner currentTime await new Promise((resolve) => { if (audio.readyState >= 1) return resolve() const onLoaded = () => resolve() audio.addEventListener('loadedmetadata', onLoaded, { once: true }) }) // Appliquer le temps de départ audio.currentTime = wantedStart > 0 ? wantedStart : 0 this.isPaused = false await audio.play() } catch (err: any) { // Ignorer les AbortError (arrivent lorsqu'une nouvelle source est chargée rapidement) if (err && err.name === 'AbortError') return this.isPaused = true console.error('Impossible de lire la piste :', err) } } }, togglePlay() { if (!this.audio) return if (this.audio.paused) { this.isPaused = false this.audio .play() .then(() => { this.isPaused = false }) .catch((err) => console.error(err)) } else { this.audio.pause() this.isPaused = true } }, updateTime() { const audio = this.audio if (!audio) return // update current position this.position = audio.currentTime // compute and cache a stable progression value const duration = audio.duration const progression = (this.position / duration) * 100 if (!isNaN(progression)) { this.progressionLast = progression } // auto advance behavior: playlists use 'ended', compilations use time boundary const track = this.currentTrack if (!track) return const dataStore = useDataStore() const t = audio.currentTime // For playlists (id length 6), do NOT forward auto-advance here; rely on 'ended' if (track.compilationId.length === 6) { const from = track.start ?? 0 const prevTrack = dataStore.getPrevTrack(track) if (t < from + 0.05) { if (prevTrack && prevTrack.compilationId === track.compilationId) { this.currentTrack = prevTrack } } return } // For compilations, forward auto-advance at segment boundary const nextTrack = dataStore.getNextTrack(track) const to = (track.order === 0) ? Math.round(this.audio?.duration ?? 0) : nextTrack?.start if (!to || isNaN(to)) return if (t >= to - 0.05) { if (nextTrack && nextTrack.compilationId === track.compilationId) { this.currentTrack = nextTrack } } else { const from = track.start ?? 0 const prevTrack = dataStore.getPrevTrack(track) if (t < from + 0.05) { if (prevTrack && prevTrack.compilationId === track.compilationId) { this.currentTrack = prevTrack } } } } }, getters: { isCurrentCompilation: (state) => { return (compilationId: string) => compilationId === state.currentTrack?.compilationId }, isPlaylistTrack: () => { return (track: Track) => { return track.compilationId.length === 6 } }, getCurrentTrack: (state) => state.currentTrack, getCurrentCompilation: (state) => { return state.currentTrack ? state.currentTrack.url : null }, currentProgression(state) { if (!state.audio) return 0 const duration = state.audio.duration const progression = (state.position / duration) * 100 return isNaN(progression) ? state.progressionLast : progression } } })