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:
854
docs/domains/content/rules/audio-guides.md
Normal file
854
docs/domains/content/rules/audio-guides.md
Normal 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
|
||||
757
docs/domains/content/rules/contenus-geolocalises.md
Normal file
757
docs/domains/content/rules/contenus-geolocalises.md
Normal 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
|
||||
285
docs/domains/content/rules/creation-publication.md
Normal file
285
docs/domains/content/rules/creation-publication.md
Normal 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
|
||||
411
docs/domains/content/rules/detection-contenu-protege.md
Normal file
411
docs/domains/content/rules/detection-contenu-protege.md
Normal 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)
|
||||
254
docs/domains/content/rules/radio-live.md
Normal file
254
docs/domains/content/rules/radio-live.md
Normal 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
|
||||
Reference in New Issue
Block a user