refactor(docs): réorganiser la documentation selon principes DDD

Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend.

**Structure cible:**
```
docs/domains/
├── README.md (Context Map)
├── _shared/ (Core Domain)
├── recommendation/ (Supporting Subdomain)
├── content/ (Supporting Subdomain)
├── moderation/ (Supporting Subdomain)
├── advertising/ (Generic Subdomain)
├── premium/ (Generic Subdomain)
└── monetization/ (Generic Subdomain)
```

**Changements effectués:**

Phase 1: Création de l'arborescence des 7 bounded contexts
Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/
Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/
Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/
Phase 5: Création des README.md pour chaque domaine
Phase 6: Déplacement des features Gherkin vers domains/*/features/
Phase 7: Création du Context Map (domains/README.md)
Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation
Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh)
Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/)

**Configuration des tests:**
- Makefile: godog run docs/domains/*/features/
- scripts/generate-bdd-docs.py: features_dir → docs/domains

**Avantages:**
 Cohésion forte: toute la doc d'un domaine au même endroit
 Couplage faible: domaines indépendants, dépendances explicites
 Navigabilité améliorée: README par domaine = entrée claire
 Alignement code/docs: miroir de backend/internal/
 Onboarding facilité: exploration domaine par domaine
 Tests BDD intégrés: features au plus près des règles métier

Voir docs/REFACTOR-DDD.md pour le plan complet.
This commit is contained in:
jpgiannetti
2026-02-07 17:15:02 +01:00
parent 78422bb2c0
commit 5e5fcf4714
227 changed files with 1413 additions and 1967 deletions

View File

