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>
183 lines
6.6 KiB
Markdown
183 lines
6.6 KiB
Markdown
# 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.1 (File d'attente et commande Suivant)
|
||
- Règle Métier 17 : Section 17.2 (ETA Géolocalisé, lignes 25-65)
|
||
- **ADR-017** : Architecture des Notifications Géolocalisées
|