Files
roadwave/docs/adr/002-protocole-streaming.md
jpgiannetti 18c8901d69 docs(adr): corriger référence cassée ADR-002
Correction référence Règle Métier 05 :
- Avant : "Section 5.2 (Mode Voiture, lignes 16-84)"
- Après : "Section 5.1 (File d'attente et commande Suivant)"

Raison : La Règle 05 ne contient pas de Section 5.2.
La structure réelle est Section 5.1 pour la file d'attente.

Résout dernière incohérence P0 (INCONSISTENCIES.md item 5).
100% des incohérences P0 maintenant corrigées !

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 15:37:47 +01:00

6.6 KiB
Raw Blame History

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.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