# language: fr @api @premium @multi-device @mvp Fonctionnalité: Détection et gestion des conflits de streaming multi-device Premium En tant qu'abonné Premium Je peux écouter sur plusieurs appareils (iPhone, iPad, Android, Web) Mais un seul stream audio peut être actif à la fois Afin de respecter les conditions d'abonnement Premium individuel Contexte: Étant donné un utilisateur avec abonnement Premium actif Et plusieurs appareils enregistrés sur le compte : | device_id | platform | nom | last_seen | | iphone-123 | iOS | iPhone de Jean | 2026-02-03 14:00:00 | | ipad-456 | iOS | iPad Pro | 2026-02-03 12:30:00 | | android-789 | Android | Samsung Galaxy | 2026-02-02 18:00:00 | | web-abc | Web | Chrome MacBook | 2026-02-03 10:00:00 | # ============================================================================ # DÉTECTION STREAM ACTIF ET CONFLIT # ============================================================================ Scénario: Premier stream - aucun conflit Étant donné aucun stream n'est actuellement actif sur le compte Quand l'utilisateur démarre la lecture sur "iPhone de Jean" Alors le stream doit démarrer normalement Et une session active doit être enregistrée dans Redis : | user_id | user-123 | | device_id | iphone-123 | | started_at | 2026-02-03 14:00:00| | content_id | content-xyz | | stream_token | token-abc123 | Et le TTL Redis doit être de 2 heures Scénario: Tentative de stream simultané sur second appareil Étant donné un stream actif sur "iPhone de Jean" depuis 10 minutes Quand l'utilisateur tente de démarrer la lecture sur "iPad Pro" Alors une réponse HTTP 409 Conflict doit être retournée Et le message doit indiquer : """ Un contenu est déjà en cours de lecture sur "iPhone de Jean". Voulez-vous arrêter la lecture sur cet appareil et continuer ici ? """ Et les options proposées doivent être : | option | action | | Continuer ici | kill_previous_session | | Annuler | cancel_new_session | Scénario: Utilisateur choisit "Continuer ici" - kill de l'ancienne session Étant donné un stream actif sur "iPhone de Jean" Et l'utilisateur tente de lire sur "iPad Pro" Et le conflit est détecté Quand l'utilisateur choisit "Continuer ici" Alors l'ancienne session sur "iPhone de Jean" doit être terminée immédiatement Et un message WebSocket doit être envoyé à "iPhone de Jean" : """ {"type": "stream_stopped", "reason": "playback_started_on_other_device", "device": "iPad Pro"} """ Et la lecture sur "iPhone de Jean" doit s'arrêter avec notification : """ Lecture arrêtée : un autre appareil utilise votre compte Premium. """ Et le nouveau stream sur "iPad Pro" doit démarrer normalement Scénario: Utilisateur choisit "Annuler" - maintien de la session actuelle Étant donné un stream actif sur "iPhone de Jean" Et l'utilisateur tente de lire sur "iPad Pro" Et le conflit est détecté Quand l'utilisateur choisit "Annuler" Alors la session sur "iPhone de Jean" doit continuer normalement Et aucun stream ne doit démarrer sur "iPad Pro" Et l'utilisateur doit être redirigé vers l'écran d'accueil sur "iPad Pro" # ============================================================================ # GESTION AUTOMATIQUE DES SESSIONS EXPIRÉES # ============================================================================ Scénario: Session expirée automatiquement après pause prolongée Étant donné un stream actif sur "iPhone de Jean" Et l'utilisateur met en pause à 14:00:00 Et le TTL Redis est configuré pour expirer après 30 minutes de pause Quand il est 14:35:00 (>30 min de pause) Alors la session Redis doit expirer automatiquement Et l'utilisateur peut démarrer un nouveau stream sur n'importe quel appareil Et aucun conflit ne doit être détecté Scénario: Heartbeat maintient la session active pendant la lecture Étant donné un stream actif sur "iPhone de Jean" Quand l'application envoie un heartbeat toutes les 30 secondes Alors le TTL Redis doit être renouvelé à 2 heures à chaque heartbeat Et la session doit rester active tant que les heartbeats continuent Et si 3 heartbeats consécutifs échouent, la session doit expirer Scénario: Fermeture propre de l'application libère la session Étant donné un stream actif sur "iPhone de Jean" Quand l'utilisateur ferme proprement l'application (swipe kill ou logout) Alors une requête "end_session" doit être envoyée à l'API Et la session Redis doit être immédiatement supprimée Et l'utilisateur peut démarrer un stream sur un autre appareil sans délai # ============================================================================ # CAS LIMITES ET EDGE CASES # ============================================================================ Scénario: Crash application sans fermeture propre Étant donné un stream actif sur "iPhone de Jean" Quand l'application crash sans envoyer "end_session" Alors la session Redis reste active avec son TTL Et après 2 minutes sans heartbeat, la session doit être marquée "stale" Et un nouveau stream peut être démarré après 2 minutes sans heartbeat Ou l'utilisateur peut forcer le kill via le conflit modal Scénario: Connexion réseau perdue pendant stream Étant donné un stream actif sur "iPhone de Jean" Quand la connexion réseau est perdue pendant 5 minutes Alors les heartbeats échouent mais l'app continue en buffer Et après 3 heartbeats manqués (90 secondes), la session est considérée "stale" Et un autre appareil peut démarrer un stream après 90 secondes Scénario: Deux appareils tentent de démarrer simultanément (race condition) Étant donné aucun stream actif Quand "iPhone de Jean" et "iPad Pro" tentent de démarrer un stream à la même milliseconde Alors le premier à obtenir le lock Redis doit réussir Et le second doit recevoir un conflit 409 Et un mécanisme de lock distribué (Redis SET NX) doit être utilisé Scénario: Utilisateur bascule rapidement entre appareils (<10s) Étant donné un stream sur "iPhone de Jean" Quand l'utilisateur kill la session et démarre sur "iPad Pro" Et tente de redémarrer sur "iPhone de Jean" 5 secondes après Alors le système doit détecter le conflit avec "iPad Pro" Et proposer à nouveau de kill la session iPad Et un délai de grâce de 5 secondes doit être respecté pour éviter les boucles # ============================================================================ # GESTION UTILISATEUR GRATUIT (pas de multi-device) # ============================================================================ Scénario: Utilisateur gratuit tente de streamer sur 2 appareils Étant donné un utilisateur avec abonnement gratuit (pas Premium) Et un stream actif sur "iPhone de Jean" Quand l'utilisateur tente de lire sur "iPad Pro" Alors une réponse HTTP 403 Forbidden doit être retournée Et le message doit indiquer : """ Le multi-device nécessite un abonnement Premium. Actuellement en lecture sur "iPhone de Jean". Passez à Premium pour écouter sur plusieurs appareils. """ Et un bouton "Passer à Premium" doit être proposé # ============================================================================ # INTERFACE ADMIN & GESTION DES CONFLITS # ============================================================================ Scénario: Dashboard admin - voir sessions actives par utilisateur Étant donné un administrateur connecté au dashboard Quand l'admin recherche l'utilisateur "user-123" Alors les sessions actives doivent être affichées : | device_id | platform | started_at | content_id | duration | | iphone-123 | iOS | 2026-02-03 14:00:00 | content-xyz | 15:30 | Et l'admin doit pouvoir "Terminer la session" manuellement Scénario: Support client - résolution conflit bloqué Étant donné un utilisateur signale ne pas pouvoir lire sur aucun appareil Et une session "fantôme" existe dans Redis (crash + heartbeat bloqué) Quand le support client force la suppression de la session Redis Alors la clé Redis "active_stream:user-123" doit être supprimée Et l'utilisateur doit pouvoir redémarrer immédiatement # ============================================================================ # MÉTRIQUES & MONITORING # ============================================================================ Scénario: Logging des conflits pour analytics Étant donné un conflit est détecté entre "iPhone de Jean" et "iPad Pro" Quand le conflit est résolu par kill de session Alors un événement analytics doit être loggé : | event_type | stream_conflict_resolved | | user_id | user-123 | | previous_device | iphone-123 | | new_device | ipad-456 | | resolution | kill_previous | | previous_session_duration | 900 | | timestamp | 2026-02-03 14:15:00 | Et ces métriques doivent être disponibles dans le dashboard Scénario: Alerte monitoring - taux de conflits élevé Étant donné le système monitore les conflits de stream Quand le taux de conflits dépasse 15% des nouvelles sessions sur 1 heure Alors une alerte Slack doit être envoyée à l'équipe technique Et le message doit indiquer : """ ⚠️ Taux de conflits stream élevé : 18% (seuil : 15%) Sessions impactées : 234 sur 1300 Action recommandée : vérifier expiration Redis et heartbeats """ # ============================================================================ # COMPATIBILITÉ AVEC D'AUTRES FEATURES # ============================================================================ Scénario: Radio live avec conflit de stream Étant donné un utilisateur écoute une radio live sur "iPhone de Jean" Quand l'utilisateur démarre un stream sur "iPad Pro" et kill la session iPhone Alors la radio live doit s'arrêter sur iPhone Et le nouveau stream sur iPad peut être une radio live ou contenu normal Et la progression audio-guide (si applicable) doit être sauvegardée Scénario: Mode offline ne déclenche PAS de conflit Étant donné un stream actif en ligne sur "iPhone de Jean" Quand l'utilisateur écoute un contenu téléchargé en mode offline sur "iPad Pro" Alors aucun conflit ne doit être détecté Car le mode offline ne consomme pas de stream en ligne Et les deux lectures peuvent coexister Scénario: Multi-device avec partage familial (post-MVP) Étant donné la fonctionnalité de partage familial est activée (post-MVP) Et le compte principal a 3 profils famille Quand chaque profil démarre un stream sur son appareil Alors jusqu'à 3 streams simultanés doivent être autorisés Et la détection de conflit doit s'appliquer par profil (1 stream/profil)