26 KiB
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) :
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) :
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 interdisent les overlays qui "takeover" l'écran
- Android Auto Media Apps Guidelines 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 :
// 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)
}
}
}
// 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 :
- User roule, app connectée à CarPlay/Android Auto
- ETA 7s atteint → notification sonore uniquement (bip)
- User entend le bip, sait qu'un contenu géolocalisé est disponible
- User appuie sur bouton "Suivant" physique au volant
- Décompte 5s démarre (audio continue normalement)
- 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) :
// 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) :
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) :
- 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 :
- Contenu buffer_1 joue (position 3:00)
- Notification contenu géolocalisé
- User clique "Suivant" → décompte 5s → contenu géolocalisé démarre
- User écoute 42 secondes du contenu géolocalisé
- User appuie "Suivant" (skip) → contenu buffer_2 démarre
- User appuie "Précédent" → retour au contenu géolocalisé à 42s
Règle : Comme décrit dans 05-interactions-navigation.md :
- 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 :
- Contenu buffer_1 joue
- Notification contenu géolocalisé (7s)
- User ne clique pas → notification disparaît
- Contenu buffer_1 continue
- 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 :
- Notification contenu géolocalisé
- User clique "Suivant" → décompte 5s démarre (5...4...3...)
- User re-clique "Suivant" pendant le décompte → annule le décompte
- Contenu suivant du buffer démarre
- 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
// 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 :
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