Files
librodrome/app/composables/useAudioPlayer.ts
T
YvvandClaude Opus 5.5 201cfbbb50 design : accueil console seul, vrai §, curseurs animés, statuts, lecteur
- accueil : la console devient l'accueil unique (variantes B/C, sélecteur
  et ancien accueil retirés) ; curseurs qui montent de l'asservissement et
  dérivent seuls, un axe mis en avant à tour de rôle, arrêt au clic,
  immobiles en prefers-reduced-motion ; « AUTONOMIE » devant chaque axe,
  leviers mis en valeur, ligne des trois autonomies en couleurs
- logo : § de Playfair Display (SIL OFL) vectorisé, composant partagé
  UiSectionSign (en-tête, page événement) + favicon.svg
- UiStatusBadge (production / développement / gestation) sur l'accueil,
  les pages d'axe et les pages projet
- liens libreDecision / SejeteralO fixés sur la prod (plus de repli localhost)
- logiciel libre : bouton v1 dans la carte projet en tête de page
- couverture du livre : recadrage corrigé (BookSection)
- texte courant en Public Sans (zéros non barrés)
- lecteur : la pastille persistante ne masque plus la navigation du
  BookPlayer ; lecture directe du MP3 (les .ogg n'existent pas : 404)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 05:59:33 +02:00

154 lines
3.5 KiB
TypeScript

import type { Song } from '~/types/song'
let audio: HTMLAudioElement | null = null
let animationFrameId: number | null = null
// Track which milestones have been fired for the current song
const firedMilestones = new Set<number>()
export function useAudioPlayer() {
const store = usePlayerStore()
const { trackAudioPlay, trackAudioComplete, trackAudioProgress } = useTracking()
function getAudio(): HTMLAudioElement {
if (!audio) {
audio = new Audio()
audio.preload = 'metadata'
audio.volume = store.volume
audio.addEventListener('loadedmetadata', () => {
store.setDuration(audio!.duration)
})
audio.addEventListener('ended', () => {
if (store.currentSong) {
trackAudioComplete(store.currentSong.id, store.currentSong.title)
}
firedMilestones.clear()
const next = store.nextSong()
if (next) {
loadAndPlay(next)
}
})
audio.addEventListener('error', (e) => {
console.error('Audio error:', e)
store.pause()
})
}
return audio
}
function startTimeUpdate() {
if (animationFrameId) return
const update = () => {
if (audio && !audio.paused) {
store.setCurrentTime(audio.currentTime)
// Track progress milestones (25 / 50 / 75 %)
if (audio.duration > 0 && store.currentSong) {
const pct = (audio.currentTime / audio.duration) * 100
for (const milestone of [25, 50, 75] as const) {
if (pct >= milestone && !firedMilestones.has(milestone)) {
firedMilestones.add(milestone)
trackAudioProgress(store.currentSong.id, milestone)
}
}
}
}
animationFrameId = requestAnimationFrame(update)
}
animationFrameId = requestAnimationFrame(update)
}
function stopTimeUpdate() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
}
async function loadAndPlay(song: Song) {
const el = getAudio()
store.setSong(song)
firedMilestones.clear()
// Only MP3 files are published (no .ogg on disk nor in production):
// asking for OGG first cost a 404 and a delay on every track.
el.src = song.file
el.volume = store.volume
try {
await el.play()
store.play()
startTimeUpdate()
trackAudioPlay(song.id, song.title)
}
catch (err) {
console.error('Playback failed:', err)
}
}
function pause() {
getAudio().pause()
store.pause()
stopTimeUpdate()
}
function resume() {
const el = getAudio()
if (el.src) {
el.play()
store.play()
startTimeUpdate()
}
}
function togglePlayPause() {
if (store.isPlaying) {
pause()
}
else {
resume()
}
}
function seek(time: number) {
const el = getAudio()
el.currentTime = time
store.setCurrentTime(time)
}
function setVolume(vol: number) {
store.setVolume(vol)
getAudio().volume = store.volume
}
function playNext() {
const song = store.nextSong()
if (song) loadAndPlay(song)
}
function playPrev() {
const song = store.prevSong()
if (song) {
loadAndPlay(song)
}
}
// Watch volume changes from store
watch(() => store.volume, (vol) => {
if (audio) audio.volume = vol
})
return {
loadAndPlay,
pause,
resume,
togglePlayPause,
seek,
setVolume,
playNext,
playPrev,
getAudio,
}
}