@@ -0,0 +1,854 @@
## 16. Audio-guides multi-séquences
### 16.1 Types d'audio-guides et modes de déplacement
**Décision** : 4 modes distincts avec détection automatique
#### 16.1.1 Classification par mode
| Mode | Vitesse détection | Déclenchement | Use case |
|------|-------------------|---------------|----------|
| **🚶 Piéton** | <5 km/h | Manuel (bouton "Suivant") | Musées, visites urbaines, monuments |
| **🚗 Voiture** | >10 km/h | Auto GPS + Manuel possible | Safari-parc, routes touristiques, circuits auto |
| **🚴 Vélo** | 5-25 km/h | Auto GPS + Manuel possible | Pistes cyclables, circuits vélo, parcours nature |
| **🚌 Transport** | Variable | Auto GPS + Manuel possible | Bus touristiques, trains panoramiques |
**Détection automatique** :
- Vitesse moyenne calculée sur 30 secondes
- Suggestion mode au démarrage : "Détection : 🚗 Voiture. Est-ce correct ? [Oui] [Changer]"
- User peut forcer mode manuellement (settings)
**Justification** :
- Flexibilité maximale créateurs et utilisateurs
- Expériences optimisées par type de déplacement
- Gestion cas limites (vélo lent vs piéton rapide)
---
#### 16.1.2 Création d'un audio-guide (côté créateur)
**Formulaire création** :
```
┌────────────────────────────────────────┐
│ Nouvel audio-guide multi-séquences │
├────────────────────────────────────────┤
│ Titre : [Safari du Paugre] │
│ Description : [Découvrez les animaux │
│ du parc en voiture...] │
│ │
│ Mode de déplacement : *obligatoire │
│ ○ 🚶 Piéton (navigation manuelle) │
│ ● 🚗 Voiture (GPS auto + manuel) │
│ ○ 🚴 Vélo (GPS auto + manuel) │
│ ○ 🚌 Transport (GPS auto + manuel) │
│ │
│ Vitesse recommandée : 30-50 km/h │
│ (si voiture/vélo/transport) │
│ │
│ ──────────────────────────────────── │
│ │
│ Séquences (ordre lecture) : │
│ │
│ 1. [📍] Introduction - Point d'accueil │
│ Lat: 43.1234, Lon: 2.5678 │
│ Rayon déclenchement : 30m │
│ Durée : 2:15 │
│ [🎵 Audio uploadé] [✏️] [🗑️] │
│ │
│ 2. [📍] Enclos des lions │
│ Lat: 43.1245, Lon: 2.5690 │
│ Rayon déclenchement : 30m │
│ Durée : 3:42 │
│ [📤 Upload audio] [✏️] [🗑️] │
│ │
│ 3. [📍] Enclos des girafes │
│ [+ Ajouter point GPS] │
│ │
│ [+ Ajouter séquence] │
│ │
│ 📊 Statistiques : │
│ · 2 séquences complètes │
│ · 5:57 durée totale │
│ · 320m distance totale │
│ │
│ [🗺️ Aperçu sur carte] │
│ [✅ Publier audio-guide] │
└────────────────────────────────────────┘
```
**Métadonnées obligatoires** :
| Champ | Requis | Détails |
|-------|--------|---------|
| **Titre audio-guide** | ✅ | 5-100 caractères |
| **Description** | ✅ | 10-500 caractères |
| **Mode déplacement** | ✅ | Piéton / Voiture / Vélo / Transport |
| **Nombre séquences** | ✅ | Minimum 2, maximum 50 |
| **Point GPS par séquence** | ✅ (sauf piéton) | Latitude, longitude (WGS84) |
| **Rayon déclenchement** | ✅ (sauf piéton) | 10-100m selon mode |
| **Vitesse recommandée** | ❌ | Optionnel, affichée utilisateur |
| **Tags** | ✅ | 1-3 parmi liste prédéfinie |
| **Classification âge** | ✅ | Tout public / 13+ / 16+ / 18+ |
| **Zone diffusion** | ✅ | Polygon géographique |
**Wizard de création** :
- Étape 1 : Infos générales (titre, description, mode)
- Étape 2 : Ajout séquences une par une
- Étape 3 : Preview carte (trace + points)
- Étape 4 : Validation modération (3 premiers audio-guides)
**Justification** :
- Contrôle total créateur sur expérience
- Carte preview aide visualiser parcours
- Wizard guidé = réduction friction création
---
### 16.2 Mode Piéton (manuel)
**Décision** : Navigation manuelle avec pub auto-play
#### 16.2.1 Passage entre séquences
**Séquence normale (sans pub)** :
1. Séquence 1 se termine
2. Player se met en **pause automatique**
3. Message affiché : "Séquence 1 terminée. Appuyez sur Suivant quand vous êtes prêt."
4. User appuie sur [▶|] → Séquence 2 démarre immédiatement
**Séquence avec publicité** (1 pub / 5 séquences) :
1. Séquence 2 se termine
2. **Publicité s'enchaîne automatiquement** (pas d'attente bouton)
3. Pub se lit (skippable après 5s)
4. Pub se termine → Player se met en **pause automatique**
5. Message : "Séquence 3 prête. Appuyez sur Suivant."
6. User appuie sur [▶|] → Séquence 3 démarre
**Schéma flux** :
```
Séquence 1 [fin] → PAUSE → User clique → Séquence 2 [fin] → PUB AUTO-PLAY → PAUSE → User clique → Séquence 3
```
**Fréquence pub** :
- Gratuits : 1 pub toutes les 5 séquences (paramétrable admin 1/3 à 1/10)
- Premium : 0 pub
**Justification** :
- Pub s'insère naturellement (pas d'attente utilisateur pour déclencher)
- User garde contrôle rythme visite (pause après pub)
- Monétisation effective créateurs
- Premium reste attractif (0 interruption)
---
#### 16.2.2 Navigation et contrôles
**Décision** : Liberté totale utilisateur
**Contrôles disponibles** :
| Bouton | Fonction | Comportement |
|--------|----------|--------------|
| **[▶\|] Suivant** | Passe séquence suivante | Immédiat, même si séquence actuelle pas terminée |
| **[\|◀] Précédent** | Retour séquence précédente | Saut direct séquence avant (pas de logique "replay si >10s") |
| **[⏸️] Pause** | Pause temporaire | Reprend à position exacte |
| **[▶️] Play** | Reprend lecture | Continue position actuelle |
| **Liste séquences** | Navigation libre | Tap séquence → saut direct (même séquences non écoutées) |
**Interface liste séquences** :
```
┌────────────────────────────────────────┐
│ 🚶 Audio-guide Piéton │
│ Musée du Louvre │
├────────────────────────────────────────┤
│ [Cover image] │
│ │
│ ▶️ 0:00 ──●────────── 3:42 │
│ │
│ Séquence 3/12 : La Joconde │
│ │
│ [|◀] [⏸️] [▶|] │
│ │
│ ──────────────────────────────────── │
│ │
│ 📋 Liste des séquences │
│ │
│ ✅ 1. Introduction (2:15) │
│ Écouté le 15/01/2026 │
│ │
│ ✅ 2. Pyramide du Louvre (1:48) │
│ Écouté le 15/01/2026 │
│ │
│ ▶️ 3. La Joconde (3:42) - EN COURS │
│ ──●──────────── 1:22/3:42 │
│ │
│ ⭕ 4. Vénus de Milo (2:58) │
│ │
│ ⭕ 5. Code d'Hammurabi (4:12) │
│ │
│ ⭕ 6. Victoire de Samothrace (3:25) │
│ │
│ ... +6 séquences │
│ │
│ [Tout afficher ▼] │
└────────────────────────────────────────┘
```
**Navigation libre** :
- User peut sauter séquences déjà connues
- User peut revenir en arrière à tout moment
- User peut aller directement à séquence 8 (même si 4-7 non écoutées)
**Sauvegarde progression** :
- Checkmarks ✅ sur séquences écoutées >80%
- Position exacte sauvegardée dans séquence en cours
**Justification** :
- Utilisateur contrôle 100% son rythme
- Adapté musées : visitor peut voir physiquement une œuvre lointaine et vouloir écouter sa description
- Pas de frustration (liberté totale)
---
### 16.3 Mode Voiture (GPS automatique)
**Décision** : GPS auto avec navigation manuelle conservée
#### 16.3.1 Déclenchement et contrôles
**Distinction audio-guides vs contenus géolocalisés simples** :
⚠️ **Important** : Les audio-guides multi-séquences fonctionnent différemment des contenus géolocalisés simples.
| Type | Séquences | Déclenchement | Notification | Enchaînement | Comptabilité quota |
|------|-----------|---------------|--------------|--------------|-------------------|
| **Contenu géolocalisé simple** | 1 séquence unique | Notification 7s avant (temps ETA) | Sonore + icône | Fin → retour buffer normal | 1 contenu = 1 quota |
| **Audio-guide multi-séquences** | 2 à 50 séquences | Au point GPS exact (distance 30m) | Ding + toast 2s | Séquences s'enchaînent auto | 1 audio-guide = 1 quota (toutes séquences) |
**Fonctionnement GPS automatique** :
1. User démarre audio-guide en voiture (voir section 16.1 pour démarrage)
2. Séquence 1 démarre automatiquement au point GPS défini (rayon 30m)
3. Séquence 1 se termine
4. **Affichage progress bar** : distance temps réel + ETA jusqu'au prochain point
5. User roule vers point GPS suivant
6. Arrivée au point GPS suivant (rayon 30m) → **déclenchement automatique** séquence suivante
7. Notification sonore discrète : "Ding" (0.3s) + toast 2s : "Enclos des girafes"
8. Séquence suivante démarre immédiatement (pas de décompte)
**Pas de système "7 secondes avant" pour les audio-guides** :
- Contrairement aux contenus géolocalisés simples (voir [05-interactions-navigation.md](05-interactions-navigation.md#511-file-dattente-et-commande-suivant))
- Les séquences se déclenchent **au point GPS exact** (rayon 30m)
- Raison : expérience guidée continue, user sait qu'il suit un parcours
**Navigation manuelle CONSERVÉE** :
| Bouton | État | Comportement |
|--------|------|--------------|
| **[▶\|] Suivant** | ✅ Toujours actif | Passe séquence suivante immédiatement (même hors point GPS) |
| **[\|◀] Précédent** | ✅ Toujours actif | Retour séquence précédente (même hors point GPS) |
| **[⏸️] Pause** | ✅ | Pause temporaire |
| **Liste séquences** | ✅ | Saut direct possible |
**Use cases navigation manuelle** :
| Situation | Solution manuelle |
|-----------|-------------------|
| Embouteillage (séquence finie, point GPS loin) | User clique Suivant → avance manuellement |
| Point GPS inaccessible (route fermée) | User clique Suivant → skip point |
| Envie réécouter séquence précédente | User clique Précédent → retour |
| Passager manipule l'app | Passager navigue librement |
**Avertissement sécurité** :
- Si vitesse **>10 km/h** ET user clique bouton (Suivant/Précédent) :
- Toast 3 secondes : "⚠️ Manipulation en conduite détectée. Pour votre sécurité, demandez à un passager."
- **Action quand même exécutée** (pas de blocage)
- Justification : sensibilisation sans bloquer (passager peut légitimement manipuler)
**Schéma flux** :
```
Point GPS 1 (30m) → Séquence 1 AUTO → User roule → Distance affichée → Point GPS 2 (30m) → Séquence 2 AUTO
User clique Suivant (manuel) → Séquence 2 immédiate
```
**Justification** :
- Flexibilité maximale : GPS optimise expérience MAIS user garde contrôle
- Gestion cas limites : routes fermées, détours, embouteillages
- Sécurité : warning sensibilise sans bloquer (passager légitime)
---
#### 16.3.2 Affichage distance et guidage
**Décision** : Distance + direction (PAS de carte miniature)
**Interface en conduite** :
```
┌────────────────────────────────────────┐
│ 🚗 Audio-guide Voiture │
│ Safari du Paugre │
├────────────────────────────────────────┤
│ │
│ ▶️ 0:00 ──●────────── 2:15 │
│ │
│ Séquence 2/8 : Les lions │
│ │
│ ──────────────────────────────────── │
│ │
│ 📍 Prochain point │
│ │
│ Enclos des girafes │
│ │
│ ┌────────────────────────────────┐ │
│ │ │ │
│ │ ↗️ │ │
│ │ (direction) │ │
│ │ │ │
│ │ 320 mètres │ │
│ │ ≈ 40 secondes │ │
│ │ │ │
│ └────────────────────────────────┘ │
│ │
│ Vitesse actuelle : 28 km/h │
│ Vitesse recommandée : 20-30 km/h │
│ │
│ [|◀] [⏸️] [▶|] [📋 Liste] │
└────────────────────────────────────────┘
```
**Affichage entre deux séquences** :
Quand une séquence se termine et qu'il reste un point GPS suivant, l'interface bascule en mode "attente prochain point" :
```
┌────────────────────────────────────────┐
│ 🚗 Audio-guide Voiture │
│ Safari du Paugre │
├────────────────────────────────────────┤
│ │
│ ✅ Séquence 2/8 terminée │
│ Les lions │
│ │
│ ──────────────────────────────────── │
│ │
│ 📍 Prochain point │
│ │
│ Enclos des girafes │
│ │
│ ┌────────────────────────────────┐ │
│ │ [Progress bar] │ │
│ │ ████████░░░░░░░░░ 65% │ │
│ │ │ │
│ │ ↗️ │ │
│ │ (direction) │ │
│ │ │ │
│ │ 320 mètres │ │
│ │ ≈ 40 secondes │ │
│ │ │ │
│ └────────────────────────────────┘ │
│ │
│ Vitesse actuelle : 28 km/h │
│ │
│ [|◀] [▶️ Rejouer séq.] [▶|] │
└────────────────────────────────────────┘
```
**Progress bar dynamique** :
- Se remplit au fur et à mesure qu'on se rapproche du point
- Calcul : `progress = 100 - (distance_actuelle / distance_initiale * 100)`
- Exemple : distance initiale 500m, distance actuelle 175m → progress = 65%
- Couleur : vert (#4CAF50) pour la partie remplie, gris (#E0E0E0) pour le reste
**Bouton "Rejouer séq."** :
- Permet de réécouter la séquence qui vient de se terminer
- User clique → séquence actuelle redémarre depuis 0:00
- Utile si distraction pendant l'écoute
---
**Informations affichées** :
| Info | Mise à jour | Format |
|------|-------------|--------|
| **Distance** | Chaque seconde | "320 m" / "1.2 km" |
| **ETA** | Chaque seconde | "≈ 40 secondes" / "≈ 2 minutes" |
| **Direction** | Chaque 5s | Flèche indique direction (8 directions : ↑ ↗ → ↘ ↓ ↙ ← ↖) |
| **Vitesse actuelle** | Chaque seconde | "28 km/h" |
| **Vitesse recommandée** | Statique | "20-30 km/h" (définie par créateur) |
| **Progress bar** | Chaque seconde | Pourcentage parcouru vers prochain point |
**Calcul direction** :
```javascript
// Calcul angle entre position actuelle et prochain point
const currentGPS = getCurrentLocation();
const nextPoint = audioGuide.sequences[currentIndex + 1].location;
const angle = calculateBearing(currentGPS, nextPoint); // 0-360°
// Conversion en flèche (8 directions)
const arrows = ['↑', '↗', '→', '↘', '↓', '↙', '←', '↖'];
const index = Math.round(angle / 45) % 8;
const direction = arrows[index];
```
**Calcul ETA** :
```javascript
const distance = calculateDistance(currentGPS, nextPoint); // mètres
const currentSpeed = getCurrentSpeed(); // km/h
if (currentSpeed > 5) {
const eta = (distance / 1000) / currentSpeed * 3600; // secondes
return formatETA(eta); // "≈ 40 secondes" ou "≈ 2 minutes"
} else {
return "En attente de déplacement";
}
```
**Justification** :
- Distance + ETA = info essentielle sans surcharge visuelle
- Direction (flèche) = aide se repérer sans carte complexe
- Simplicité = moins distraction conducteur
- Économie batterie (pas de rendu carte)
---
#### 16.3.3 Rayon de déclenchement et tolérance
**Décision** : Rayon configurable créateur avec défauts intelligents
**Rayons par défaut** :
| Mode | Rayon déclenchement | Rayon "point manqué" | Justification |
|------|---------------------|----------------------|---------------|
| **🚗 Voiture** | 30 mètres | 100 mètres | Vitesse élevée = anticipation |
| **🚴 Vélo** | 50 mètres | 75 mètres | Vitesse variable, arrêts fréquents |
| **🚌 Transport** | 100 mètres | 150 mètres | Arrêts bus/train, moins précis |
**Configuration créateur** :
- Curseur rayon : **10m → 200m**
- Défaut pré-sélectionné selon mode choisi
- Preview visuel : cercle sur carte (lors création)
- Suggestion auto : "Recommandé : 30m pour voiture à 30 km/h"
**Gestion point manqué** :
```
User passe à 110m du point GPS
(hors rayon déclenchement 30m MAIS dans rayon tolérance 100m)
Toast : "⚠️ Point manqué : Enclos des girafes"
Popup 5 secondes :
┌────────────────────────────────────┐
│ Point manqué │
│ │
│ "Enclos des girafes" │
│ Vous êtes passé à 110m du point │
│ │
│ [🔊 Écouter quand même] │
│ [⏭️ Passer au suivant] │
│ [🔙 Faire demi-tour] │
└────────────────────────────────────┘
```
**Actions popup** :
| Bouton | Comportement |
|--------|--------------|
| **Écouter quand même** | Lance séquence immédiatement (même hors zone) |
| **Passer au suivant** | Skip séquence, continue vers prochain point |
| **Faire demi-tour** | Lance navigation GPS externe (Google Maps / Waze) vers point manqué |
**Si user au-delà rayon tolérance (>100m)** :
- Aucun popup (point trop loin, probablement hors itinéraire)
- User peut naviguer manuellement (bouton Suivant)
**Justification** :
- Flexibilité créateur (ajuste selon terrain, vitesse prévue)
- Gestion intelligente imprévus (détours, routes fermées)
- User pas bloqué (toujours moyen avancer)
---
#### 16.3.4 Système double clic et sortie audio-guide
**Problème** : Que se passe-t-il si l'utilisateur clique "Suivant" pendant un audio-guide en mode voiture, alors qu'il est bloqué dans un embouteillage et va mettre 30 minutes pour atteindre le prochain point GPS ?
**Solution** : Système intelligent à double clic permettant de désactiver le GPS automatique puis de sortir de l'audio-guide.
##### Comportement bouton [▶|] Suivant
**1. Premier clic (mode GPS auto actif)** :
- Désactive GPS automatique
- Passe à la séquence suivante immédiatement
- Bascule en **mode manuel**
- Toast 3s : "Mode manuel activé. Cliquez à nouveau pour quitter l'audio-guide."
- Timer 10 secondes démarre
**2. Deuxième clic (dans les 10 secondes suivantes)** :
- Sort de l'audio-guide
- Audio-guide mis en **pause** (historique conservé)
- Retour au **flux normal** (algorithme de recommandation)
- Toast 2s : "Audio-guide en pause"
**3. Clics suivants (après 10 secondes)** :
- Passe à la séquence suivante (comportement standard mode manuel)
- Timer 10 secondes redémarre à chaque clic
**Schéma** :
```
Mode GPS auto → [Clic 1] → Mode manuel + séquence suivante + timer 10s
[Clic 2 < 10s] → Sortie audio-guide + pause
[Clic après 10s] → Séquence suivante + reset timer
```
##### Comportement bouton [|◀] Précédent
| Contexte | Action | Résultat |
|----------|--------|----------|
| **Dans audio-guide** (GPS auto ou manuel) | Séquence précédente (ou replay si <10s) | Reste dans audio-guide |
| **Hors audio-guide** (flux normal) | Contenu précédent | Si contenu précédent = audio-guide → reprend audio-guide |
##### Mode manuel
- **Persistant** : Reste actif jusqu'à fin audio-guide
- **Pas de réactivation GPS auto** pendant session en cours
- **Reset au redémarrage** : Si audio-guide relancé plus tard → repart en mode GPS auto par défaut
##### Détection et reprise après détour
**Règle 1 : Détection "hors itinéraire"**
```
Si user à >1 km de tous points GPS pendant >10 min :
→ Toast : "Audio-guide en pause (hors itinéraire)"
→ Icône audio-guide passe en gris (inactif)
```
**Règle 2 : Détection "retour sur itinéraire"**
```
Si user revient à <100m d'un point GPS non écouté :
→ Popup : "Reprendre audio-guide à la séquence X ?"
→ 3 boutons : Reprendre / Voir liste / Ignorer
```
**Règle 3 : Respect des clics manuels**
```
Si user a cliqué "Suivant" manuellement :
→ Séquence considérée "skippée volontairement"
→ Pas de proposition automatique de revenir en arrière
→ User peut revenir manuellement via liste séquences
```
##### Implémentation technique (référence)
```javascript
let lastSuivantClickTime = null;
const DOUBLE_CLICK_DELAY = 10000; // 10 secondes
function onSuivantClick() {
const now = Date.now();
if (audioGuideState.mode === 'gps_auto') {
// 1er clic : Désactive GPS auto
audioGuideState.mode = 'manual';
playNextSequence();
showToast("Mode manuel activé. Cliquez à nouveau pour quitter.", 3000);
lastSuivantClickTime = now;
}
else if (audioGuideState.mode === 'manual') {
if (lastSuivantClickTime && (now - lastSuivantClickTime) < DOUBLE_CLICK_DELAY) {
// 2ème clic rapide : Sort de l'audio-guide
pauseAudioGuide();
returnToNormalFlow();
showToast("Audio-guide en pause", 2000);
} else {
// Clic normal : Séquence suivante
playNextSequence();
lastSuivantClickTime = now;
}
}
}
```
**Justification** :
- Résout le problème des embouteillages (30 min sans contenu)
- Double intention claire : désactiver GPS puis sortir
- User garde toujours le contrôle (peut reprendre audio-guide plus tard)
- Historique conservé (pas de perte de progression)
---
### 16.4 Modes Vélo et Transport
**Décision** : Même logique voiture avec tolérances ajustées
**Différences par rapport à mode voiture** :
| Paramètre | Voiture | Vélo | Transport |
|-----------|---------|------|-----------|
| **Rayon déclenchement** | 30m | 50m | 100m |
| **Rayon tolérance "point manqué"** | 100m | 75m | 150m |
| **Vitesse recommandée affichée** | 20-50 km/h | 10-25 km/h | Variable (selon ligne) |
| **Warning sécurité** | >10 km/h | >5 km/h | Désactivé |
**Mode Vélo spécificités** :
- Rayon plus large : vitesse variable, nombreux arrêts (feux, piétons)
- Warning sécurité dès 5 km/h (vélo en mouvement)
- Tolérance GPS moins stricte (tracé moins prévisible qu'auto)
**Mode Transport spécificités** :
- Rayon très large : arrêts fréquents (bus, train), ligne fixe
- Pas de warning sécurité (user = passager, pas conducteur)
- Vitesse recommandée = "Selon ligne" (pas de valeur fixe)
- Tolérance horaire : si bus en retard, point peut se déclencher avec 2-3 min de délai
**Comportement identique voiture** :
- Navigation manuelle conservée (boutons actifs)
- Affichage distance + ETA + direction
- Gestion point manqué
- Pub entre séquences
**Justification** :
- Vélo : moins de contrôle qu'auto (obstacles, arrêts), nécessite tolérance
- Transport : moins de contrôle utilisateur (suit ligne fixe), rayon large compense
- Même UX globale = cohérence
---
### 16.5 Publicités dans audio-guides
**Décision** : Pub auto-play entre séquences TOUS modes
#### 16.5.1 Règles universelles
**Insertion publicité** :
- Fréquence : **1 pub toutes les 5 séquences** (paramétrable admin 1/3 à 1/10)
- Gratuits uniquement, **Premium 0 pub**
- Pub s'enchaîne **automatiquement** après séquence
- Skippable après **5 secondes** (règle standard RoadWave)
- Volume normalisé -14 LUFS (comme pubs normales)
**Comportement MODE PIÉTON** :
```
Séquence 2 [fin]
→ Pub AUTO-PLAY
→ Pub se termine
→ PAUSE AUTO
→ Message "Séquence 3 prête. Appuyez sur Suivant."
→ User clique [▶|]
→ Séquence 3 démarre
```
**Comportement MODE VOITURE/VÉLO/TRANSPORT** :
```
Séquence 2 [fin]
→ Pub AUTO-PLAY
→ Pub se termine
→ ATTENTE point GPS suivant OU user clique Suivant
→ Séquence 3 démarre
```
**Schéma complet** :
| Mode | Après séquence normale | Après pub |
|------|------------------------|-----------|
| **Piéton** | Pause + attente user | Pause + attente user |
| **Voiture** | Attente GPS OU user clique Suivant | Attente GPS OU user clique Suivant |
| **Vélo** | Attente GPS OU user clique Suivant | Attente GPS OU user clique Suivant |
| **Transport** | Attente GPS OU user clique Suivant | Attente GPS OU user clique Suivant |
**Justification** :
- Monétisation équitable créateurs (tous modes participent)
- Pub s'insère naturellement (auto-play, pas d'attente utilisateur)
- User garde contrôle : piéton clique Suivant, voiture peut skip manuel
- Premium reste attractif (expérience 0 interruption)
- Modèle économique viable
---
#### 16.5.2 Métriques pub audio-guides
**Dashboard créateur** :
| Métrique | Affichage |
|----------|-----------|
| **Impressions pub** | Nombre de pubs insérées dans audio-guides |
| **Écoutes complètes pub** | Nombre de pubs écoutées >80% |
| **Taux skip pub** | % pubs skippées avant 5s vs après |
| **Revenus pub audio-guides** | 3€ / 1000 écoutes complètes (6% CA pub) |
**Distinction contenus normaux vs audio-guides** :
- Dashboard sépare : "Revenus contenus classiques" / "Revenus audio-guides"
- Permet créateur voir performance par type
**Justification** :
- Transparence créateur (comprend revenus)
- Incite création audio-guides (nouvelle source revenus)
---
### 16.6 Reprise et sauvegarde progression
**Décision** : Sauvegarde complète automatique avec popup intelligente
#### 16.6.1 Sauvegarde automatique
**Données sauvegardées** :
| Info | Détail | Utilité |
|------|--------|---------|
| **Audio-guide ID** | Identifiant unique | Retrouver audio-guide |
| **Séquence actuelle** | Index (ex: 3/12) | Reprise position |
| **Position dans séquence** | Timestamp exact (ex: 1:42/3:20) | Reprise exacte |
| **Séquences écoutées** | Liste avec checkmarks ✅ | Historique progression |
| **Date dernière écoute** | Timestamp | Proposer reprise si <30j |
| **GPS dernière position** | Coordonnées optionnelles | Info contextuelle (non utilisée pour reprise) |
**Stockage** :
| Environnement | Technologie | Utilité |
|---------------|-------------|---------|
| **Local** | SQLite mobile | Fonctionnement offline |
| **Cloud** | PostgreSQL (sync auto) | Multi-device (reprendre sur autre appareil) |
**Synchronisation** :
- Sauvegarde locale : chaque fin de séquence + chaque 30s
- Sync cloud : à la reconnexion réseau (batch)
**Justification** :
- Expérience fluide (pas de perte progression)
- Multi-device (démarrer sur iPhone, continuer sur iPad)
- Offline-first (fonctionne sans réseau)
---
#### 16.6.2 Interface de reprise
**Conditions popup** :
- Dernière écoute **<30 jours**
- Progression **>0%** et **<100%** (pas terminé)
**Popup reprise** :
```
┌────────────────────────────────────────┐
│ Reprendre l'audio-guide ? │
├────────────────────────────────────────┤
│ 🚗 Safari du Paugre │
│ @safari_createur │
│ │
│ Progression : 3/8 séquences écoutées │
│ Dernière écoute : il y a 2 jours │
│ │
│ Vous étiez à : │
│ "Les lions" (1:42/3:20) │
│ │
│ [▶️ Reprendre] [🔄 Recommencer] │
│ [📋 Voir toutes les séquences] │
└────────────────────────────────────────┘
```
**Actions** :
| Bouton | Comportement |
|--------|--------------|
| **Reprendre** | Continue séquence 3 à position 1:42 exacte |
| **Recommencer** | Reset progression, démarre séquence 1 depuis 0:00 |
| **Voir séquences** | Affiche liste complète, user choisit séquence départ |
**Expiration progression** :
- Progression conservée **30 jours**
- Après 30j : popup "Audio-guide expiré. Recommencez depuis le début ?"
- Suppression données progression (mais historique "écouté" préservé)
**Justification** :
- Contexte clair : user sait exactement où il en est
- Flexibilité : reprendre OU recommencer (choix utilisateur)
- 30 jours = raisonnable pour tourisme multi-jours ou retour ultérieur
---
#### 16.6.3 Multi-device
**Scénario** :
1. User démarre audio-guide sur iPhone (séquences 1-3)
2. Progression sync cloud
3. Lendemain : user ouvre app sur iPad
4. Popup : "Reprendre Safari du Paugre sur cet appareil ?"
5. User clique Reprendre → continue séquence 4
**Conflit de version** :
- Si modifications simultanées 2 appareils (rare) : **dernière modification gagne**
- Toast : "Progression mise à jour depuis votre autre appareil"
**Justification** :
- Confort utilisateur (change d'appareil librement)
- Use case réel : planning trajet sur tablette, écoute sur smartphone en voiture
---
## Récapitulatif Section 16
| Point | Décision | Coût | Complexité |
|-------|----------|------|------------|
| **16.1** Types audio-guides | 4 modes (piéton/voiture/vélo/transport) avec détection auto | 0€ | Moyenne |
| **16.1.2** Création | Formulaire séquences + GPS + rayon + wizard guidé | 0€ | Moyenne |
| **16.2.1** Piéton - Passages | Manuel AVEC pub auto-play entre séquences, pause après | 0€ | Faible |
| **16.2.2** Piéton - Navigation | Liberté totale (skip, retour, saut direct liste) | 0€ | Faible |
| **16.3.1** Voiture - Déclenchement | GPS auto + boutons manuels actifs (warning sécurité si >10 km/h) | 0€ | Moyenne |
| **16.3.2** Voiture - Affichage | Distance + ETA + direction (flèche) + vitesse (PAS de carte) | 0€ | Faible |
| **16.3.3** Voiture - Rayon | Configurable créateur (défauts 30m/50m/100m selon mode) | 0€ | Faible |
| **16.4** Vélo & Transport | Mêmes règles avec tolérances ajustées + warning adapté | 0€ | Faible |
| **16.5** Publicités | 1/5 séquences, auto-play TOUS modes, skippable 5s | 0€ | Faible |
| **16.6.1** Sauvegarde | Complète (séquence + position + historique) local + cloud | 0€ | Faible |
| **16.6.2** Reprise | Popup intelligente avec choix (reprendre/recommencer), expiration 30j | 0€ | Faible |
| **16.6.3** Multi-device | Sync cloud PostgreSQL (reprendre sur autre appareil) | 0€ | Faible |
**Coût total MVP : 0€** (GPS natif, calcul distance PostGIS)
---
## Points d'attention pour Gherkin
- Tester 4 modes audio-guides (détection vitesse auto)
- Tester création séquences avec points GPS + rayon configurable
- Tester mode piéton : pause après séquence + pub auto-play + pause après pub + clic Suivant
- Tester navigation libre piéton (skip, retour, saut direct liste)
- Tester mode voiture : déclenchement GPS auto rayon 30m
- Tester navigation manuelle voiture : boutons actifs + warning si vitesse >10 km/h
- **Tester 1er clic Suivant (mode GPS auto) : passage mode manuel + séquence suivante immédiate + toast + timer 10s**
- **Tester 2ème clic Suivant <10s : sortie audio-guide + pause + retour flux normal**
- **Tester clics Suivant >10s : navigation séquences normale + reset timer**
- **Tester Précédent : retour séquence précédente ou replay si <10s écoulé**
- **Tester détection hors itinéraire >1km + >10min : toast + icône gris**
- **Tester détection retour sur itinéraire <100m point GPS non écouté : popup 3 boutons**
- **Tester respect clics manuels : séquences skippées volontairement non reproposées auto**
- Tester affichage distance + ETA + direction (flèche 8 directions)
- Tester rayon tolérance "point manqué" (popup 3 actions)
- Tester mode vélo (rayon 50m) et transport (rayon 100m)
- Tester insertion pub 1/5 séquences tous modes avec auto-play
- Tester sauvegarde progression locale + sync cloud
- Tester popup reprise (3 boutons : reprendre/recommencer/voir liste)
- Tester expiration progression 30 jours
- Tester multi-device : démarrer iPhone, continuer iPad
- Tester gestion conflit progression simultanée 2 appareils

View File

@@ -0,0 +1,757 @@
## 17. Contenus géolocalisés en mode voiture
### 17.1 Principe général
**Objectif** : Proposer des contenus audio au moment précis où l'utilisateur passe devant un point d'intérêt géographique, pour enrichir son trajet avec des informations contextuelles liées au paysage.
**Contrainte principale** : **Sécurité routière**
- Aucune distraction visuelle (pas de texte à lire)
- Notification sonore uniquement + icône minimale
- Validation par un seul bouton physique au volant ("Suivant")
- App doit être ouverte (premier plan)
**Distinction avec audio-guides** :
| Type | Description | Use case |
|------|-------------|----------|
| **Contenu géolocalisé simple** (cette section) | 1 point GPS unique, 1 contenu audio | Monument, panneau historique, point de vue |
| **Audio-guide multi-séquences** (section 16) | Plusieurs points GPS, séquences enchaînées | Safari-parc, circuit touristique, parcours urbain |
---
### 17.2 Détection et notification
#### 17.2.1 Calcul ETA (Estimated Time of Arrival)
**Méthode** : API GPS native iOS/Android
**Principe** :
- Calcul temps d'arrivée au point GPS basé sur vitesse actuelle et distance
- Notification déclenchée **7 secondes avant** d'atteindre le point
- Permet au conducteur d'avoir le temps de réagir (2s) + décompte (5s)
**Implémentation iOS (Swift)** :
```swift
import CoreLocation
class GeoContentDetector {
let locationManager = CLLocationManager()
var geoContents: [GeoContent] = [] // Points GPS proches
func calculateETA(to targetLocation: CLLocation) -> TimeInterval? {
guard let currentLocation = locationManager.location,
let currentSpeed = currentLocation.speed, // m/s
currentSpeed > 0 else { return nil }
let distance = currentLocation.distance(from: targetLocation) // mètres
let eta = distance / currentSpeed // secondes
return eta
}
func checkTriggers() {
// Appelé toutes les 1 seconde
for geoContent in geoContents {
let targetLocation = CLLocation(
latitude: geoContent.latitude,
longitude: geoContent.longitude
)
// Cas particulier : vitesse très faible
if let speed = locationManager.location?.speed,
speed < 1.4, // < 5 km/h
let distance = locationManager.location?.distance(from: targetLocation),
distance < 50 {
triggerNotification(for: geoContent)
continue
}
// Cas normal : calcul ETA
if let eta = calculateETA(to: targetLocation),
eta <= 7.0 && eta > 0 {
triggerNotification(for: geoContent)
}
}
}
}
```
**Implémentation Android (Kotlin)** :
```kotlin
import android.location.Location
import com.google.android.gms.location.FusedLocationProviderClient
class GeoContentDetector(private val fusedLocationClient: FusedLocationProviderClient) {
private var geoContents: List<GeoContent> = emptyList()
fun calculateETA(targetLocation: Location, currentLocation: Location): Double? {
val speed = currentLocation.speed // m/s
if (speed <= 0) return null
val distance = currentLocation.distanceTo(targetLocation) // mètres
return distance / speed // secondes
}
fun checkTriggers(currentLocation: Location) {
for (geoContent in geoContents) {
val targetLocation = Location("").apply {
latitude = geoContent.latitude
longitude = geoContent.longitude
}
// Cas particulier : vitesse très faible (< 5 km/h)
if (currentLocation.speed < 1.4 &&
currentLocation.distanceTo(targetLocation) < 50) {
triggerNotification(geoContent)
continue
}
// Cas normal : calcul ETA
val eta = calculateETA(targetLocation, currentLocation)
if (eta != null && eta <= 7.0 && eta > 0) {
triggerNotification(geoContent)
}
}
}
}
```
**Cas particulier : vitesse nulle ou très faible** :
- Si vitesse < 5 km/h (1.4 m/s) ET distance < 50m → notification immédiate
- Exemple : user arrêté à un feu rouge à 30m du point
- Évite notification trop tardive quand user redémarre
**Fréquence de vérification** :
- GPS updates : toutes les 1 seconde (balance batterie/précision)
- Calcul ETA : à chaque update GPS
- Notification : déclenchée immédiatement quand ETA ≤ 7s
---
#### 17.2.2 Format de la notification
**Philosophie** : **Minimalisme absolu** pour sécurité routière
**Pas de** :
- Titre texte à lire
- Description longue
- Bouton "Annuler" ou "Plus tard"
- Popup bloquante
- Image/cover du contenu
**Uniquement** :
- Son bref (notification)
- Icône selon tag du contenu
- Compteur chiffres (7→1)
**Icônes par tag** :
| Tag | Icône | Couleur |
|-----|-------|---------|
| Culture générale | 🏛️ | Bleu |
| Histoire | 📜 | Marron |
| Voyage | ✈️ | Vert |
| Famille | 👨‍👩‍👧 | Orange |
| Musique | 🎵 | Rose |
| Sport | ⚽ | Rouge |
| Technologie | 💻 | Gris |
| Automobile | 🚗 | Noir |
| Politique | 🏛️ | Bleu foncé |
| Économie | 📈 | Vert foncé |
| Cryptomonnaie | ₿ | Or |
| Santé | 🏥 | Rouge clair |
| Amour | ❤️ | Rose vif |
**Interface visuelle (minimaliste)** :
```
┌────────────────────────────────────────┐
│ [Player actuel] │
│ ▶️ Podcast en cours... │
│ 0:00 ────●──────────── 12:34 │
│ │
│ [|◀] [⏸️] [▶|] │
├────────────────────────────────────────┤
│ 🏛️ │
│ 7 │
└────────────────────────────────────────┘
```
**Évolution du compteur** :
- Affichage pendant 7 secondes
- Compteur décrémente : 7 → 6 → 5 → 4 → 3 → 2 → 1 → disparaît
- Police grande (72pt), bold, couleur blanche
- Background semi-transparent (noir 50% opacity)
**Notification sonore** :
- **Son** : bip court (0.5s) ou "ding" doux personnalisé RoadWave
- **Volume** : suit le volume système notification (indépendant du volume media)
- **Pas de vibration** : inutile en voiture (téléphone sur support)
- **Configurable** : user peut choisir dans settings :
- Bip système
- Son RoadWave personnalisé
- Muet (visuel uniquement)
---
#### 17.2.2b Conformité CarPlay / Android Auto
**Décision** : Notification sonore uniquement en mode CarPlay/Android Auto
**Contexte** :
- [CarPlay Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/carplay) interdisent les overlays qui "takeover" l'écran
- [Android Auto Media Apps Guidelines](https://developer.android.com/training/cars/media) imposent des interactions minimales
- Sécurité routière maximale = pas de distraction visuelle
**Comportement en mode CarPlay/Android Auto** :
**Désactivé** :
- Icône overlay
- Compteur visuel (7...6...5...)
- Tout élément graphique supplémentaire
**Activé uniquement** :
- Notification sonore (bip ou ding)
- Bouton "Suivant" standard (déjà présent)
**Détection automatique** :
```swift
// iOS - Détection CarPlay
import MediaPlayer
class NotificationManager {
var isCarPlayActive: Bool {
if #available(iOS 14.0, *) {
return MPNowPlayingInfoCenter.default().playbackState == .playing &&
MPPlayableContentManager.shared().context != nil
}
return false
}
func showGeoNotification(content: GeoContent) {
if isCarPlayActive {
// CarPlay : sonore uniquement
AudioServicesPlaySystemSound(1052) // Notification beep
// Pas d'overlay visuel
} else {
// Mode normal : sonore + icône + compteur
showFullNotification(content)
}
}
}
```
```kotlin
// Android - Détection Android Auto
import android.car.Car
class NotificationManager(private val context: Context) {
private fun isAndroidAutoActive(): Boolean {
val carManager = context.getSystemService(Context.CAR_SERVICE) as? Car
return carManager?.isConnected == true
}
fun showGeoNotification(content: GeoContent) {
if (isAndroidAutoActive()) {
// Android Auto : sonore uniquement
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setSound(defaultSoundUri)
.setCategory(NotificationCompat.CATEGORY_NAVIGATION)
.build()
notificationManager.notify(NOTIFICATION_ID, notification)
} else {
// Mode normal : sonore + overlay visuel
showFullNotification(content)
}
}
}
```
**Expérience utilisateur en CarPlay/Android Auto** :
1. User roule, app connectée à CarPlay/Android Auto
2. ETA 7s atteint → notification sonore uniquement (bip)
3. User entend le bip, sait qu'un contenu géolocalisé est disponible
4. User appuie sur bouton "Suivant" physique au volant
5. Décompte 5s démarre (audio continue normalement)
6. Contenu géolocalisé démarre après 5s
**Justification** :
- ✅ Conformité maximale CarPlay/Android Auto guidelines
- ✅ Sécurité routière (pas de distraction visuelle)
- ✅ User peut toujours valider via bouton "Suivant" standard
- ✅ Apps comparables (Waze, Apple Maps) utilisent alertes sonores similaires
**Alternative envisagée** : Mini-badge sur bouton "Suivant"
- Moins invasif qu'un compteur
- Mais toujours considéré comme overlay (zone grise)
- **Décision** : Privilégier conformité maximale (sonore uniquement)
---
#### 17.2.3 Décompte après validation
**User appuie sur "Suivant"** :
**Étape 1 : Transition visuelle (0.3s)**
- Icône + compteur "7" disparaissent avec fade out
- Nouveau compteur "5" apparaît (plus grand, centré)
**Étape 2 : Décompte 5 secondes**
- Compteur décrémente : 5 → 4 → 3 → 2 → 1
- Contenu actuel continue de jouer **normalement** (pas de baisse volume)
- User entend le contenu en cours pendant le décompte
**Étape 3 : Fin du décompte**
- Compteur atteint "0"
- Fade out 0.3s du contenu actuel
- Fade in 0.3s du contenu géolocalisé
- Transition audio fluide (pas de silence)
**Cas particulier : contenu actuel se termine pendant le décompte**
Exemple : User clique "Suivant", décompte démarre à 5s, mais contenu actuel se termine après 2s.
```
Timeline :
T+0s : User clique "Suivant", décompte démarre (5...4...3...)
T+2s : Contenu actuel se termine
→ Contenu suivant du buffer démarre immédiatement
→ Décompte continue (2...1...)
T+5s : Décompte atteint 0
→ Fade out du contenu buffer (celui qui a démarré à T+2s)
→ Fade in du contenu géolocalisé
```
**Justification** :
- Évite silence inconfortable pendant décompte
- User continue d'écouter du contenu intéressant
- Transition naturelle
**Interface pendant décompte** :
```
┌────────────────────────────────────────┐
│ [Player actuel] │
│ ▶️ Podcast en cours... │
│ 0:00 ────●──────────── 12:34 │
│ │
│ [|◀] [⏸️] [▶|] │
├────────────────────────────────────────┤
│ │
│ 3 │
│ │
│ Contenu géolocalisé arrive │
└────────────────────────────────────────┘
```
---
### 17.3 Limitation anti-spam
**Problème identifié** :
- Routes riches en points d'intérêt (parcours touristiques)
- Risque de notifier toutes les 30 secondes
- Fatigue utilisateur + distraction conducteur
**Solution : quota horaire + cooldown**
#### 17.3.1 Quota : 6 contenus géolocalisés par heure
**Règle** :
- Maximum **6 contenus géolocalisés** notifiés par heure
- Fenêtre glissante : calcul sur les 60 dernières minutes (pas réinitialisation à 0h)
- Si quota atteint : notifications suivantes ignorées silencieusement
**Exemple** :
```
10h05 : Contenu 1 notifié ✅ (quota : 1/6)
10h12 : Contenu 2 notifié ✅ (quota : 2/6)
10h18 : Contenu 3 notifié ✅ (quota : 3/6)
10h25 : Contenu 4 notifié ✅ (quota : 4/6)
10h33 : Contenu 5 notifié ✅ (quota : 5/6)
10h41 : Contenu 6 notifié ✅ (quota : 6/6)
10h52 : Contenu 7 détecté mais ignoré ❌ (quota atteint)
11h06 : Contenu 1 expire (>60 min) → quota libéré (5/6)
11h08 : Contenu 8 notifié ✅ (quota : 6/6)
```
**Exception : audio-guides multi-séquences** :
- Un audio-guide avec N séquences compte comme **1 seul contenu** dans le quota
- Une fois démarré, toutes ses séquences sont jouables (pas de limite)
- Exemple : audio-guide 8 séquences = 1 quota, contenus simples restants = 5
**Implémentation backend (Redis)** :
```go
// Structure quota (sorted set avec timestamps)
// Clé : user:{user_id}:geo_quota
// Valeur : ZADD avec timestamp
func CanNotify(userID string) bool {
key := fmt.Sprintf("user:%s:geo_quota", userID)
now := time.Now().Unix()
oneHourAgo := now - 3600
// Supprimer entrées > 1h
redis.ZRemRangeByScore(key, "-inf", oneHourAgo)
// Compter entrées restantes
count := redis.ZCard(key)
return count < 6
}
func RecordNotification(userID string, contentID string) {
key := fmt.Sprintf("user:%s:geo_quota", userID)
now := time.Now().Unix()
// Ajouter entrée avec timestamp actuel
redis.ZAdd(key, now, contentID)
// TTL 1h (sécurité)
redis.Expire(key, 3600)
}
```
---
#### 17.3.2 Cooldown : 10 minutes après notification ignorée
**Règle** :
- Si user ne clique pas sur "Suivant" pendant les 7 secondes
- → Cooldown de **10 minutes** activé
- → Aucune nouvelle notification pendant ce délai
- → Même si quota non atteint
**Justification** :
- User a probablement une raison de ne pas vouloir de contenu géolocalisé maintenant
- Évite harcèlement (notifications répétées ignorées)
- Respecte choix implicite de l'utilisateur
**Timeline exemple** :
```
10h00 : Notification contenu A (7s)
10h00+7s : User n'a pas cliqué → cooldown activé
10h05 : Contenu B détecté → ignoré (cooldown actif)
10h08 : Contenu C détecté → ignoré (cooldown actif)
10h10 : Cooldown expire
10h11 : Contenu D détecté → notification affichée ✅
```
**Implémentation (Redis)** :
```go
func IsInCooldown(userID string) bool {
key := fmt.Sprintf("user:%s:geo_cooldown", userID)
exists := redis.Exists(key)
return exists == 1
}
func ActivateCooldown(userID string) {
key := fmt.Sprintf("user:%s:geo_cooldown", userID)
redis.Set(key, "1", 10*time.Minute)
}
```
**Exception : notification validée (user a cliqué)** :
- Pas de cooldown si user a cliqué sur "Suivant"
- Même si user skip le contenu ensuite (pendant le décompte ou après)
- Cooldown = pénalité uniquement pour notification complètement ignorée
---
### 17.4 Navigation avec contenus géolocalisés
#### 17.4.1 Historique de navigation
**Structure** (voir [05-interactions-navigation.md](05-interactions-navigation.md#52-commande-précédent)) :
- **10 contenus maximum** en mémoire (Redis List)
- Structure : `[{content_id, position_seconds, listened_at, type}, ...]`
- FIFO : au-delà de 10, suppression du plus ancien
**Comportement avec contenus géolocalisés** :
Les contenus géolocalisés s'intègrent dans l'historique comme des contenus normaux :
```
Historique :
[
{id: "buffer_1", position: 180, type: "contextuel"},
{id: "geo_tour_eiffel", position: 42, type: "geo_anchored"},
{id: "buffer_2", position: 90, type: "neutre"},
{id: "buffer_3", position: 0, type: "contextuel"}
]
```
---
#### 17.4.2 Commande "Précédent" avec contenu géolocalisé
**Cas A : User écoute contenu géolocalisé, puis skip**
Timeline :
1. Contenu buffer_1 joue (position 3:00)
2. Notification contenu géolocalisé
3. User clique "Suivant" → décompte 5s → contenu géolocalisé démarre
4. User écoute 42 secondes du contenu géolocalisé
5. User appuie "Suivant" (skip) → contenu buffer_2 démarre
6. User appuie "Précédent" → **retour au contenu géolocalisé à 42s**
**Règle** : Comme décrit dans [05-interactions-navigation.md](05-interactions-navigation.md#52-commande-précédent) :
- Si temps écouté ≥ 10 secondes → replay contenu actuel depuis début
- Si temps écouté < 10 secondes → retour contenu précédent (position exacte)
Dans ce cas : 42s ≥ 10s → le contenu géolocalisé reprend à 42s (où user l'avait arrêté)
---
**Cas B : User ne clique pas pendant notification (ignore)**
Timeline :
1. Contenu buffer_1 joue
2. Notification contenu géolocalisé (7s)
3. User ne clique pas → notification disparaît
4. Contenu buffer_1 continue
5. User appuie "Précédent" → **retour contenu avant buffer_1**
**Règle** : Contenu géolocalisé ignoré n'entre PAS dans l'historique.
---
**Cas C : User skip pendant le décompte**
Timeline :
1. Notification contenu géolocalisé
2. User clique "Suivant" → décompte 5s démarre (5...4...3...)
3. User re-clique "Suivant" pendant le décompte → **annule le décompte**
4. Contenu suivant du buffer démarre
5. Contenu géolocalisé n'entre PAS dans l'historique
**Justification** : User a explicitement annulé, pas d'intérêt pour ce contenu.
---
### 17.5 Basculement automatique voiture ↔ piéton
**Principe** : Détection automatique du mode selon vitesse GPS, sans action utilisateur.
#### 17.5.1 Calcul vitesse moyenne
**Méthode** : Moyenne glissante sur 30 secondes
```javascript
// Historique GPS : 30 derniers points (1 point/seconde)
const gpsHistory = [
{lat: 48.8584, lon: 2.2945, speed: 8.3, timestamp: 1642861200},
{lat: 48.8585, lon: 2.2946, speed: 8.5, timestamp: 1642861201},
// ... 28 autres points
];
// Calcul vitesse moyenne
const avgSpeed = gpsHistory
.reduce((sum, point) => sum + point.speed, 0) / gpsHistory.length;
// km/h = m/s × 3.6
const avgSpeedKmh = avgSpeed * 3.6;
if (avgSpeedKmh < 5) {
mode = "piéton";
} else {
mode = "voiture";
}
```
**Hysteresis (éviter basculements intempestifs)** :
- Nouvelle vitesse doit être stable pendant **10 secondes** avant basculement
- Exemple : user passe de 20 km/h à 3 km/h (arrêt feu rouge)
- Si vitesse remonte à 20 km/h après 8s → pas de basculement
- Si vitesse reste à 3 km/h pendant 10s → basculement piéton
---
#### 17.5.2 Effets du basculement
**Passage voiture → piéton** :
| Paramètre | Avant (voiture) | Après (piéton) |
|-----------|----------------|----------------|
| **App requise** | Premier plan | Arrière-plan OK |
| **Notification** | Sonore + icône + compteur | Push système standard |
| **Rayon détection** | ETA 7s (distance variable) | 200 mètres fixes |
| **Type contenu** | Tous contenus géolocalisés | Audio-guides uniquement |
**Passage piéton → voiture** :
| Paramètre | Avant (piéton) | Après (voiture) |
|-----------|---------------|----------------|
| **App requise** | Arrière-plan OK | Premier plan |
| **Notification** | Push système standard | Sonore + icône + compteur |
| **Rayon détection** | 200 mètres fixes | ETA 7s (distance variable) |
| **Type contenu** | Audio-guides uniquement | Tous contenus géolocalisés |
**Transition fluide** :
- Pas de popup ou message à l'utilisateur
- Basculement invisible et automatique
- Permissions ajustées automatiquement (si déjà accordées)
---
### 17.6 Edge cases
#### 17.6.1 User passe trop vite devant le point (>130 km/h)
**Scénario** : Autoroute A6, vitesse 130 km/h, contenu géolocalisé détecté.
**Calcul** :
- Vitesse : 130 km/h = 36.1 m/s
- ETA 7s → distance notification : 7 × 36.1 = **252 mètres** avant le point
- User a 7s pour cliquer "Suivant"
- Décompte 5s démarre
- À 130 km/h, user parcourt : 5 × 36.1 = 180m pendant décompte
- Distance restante au démarrage contenu : 252 - 180 = **72 mètres avant le point**
**Conclusion** : Le système fonctionne même à très haute vitesse ✅
**Cas extrême : 180 km/h** (illégal mais théoriquement possible) :
- Vitesse : 180 km/h = 50 m/s
- ETA 7s → distance notification : 350m avant le point
- Décompte 5s : user parcourt 250m
- Distance restante : 350 - 250 = **100m avant le point**
- Contenu démarre encore avant le point ✅
---
#### 17.6.2 Multiples points géolocalisés proches
**Scénario** : Route départementale avec 3 châteaux espacés de 800m chacun.
```
Château A ----800m---- Château B ----800m---- Château C
```
À 50 km/h (13.9 m/s), user met 57 secondes pour parcourir 800m.
**Timeline** :
```
T+0s : Notification Château A (7s avant)
T+7s : User clique "Suivant" → décompte 5s
T+12s : Contenu Château A démarre
T+30s : Contenu Château A se termine (durée 18s)
T+30s : Retour buffer normal (contenu contextuel)
T+50s : Notification Château B devrait se déclencher
MAIS : quota 1/6 utilisé, pas de cooldown (user a validé A)
→ Notification affichée ✅
T+57s : User clique "Suivant" → contenu Château B démarre
...
```
**Problème** : Si user ignore une notification, cooldown 10 min bloque les suivantes.
**Solution proposée** : **Ajuster le cooldown selon validation précédente**
Nouvelle règle :
- Notification validée (user a cliqué) : pas de cooldown
- Notification ignorée (user n'a pas cliqué) : cooldown 10 min
- Exception : si 2+ notifications validées consécutives, cooldown réduit à 5 min
**Implémentation** :
```go
func CalculateCooldown(userID string) time.Duration {
// Récupérer historique dernières notifications
history := getNotificationHistory(userID, 3) // 3 dernières
validatedCount := 0
for _, notif := range history {
if notif.Validated {
validatedCount++
}
}
if validatedCount >= 2 {
return 5 * time.Minute // Cooldown réduit
}
return 10 * time.Minute // Cooldown standard
}
```
---
#### 17.6.3 User arrêté longtemps près d'un point (parking)
**Scénario** : User se gare à 30m d'un château, sort de voiture, visite 1h, revient.
**Problème** :
- Vitesse < 5 km/h + distance < 50m → notification immédiate
- Mais user en mode "stationnement", pas en mode "conduite"
**Solution** : **Détection mode stationnement**
Règle :
- Si vitesse < 1 km/h pendant **2 minutes** consécutives
- → Mode "stationnement" activé
- → Pas de notification de contenus géolocalisés
- → Basculement automatique en mode piéton (push arrière-plan)
**Reprise conduite** :
- Vitesse > 5 km/h pendant 10s
- → Mode "voiture" réactivé
- → Notifications reprennent (si quota non atteint)
---
### 17.7 Récapitulatif
| Point | Décision | Justification |
|-------|----------|---------------|
| **Détection** | ETA 7s avant point (API GPS native) | Précis, fiable, adapté à toutes vitesses |
| **Notification** | Sonore + icône + compteur (pas de texte) | Sécurité routière, minimalisme |
| **Validation** | Bouton "Suivant" uniquement | Un seul geste, simple, pas de distraction |
| **Décompte** | 5s avec contenu actuel qui continue | Évite silence, fluidité |
| **Quota** | 6 contenus/heure, fenêtre glissante | Anti-spam efficace |
| **Cooldown** | 10 min après notification ignorée | Respecte choix utilisateur |
| **Exception audio-guides** | Toutes séquences comptent comme 1 | Encourage création audio-guides |
| **Basculement auto** | Vitesse < 5 km/h = piéton, ≥ 5 km/h = voiture | Transparent, pas de friction |
| **Mode stationnement** | Vitesse < 1 km/h pendant 2 min → piéton | Évite notifications inutiles |
---
## Récapitulatif Section 17
| Point | Décision | Coût | Complexité |
|-------|----------|------|------------|
| **17.1** Principe général | Notification 7s avant, app ouverte requise | 0€ | Faible |
| **17.2.1** Calcul ETA | API GPS native iOS/Android | 0€ | Faible |
| **17.2.2** Format notification | Sonore + icône + compteur (minimaliste) | 0€ | Faible |
| **17.2.3** Décompte 5s | Contenu actuel continue, transition fluide | 0€ | Faible |
| **17.3.1** Quota 6/h | Redis sorted set, fenêtre glissante | 0€ | Moyenne |
| **17.3.2** Cooldown 10 min | Redis TTL après ignorance | 0€ | Faible |
| **17.4** Historique navigation | 10 contenus max, FIFO, comme contenus normaux | 0€ | Faible |
| **17.5** Basculement auto | Vitesse moyenne 30s, hysteresis 10s | 0€ | Moyenne |
| **17.6** Edge cases | Haute vitesse, points multiples, stationnement | 0€ | Moyenne |
**Coût total MVP : 0€** (GPS natif, Redis existant)
---
## Points d'attention pour Gherkin
- Tester calcul ETA à différentes vitesses (10 km/h, 50 km/h, 130 km/h)
- Tester cas vitesse < 5 km/h ET distance < 50m (notification immédiate)
- Tester affichage notification (icône + compteur, pas de texte)
- Tester validation "Suivant" → décompte 5s → contenu démarre
- Tester contenu actuel se termine pendant décompte → buffer démarre
- Tester quota 6/h : 7e notification ignorée
- Tester cooldown 10 min après notification ignorée
- Tester exception audio-guides (toutes séquences = 1 quota)
- Tester navigation "Précédent" après skip contenu géolocalisé (position sauvegardée)
- Tester basculement voiture → piéton (vitesse < 5 km/h stable 10s)
- Tester basculement piéton → voiture (vitesse ≥ 5 km/h stable 10s)
- Tester mode stationnement (vitesse < 1 km/h pendant 2 min)
- Tester multiples points proches (notifications successives si quota OK)
- Tester haute vitesse (130 km/h) : notification 252m avant, contenu démarre au bon moment

View File

@@ -0,0 +1,285 @@
## 4. Création et publication de contenu
### 4.1 Upload et encodage
**Décision** : Formats universels avec encodage asynchrone
**Formats acceptés** :
- ✅ MP3 (`.mp3`)
- ✅ AAC (`.aac`, `.m4a`)
- ❌ WAV, FLAC (trop lourds, inutiles en voiture)
**Limites** :
| Paramètre | Valeur | Justification |
|-----------|--------|---------------|
| **Taille maximale** | 200 MB | ~4h de podcast à 128 kbps |
| **Durée maximale** | 4 heures | Suffisant pour podcasts longs |
| **Validation format** | Client + backend | Double sécurité |
**Pipeline d'encodage** :
```
1. Upload fichier (MP3/AAC) → OVH Object Storage temporaire
2. Job asynchrone (worker Go + FFmpeg) :
- Validation format et intégrité
- Réencodage Opus 3 profils (24/48/64 kbps)
- Génération segments HLS (.m3u8 + .ts)
- Génération image couverture par défaut
3. Suppression fichier original (économie stockage)
4. Notification créateur : "Contenu prêt à publier"
```
**Temps d'encodage estimé** :
- Contenu 5 min → ~30 secondes
- Podcast 1h → ~5 minutes
- Podcast 4h → ~20 minutes
**Profils Opus générés** :
| Qualité | Bitrate | Usage |
|---------|---------|-------|
| Basse | 24 kbps | 2G/Edge |
| Standard | 48 kbps | 3G (défaut) |
| Haute | 64 kbps | 4G/5G |
**Écoute accélérée** :
| Vitesse | Usage |
|---------|-------|
| 0.75x | Compréhension difficile (accent, technique) |
| 1.0x | Normal (défaut) |
| 1.25x | Gain léger |
| 1.5x | Podcasts longs |
| 2.0x | Survol rapide (modérateurs) |
**Disponible pour** :
- ✅ Modérateurs (validation rapide : 30s → 15s à 2x)
- ✅ Auditeurs (tous les contenus)
- ✅ Standard industrie (YouTube, Spotify, Apple Podcasts)
**Justification** :
- **Simplicité** : 2 formats couvrent 95% des cas d'usage
- **Coût optimisé** : pas de conversion WAV/FLAC lourds
- **Stockage réduit** : suppression original après encodage
- **Scalabilité** : workers horizontalement (Kubernetes jobs)
- **Productivité** : écoute accélérée = double productivité modération
---
### 4.2 Métadonnées obligatoires
**Décision** : Minimaliste pour réduire friction
**Champs obligatoires** :
| Champ | Format | Validation |
|-------|--------|------------|
| **Titre** | 5-100 caractères | Alphanumérique + ponctuation basique |
| **Type géo** | Enum | Ancré / Contextuel / Neutre |
| **Zone diffusion** | Composite | Voir détails ci-dessous |
| **Tags** | Enum | 1 à 3 parmi liste prédéfinie |
| **Classification âge** | Enum | Tout public / 13+ / 16+ / 18+ |
**Zone de diffusion (obligatoire)** :
Options mutuellement exclusives :
- **Point GPS** : latitude + longitude + rayon (100m à 10km)
- **Ville** : sélection dans référentiel INSEE
- **Département** : sélection liste
- **Région** : sélection liste
- **National** : France entière
**Tags disponibles** (1 à 3 obligatoires) :
- Automobile
- Voyage
- Famille
- Amour
- Musique
- Économie
- Cryptomonnaie
- Politique
- Culture générale
- Sport
- Technologie
- Santé
**Champs optionnels** :
- ❌ Description (ajout ultérieur)
- ❌ Image couverture (génération auto)
**Image de couverture par défaut** :
Génération automatique selon règles :
- Icône selon type géo : 📍 Ancré / 🌍 Contextuel / 🎧 Neutre
- Couleur selon tag principal : bleu (Auto), vert (Voyage), rouge (Musique), etc.
- Format 800×800px, PNG
- Personnalisable ultérieurement (post-MVP)
**Exemple de publication** :
```
Titre : "Histoire de la Tour Eiffel"
Type géo : Ancré
Zone : Point GPS (48.8584, 2.2945, rayon 500m)
Tags : Voyage, Culture générale
Classification : Tout public
→ Image auto : 📍 fond bleu-vert (Voyage)
```
**Justification** :
- **Friction minimale** : 5 champs max = 2 min de publication
- **Publication rapide** : pas de blocage sur description/image
- **Coût 0** : pas de génération IA au MVP
- **Évolutif** : champs optionnels ajoutables ultérieurement
---
### 4.3 Validation des 3 premiers contenus
**Décision** : Validation manuelle par équipe modération RoadWave
**Processus nouveau créateur** :
1. Créateur upload ses 3 premiers contenus
2. Contenus passent en **file d'attente modération**
3. Modérateur junior RoadWave :
- Écoute 30 secondes (ou 15s à 2x)
- Vérifie métadonnées
- Valide ou rejette avec raison
4. Si accepté : contenu publié + notification créateur
5. Si refusé : notification avec raison détaillée + lien vers règles
6. Après 3 contenus validés : créateur passe en **statut vérifié**
**Critères de validation** :
| Critère | Détails |
|---------|---------|
| **Qualité audio** | Compréhensible (pas de grésillement excessif) |
| **Respect règles** | Pas de contenu prohibé évident (haine, spam, illégal) |
| **Classification âge** | Cohérente avec contenu écouté |
| **Tags pertinents** | Correspondance minimale avec contenu |
| **Zone diffusion** | Cohérente (pas "Tour Eiffel" avec zone "National") |
**Délai de validation** :
- Objectif : **24-48h** (jours ouvrés)
- Priorité : FIFO (First In First Out)
- Weekend : délai peut atteindre 72h
- Message au créateur : "Validation en cours, délai estimé 24-48h"
**Notification créateur** :
**Si accepté** :
- Email + push : "✅ Votre contenu '[Titre]' est en ligne !"
- Lien direct vers le contenu
- Compteur : "2/3 contenus validés pour devenir créateur vérifié"
**Si refusé** :
- Email + push : "❌ Contenu '[Titre]' refusé"
- Raison détaillée : "Qualité audio insuffisante" / "Tags non pertinents" / "Classification incorrecte" / etc.
- Lien vers règles de publication
- Possibilité de correction + resoumission
**Après 3 validations** :
Créateur obtient **statut "Vérifié"** :
- Badge ✓ visible sur profil
- Contenus futurs publiés **immédiatement** (modération a posteriori uniquement)
- Modération seulement si signalé par utilisateurs
**Outils modérateur** :
- Écoute accélérée (1.5x ou 2x) = double productivité
- Interface dédiée : queue de contenus à valider
- Raccourcis clavier : A (Accepter), R (Rejeter), Espace (Pause)
- Historique créateur visible (si déjà 1-2 contenus validés)
**Modération communautaire (post-MVP)** :
⚠️ **Non implémenté au MVP** (complexité juridique)
Vision future (envisageable) :
- Créateurs établis peuvent opt-in "Modérateur communautaire"
- Formation obligatoire (30 min) + quiz (80%)
- Pré-validation uniquement (validation finale toujours par équipe RoadWave)
- Compensation : badges, premium offert
- Attribution aléatoire (pas de collusion)
**Justification décision MVP** :
- **Responsabilité juridique** : plateforme reste responsable (DSA EU)
- **Qualité garantie** : modérateurs formés et mandatés
- **Anti-spam efficace** : bloque 95% des abus dès le début
- **Coût raisonnable** : 30s × 3 contenus = 1.5 min/créateur
- **UX acceptable** : délai 24-48h expliqué clairement
- **Pas de validation par pairs** au MVP = évite risques juridiques (collusion, compétence, conflits)
---
### 4.4 Modification et suppression
**Décision** : Modification métadonnées uniquement, suppression immédiate
**Modification autorisée** :
| Élément | Modifiable | Justification |
|---------|------------|---------------|
| **Titre** | ✅ | Correction coquilles |
| **Description** | ✅ | Si ajoutée ultérieurement |
| **Tags** | ✅ | Ajustement pertinence |
| **Image couverture** | ✅ | Personnalisation |
| **Audio** | ❌ | Intégrité contenu |
| **Zone diffusion** | ❌ | Évite manipulation algo |
| **Type géo** | ❌ | Évite manipulation algo |
| **Classification âge** | ❌ | Sécurité mineurs |
**Raisons restrictions** :
**Audio non modifiable** :
- Évite fraude : uploader contenu validé → remplacer par spam
- Intégrité : auditeurs doivent écouter ce qui a été validé
**Zone/Type non modifiables** :
- Évite manipulation : créer "Local Paris" → changer en "National" pour boost visibilité
- Évite abus : créer "Neutre" (faible pondération géo) → changer en "Ancré" (forte pondération)
**Classification non modifiable** :
- Évite contournement : uploader "Tout public" → passer en "18+" sans revalidation
- Sécurité : garantit que classification a été vérifiée
**Si besoin de changer audio/zone/classification** :
- Action : **Supprimer contenu + republier**
- Si créateur <3 contenus validés : retourne en file validation
- Si créateur ≥3 contenus validés : publication immédiate
**Suppression de contenu** :
| Aspect | Comportement |
|--------|--------------|
| **Délai** | Immédiat | Suppression BDD + cache sous 5 min |
| **Réversibilité** | Non | Suppression définitive |
| **Historique auditeurs** | Marqué "Contenu supprimé par créateur" | Conserve écoute dans historique |
| **Analytics plateforme** | Anonymisé et conservé | Métriques globales (RGPD compliant) |
| **Fichiers cache** | Supprimés sous 24h | Purge NGINX Cache (OVH VPS) et OVH Object Storage |
**Exemple scénario suppression** :
```
Créateur supprime podcast écouté par 1000 personnes
→ Cache/Storage : fichiers purgés sous 24h (NGINX Cache + OVH Object Storage)
→ BDD : entrée marquée "deleted", auteur anonymisé
→ Historique auditeurs : "Contenu supprimé" (conserve durée écoute pour stats)
→ Analytics : métriques globales conservées (anonymes, RGPD OK)
```
**Notifications suppression** :
- Pas de notification aux auditeurs (pour éviter effet Streisand)
- Historique reste consultable : "Vous avez écouté ce contenu le [date]"
- Si auditeur tente de réécouter : "Ce contenu n'est plus disponible"
**Justification** :
- **Simplicité** : règles claires et non-ambiguës
- **Sécurité** : évite manipulations algorithme et contournements modération
- **Contrôle créateur** : liberté totale de supprimer (RGPD)
- **Traçabilité** : historique conservé pour analytics (anonymisé)
- **Coût 0** : pas de revalidation métadonnées
---
## Récapitulatif Section 4

View File

@@ -0,0 +1,411 @@
## 18. Détection de contenu protégé par droits d'auteur
### 18.1 Périmètre et objectifs
**Décision** : Focus musique uniquement, approche minimaliste MVP
**Contenu protégé couvert** :
| Type | Couvert MVP | Justification |
|------|-------------|---------------|
| **Musique** | ✅ OUI | Risque principal (80% violations), détectable auditivement |
| **Films/séries** | ❌ NON | Rare dans podcasts audio, complexité technique élevée |
| **Livres audio** | ❌ NON | Difficile à distinguer de lecture légitime |
| **Jingles/pubs** | ❌ NON | Usage souvent transformatif, faible risque juridique |
**Objectifs** :
1. **Juridique** : Conformité droits d'auteur UE (directive 2019/790)
2. **Protection plateforme** : Éviter contentieux avec ayants droit (SACEM, labels)
3. **Qualité** : Encourager contenu original créateurs
4. **Pragmatisme** : Coût 0€ au MVP, scalable post-MVP
**Non-objectifs MVP** :
- ❌ Détection automatisée (fingerprinting audio)
- ❌ Intégration bases de données commerciales (ACRCloud, etc.)
- ❌ Détection temps réel sur lives (voir section 7)
- ❌ Système de licences musicales
---
### 18.2 Règles d'utilisation de musique
**Décision** : Tolérance 30 secondes pour extraits (fair use)
**Cas autorisés** :
| Cas d'usage | Durée max | Condition | Exemple |
|-------------|-----------|-----------|---------|
| **Citation/critique** | 30 secondes | Commentaire ajouté, contexte éditorial | Review album, analyse musicale |
| **Musique libre de droits** | Illimitée | Preuve licence si demandée | Epidemic Sound, Artlist, CC0 |
| **Musique originale** | Illimitée | Créateur = compositeur/interprète | Podcast musical créateur |
| **Domaine public** | Illimitée | Œuvre >70 ans après mort auteur | Classique pré-1950 |
**Cas interdits** :
| Violation | Description | Détection |
|-----------|-------------|-----------|
| **Musique intégrale** | Titre complet en fond ou standalone | Écoute modérateur (évident) |
| **Compilation DJ** | Mix de titres protégés sans droits | Écoute modérateur |
| **Extrait >30s** | Citation longue sans transformation | Chronométrage manuel si signalé |
| **Karaoké** | Instrumental protégé + voix créateur | Écoute modérateur (reconnaissable) |
**Exception fair use (30 secondes)** :
**Conditions cumulatives** :
1. Extrait ≤30 secondes **ET**
2. Usage transformatif : commentaire, critique, analyse **ET**
3. Pas de substitution à l'œuvre originale **ET**
4. Mention titre + artiste dans métadonnées (recommandé)
**Justification juridique** :
- Directive UE 2019/790 : exception citation à des fins de critique
- Jurisprudence FR : citation courte autorisée si justifiée
- 30s = standard industrie (YouTube, TikTok)
---
### 18.3 Processus de détection MVP
**Décision** : Validation manuelle lors des 3 premiers contenus uniquement
**Workflow intégré à la validation existante** (voir section 4.3) :
```
Upload contenu (créateur <3 validés)
File d'attente modération
Modérateur écoute 30s (déjà existant)
Vérification AJOUTÉE :
├─ Musique en fond détectée ?
│ ├─ OUI → Musique reconnaissable (titre connu) ?
│ │ ├─ OUI → Durée >30s ?
│ │ │ ├─ OUI → REFUS (violation droits)
│ │ │ └─ NON → ACCEPTÉ (fair use)
│ │ └─ NON → ACCEPTÉ (musique libre probable)
│ └─ NON → ACCEPTÉ (pas de musique)
└─
Validation normale continue
```
**Critères de détection manuelle** :
| Indicateur | Action modérateur |
|------------|-------------------|
| **Musique reconnaissable** (hit radio, classique célèbre) | Chronométrer l'extrait |
| **Extrait >30s** | Refus automatique |
| **Extrait ≤30s** | Vérifier usage transformatif (commentaire ?) |
| **Musique d'ambiance inconnue** | Accepter (probable musique libre) |
| **Doute** | Demander preuve licence au créateur |
**Après 3 contenus validés** :
- ✅ Créateur = statut "Vérifié"
- ✅ Publication immédiate sans validation préalable
- ✅ Modération **a posteriori uniquement** (si signalé)
**Justification** :
- **Coût 0€** : réutilise écoute 30s déjà effectuée
- **Scalable** : pas de validation pour créateurs établis
- **Pragmatique** : détecte violations évidentes (90% des cas)
- **Humain nécessaire** : fair use impossible à automatiser fiablement
---
### 18.4 Signalement et modération a posteriori
**Décision** : Réutilise système existant section 14
**Signalement utilisateur** :
- Catégorie existante : **🎵 Droits d'auteur**
- Formulaire : identique autres signalements
- Commentaire optionnel : "Quelle musique ? À quel timestamp ?"
**Traitement modérateur** :
1. Signalement reçu → file d'attente priorité MOYENNE (délai 24-48h)
2. Modérateur écoute le timestamp indiqué
3. Vérification :
- Musique identifiable ? → Recherche Shazam/SoundHound (outil externe)
- Durée extrait ?
- Usage transformatif (commentaire/critique) ?
4. Décision :
- **Violation confirmée** → Application sanctions (voir 18.5)
- **Fair use** → Rejet signalement
- **Doute** → Escalade modérateur senior
**Outils modérateur** :
| Outil | Usage | Coût |
|-------|-------|------|
| **Écoute manuelle** | Détection présence musique | 0€ |
| **Shazam/SoundHound** | Identification titre (externe, outil perso modérateur) | 0€ |
| **Chronomètre** | Mesure durée extrait | 0€ |
| **Notes** | Documentation décision (audit DSA) | 0€ |
**Priorité traitement** :
- Score IA : NON APPLICABLE (pas d'IA au MVP)
- Priorité : **MOYENNE** (délai 24-48h jours ouvrés)
- Escalade senior si :
- Créateur conteste avec preuve licence
- Doute fair use complexe
- Récidive (>2 violations)
---
### 18.5 Sanctions
**Décision** : Échelle progressive avec tolérance première violation
**Grille de sanctions** :
| Occurrence | Sanction | Durée | Justification |
|------------|----------|-------|---------------|
| **1ère violation** | ⚠️ Avertissement + Suppression contenu | - | Tolérance erreur bonne foi |
| **2e violation** | 🟡 Strike 1 + Suppression + Suspension upload | 3 jours | Négligence confirmée |
| **3e violation** | 🟠 Strike 2 + Suppression + Suspension upload | 7 jours | Récidive caractérisée |
| **4e violation** | 🟠 Strike 3 + Suppression + Suspension upload | 30 jours | Abus répété |
| **5e violation** | 🔴 Strike 4 + Ban définitif compte créateur | Permanent | Abus délibéré |
**Détail sanctions** :
**Avertissement (1ère fois)** :
- Suppression contenu immédiate
- Email + push + in-app : "⚠️ Contenu retiré pour violation droits d'auteur"
- Explication pédagogique : règles musique, lien vers CGU
- **Pas de strike** (tolérance)
- Créateur peut republier version corrigée
**Strike 1 (2e fois)** :
- Suppression contenu
- Strike ajouté au compteur (visible profil créateur)
- Suspension upload **3 jours**
- Email détaillé : titre détecté, timestamp, règle violée
**Strike 2 (3e fois)** :
- Idem Strike 1
- Suspension upload **7 jours**
- Warning : "Strike 2/4 - Vous approchez du seuil critique"
**Strike 3 (4e fois)** :
- Idem Strike 2
- Suspension upload **30 jours**
- Warning : "Strike 3/4 - Prochaine violation = ban définitif"
**Strike 4 - Ban définitif (5e fois)** :
- Désactivation compte créateur
- Tous contenus dépubliés
- Pas de création nouveau compte (email/téléphone blacklisté)
**Exceptions - Pas de sanction si** :
- ✅ Créateur prouve licence acquise (facture, abonnement Epidemic Sound, etc.)
- ✅ Musique = domaine public vérifié
- ✅ Musique = œuvre originale créateur (preuve registre SACEM si demandée)
- ✅ Extrait ≤30s + usage transformatif évident (critique musicale)
**Réhabilitation** :
- **-1 strike automatique** tous les **6 mois** sans nouvelle violation
- Conditions : aucun signalement validé pendant la période
- Minimum : 0 strikes (pas de valeur négative)
- Avertissement (1ère fois) ne compte pas pour la réhabilitation
**Exemple** :
```
Créateur a Strike 2 (7 jours de suspension)
→ 6 mois sans incident
→ Strike 2 devient Strike 1
→ 6 mois additionnels sans incident
→ Strike 1 effacé, compte propre
```
**Justification** :
- **Tolérance 1ère fois** : évite punir erreurs honnêtes
- **Escalade progressive** : dissuasion sans brutalité
- **4 strikes avant ban** : cohérent avec système global (sections 9, 14)
- **Réhabilitation 6 mois** : encourage bon comportement long terme
- **Conforme DSA** : sanctions proportionnées + droit d'appel
---
### 18.6 Processus d'appel
**Décision** : Réutilise système existant section 14.3.3
**Accès** :
- Bouton "Contester cette décision" dans notification sanction
- Délai : **7 jours** après notification
**Formulaire d'appel** :
Champs standards (voir section 14) **+** champs spécifiques :
| Champ additionnel | Type | Obligatoire |
|-------------------|------|-------------|
| **Preuve licence** | Upload PDF/image (facture, contrat) | ❌ Optionnel |
| **Lien source musique libre** | URL (YouTube Audio Library, etc.) | ❌ Optionnel |
| **Déclaration œuvre originale** | Checkbox | ❌ Optionnel |
**Traitement appel** :
1. Modérateur senior examine :
- Preuves fournies (licence, facture)
- Réécoute contenu (usage transformatif ?)
- Recherche musique (domaine public ?)
2. Délai : **72h** (standard section 14)
3. Décision finale :
- **Appel accepté** → Strike retiré + Contenu rétabli + Excuse formelle
- **Appel rejeté** → Sanction maintenue + Explication détaillée
**Cas particulier : Musique libre mal détectée**
Si créateur prouve musique = licence Epidemic Sound / Artlist :
- ✅ Appel automatiquement accepté
- ✅ Ajout titre à **whitelist interne** (évite futures erreurs)
- ✅ Excuse + compensation (ex: 1 mois Premium offert)
**Justification** :
- Réutilise processus éprouvé (section 14)
- Preuve licence = résout 90% des cas
- Délai 72h acceptable (pas de suspension immédiate sur appel)
---
### 18.7 Éducation créateurs
**Décision** : Prévention via documentation + tooltips
**Ressources disponibles** :
| Ressource | Contenu | Accès |
|-----------|---------|-------|
| **Page CGU dédiée** | Règles musique détaillées + exemples | Lien dans CGU + FAQ |
| **Tooltip upload** | "⚠️ Pas de musique protégée >30s" | Interface upload contenu |
| **Liste musique libre** | Liens Epidemic, Artlist, YouTube Audio Library | Page aide créateurs |
| **Exemples fair use** | Cas OK : review 20s + commentaire / Cas KO : hit complet en fond | FAQ illustrée |
**Messages préventifs** :
**Lors du premier upload** (popup) :
```
🎵 Attention aux droits d'auteur
Vous pouvez utiliser :
✅ Votre propre musique originale
✅ Musique libre de droits (Epidemic Sound, etc.)
✅ Extraits courts ≤30s pour critique/analyse
Interdit :
❌ Musique populaire en intégrale ou fond prolongé
❌ Compilation de hits sans droits
[J'ai compris] [En savoir plus]
```
**Justification** :
- Prévention > sanction (économie modération)
- Créateurs informés = moins de violations
- Coût : 0€ (documentation statique)
---
### 18.8 Évolution post-MVP
**Décision** : Audio fingerprinting open-source si besoin scalabilité
**Déclencheurs réintégration** :
1. Volume signalements "Droits d'auteur" >50/mois
2. Temps modération musique >20h/mois
3. Contentieux avec ayants droit (SACEM, labels)
**Solution technique prévue** :
| Composant | Technologie | Fonction | Coût |
|-----------|-------------|----------|------|
| **Fingerprinting** | Chromaprint (open-source) | Génération empreinte audio | 0€ |
| **Base de référence** | MusicBrainz + AcoustID | Comparaison empreintes | 0€ |
| **Matching** | Python + PostgreSQL | Détection similarité >85% | 0€ |
| **Infrastructure** | VPS 8GB RAM + 4 vCPU | Processing async | 50-100€/mois |
**Workflow automatisé** :
```
Upload contenu
Job async : extraction empreinte audio (Chromaprint)
Comparaison avec base MusicBrainz
Match >95% → FLAG automatique "Musique détectée : [Titre]"
Modérateur humain vérifie :
├─ Durée extrait ?
├─ Usage transformatif ?
└─ Décision finale (accept/reject)
```
**Limites connues** :
- ❌ Précision ~70% (vs 95%+ API commerciales)
- ❌ Base MusicBrainz incomplète (surtout hits récents)
- ❌ Faux positifs possibles (musique libre similaire)
- ✅ Mais : **gratuit** + **self-hosted** + **scalable**
**Alternative commerciale** (si budget disponible) :
- ACRCloud : 300-800€/mois, précision 95%+, base exhaustive
- Audible Magic : 500-1000€/mois, musique + films/TV
- **Décision différée** post-MVP selon besoins réels
---
## Récapitulatif Section 18
| Point | Décision | Coût |
|-------|----------|------|
| **Périmètre MVP** | Musique uniquement (80% des violations) | 0€ |
| **Tolérance** | 30 secondes pour extraits (fair use) | 0€ |
| **Détection MVP** | Manuelle lors des 3 premiers contenus | 0€ |
| **Outils modérateur** | Écoute + Shazam externe + Chronomètre | 0€ |
| **Sanctions** | Progressive : Avertissement → Strike 1 (3j) → Strike 2 (7j) → Strike 3 (30j) → Strike 4 (ban) | 0€ |
| **Appel** | Réutilise processus section 14 (délai 72h) | 0€ |
| **Éducation** | CGU + Tooltips + FAQ musique libre | 0€ |
| **Post-MVP** | Chromaprint + MusicBrainz (si >50 signalements/mois) | 50-100€/mois |
**Coût total MVP** : **0€** (validation manuelle intégrée)
**Conformité juridique** :
- ✅ Directive UE 2019/790 (droit d'auteur + exception citation)
- ✅ DSA (Digital Services Act) : modération réactive + droit d'appel
- ✅ SACEM/SDRM : protection ayants droit + processus contentieux
- ✅ Fair use : tolérance 30s conforme jurisprudence FR/UE
**Scalabilité** :
- 0-1000 contenus/mois : validation manuelle suffisante (3h/mois modération)
- 1000-10K contenus/mois : fingerprinting open-source requis
- 10K+ contenus/mois : API commerciale à considérer (ACRCloud)
**Risques identifiés** :
| Risque | Probabilité | Impact | Mitigation |
|--------|-------------|--------|------------|
| **Contentieux ayant droit** | Faible | Élevé | Réactivité suppression (<24h) + CGU claires |
| **Faux négatifs** (violation non détectée) | Moyenne | Moyen | Signalements utilisateurs + modération a posteriori |
| **Faux positifs** (musique libre bloquée) | Faible | Faible | Processus d'appel 72h + whitelist |
| **Volume signalements** | Faible | Moyen | Évolution fingerprinting si >50/mois |
---
**Lien avec autres sections** :
- Section 4.3 : Validation des 3 premiers contenus (workflow intégré)
- Section 7.2 : Interdictions lives + fingerprinting post-MVP
- Section 14 : Système modération (signalements, sanctions, appels)
**Prochaine section à clarifier** : Section 11 (Mode offline) ou Section 12 (Gestion des erreurs)

View File

@@ -0,0 +1,254 @@
## 7. Radio live
### 7.1 Démarrage d'un live
**Décision** : Buffer 15s + notification abonnés + limite 8h
**Processus de démarrage** :
1. Créateur appuie "Démarrer live" dans l'app
2. **Vérification pré-live** :
- Connexion ≥1 Mbps upload (warning si insuffisant)
- Micro autorisé
- Zone diffusion déjà définie (ville, département, région, national)
3. **Buffer initial 15 secondes** avant diffusion publique
- Créateur parle pendant 15s → accumulation buffer serveur
- Message créateur : "Live démarre dans 15s... Testez votre micro"
- Permet vérifier qualité audio avant diffusion
4. Après 15s → **Live public**, auditeurs peuvent rejoindre
**Notification abonnés** :
-**Push notification immédiate** à tous les abonnés dans la zone géographique
- Message : "🔴 [Nom créateur] est en direct : [Titre live]"
- Tap notification → ouverture app + lecture live immédiate
- **Filtrage géographique** : si abonné hors zone, pas de notif (évite frustration)
**Limite de durée** :
- **Maximum 8 heures** par session live
- Warning créateur à 7h30 : "Votre live se terminera dans 30 min"
- Si besoin continuer → arrêt + redémarrage nouveau live (évite abus ressources serveur)
**Métadonnées obligatoires** :
| Champ | Format | Validation |
|-------|--------|------------|
| **Titre** | 5-100 caractères | Ex: "Discussion politique en direct" |
| **Tags** | 1-3 centres d'intérêt | Sélection liste prédéfinie |
| **Classification âge** | Enum | Tout public / 13+ / 16+ / 18+ |
| **Zone diffusion** | Geo | Ville / Département / Région / National |
**Contenus interdits en live** :
| Type | Description | Sanction |
|------|-------------|----------|
| **Concert/spectacle** | Diffusion concert en direct depuis la salle | Strike 2 immédiat + suspension 7 jours |
| **Événement sportif payant** | Match, compétition avec droits TV | Strike 2 immédiat + suspension 7 jours |
| **Œuvre protégée** | Film, série, musique en fond sans droits | Strike 1 + suspension 3 jours + suppression live |
| **Contenu violent** | Agression, violence physique | Strike 3 immédiat + suspension 30 jours |
| **Contenu illégal** | Apologie terrorisme, pédopornographie | Strike 4 (ban définitif) + signalement autorités |
**Exemple usecase interdit** :
```
❌ Utilisateur dans salle de concert diffuse live performance
→ Violation droits d'auteur + droits de diffusion
→ Détection : modération réactive (signalements) + IA audio fingerprint
→ Sanction : Strike 2 (suspension 7 jours) + suppression live + suppression replay
```
**Détection violations** :
- **Signalement utilisateurs** : bouton "Signaler" accessible pendant live
- **IA audio fingerprint** : détection musique protégée en arrière-plan (post-MVP, voir [Section 18](18-detection-contenu-protege.md))
- **Modération réactive** : modérateurs peuvent écouter lives signalés en temps réel
- **Coupure immédiate** : modérateur peut arrêter live si contenu illégal évident
**Justification** :
- **Buffer 15s** : équilibre entre test qualité et friction minimale
- **Notification abonnés** : engagement maximal, valeur ajoutée live
- **8h max** : couvre 99% cas usage (podcasts longs, émissions radio) sans abus
- **Interdictions strictes** : protection juridique plateforme (DSA EU, droits d'auteur)
- **Coût** : WebRTC ingestion + HLS distribution (réutilise infra existante)
---
### 7.2 Arrêt du live
**Décision** : Compte à rebours 5s + tolérance déconnexion 60s + enregistrement auto
**Fin manuelle créateur** :
1. Créateur appuie "Arrêter live"
2. **Compte à rebours 5 secondes** affiché
- Message audio : "Ce live se termine dans 5... 4... 3... 2... 1"
- Permet au créateur de faire un outro propre
- Annulable pendant décompte (bouton "Annuler")
3. Timer atteint 0 → arrêt diffusion
4. **Traitement post-live automatique** démarre (voir ci-dessous)
**Fin automatique si déconnexion** :
| Durée coupure | Comportement |
|---------------|--------------|
| **<60 secondes** | Message auditeurs : "Connexion créateur perdue, reconnexion en cours..." |
| **≥60 secondes** | Arrêt automatique live + message : "Le live est terminé suite à une coupure de connexion" |
**Enregistrement automatique** :
**Obligatoire et automatique** (valeur ajoutée énorme)
**Processus** :
1. Pendant live : enregistrement continu serveur (format Opus raw)
2. Fin live → **job asynchrone** (worker Go + FFmpeg) :
- Conversion MP3 256 kbps (qualité optimale)
- Génération segments HLS (comme contenu classique)
- Normalisation volume -14 LUFS
- Détection silences prolongés (nettoyage)
3. **Publication automatique** du replay :
- Titre : "[REPLAY] [Titre live original]"
- Même zone diffusion, tags, classification
- Disponible sous **5-10 minutes** après fin live
- Type géo : automatiquement "Géo-neutre" (replay = contenu pérenne)
**Options créateur** :
| Option | Défaut | Description |
|--------|--------|-------------|
| **Publier replay automatiquement** | ✅ OUI | Désactivable avant démarrage live |
| **Supprimer replay après coup** | ✅ Possible | Suppression standard contenu |
| **Modifier replay** | ❌ Non | Intégrité enregistrement |
**Conservation fichier source** :
- Opus raw conservé **7 jours** après fin live (backup)
- Suppression automatique après 7j (économie stockage)
- Si replay supprimé par créateur → fichier raw supprimé immédiatement
**Justification** :
- **Compte à rebours 5s** : outro propre, pas de coupure brutale
- **Tolérance 60s** : évite arrêts intempestifs (tunnel, changement cellule)
- **Enregistrement auto** : valorisation contenu éphémère, génération contenu pérenne
- **MP3 256 kbps** : qualité optimale pour replay (vs 48 kbps live)
- **Coût** : stockage minimal (Opus → MP3 1× par live, puis suppression raw après 7j)
---
### 7.3 Comportement auditeur
**Décision** : Buffer 15s + continuation hors zone + reconnexion au live actuel + écoute passive uniquement
**Buffer de synchronisation** :
- **15 secondes** entre créateur et auditeurs
- Raisons :
- Stabilité réseau mobile (3G/4G fluctuant)
- Synchronisation approximative acceptable (pas besoin temps réel strict)
- Permet buffering anticiper coupures courtes (tunnels)
**Comparaison buffers** :
| Buffer | Avantages | Inconvénients | Décision |
|--------|-----------|---------------|----------|
| 5s | Quasi temps réel | Instable 3G, coupures fréquentes | ❌ |
| 10s | Bon compromis | Légèrement juste pour 3G | ❌ |
| **15s** | **Stabilité optimale 3G/4G** | Léger décalage acceptable | ✅ |
| 20s+ | Très stable | Décalage trop perceptible | ❌ |
**Zone géographique pendant live** :
-**Continuation si sortie de zone**
- Scénario : auditeur écoute live régional → sort du département → **live continue**
- Raisons :
- Pas de coupure brutale (mauvaise UX)
- Écoute engagée = terminer naturellement
- Après fin live → algo normal (pas de contenus hors zone)
**Reconnexion après coupure réseau** :
| Durée coupure | Comportement |
|---------------|--------------|
| **<90 secondes** | Reprend au live actuel (pas au buffer ancien) + saut temporel transparent |
| **≥90 secondes** | Message : "Live en cours perdu, passage au contenu suivant" + algo propose contenu normal |
**Interactions disponibles** :
**Décision ferme** : ❌ **Aucun chat en direct, ni maintenant ni dans le futur**
**Raisons** :
- **Sécurité routière** : pas de distraction en voiture (focus UX)
- **Harcèlement** : évite contenu haineux, insultes, trolling
- **Modération** : pas de coût modération temps réel (impossible à scale)
- **Simplicité** : écoute passive = expérience uniforme
**Actions autorisées pendant live** :
| Action | Disponible | Effet |
|--------|------------|-------|
| **Like** | ✅ | Bouton cœur interface mobile (véhicule arrêté) |
| **Abonnement créateur** | ✅ | Bouton profil créateur (interface mobile) |
| **Skip** | ✅ | Passe au contenu suivant, sort du live |
| **Précédent** | ❌ | Pas de sens sur live (flux temps réel) |
| **Chat** | ❌ | Jamais implémenté (décision définitive) |
| **Réactions emoji** | ❌ | Jamais implémenté (décision définitive) |
**Messages utilisateur** :
- "💬 Les discussions ne sont pas disponibles sur RoadWave pour garantir votre sécurité en voiture et éviter le harcèlement."
**Justification décision définitive** :
- **UX cohérente** : RoadWave = écoute en conduisant, pas réseau social interactif
- **Bien-être** : évite toxicité, harcèlement, haine (fléau réseaux sociaux)
- **Juridique** : pas de risque contentieux modération chat (DSA EU)
- **Coût** : 0€ infra chat, 0€ modération temps réel
- **Différenciation** : positionnement "audio safe" vs plateformes toxiques
---
### 7.4 Architecture technique
**Stack** :
```
Créateur (App mobile)
↓ WebRTC (OPUS 48 kbps)
Serveur Ingestion (Go + Pion WebRTC)
↓ Conversion temps réel
Serveur HLS (segments .ts)
↓ NGINX Cache (OVH VPS)
Auditeurs (App mobile, HLS natif)
```
**Flux détaillé** :
1. **Créateur** → WebRTC OPUS 48 kbps vers serveur Go
2. **Serveur Go** → Conversion temps réel OPUS → segments HLS (.m3u8 + .ts)
3. **NGINX Cache (OVH VPS)** → Distribution HLS avec cache
4. **Auditeurs** → Lecture HLS native iOS/Android (buffer 15s)
5. **Enregistrement parallèle** → Opus raw stocké temporairement
6. **Post-live** → Job async : Opus → MP3 256 kbps → Publication replay
**Dépendances** :
-**Pion WebRTC** (Go library, open source, MIT license)
-**FFmpeg** (conversion audio, LGPL/GPL)
-**NGINX** (cache et distribution HLS, open source)
-**OVH Object Storage** (stockage origin, compatible S3)
-**PostgreSQL + Redis** (métadonnées live + cache)
**Avantages** :
- ✅ Pas de dépendance Google/Facebook/Cloudflare (souveraineté)
- ✅ WebRTC standard ouvert (Pion = lib Go pure)
- ✅ Réutilise infra HLS existante (pas de doublon)
- ✅ NGINX Cache (OVH VPS) optimise la distribution (coût réduit)
- ✅ Scalable horizontalement (workers Go)
**Coût estimé infrastructure** :
| Phase | Utilisateurs | Infra live | Coût/mois |
|-------|--------------|------------|-----------|
| **MVP** | 0-100K | 1 instance Go (ingestion 100 lives simultanés) | +50€ (serveur) + bande passante |
| **Growth** | 100K-1M | 3-5 instances Go (500 lives simultanés) | +200€ + bande passante |
| **Scale** | 1M-10M | Kubernetes auto-scale (2000+ lives) | +1K€ + bande passante |
**Bande passante** :
- Live : 48 kbps × nb_auditeurs (via NGINX Cache, segments)
- Exemple : 100 auditeurs = 4.8 Mbps = ~2 Go/heure via cache
- Coût estimé : ~0.02€/heure pour 100 auditeurs
---
## Récapitulatif Section 7