6.6 KiB
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 :
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 :
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.Servicepour 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_audioavec cache local (flutter_cache_manager) - Stockage : Max 100 MB de cache audio (auto-purge LRU)
- Logique :
PreBufferServiceavec 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
- Low-Latency HLS (LL-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