Ticket #92 (ne pas couper la musique avec les chronos de step) validé GO par QA, passé en statut closed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.1 KiB
issueRef, version, updatedBy, updatedAt
| issueRef | version | updatedBy | updatedAt | ||||
|---|---|---|---|---|---|---|---|
| #92 | 4 |
|
1784704108458 |
Architect — Cadrage technique #92
Problème
Le bip des chronos de step (audio/step_countdown_short.wav, audio/step_completion_long.wav) est joué par AudioplayersExerciseStepAudioCuePlayer dans lib/presentation/exercise_step_audio.dart via audioplayers.
Avec audioplayers 6.8.1, le contexte audio par défaut côté Android utilise AudioContextAndroid.audioFocus = AndroidAudioFocus.gain. Ce focus exprime que l'app devient la source audio principale, ce qui peut mettre Spotify ou une autre app audio en pause au moment des bips.
Décision minimale
Configurer explicitement le player des bips comme un son court de sonification qui se mélange avec l'audio déjà en cours, sans réclamer de focus exclusif.
Contrat à appliquer :
final stepCueAudioContext = AudioContext(
android: const AudioContextAndroid(
contentType: AndroidContentType.sonification,
usageType: AndroidUsageType.assistanceSonification,
audioFocus: AndroidAudioFocus.none,
),
iOS: AudioContextIOS(
category: AVAudioSessionCategory.playback,
options: const {AVAudioSessionOptions.mixWithOthers},
),
);
Raisons :
- Android
AndroidAudioFocus.nonene demande pas de focus audio ; les bips se mixent avec la musique au lieu de la couper.gainTransientMayDuckest acceptable techniquement si l'on veut baisser temporairement la musique, mais ce n'est pas le contrat MVP : l'utilisateur demande de ne pas interrompre la musique, doncnoneest plus direct. - Android
contentType.sonification+usageType.assistanceSonificationdécrit mieux un bip UI/timer que les valeurs par défautmusic/media. - iOS
playback + mixWithOthersconserve l'intention actuelle d'un son audible tout en autorisant le mix avec les autres apps.ambientmixerait aussi, mais changerait davantage le comportement car cette catégorie est silencée par le switch silencieux/verrouillage ; ne pas l'utiliser pour le MVP sans décision produit.
Où appliquer la configuration
Fichier concerné : lib/presentation/exercise_step_audio.dart.
Appliquer le contexte sur le AudioPlayer dédié aux bips, pas via AudioPlayer.global.setAudioContext côté Dart, afin de limiter le changement au player de ExerciseStepAudioCuePlayer sur Android.
Forme recommandée : initialisation lazy et await avant la première lecture, car setAudioContext est async et le constructeur ne peut pas l'attendre.
final class AudioplayersExerciseStepAudioCuePlayer
implements ExerciseStepAudioCuePlayer {
AudioplayersExerciseStepAudioCuePlayer();
final AudioPlayer _player = AudioPlayer();
late final Future<void> _audioContextReady = _player.setAudioContext(
AudioContext(
android: const AudioContextAndroid(
contentType: AndroidContentType.sonification,
usageType: AndroidUsageType.assistanceSonification,
audioFocus: AndroidAudioFocus.none,
),
iOS: AudioContextIOS(
category: AVAudioSessionCategory.playback,
options: const {AVAudioSessionOptions.mixWithOthers},
),
),
);
Future<void> _play(String assetPath) async {
await _audioContextReady;
await _player.stop();
await _player.play(AssetSource(assetPath));
}
}
Note iOS importante : audioplayers_darwin indique que iOS ne permet pas réellement un contexte audio spécifique par player ; player.setAudioContext applique donc la session audio globalement comme AudioPlayer.global.setAudioContext. C'est une contrainte plateforme acceptable ici, mais elle doit être connue : si d'autres players audio sont ajoutés plus tard, ils hériteront potentiellement de cette session globale côté iOS.
Frontière architecture
Cette correction reste en presentation, dans l'adapter concret qui joue les sons UI. Pas de modification domain/application : le domaine ne connaît ni Spotify, ni focus audio, ni audioplayers.
Ne pas créer de port supplémentaire : ExerciseStepAudioCuePlayer existe déjà et suffit. Le contrat métier ne change pas (playShortCountdownBeep, playLongCompletionBeep) ; seule la politique d'interaction audio de l'implémentation concrète change.
Tests
Un test automatisé Flutter standard ne peut pas prouver que Spotify/YouTube Music continue de jouer : le comportement dépend du focus audio natif Android/iOS et d'une app tierce en arrière-plan.
Tests utiles :
- Test manuel Android obligatoire : lancer Spotify en arrière-plan, démarrer une step avec chrono, vérifier que les bips des 3 dernières secondes ne mettent pas la musique en pause et que la musique continue après le bip de fin.
- Test manuel iOS si environnement disponible : même scénario avec Apple Music/Spotify.
- Test automatisé optionnel seulement si peu coûteux : vérifier que l'implémentation appelle
setAudioContextavantplay. Ne pas introduire une abstraction lourde uniquement pour ce test ; la valeur principale est le test manuel sur device.
Lot de travail
F1 — Correction player audio step
- Modifier
lib/presentation/exercise_step_audio.dartpour appliquer leAudioContextci-dessus sur le player dédié. - Garder les assets et les méthodes publiques inchangés.
- Vérifier que les bips fonctionnent toujours dans l'exécution de séance.
- Documenter dans le retour DevFrontend que la validation réelle est manuelle sur device avec une app musicale en arrière-plan.
Aucun lot backend, aucune migration, aucun impact sync.
DevFrontend — Implémentation F1
Réalisé
AudioplayersExerciseStepAudioCuePlayerconfigure maintenant unAudioContextsur sonAudioPlayerdédié avant la première lecture.- Android :
AndroidAudioFocus.none,AndroidContentType.sonification,AndroidUsageType.assistanceSonification. - iOS :
AVAudioSessionCategory.playbackavecAVAudioSessionOptions.mixWithOthers. - Aucun appel à
AudioPlayer.global.setAudioContextajouté ; la recherche du code ne montre pas d'autre usageAudioPlayerdans l'app. - Assets et méthodes publiques inchangés :
playShortCountdownBeep,playLongCompletionBeep,dispose.
Fichier touché
lib/presentation/exercise_step_audio.dart
Vérifications DevFrontend
- API vérifiée dans le package installé
audioplayers 6.8.1/audioplayers_platform_interface 7.2.0. HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/presentation/exercise_step_audio.dart: OK, aucune issue.HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/workout_execution_screen_test.dart: non exécutable dans ce sandbox, le wrapper Flutter tente d'écrire dans/opt/flutter/bin/cacheen lecture seule.
Écarts / limites
- Le comportement "ne coupe pas Spotify/Apple Music" ne peut pas être prouvé par test widget standard ; validation manuelle requise sur device Android, et iOS si disponible.
- Côté iOS,
audioplayers_darwinappliqueplayer.setAudioContextà la session audio globale par contrainte plateforme, comme cadré par Architect. - L'outil
idea_ticket_update_carnetn'était pas exposé dans cette session ; résumé ajouté directement au fichier carnet du ticket.