Files
roadwave/docs/adr/002-protocole-streaming.md
jpgiannetti 852f6d5e16 refactor(docs): réorganiser ADR et règles métier pour clarté
**Changements majeurs** :

1. **Suppression ADR-010 (Commandes volant et likes)** :
   - Contenu consolidé dans Règle 05 (section 5.3)
   - Raison : ADR-010 était du métier déguisé en architecture
   - Section "Implémentation Technique" ajoutée à Règle 05
   - Pattern correct (addition) vs incorrect (multiplication)

2. **Déplacement ADR-011 → Compliance** :
   - `docs/adr/011-conformite-stores.md` → `docs/compliance/stores-submission.md`
   - Raison : Nature opérationnelle/légale, pas architecture technique
   - Nouveau dossier `/docs/compliance/` créé

3. **Renumérotation ADR (010-022)** :
   - Combler les trous de numérotation (010 et 011)
   - ADR-012→010, ADR-013→011, ..., ADR-024→022
   - 22 ADR numérotés en continu (001-022)
   - Historique Git préservé (git mv)

4. **Mise à jour références** :
   - Règle 03 : ADR-010 → Règle 05 (section 5.3)
   - Règle 09 : ADR-010 → Règle 05 (section 5.3)
   - INCONSISTENCIES-ANALYSIS.md : toutes références mises à jour
   - Incohérence #15 annulée (faux problème : modes séparés)

**Résultat** :
-  Séparation claire ADR (technique) vs Règles métier (fonctionnel)
-  Documentation compliance séparée
-  Numérotation ADR continue sans trous
-  Single Source of Truth (pas de redondance)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 14:34:12 +01:00

183 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-017** : Architecture des Notifications Géolocalisées