WIP: implement audio base front+back #29
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "audio"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
14dd736d43to6e66a5dade6e66a5dadeto2bcdb2d1312bcdb2d131toa24607ab0e8e301523c9to8de41c2bfcCommentaire de Claude mdr ->
Le conflit n'est pas rattrapable côté front
apps/web/components/ChapterAudioPlayer.tsxn'existe plus surmain: il a été supprimé para670abd(refonte lecture 2026-07), arrivé surmainviae1535bf(PR #30) — ete1535bfestjustement la base de cette PR. La résolution du conflit ne peut donc être que « la suppression
gagne ». Ce qui l'a remplacé :
components/AudioPlayerProvider.tsx— un seul élément<audio>caché pour toute l'app, montéune fois dans
app/layout.tsx, derrière un contexte React. La lecture survit à la navigation.components/AudioPlayerBar.tsx— la barre de transport fixe du handoff 2026-07 : −15 s /play-pause / +15 s, titre +
m:ss / m:ss,role="slider"utilisable au clavier comme à lasouris, vitesse cyclique (1× → 1,25× → 1,5× → 0,75×), bascule « Lecture continue »
(
aria-pressed), fermeture.localStorage,aec-audio:v1), mode audiobook(enchaînement sur
nextHrefen fin de piste), Espace = play/pause avec garde sur les champs desaisie, et le CTA jaune
ListenButton.components/AudioPlayer.test.tsx— 9 tests, dont unaxesans violation en état ouvert etfermé.
Du coup, remplacer le lecteur ne serait pas un changement d'un fichier : six appelants dépendent
de cette API (
app/layout.tsx,app/page.tsx,app/programme/[slug]/page.tsx,app/programme/[slug]/[section]/page.tsx,components/RandomMeasureCard.tsx,components/ReadingComfortBar.tsx).Trois points bloquants indépendamment du conflit
autoPlay— c'est un manquement WCAG 1.4.2 / RGAA : la lecture ne doit démarrer que suraction explicite. Un test existant l'affirme noir sur blanc (« stays closed until a listen
button is pressed — never autoplays on mount ») et vérifie que
play()n'est pas appelé.aria-labelmis en commentaire — ça retire le seul nom accessible du lecteur.src="http://localhost:3001/audio/track.mp3"en dur — ça court-circuitegetAudioUrl(slug)(apps/web/lib/audio.ts), qui existe précisément pour ça :docs/plans/audio-narration-rgaa/research.md§5 point 3 dit « don't hardcodepublic/audio/...paths through the app … so swapping local files for a CDN base URL later isa one-line change, not a refactor ». Effet de bord : un seul
track.mp3pour tous leschapitres, et
slug/titledeviennent inutilisés.Trois remarques plus mineures :
apps/backend/public/track.mp3fait 4,8 Mo commités — le dépôtest déjà à ~154 Mo et il n'y a pas de LFS, donc l'audio réel (18 chapitres × ~5 Mo ≈ 80 Mo) ne peut
pas vivre dans l'historique git ; l'import
getAudioUrldevenu inutilisé traînerait au lint ; et leport 3001 est déjà pris — c'est celui sur lequel le
docker composed'open-dataec publie sonAPI en local (vérifié sur ma machine :
0.0.0.0:3001->3000). On a pris 8081 pour cette raison.Ce qu'on garde de ta PR — et où ça a atterri
L'idée de fond est la bonne et on la reprend :
public/. Repris, maisbranché sur le point de config existant :
AUDIO_CDN_BASE_URL+getAudioUrl(slug), sans URLen dur dans un composant. On a aussi profité de l'occasion pour corriger une doc devenue
fausse — cette variable était documentée comme build arg au motif que les pages étaient
prégénérées, alors qu'elles sont toutes en
force-dynamicdepuis l'intégration open-dataec :elle redevient une variable d'environnement de runtime, modifiable sans rebuild.
mise.toml, ni.nvmrc, niengines, ni CI). On prend, en un seul pin à la racine, et onmonte l'image Docker à
node:24-alpinepour que le pin et le conteneur soient d'accord — lesdeux
mise.tomlpar app à Node 24 face à unDockerfileennode:22-alpine, c'était lescénario « ça marche chez moi ».
Deux écarts d'implémentation, pour info :
apps/backend/. Ce dossier est un placeholder assumé, sanspackage.json, justement pour que npm workspaces ne le prenne pas comme package (voirapps/backend/README.mdetCLAUDE.md) ; lui en donner un changerait ce sur quoinpm run dev|build|test|typecheckse propage. Et comme la cible de prod est du stockage objet(R2/S3 + CDN, §5.3), un serveur maison serait un service à maintenir et dockeriser pour zéro
gain en prod. On passe donc par un service
nginx:alpinedansdocker-compose.ymlqui monte undossier local en lecture seule : mêmes capacités en dev (dont les requêtes
Range, nécessairespour le seek de la barre), zéro code à notre charge, et l'audio reste hors de git et hors de
l'image.
Access-Control-Allow-Origin: *n'est pas nécessaire ici : un<audio src>charge en cross-origin sans CORS tant qu'on ne met pascrossOrigin, ce que leprovider ne fait pas. Ça le redeviendra si on ajoute un jour une analyse Web Audio API
(forme d'onde).
Enfin, ta PR a eu un effet secondaire utile : elle nous a fait regarder de près
lib/audioAvailability.ts, qui déclarait en dur deux chapitres comme ayant une narration alorsque les seuls fichiers présents s'appelaient
chapitre-1/2.mp3— donc tous les boutons« Écouter le chapitre » renvoyaient un 404, avec un test vert par-dessus. Comme il n'existe
aucun fichier de narration réel pour l'instant, la disponibilité devient déclarative et vide par
défaut : pas de fichier, pas de bouton (règle d'honnêteté du contenu de
design.md).Le détail est dans
docs/plans/pr29-audio-backend/sur la branchepr29-audio-backend. Vu l'étatdu conflit, le plus simple est probablement de fermer cette PR une fois la branche partie en
review — mais dis-nous si tu préfères la garder ouverte pour suivre le sujet.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.