# ADR-002 : Protocole de Streaming **Statut** : Accepté **Date** : 2025-01-17 ## Contexte Streaming audio vers des utilisateurs mobiles en voiture, avec réseaux instables (tunnels, zones rurales, handoff cellulaire). ## Décision **HLS** (HTTP Live Streaming) pour le contenu à la demande. **WebRTC** réservé à la radio live. ## Alternatives considérées | Option | Latence | Fiabilité mobile | Cache CDN | Complexité | |--------|---------|------------------|-----------|------------| | **HLS** | 5-30s | Excellente | Oui | Faible | | DASH | 5-30s | Bonne | Oui | Moyenne | | WebRTC | <500ms | Moyenne | Non | Élevée | | UDP brut | Minimale | Faible | Non | Très élevée | ## Justification - **Réseaux mobiles** : HLS gère les coupures et changements de cellule nativement - **Cache CDN** : Segments .ts cachables = réduction des coûts - **Compatibilité** : Support natif iOS/Android - **Bitrate adaptatif** : Ajustement automatique selon la qualité réseau ## Pourquoi pas UDP ? - Problèmes NAT/firewall sur réseaux mobiles - Perte de paquets = artefacts audio - Impossible à cacher sur CDN - Complexité sans bénéfice pour du contenu non-interactif ## Conséquences - Latence de 5-30s acceptable pour podcasts/audio-guides (avec pré-buffering, voir section 4.3) - WebRTC à implémenter séparément pour la radio live ## Gestion de la Latence et Synchronisation Géolocalisée ### Problème Identifié La latence HLS (5-30s) entre en conflit avec les notifications géolocalisées qui doivent déclencher l'audio **au moment précis** où l'utilisateur atteint un point d'intérêt. **Exemple critique** : - Utilisateur en voiture à 90 km/h (25 m/s) - ETA de 7 secondes avant le point → notification affichée - Latence HLS de 15 secondes - Résultat : audio démarre **200 mètres après** le point d'intérêt ❌ ### Solution : Pre-buffering Anticipé + ETA Adaptatif #### 4.3.1 Pre-buffering Automatique **Déclenchement** : À ETA = 30 secondes du prochain point d'intérêt ``` [App Mobile] ↓ (ETA=30s, position GPS détectée) [Cache Manager] ↓ (télécharge en arrière-plan) [CDN NGINX] → /audio/poi-{id}/intro.m4a (10-15s d'audio, ~5-8 MB) ↓ [Cache Local Mobile] (max 3 POI simultanés) ``` **Stratégie de cache** : - Télécharge les **15 premières secondes** de chaque POI à proximité - Limite : 3 POI simultanés en cache (max ~25 MB) - Purge automatique après 200m de distance passée - Format : M4A haute qualité (128 kbps) pour intro, puis bascule HLS pour la suite #### 4.3.2 ETA de Notification Adaptatif **Algorithme** : ```python def calculate_notification_eta(poi, user_position, user_speed): distance_to_poi = haversine(user_position, poi.position) is_cached = cache.has(poi.audio_id) hls_latency = metrics.get_average_latency(user_id) # 8-18s typique if is_cached: # Cache prêt → notification courte (temps de réaction) notification_eta = 5 # secondes else: # Pas de cache → compenser latence HLS + marge notification_eta = hls_latency + 3 + 2 # latence + marge + réaction # Typique: 10s + 3s + 2s = 15s time_to_poi = distance_to_poi / user_speed if time_to_poi <= notification_eta: send_notification(poi) if time_to_poi <= 30 and not is_cached: cache.preload_async(poi.audio_id) ``` **Résultat** : | Situation | ETA Notification | Distance (à 90 km/h) | Expérience | |-----------|------------------|----------------------|------------| | Cache prêt | 5s | 125m avant | ✅ Lecture instantanée | | Cache en cours | 15s | 375m avant | ✅ Lecture à temps | | 3G lent | 20s | 500m avant | ✅ Compensation latence | #### 4.3.3 Mesure Dynamique de Latence **Tracking par utilisateur** : ```go type HLSMetrics struct { UserID uuid.UUID AvgLatency time.Duration // Moyenne glissante 10 lectures NetworkType string // "4G", "5G", "3G", "wifi" LastMeasured time.Time } // Mesure lors de chaque lecture func (m *HLSMetrics) RecordPlaybackStart(requestTime, firstByteTime time.Time) { latency := firstByteTime.Sub(requestTime) m.AvgLatency = (m.AvgLatency*9 + latency) / 10 // Moyenne glissante } ``` **Utilisation** : L'ETA adaptatif utilise `AvgLatency` personnalisé par utilisateur au lieu d'une valeur fixe. #### 4.3.4 Fallback et Indicateurs Visuels Si le pre-buffer échoue (réseau faible, pas de cache), afficher un **loader avec progression** : ``` ┌─────────────────────────────────┐ │ 🏰 Château de Fontainebleau │ │ │ │ [████████░░] 80% │ │ Chargement de l'audio... │ │ │ │ 📍 Vous arrivez dans 12s │ └─────────────────────────────────┘ ``` - **Feedback visuel** : Utilisateur comprend que ça charge - **ETA affiché** : Maintient l'attention et réduit la frustration perçue - **Timeout** : Si > 30s sans succès, proposer mode dégradé (texte seul) ### Impact sur l'Infrastructure #### Backend (Go) - **Nouveau service** : `audiocache.Service` pour préparer les extraits M4A - **Endpoint** : `GET /api/v1/audio/poi/:id/intro` (retourne 15s d'audio) - **CDN** : Cache NGINX avec TTL 7 jours sur `/audio/*/intro.m4a` #### Mobile (Flutter) - **Package** : `just_audio` avec cache local (`flutter_cache_manager`) - **Stockage** : Max 100 MB de cache audio (auto-purge LRU) - **Logique** : `PreBufferService` avec scoring de priorité POI #### Coûts - **Bande passante** : +10-15 MB/utilisateur/session (vs streaming pur) - **Stockage CDN** : +500 MB pour 1000 POI × 5 MB intro (négligeable) - **Économie** : Cache CDN réduit les requêtes origin (-60% selon tests) ### Métriques de Succès - **Latence perçue** : < 1 seconde dans 95% des cas (cache hit) - **Synchronisation** : Audio démarre à ±10 mètres du POI (objectif ±50m) - **Cache hit rate** : > 90% pour utilisateurs en mode navigation - **Consommation data** : +15% vs HLS pur, mais acceptable pour UX ### Références - [HLS Authoring Specification](https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices) - [Low-Latency HLS (LL-HLS)](https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls) - Règle Métier 05 : Section 5.2 (Mode Voiture, lignes 16-84) - Règle Métier 17 : Section 17.2 (ETA Géolocalisé, lignes 25-65) - **ADR-019** : Architecture des Notifications Géolocalisées