23 KiB
15. Autres comportements
15.1 Partage de contenu
Décision : Système de partage complet avec web player
15.1.1 Bouton "Partager"
Disponibilité : Partout dans l'application
Emplacements :
- Player en lecture (bouton dans contrôles)
- Page profil créateur (sur chaque contenu)
- Liste de recherche (menu contextuel)
- Historique personnel
Icône : ⬆️ (universelle iOS/Android)
Menu options :
- Copier le lien
- SMS
- Plus... (sheet natif OS)
Justification :
- Viralité = croissance organique gratuite
- Aucune friction, partage universel
15.1.2 Comportement du lien partagé
Format URL : https://roadwave.fr/share/c/[content_id]
Comportement multi-plateforme :
User clique lien partagé
↓
Page web responsive
↓
┌─────────────────────────────────┐
│ Si app installée │
│ → Deep link (ouverture directe) │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Si app non installée │
│ → Web player + CTA téléchargement│
└─────────────────────────────────┘
Contenu de la page web :
┌───────────────────────────────────────┐
│ RoadWave │
├───────────────────────────────────────┤
│ [Image cover 16:9] │
│ │
│ 📻 Titre du contenu │
│ Par @créateur · 12 min · 🎧 2.3K │
│ │
│ 📍 Paris 5e · Ancré │
│ 🏷️ #Voyage #Histoire │
│ │
│ Description : Lorem ipsum... │
│ │
│ [▶️ Écouter maintenant] │
│ (Player HTML5 si contenu public) │
│ │
│ ────────────────────────────────── │
│ │
│ 📱 Télécharger l'app RoadWave │
│ [App Store] [Google Play] │
│ │
│ [Voir le profil de @créateur] │
└───────────────────────────────────────┘
Métadonnées Open Graph (SEO) :
<meta property="og:title" content="[Titre contenu] - RoadWave">
<meta property="og:description" content="[Description ou extrait]">
<meta property="og:image" content="[URL cover image]">
<meta property="og:audio" content="[URL audio si public]">
<meta property="og:type" content="music.song">
<meta property="og:site_name" content="RoadWave">
<meta name="twitter:card" content="player">
<meta name="twitter:player" content="https://roadwave.fr/player/[content_id]">
Deep linking :
- iOS : Universal Links (configuration
apple-app-site-association) - Android : App Links (configuration
assetlinks.json) - URL scheme :
roadwave://content/[content_id]
Justification :
- Meilleure viralité (partage social optimisé)
- SEO (contenus indexés Google)
- UX optimale (web + app)
- Coût : 0€ (backend simple + CDN existant)
15.1.3 Contenus Premium partagés
Décision : Preview 30 secondes + paywall
Comportement :
- User clique lien contenu Premium partagé
- Page web affiche badge "👑 Contenu Premium"
- Player démarre automatiquement
- Après 30 secondes exactement :
- Fade out audio (2 secondes)
- Overlay apparaît :
┌─────────────────────────────────┐
│ 👑 Contenu réservé Premium │
│ │
│ Profitez de ce contenu complet │
│ et de milliers d'autres │
│ sans publicité │
│ │
│ [Passer Premium - 4.99€/mois] │
│ [Télécharger l'app] │
└─────────────────────────────────┘
- Utilisateur peut :
- S'abonner Premium (redirection web Mangopay)
- Télécharger l'app (redirection stores)
- Rejouer les 30 premières secondes (illimité)
Tracking :
- Métriques créateur : "Partages Premium" + "Conversions Premium"
- Créateur touche sa part si conversion (70%)
Justification :
- Équilibre viralité / monétisation
- 30s = assez pour donner envie, pas assez pour satisfaire
- Protège revenus créateurs
15.2 Profil créateur
Décision : Profil public complet et transparent
15.2.1 Structure de la page profil
URL : https://roadwave.fr/@[pseudo]
Layout :
┌────────────────────────────────────────┐
│ [Photo profil 120×120] │
│ @pseudo ✓ │
│ [Badge vérifié si applicable] │
│ │
│ Bio : Lorem ipsum dolor sit amet... │
│ (300 caractères max) │
│ │
│ 🎧 1.2K abonnés │
│ 📻 42 contenus │
│ ⏱️ 18h de contenu créé │
│ 🔊 54K écoutes totales │
│ │
│ [S'abonner] [Partager profil] [•••] │
│ │
│ ──────────────────────────────────── │
│ │
│ Contenus ▼ [Plus récents ▼] │
│ │
│ ┌──────────────────────────────────┐ │
│ │ [Cover] Titre contenu 1 │ │
│ │ 12 min · 🎧 2.3K · 📍 Paris │ │
│ │ [▶️] │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ [Cover] Titre contenu 2 │ │
│ │ 8 min · 🎧 5.1K · 📍 Lyon │ │
│ │ [▶️] │ │
│ └──────────────────────────────────┘ │
│ │
│ [Charger plus] │
└────────────────────────────────────────┘
Informations affichées :
| Élément | Visibilité | Détails |
|---|---|---|
| Photo + pseudo | ✅ Public | Identité visuelle |
| Badge vérifié ✓ | ✅ Public (si applicable) | Compte authentique |
| Bio | ✅ Public | 0-300 caractères, markdown basique (gras, italique, liens) |
| Nombre abonnés | ✅ Public | Arrondi si >1000 (ex: 1.2K, 54K) |
| Nombre contenus | ✅ Public | Exact |
| Durée totale créée | ✅ Public | Arrondi en heures (ex: 18h, 142h) |
| Écoutes totales | ✅ Public | Arrondi (ex: 54K, 1.2M) |
| Liste abonnés | ❌ Privé | Protection vie privée (RGPD) |
| Revenus | ❌ Privé | Confidentialité financière |
| Localisation précise | ❌ Privé | Sécurité |
| ❌ Privé | Anti-spam |
Tri des contenus :
| Option | Comportement |
|---|---|
| Plus récents | Date publication DESC (défaut) |
| Plus populaires | Écoutes complètes × (1 + (date_publication - now) / 90 jours) |
| Plus anciens | Date publication ASC |
| Par tag | Filtre multi-sélection tags |
Recherche locale :
- Barre recherche dans profil : "Rechercher dans les contenus de @pseudo"
- Recherche full-text sur titres + descriptions
Actions menu [•••] :
- Partager profil
- Signaler profil (spam, usurpation)
- Bloquer créateur (masque tous ses contenus)
15.2.2 Statistiques publiques
Décision : Stats arrondies et motivantes
Affichage public :
| Métrique | Format affichage | Exemple |
|---|---|---|
| Abonnés | Exact si <1000, arrondi sinon | 342 / 1.2K / 54K / 1.2M |
| Écoutes totales | Arrondi dès 1000 | 842 / 5.4K / 142K / 2.1M |
| Contenus publiés | Exact | 42 contenus |
| Durée totale | Arrondi en heures | 18h / 142h de contenu |
Métriques PRIVÉES (créateur uniquement) :
| Métrique | Disponible dans dashboard créateur |
|---|---|
| Taux complétion moyen | 78% (écoutes >80% / écoutes totales) |
| Évolution abonnés | Graphique 30j / 90j / 1 an |
| Écoutes par contenu | Tableau détaillé |
| Revenus | Dashboard monétisation dédié |
| Taux conversion Premium | Partages → conversions |
| Démographie | Âge / zone géo (agrégée, anonymisée) |
Justification :
- Arrondi = évite comparaisons anxiogènes
- Preuve sociale pour nouveaux auditeurs (trust)
- Gamification douce (motivation créateurs)
- Privacy by design
15.2.3 Badge vérifié
Décision : Badge unique ✓ (vérifié officiel)
Critères d'attribution (au moins UN des critères) :
- KYC monétisation validé : identité vérifiée via Mangopay KYC
- Célébrité / Média officiel : validation manuelle équipe RoadWave
- Communauté significative : ≥10K abonnés + compte actif >6 mois
Affichage :
- Badge bleu ✓ accolé au pseudo (partout : profil, player, recherche)
- Tooltip au survol/appui long : "Compte vérifié"
Processus d'obtention :
| Type | Processus |
|---|---|
| Automatique (KYC) | Badge attribué dès validation documents Mangopay |
| Manuel (célébrité) | Formulaire demande → équipe vérifie identité → validation 48-72h |
| Automatique (10K) | Badge attribué automatiquement à 10K abonnés si compte >6 mois |
Retrait du badge :
- Suspension monétisation → badge retiré temporairement
- Strikes multiples → badge retiré définitivement
- Usurpation identité détectée → ban + retrait
Justification :
- Combat usurpations d'identité
- Trust auditeurs (surtout pour médias/personnalités)
- Simplicité (1 seul badge, pas de gamification excessive)
- Coût : 0€ (champ boolean
verifieden DB)
15.3 Recherche
Décision : Recherche full-text + géo + filtres avancés
15.3.1 Recherche par mot-clé
Implémentation : PostgreSQL full-text search (français)
Configuration technique :
-- Index full-text optimisé français
CREATE INDEX idx_content_search ON contents
USING GIN(
to_tsvector('french',
coalesce(title, '') || ' ' ||
coalesce(description, '') || ' ' ||
coalesce(creator_pseudo, '')
)
);
-- Recherche avec ranking
SELECT
c.*,
ts_rank(
to_tsvector('french', c.title || ' ' || c.description),
plainto_tsquery('french', $search_query)
) AS rank
FROM contents c
WHERE to_tsvector('french', c.title || ' ' || c.description)
@@ plainto_tsquery('french', $search_query)
ORDER BY rank DESC, listen_count DESC
LIMIT 20;
Champs indexés :
- Titre du contenu (poids × 3)
- Description (poids × 1)
- Pseudo créateur (poids × 2)
- Tags (poids × 1.5)
Fonctionnalités :
| Feature | Description |
|---|---|
| Stemming français | "voyages" trouve "voyage", "voyager", etc. |
| Correction auto | Suggestion si 0 résultat |
| Recherches populaires | "Essayez plutôt : balade paris, audio-guide louvre" |
| Historique personnel | 10 dernières recherches sauvegardées |
| Autocomplete | Suggestions pendant frappe (top 5) |
Coût : 0€ (PostgreSQL natif)
Migration future :
- Si >100K contenus : Meilisearch (typo-tolerance avancée, ~20-50€/mois)
- Si >1M contenus : Elasticsearch cluster
Justification :
- PostgreSQL full-text = performant jusqu'à 500K contenus
- Stemming français natif
- 0€, aucune dépendance externe
15.3.2 Recherche géographique
Décision : Recherche lieu + rayon paramétrable
Interface utilisateur :
┌─────────────────────────────────────┐
│ 🔍 Recherche contenu... │
├─────────────────────────────────────┤
│ <20><> Lieu │
│ [Paris, France ▼] │
│ · Autour de moi (GPS actuel) │
│ · Entrer une adresse/ville │
│ │
│ 📏 Rayon de recherche │
│ [●─────────────────] 50 km │
│ (curseur 5 km → 500 km) │
│ │
│ 🗺️ [Afficher sur carte] │
└─────────────────────────────────────┘
Géocodage :
| Service | Usage | Coût |
|---|---|---|
| Nominatim (OSM) | MVP (API publique) | 0€ (rate limit 1 req/s) |
| Nominatim self-hosted | Scale (Docker) | 20-50€/mois VPS |
| Mapbox Geocoding | Fallback premium | 0.50€ / 1000 requêtes |
Processus de recherche géo :
- User tape "Louvre" ou "Paris"
- Autocomplete via Nominatim → liste suggestions
- User sélectionne → récupération coordonnées (lat, lon)
- Requête PostGIS :
SELECT c.*,
ST_Distance(c.location::geography, ST_Point($lon, $lat)::geography) AS distance
FROM contents c
WHERE ST_DWithin(
c.location::geography,
ST_Point($lon, $lat)::geography,
$radius_meters
)
ORDER BY distance ASC;
Affichage résultats :
- Tri par défaut : distance croissante
- Indication distance : "À 2.3 km" / "À 15 km" / "À 142 km"
- Option carte : markers cliquables (clustering si >50 résultats)
Coût :
- MVP : 0€ (Nominatim public)
- Scale : 20-50€/mois (Nominatim self-hosted Docker)
Justification :
- Essentiel pour tourisme / planification trajet
- OpenStreetMap = pas de dépendance Google
- PostGIS = performant (index GIST natif)
15.3.3 Filtres avancés
Décision : 7 catégories de filtres combinables
Interface filtres :
┌─────────────────────────────────────┐
│ Filtres [×] │
├─────────────────────────────────────┤
│ Type de contenu │
│ ☐ Contenu court (<5 min) │
│ ☐ Podcast (>5 min) │
│ ☐ Radio live │
│ ☐ Audio-guide │
│ │
│ Durée │
│ ○ Toutes durées │
│ ○ <5 min │
│ ○ 5-15 min │
│ ○ 15-30 min │
│ ○ >30 min │
│ │
│ Classification âge │
│ ☐ Tout public │
│ ☐ 13+ │
│ ☐ 16+ │
│ ☐ 18+ │
│ │
│ Géo-pertinence │
│ ☐ Ancré (lieu précis) │
│ ☐ Contextuel (zone large) │
│ ☐ Neutre (national) │
│ │
│ Tags (multi-sélection) │
│ ☐ Automobile ☐ Voyage │
│ ☐ Famille ☐ Histoire │
│ ☐ Économie ☐ Sciences │
│ ... (liste complète tags) │
│ │
│ Date de publication │
│ ○ Toutes dates │
│ ○ Dernières 24h │
│ ○ Cette semaine │
│ ○ Ce mois │
│ ○ Cette année │
│ │
│ Abonnement │
│ ○ Tous les contenus │
│ ○ Gratuits uniquement │
│ ○ Premium uniquement 👑 │
│ │
│ ────────────────────────────── │
│ [Réinitialiser] [Appliquer] │
└─────────────────────────────────────┘
Options de tri :
| Tri | Algorithme |
|---|---|
| Pertinence | Score recherche × (1 + log(listen_count + 1)) |
| Popularité | Écoutes complètes derniers 30j DESC |
| Récent | Date publication DESC |
| Proximité | Distance GPS ASC (si recherche géo active) |
| Durée | Durée audio ASC ou DESC |
Sauvegarde de recherches :
- Bouton "💾 Sauvegarder cette recherche"
- Nom personnalisable : "Podcasts voyage Paris"
- Maximum 5 recherches sauvegardées
- Accès rapide : onglet "Recherches sauvegardées" dans page recherche
- Notifications optionnelles : "3 nouveaux contenus dans 'Podcasts voyage Paris'"
Performances :
-- Index composites pour filtres
CREATE INDEX idx_content_filters ON contents (
content_type,
duration,
age_rating,
geo_type,
published_at
);
-- Index GIN pour tags
CREATE INDEX idx_content_tags ON contents USING GIN(tags);
Coût : 0€ (PostgreSQL + index standards)
Justification :
- Filtres essentiels pour découvrabilité
- Combinables = puissance maximale
- Sauvegarde = gain temps utilisateurs réguliers
15.3.4 Page de résultats
Décision : Liste avec previews enrichies
Layout résultats :
┌─────────────────────────────────────────┐
│ 🔍 "voyage paris" │
│ 42 résultats · Tri : Pertinence ▼ │
│ [Filtres] [Carte] │
├─────────────────────────────────────────┤
│ ┌─────────────────────────────────────┐ │
│ │ [Cover ] Balade à Paris │ │
│ │ [16:9 ] @paris_stories ✓ │ │
│ │ [Image ] 12 min · 🎧 2.3K │ │
│ │ 📍 Paris 5e · Ancré │ │
│ │ 🏷️ #Voyage #Histoire │ │
│ │ [▶️ Écouter] [⋮] │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ [Cover ] Secrets Montmartre │ │
│ │ [16:9 ] @explore_paris │ │
│ │ [Image ] 8 min · 🎧 5.1K │ │
│ │ 📍 Paris 18e · Guide │ │
│ │ 🏷️ #Voyage #Art │ │
│ │ [▶️ Écouter] [⋮] │ │
│ └─────────────────────────────────────┘ │
│ │
│ [Charger plus] (20 suivants) │
└─────────────────────────────────────────┘
Informations par résultat :
| Élément | Affichage |
|---|---|
| Cover image | 16:9, 120×68 px, lazy loading |
| Titre | Tronqué 2 lignes max |
| Créateur | @pseudo + badge ✓ si vérifié, cliquable → profil |
| Durée | Format : "3 min" / "12 min" / "1h 24 min" |
| Écoutes | Arrondi : "2.3K" / "54K" / "1.2M" |
| Localisation | Ville + type géo (Ancré/Contextuel/Neutre) |
| Tags | Maximum 3 premiers tags |
| Badge Premium | 👑 si contenu premium |
| Distance | Si recherche géo : "À 2.3 km" |
Actions contextuelles [⋮] :
- Partager
- Ajouter à une playlist (future feature)
- Télécharger (offline)
- Signaler
Pagination :
- 20 résultats par page
- Infinite scroll (charger automatiquement si scroll >80%)
- Bouton "Charger 20 suivants" en bas (fallback si scroll auto désactivé)
Vue carte (alternative) :
- Bouton toggle "Liste / Carte"
- Map Leaflet (OpenStreetMap)
- Markers cliquables → popup avec preview
- Clustering si >50 résultats proches
Coût : 0€ (Leaflet open source + OSM tiles gratuit)
Justification :
- Équilibre information / compacité
- Lazy loading = performances
- Infinite scroll = UX moderne
Récapitulatif Section 15
| Point | Décision | Coût | Complexité |
|---|---|---|---|
| 15.1.1 Bouton partager | Disponible partout (⬆️), menu natif OS | 0€ | Faible |
| 15.1.2 Lien partagé | Web player + deep link + Open Graph SEO | 0€ | Moyenne |
| 15.1.3 Premium partagé | Preview 30s + paywall overlay | 0€ | Faible |
| 15.2.1 Page profil | Profil public complet (stats + bio + contenus + tri) | 0€ | Faible |
| 15.2.2 Stats publiques | Arrondies (abonnés, écoutes, durée totale) | 0€ | Faible |
| 15.2.3 Badge vérifié | ✓ si KYC/célébrité/>10K abonnés | 0€ | Faible |
| 15.3.1 Recherche texte | PostgreSQL full-text french + stemming | 0€ | Moyenne |
| 15.3.2 Recherche géo | Lieu + rayon (Nominatim OSM) | 0-50€/mois | Moyenne |
| 15.3.3 Filtres | 7 catégories combinables + sauvegarde recherches | 0€ | Moyenne |
| 15.3.4 Page résultats | Liste enrichie + vue carte Leaflet + infinite scroll | 0€ | Moyenne |
Coût total MVP : 0-50€/mois (Nominatim self-hosted optionnel)
Points d'attention pour Gherkin
- Tester partage contenu public vs Premium (preview 30s)
- Tester deep linking iOS/Android (ouverture app si installée)
- Tester Open Graph (aperçu correct sur WhatsApp, Twitter, Facebook)
- Tester profil public (stats arrondies, badge vérifié)
- Tester recherche full-text français (stemming, accents)
- Tester recherche géo + rayon (PostGIS distance)
- Tester combinaison filtres multiples (AND logic)
- Tester sauvegarde recherches (max 5)
- Tester pagination infinite scroll + fallback bouton
- Tester vue carte Leaflet (clustering, markers cliquables)