517 lines
18 KiB
Markdown
517 lines
18 KiB
Markdown
## 5. Interactions et navigation
|
||
|
||
### 5.1 File d'attente et commande "Suivant"
|
||
|
||
**Décision** : Pré-calcul 5 contenus avec insertion prioritaire pour points géographiques
|
||
|
||
**File d'attente** :
|
||
- **5 contenus pré-calculés** en cache (Redis)
|
||
- Recalcul automatique si :
|
||
- Déplacement >10km
|
||
- Toutes les 10 minutes (rafraîchissement contenu)
|
||
- File d'attente <3 contenus restants
|
||
|
||
**Insertion prioritaire géo-ancrée (mode voiture uniquement)** :
|
||
|
||
**Détection** :
|
||
- Calcul ETA (Estimated Time of Arrival) via API GPS native iOS/Android
|
||
- Notification déclenchée **7 secondes avant** d'arriver au point GPS
|
||
- Si vitesse < 5 km/h ET distance < 50m → notification immédiate
|
||
- ⚠️ **App doit être ouverte** (pas de détection en arrière-plan en mode voiture)
|
||
|
||
**Notification** :
|
||
- **Sonore uniquement** : bip court ou son personnalisé RoadWave
|
||
- **Visuelle minimale** : icône selon type de contenu (🏛️ culture, 👨👩👧 famille, 🎵 musique, etc.)
|
||
- **Compteur visible** : 7...6...5...4...3...2...1 (décompte des secondes)
|
||
- **Pas de texte affiché** (éviter distraction conducteur)
|
||
- **Pas de bouton "Annuler"** : seul le bouton "Suivant" permet validation
|
||
|
||
**Actions utilisateur** :
|
||
1. User entend notification sonore + voit icône et compteur
|
||
2. User appuie "Suivant" dans les 7 secondes → décompte 5s démarre
|
||
3. Pendant décompte : contenu actuel continue, compteur visible (5...4...3...2...1)
|
||
4. Si contenu actuel se termine pendant décompte → contenu suivant du buffer démarre
|
||
5. À la fin du décompte → contenu géolocalisé démarre (fade out/in 0.3s)
|
||
|
||
**Si user n'appuie pas sur "Suivant"** :
|
||
- Notification disparaît après 7 secondes
|
||
- Contenu géolocalisé est perdu (pas d'insertion dans file)
|
||
- Pas de nouveau contenu géolocalisé pendant **10 minutes** (éviter spam)
|
||
|
||
**Limitation anti-spam** :
|
||
- Maximum **6 contenus géolocalisés par heure**
|
||
- Timer reset toutes les heures (rolling window)
|
||
- Exception : séquences d'un même audio-guide multi-séquences (comptent comme 1)
|
||
- Si quota atteint : notifications suivantes ignorées jusqu'à libération du quota
|
||
|
||
**Invalidation immédiate** :
|
||
- Utilisateur change ses préférences (curseurs géo/découverte/politique)
|
||
- ⚠️ **Modification bloquée si vitesse GPS >10 km/h** (sécurité routière)
|
||
- Live démarre d'un créateur suivi dans la zone
|
||
|
||
**Implémentation** :
|
||
```
|
||
Redis cache :
|
||
- Clé : user:{user_id}:queue
|
||
- Structure : [content_1, content_2, ..., content_5]
|
||
- Métadonnées : {last_lat, last_lon, computed_at, mode: "voiture"|"pieton"}
|
||
- TTL : 15 minutes
|
||
|
||
Tracking GPS temps réel (mobile) :
|
||
- Vérification toutes les 1 seconde
|
||
- Calcul ETA vers points géolocalisés proches (rayon 500m)
|
||
- Si ETA ≤ 7s → trigger notification
|
||
- Historique GPS : 30 derniers points pour calcul vitesse moyenne
|
||
|
||
Quota anti-spam (Redis) :
|
||
- Clé : user:{user_id}:geo_quota
|
||
- Structure : sorted set avec timestamps des 6 derniers contenus
|
||
- TTL : 1 heure
|
||
- Vérification avant notification : ZCOUNT pour compter contenus dernière heure
|
||
|
||
Cooldown après ignorance (Redis) :
|
||
- Clé : user:{user_id}:geo_cooldown
|
||
- TTL : 10 minutes
|
||
- Set après notification ignorée
|
||
```
|
||
|
||
**Justification** :
|
||
- **Expérience fluide** : pas de latence au clic "Suivant"
|
||
- **Réactivité géo** : contenu local inséré immédiatement
|
||
- **Coût optimisé** : recalcul uniquement si nécessaire
|
||
- **Sécurité** : pas de modification en conduite
|
||
|
||
---
|
||
|
||
### 5.1.2 Mode piéton (audio-guides)
|
||
|
||
**Décision** : Notifications push en arrière-plan avec rayon large
|
||
|
||
**Contexte** :
|
||
- Mode piéton détecté automatiquement si vitesse moyenne < 5 km/h
|
||
- Cas d'usage : visites à pied, musées, monuments, quartiers historiques
|
||
- User n'a pas besoin d'avoir l'app ouverte
|
||
- ⚠️ **Fonctionnalité optionnelle** : requiert permission "localisation en arrière-plan" (activée par user)
|
||
|
||
**Détection** :
|
||
- App peut être en arrière-plan (si permission accordée)
|
||
- Rayon de détection : **200 mètres** autour du point GPS
|
||
- Geofencing iOS/Android pour minimiser consommation batterie
|
||
- Permission demandée uniquement si user active "Notifications audio-guides piéton" dans settings
|
||
|
||
**Notification push système** :
|
||
|
||
Format :
|
||
```
|
||
Titre : "Audio-guide à proximité"
|
||
Body : "[Nom du contenu] - [Nom créateur]"
|
||
Action : Tap → ouvre app sur le contenu
|
||
```
|
||
|
||
Exemple :
|
||
```
|
||
Audio-guide à proximité
|
||
Musée du Louvre : La Joconde - @paris_museum
|
||
```
|
||
|
||
**Permissions requises** :
|
||
|
||
⚠️ **Important** : Permission "Always Location" est **optionnelle** et demandée uniquement si user active le mode piéton dans settings.
|
||
|
||
iOS (`Info.plist`) :
|
||
```xml
|
||
<key>NSLocationWhenInUseUsageDescription</key>
|
||
<string>RoadWave utilise votre position pour vous proposer des contenus audio géolocalisés adaptés à votre trajet en temps réel.</string>
|
||
|
||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||
<string>Si vous activez les notifications audio-guides piéton, RoadWave peut vous alerter lorsque vous passez près d'un monument ou musée, même quand l'app est en arrière-plan. Cette fonctionnalité est optionnelle et peut être désactivée à tout moment dans les réglages.</string>
|
||
```
|
||
|
||
Android (`AndroidManifest.xml`) :
|
||
```xml
|
||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||
```
|
||
|
||
**Disclosure avant demande permission** (Android requis, iOS recommandé) :
|
||
|
||
Écran affiché avant demande permission "Always Location" :
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ 📍 Notifications audio-guides piéton │
|
||
├────────────────────────────────────────┤
|
||
│ Pour vous alerter d'audio-guides à │
|
||
│ proximité même quand vous marchez avec │
|
||
│ l'app fermée, RoadWave a besoin de │
|
||
│ votre position en arrière-plan. │
|
||
│ │
|
||
│ Votre position sera utilisée pour : │
|
||
│ ✅ Détecter monuments à 200m │
|
||
│ ✅ Vous envoyer une notification │
|
||
│ │
|
||
│ Votre position ne sera jamais : │
|
||
│ ❌ Vendue à des tiers │
|
||
│ ❌ Utilisée pour de la publicité │
|
||
│ │
|
||
│ Cette fonctionnalité est optionnelle. │
|
||
│ Vous pouvez utiliser RoadWave sans │
|
||
│ cette permission. │
|
||
│ │
|
||
│ [Continuer] [Non merci] │
|
||
│ │
|
||
│ Plus d'infos : Politique confidentialité│
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
**Si user refuse** :
|
||
- Mode piéton désactivé (uniquement mode voiture disponible)
|
||
- App fonctionne normalement avec permission "When In Use"
|
||
- Audio-guides accessibles en mode manuel (user ouvre app, sélectionne contenu)
|
||
|
||
**Comportement après tap sur notification** :
|
||
1. User tap notification push
|
||
2. App s'ouvre sur la page du contenu
|
||
3. User peut démarrer la lecture manuellement
|
||
4. Navigation libre (voir section 16.2 pour audio-guides piéton)
|
||
|
||
**Basculement automatique voiture ↔ piéton** :
|
||
|
||
Détection par vitesse GPS moyenne sur 30 secondes :
|
||
- Vitesse < 5 km/h (stable 10s) → mode piéton
|
||
- Vitesse ≥ 5 km/h (stable 10s) → mode voiture
|
||
|
||
Changements de mode :
|
||
|
||
| Mode actuel | Vitesse détectée | Nouveau mode | Effet |
|
||
|-------------|------------------|--------------|-------|
|
||
| Piéton | ≥ 5 km/h | Voiture | Notifications push → sonores + icône (app ouverte requise) |
|
||
| Voiture | < 5 km/h | Piéton | Notifications sonores → push arrière-plan |
|
||
|
||
**Pas de popup confirmation** :
|
||
- Basculement transparent et automatique
|
||
- User n'a rien à faire
|
||
- Hysteresis (10s) pour éviter basculements intempestifs
|
||
|
||
**Quota anti-spam mode piéton** :
|
||
- Même limitation que mode voiture : **6 contenus/heure**
|
||
- Cooldown 10 min si notification ignorée (app pas ouverte après tap)
|
||
|
||
**Justification** :
|
||
- ✅ Expérience adaptée aux visites à pied (rayon large, pas de timing précis)
|
||
- ✅ Économie batterie (geofencing natif iOS/Android)
|
||
- ✅ User peut garder téléphone en poche
|
||
- ✅ Basculement automatique = pas de friction
|
||
|
||
---
|
||
|
||
### 5.2 Commande "Précédent"
|
||
|
||
**Décision** : Comportement smart selon progression écoute
|
||
|
||
**Règles** :
|
||
|
||
| Situation | Temps écouté | Action "Précédent" |
|
||
|-----------|--------------|-------------------|
|
||
| **Début de contenu** | <10 secondes | Retour au contenu précédent (position exacte) |
|
||
| **Milieu/fin** | ≥10 secondes | Replay contenu actuel depuis le début |
|
||
| **Premier de session** | N/A | Replay depuis début (rien avant) |
|
||
|
||
**Historique de navigation** :
|
||
- **10 contenus maximum** en mémoire (Redis List)
|
||
- Structure : `[{content_id, position_seconds, listened_at}, ...]`
|
||
- FIFO : au-delà de 10, suppression du plus ancien
|
||
|
||
**Exemple scénario** :
|
||
```
|
||
Utilisateur écoute :
|
||
1. Contenu A → écoute 5s → "Suivant"
|
||
2. Contenu B → écoute 2min30 → "Suivant"
|
||
3. Contenu C → écoute 5s → "Précédent"
|
||
→ Retour Contenu B à 2min30 (car >10s)
|
||
4. Sur Contenu B → "Précédent"
|
||
→ Retour Contenu A à 5s (position exacte)
|
||
```
|
||
|
||
**Interface (responsabilité front)** :
|
||
- ❌ Pas de message UI
|
||
- ✅ Progress bar revient au début ou à position exacte
|
||
- ✅ Animation fluide (transition 0.3s)
|
||
|
||
**Justification** :
|
||
- **UX intuitive** : comportement standard Spotify/YouTube
|
||
- **Pas de frustration** : si début, vraiment revenir en arrière
|
||
- **Simplicité** : règle unique (seuil 10s)
|
||
|
||
---
|
||
|
||
### 5.3 Interactions au volant : Like automatique et engagement
|
||
|
||
> ⚠️ **Architecture Decision Record** : Voir [ADR-010](../adr/010-commandes-volant.md) pour les détails techniques complets
|
||
|
||
**Décision** : Like automatique basé sur le temps d'écoute
|
||
|
||
**Problème technique identifié** :
|
||
- iOS et Android ne supportent **pas nativement** les appuis longs ou doubles-appuis sur les commandes média
|
||
- Les commandes physiques au volant varient selon les véhicules (pas de bouton "Pause" dédié sur beaucoup de modèles)
|
||
- Système de double-appui/appui long = **non-intuitif** et **risques sécurité** (regarder écran pour feedback)
|
||
|
||
---
|
||
|
||
#### Commandes au volant simplifiées
|
||
|
||
**Actions disponibles** (100% compatibles tous véhicules) :
|
||
|
||
| Commande physique | Action RoadWave |
|
||
|-------------------|-----------------|
|
||
| **Suivant** | Passer au contenu suivant |
|
||
| **Précédent** | Revenir au contenu précédent (règle 10s, voir section 5.2) |
|
||
| **Play/Pause** | Pause/reprise lecture (fade out 0.3s) |
|
||
|
||
**Aucune action complexe au volant** → Sécurité routière maximale.
|
||
|
||
---
|
||
|
||
#### Like automatique implicite
|
||
|
||
**Principe** : Le système détecte automatiquement l'intérêt utilisateur selon le temps d'écoute.
|
||
|
||
**Règles d'attribution** :
|
||
|
||
| Durée écoutée | Action automatique | Points jauge | Justification |
|
||
|---------------|-------------------|--------------|---------------|
|
||
| **≥ 80% du contenu** | Like renforcé | +2.0 | Écoute quasi-complète = fort intérêt |
|
||
| **30-79% du contenu** | Like standard | +1.0 | Écoute significative = intérêt |
|
||
| **< 30% du contenu** | Pas de like | 0 | Écoute trop courte |
|
||
| **Skip après <10s** | Signal négatif | -0.5 | Désintérêt marqué |
|
||
|
||
**Exemples concrets** :
|
||
```
|
||
Contenu de 3 minutes (180s) :
|
||
- Écoute 2min30 (83%) → Like renforcé (+2 points)
|
||
- Écoute 1min15 (42%) → Like standard (+1 point)
|
||
- Écoute 30s (17%) puis skip → Pas de like
|
||
- Skip après 5s → Signal négatif (-0.5 point)
|
||
|
||
Contenu de 15 minutes (900s) :
|
||
- Écoute 13min (87%) → Like renforcé (+2 points)
|
||
- Écoute 6min (40%) → Like standard (+1 point)
|
||
```
|
||
|
||
---
|
||
|
||
#### Actions complémentaires (mode piéton uniquement)
|
||
|
||
**Interface mobile** (vitesse < 5 km/h) :
|
||
|
||
| Action | Moyen | Effet |
|
||
|--------|-------|-------|
|
||
| **Like explicite** | Bouton cœur | +2 points jauge (même si déjà liké auto) |
|
||
| **Unlike** | Re-clic cœur (toggle) | -2 points jauge |
|
||
| **Abonnement** | Bouton "S'abonner" profil créateur | +5 points toutes jauges tags créateur |
|
||
| **Désabonnement** | Bouton "Se désabonner" | -5 points |
|
||
| **Signalement** | Menu contextuel "⋮" | Ouverture flux modération |
|
||
|
||
**Feedback visuel** :
|
||
- **Like automatique** : Badge discret "♥ Ajouté à vos favoris" (2s, bas de l'écran)
|
||
- **Like explicite** : Animation cœur rouge + vibration courte
|
||
- **Abonnement** : Animation étoile dorée + badge "Abonné ✓"
|
||
|
||
**Disponibilité** :
|
||
- ✅ Mode piéton (vitesse < 5 km/h) : toutes les actions disponibles
|
||
- ❌ Mode voiture (vitesse ≥ 5 km/h) : aucune de ces actions (sauf like automatique)
|
||
|
||
---
|
||
|
||
#### Gestion impacts jauges (algorithme)
|
||
|
||
**Like automatique** :
|
||
- Like renforcé (≥80%) → **+2% jauges** de tous les tags du contenu
|
||
- Like standard (30-79%) → **+1% jauges** des tags du contenu
|
||
- Signal négatif (skip <10s) → **-0.5% jauges** des tags du contenu
|
||
|
||
**Actions explicites** :
|
||
- Like manuel → **+2% jauges** (cumulable avec like auto)
|
||
- Unlike → **-2% jauges**
|
||
- Abonnement → **+5% toutes jauges** tags créateur
|
||
- Désabonnement → **-5% toutes jauges**
|
||
|
||
**Persistance** :
|
||
- Événements stockés en base (table `listen_events`)
|
||
- Mise à jour jauges : **immédiate** (Redis) + **async batch** (PostgreSQL)
|
||
|
||
---
|
||
|
||
#### Implémentation technique
|
||
|
||
**Backend** (Go) :
|
||
|
||
```go
|
||
type ListenEvent struct {
|
||
UserID string
|
||
ContentID string
|
||
StartedAt time.Time
|
||
StoppedAt time.Time
|
||
Duration int // secondes écoutées
|
||
ContentTotal int // durée totale contenu
|
||
Percentage float64 // duration / contentTotal * 100
|
||
Action string // "completed", "skipped", "paused"
|
||
}
|
||
|
||
func ProcessListenEvent(event ListenEvent) {
|
||
percentage := event.Percentage
|
||
|
||
// Signal négatif fort
|
||
if event.Action == "skipped" && event.Duration < 10 {
|
||
UpdateJauges(event.UserID, event.ContentID, -0.5)
|
||
return
|
||
}
|
||
|
||
// Like automatique
|
||
if percentage >= 80 {
|
||
AutoLike(event.UserID, event.ContentID, 2.0) // Renforcé
|
||
} else if percentage >= 30 {
|
||
AutoLike(event.UserID, event.ContentID, 1.0) // Standard
|
||
}
|
||
// < 30% : pas de like
|
||
}
|
||
```
|
||
|
||
**Mobile** (iOS/Android) :
|
||
|
||
```swift
|
||
// iOS - Tracking écoute
|
||
class AudioPlayerManager {
|
||
var startTime: Date?
|
||
let contentDuration: TimeInterval
|
||
|
||
func onPlay() {
|
||
startTime = Date()
|
||
}
|
||
|
||
func onStop(action: String) { // "completed" | "skipped" | "paused"
|
||
guard let start = startTime else { return }
|
||
let duration = Date().timeIntervalSince(start)
|
||
let percentage = (duration / contentDuration) * 100
|
||
|
||
// API call
|
||
API.track(ListenEvent(
|
||
contentId: currentContentId,
|
||
duration: Int(duration),
|
||
percentage: percentage,
|
||
action: action
|
||
))
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### Justification
|
||
|
||
**Avantages** :
|
||
- ✅ **Sécurité routière maximale** : aucune action complexe au volant
|
||
- ✅ **UX intuitive** : comportement standard industrie (Spotify, YouTube Music, Deezer)
|
||
- ✅ **Compatibilité 100%** : fonctionne sur tous véhicules, tous OS
|
||
- ✅ **Engagement amélioré** : tous les contenus écoutés génèrent des signaux
|
||
- ✅ **Algorithme plus précis** : données granulaires (30%, 50%, 80%, 100%)
|
||
- ✅ **Simplicité développement** : pas de workarounds complexes iOS/Android
|
||
|
||
**Inconvénients mitigés** :
|
||
- ⚠️ Pas de like explicite en conduite → **Mitigation** : like automatique + vocal (CarPlay/Android Auto)
|
||
- ⚠️ Pas d'abonnement en conduite → **Mitigation** : liste "Créateurs à découvrir" dans app
|
||
- ⚠️ Like automatique peut surprendre → **Mitigation** : onboarding clair + unlike possible
|
||
|
||
---
|
||
|
||
#### Communication utilisateurs (onboarding)
|
||
|
||
**Écran onboarding 1** :
|
||
```
|
||
🚗 Conduite sécurisée
|
||
|
||
RoadWave détecte automatiquement vos goûts
|
||
selon vos écoutes.
|
||
|
||
Plus vous écoutez longtemps, plus
|
||
l'algorithme s'améliore !
|
||
|
||
[Suivant]
|
||
```
|
||
|
||
**Écran onboarding 2** :
|
||
```
|
||
❤️ Likes automatiques
|
||
|
||
Pas besoin de liker manuellement :
|
||
si vous écoutez >50% d'un contenu,
|
||
on comprend que vous aimez !
|
||
|
||
[Suivant]
|
||
```
|
||
|
||
**Écran onboarding 3** :
|
||
```
|
||
⏸️ Commandes simples
|
||
|
||
Utilisez les boutons au volant :
|
||
• Suivant → Prochain contenu
|
||
• Précédent → Contenu d'avant
|
||
• Pause → Mettre en pause
|
||
|
||
[Commencer]
|
||
```
|
||
|
||
---
|
||
|
||
### 5.4 Lecture en boucle et enchaînement
|
||
|
||
**Décision** : Passage automatique après 2s + insertion pub paramétrable
|
||
|
||
**Fin de contenu** :
|
||
1. Audio termine → **Timer 2 secondes** démarre
|
||
2. UI overlay : "Contenu suivant dans 2s..." + barre décompte
|
||
3. Possibilité annuler : bouton "Rester sur ce contenu" (optionnel)
|
||
4. Timer atteint 0 → passage automatique au contenu suivant
|
||
|
||
**Délai selon contexte** :
|
||
|
||
| Mode | Délai | Justification |
|
||
|------|-------|---------------|
|
||
| **Standard** | 2 secondes | Temps réaction confortable |
|
||
| **Mode Kids** | 1 seconde | Attention courte enfants |
|
||
| **Live** | 0 seconde | Enchaînement immédiat |
|
||
|
||
**Insertion publicité** :
|
||
- Pub s'insère **pendant le délai de 2s** (transition naturelle)
|
||
- Fréquence : **paramétrable admin** (défaut : 1 pub / 5 contenus)
|
||
- Message : "Publicité (15s)" puis lecture pub
|
||
- ⚠️ **Jamais d'interruption** d'un contenu en cours
|
||
|
||
**Publicité skippable** :
|
||
- Durée minimale visionnage : **paramétrable** (défaut : 5 secondes)
|
||
- Bouton "Passer" apparaît après délai
|
||
- Métriques engagement : taux skip, durée écoute moyenne
|
||
- **Like et abonnement autorisés sur pub** (engagement créateur pub)
|
||
|
||
**Si aucun contenu disponible** :
|
||
1. Message : "Aucun contenu disponible dans cette zone"
|
||
2. Proposition : "Élargir la zone de recherche ?" (bouton)
|
||
3. Si accepté → relance algo avec rayon +50km
|
||
4. Sinon → lecture en pause, attente action utilisateur
|
||
|
||
**Gestion erreurs** :
|
||
- Échec chargement contenu suivant → **retry 3× avec backoff exponentiel**
|
||
- Si 3 échecs → message "Connexion instable, basculement mode offline"
|
||
- Mode offline → lecture contenus téléchargés uniquement
|
||
|
||
**Justification** :
|
||
- **Fluidité** : enchaînement naturel sans action utilisateur
|
||
- **Contrôle** : possibilité annuler pendant délai
|
||
- **Paramétrabilité pub** : évite frustration excès publicité
|
||
- **Engagement pub** : like/abonnement autorisé = monétisation créateurs pub
|
||
|
||
---
|
||
|
||
## Récapitulatif Section 5
|