LYRO
« Toute votre musique. Même sans réseau. »
Ce document est le dossier de conception complet : nom et positionnement, inventaire des 54 écrans, wireframes, design system, architecture technique de bout en bout, schéma de base de données, moteur de recommandation, modèle économique et roadmap. Il est écrit pour être directement exploitable par une équipe produit et une équipe technique.
Nom & shortlist
Cinq pistes, testées sur quatre critères : prononçable dans les langues cibles (FR / EN / ES / DE),
court à l'écrit (badge d'app, mot-symbole), non ambigu à l'oral (recherche vocale), et disponible
en .com ou en variante crédible.
De lyre et lyrique : la musique dans sa forme la plus ancienne, un suffixe -o moderne et neutre. Quatre lettres, deux syllabes, aucune consonne difficile — il se prononce à l'identique en français, anglais, espagnol et allemand. Le Y central donne un mot-symbole exploitable seul (un Y ambré = onde qui se sépare en deux canaux). Aucun sens parasite dans les langues cibles.
Le sillon du vinyle, et la trace que laisse votre écoute. Le plus beau conceptuellement, et le plus fort en France — mais deux syllabes nasales que le marché anglophone n'arrive pas à prononcer. À garder pour une marque franco-française ou un nom de fonctionnalité (« votre Sillon 2026 » à la place du Wrapped).
Cadence rythmique et cadence d'écoute. Le K évite la collision avec le mot commun et rend le nom déposable. Très bon pour un positionnement sport / running / focus — mais ce n'est pas notre angle, donc il tire le produit dans une direction plus étroite qu'on ne le souhaite.
Élégant, immédiatement premium, parfait pour une interface sombre par défaut. Deux problèmes : la connotation classique/jazz oriente la perception du catalogue, et le mot est long sur un écran d'accueil mobile.
L'onde sonore. Chaud, latin, quatre lettres, excellent en Espagne, en Italie et en Amérique latine. Le risque est l'encombrement : « onda » est un mot courant dans ces mêmes marchés, ce qui coûte cher en SEO et en dépôt de marque.
Décision
LYRO. C'est le seul des cinq qui soit à la fois court, international, sans
sens parasite, et porteur d'un mot-symbole autonome. Le nom de domaine de travail
lyro.lk7.fr est déjà actif ; viser lyro.app ou lyro.fm en cible.
Concept & positionnement
Lyro est le seul service qui traite le catalogue mondial et vos propres fichiers audio comme une seule et même bibliothèque — en qualité lossless, et intégralement utilisable hors-ligne.
Les trois concurrents visés ont chacun laissé une porte ouverte. Spotify a supprimé les fichiers locaux de son expérience et n'a toujours pas de lossless généralisé. Deezer a le lossless mais une application desktop qui reste une page web emballée. Napster a l'héritage et le catalogue, mais une expérience produit datée. Lyro se place exactement dans cet interstice : l'application d'un mélomane qui possède aussi de la musique, et qui refuse de perdre l'accès à sa bibliothèque parce qu'il est dans un TGV, un avion ou un parking souterrain.
Les cinq piliers produit
01 — Bibliothèque unifiée
Vos FLAC, vos raretés, vos enregistrements live rippés se rangent à côté du catalogue streamé. Même moteur de recherche, mêmes playlists, mêmes recommandations, mêmes paroles. Aucun concurrent ne le fait proprement aujourd'hui.
02 — Hors-ligne réel
L'application n'est pas dégradée sans réseau : recherche locale, files d'attente, playlists, égaliseur, paroles, statistiques — tout fonctionne. Les écritures partent dans une file de synchronisation qui se rejoue au retour du réseau.
03 — Audio honnête
Un « Passeport audio » affiche en direct le codec, le débit réel, la fréquence d'échantillonnage, la sortie matérielle et le gain de normalisation appliqué. Fin du HiFi marketing invérifiable.
04 — Écoute vivante
Le mode Party synchronise une lecture entre plusieurs appareils à la milliseconde, avec file collaborative et vote. Ce n'est pas un gadget social : c'est le mode « soirée », « voiture », « salle de sport ».
05 — Crédits & artistes
Producteur, ingénieur du son, studio, instrumentistes, samples : les crédits complets sont cliquables. Couplé à une rémunération centrée utilisateur, c'est l'argument qui fait parler les artistes de nous.
Ce que Lyro ne sera pas
Une anti-promesse claire vaut mieux qu'un produit qui fait tout à moitié. Lyro n'aura pas de fil vidéo vertical, pas de réseau social interne, pas de clips, pas de commentaires publics sur les titres. L'application s'ouvre sur de la musique et se referme sur de la musique.
Personas
| Persona | Contexte | Besoin dominant | Palier visé |
|---|---|---|---|
| Léa, 24 étudiante | Mobile 90 %, transports, colocation | Playlists, découverte, mode Party en soirée | Étudiant 5,99 € |
| Marc, 38 ingénieur | Casque filaire + DAC, 4 To de FLAC | Lossless vérifiable, import de sa collection, crédits | HiFi 16,99 € |
| Famille Diallo 2 + 3 enfants | Enceinte cuisine, tablette, voiture | Profils enfants filtrés, 6 comptes, hors-ligne pour les trajets | Famille 17,99 € |
| Sofia, 31 commerciale | 2 h de route par jour, zones blanches | Podcasts et livres audio téléchargés, reprise multi-appareils | Premium 10,99 € |
| Yanis, 19 lycéen | Android entrée de gamme, forfait limité | Gratuit, faible consommation data, découverte | Free (pubs) |
Plateformes & stack client
Le cahier des charges impose une application téléchargeable, pas un site web. Cette contrainte n'est pas cosmétique : elle décide de tout ce qui suit, parce que seule une application native peut ouvrir un flux audio exclusif vers la carte son, décoder du FLAC 24 bits sans rééchantillonnage, chiffrer des fichiers sur disque et continuer à jouer écran éteint.
Desktop — Tauri 2 + React, cœur audio en Rust
| Critère | Tauri 2 (retenu) | Electron |
|---|---|---|
| Poids de l'installeur | ~12 Mo | ~180 Mo |
| RAM au repos | ~90 Mo | ~320 Mo |
| Sortie audio bit-perfect | Oui — WASAPI exclusif, CoreAudio hog mode, ALSA direct via cpal | Non — passe par le mixeur du navigateur, rééchantillonné en 48 kHz |
| Décodage FLAC / lecture gapless | Natif via symphonia, contrôle du ring buffer | Dépend du moteur média de Chromium |
| Widevine / FairPlay | À intégrer soi-même | Fourni par Chromium (avantage réel) |
| Maturité écosystème | Correcte depuis Tauri 2 | Supérieure |
Electron gagne sur le DRM, Tauri gagne sur tout ce qui définit le produit. Comme le lossless FLAC sort de toute façon du périmètre de Widevine (voir §10), le DRM navigateur n'est pas l'argument décisif qu'il paraît. Tauri 2, donc, avec le pipeline audio entier écrit en Rust et exposé au front par des commandes IPC.
Mobile — React Native (New Architecture) + modules audio natifs
React Native plutôt que Flutter, pour une raison structurante : le monorepo. Le state, le client
API, la logique de file d'attente, le moteur de synchronisation hors-ligne et les tokens de design
sont écrits une fois en TypeScript dans packages/core et consommés par les trois
cibles. Flutter obligerait à réécrire cette couche en Dart. La lecture, elle, n'est jamais confiée
à JavaScript : un module natif par plateforme (Media3/ExoPlayer sur Android,
AVAudioEngine sur iOS) gère décodage, mise en cache, notification média et audio de fond.
lyro/
├── apps/
│ ├── desktop/ Tauri 2 · React 19 · Vite → Win, macOS, Linux
│ │ └── src-tauri/ Rust : symphonia, cpal, rubato, chiffrement offline
│ ├── mobile/ React Native 0.8x (Fabric/TurboModules)
│ │ ├── android/ Kotlin · Media3 · MediaSessionService
│ │ └── ios/ Swift · AVAudioEngine · AVAudioSession
│ └── web/ Lecteur web réduit (SEO, partage, invitations Party)
├── packages/
│ ├── core/ State (Zustand) · client API · file d'attente · sync
│ ├── ui/ Design system : tokens, primitives, icônes
│ ├── player-api/ Interface commune vers les 3 moteurs de lecture natifs
│ └── db/ Schéma SQLite local + migrations (partagé mobile/desktop)
└── services/ Backend (voir §08)
Code partagé effectif : ~68 % · UI spécifique par plateforme : ~32 %
Distribution
| Cible | Format | Canal | Mise à jour |
|---|---|---|---|
| Windows | .msi + .exe (NSIS) | Site + Microsoft Store | Tauri updater, delta signé |
| macOS | .dmg universal2 | Site (notarisé) + Mac App Store | Tauri updater / Sparkle |
| Linux | .deb, .rpm, AppImage, Flatpak | Site + Flathub | Gestionnaire de paquets |
| Android | .aab | Play Store + APK direct | Play + CodePush pour le JS |
| iOS / iPadOS | .ipa | App Store + TestFlight | App Store |
Les 15 fonctionnalités, spécifiées
| Fonction | Spécification opérationnelle | Phase |
|---|---|---|
| 1. Lecteur | Play / pause / suivant / précédent, shuffle Fisher-Yates à graine persistée (le mélange survit au redémarrage), repeat off/all/one, file d'attente réordonnable au drag, historique 200 titres. Objectifs : < 120 ms entre l'appui et le premier son, gapless réel (pré-décodage du titre N+1 pendant les 15 dernières secondes du titre N), crossfade 0–12 s, normalisation ReplayGain / EBU R128. | MVP |
| 2. Bibliothèque | Titres likés, playlists (privées, publiques, collaboratives), albums et artistes suivis, podcasts abonnés, livres audio. Tri par ajout / titre / artiste / durée / qualité. Filtres : téléchargé, lossless, jamais écouté. Import de fichiers locaux avec lecture des tags et récupération automatique des pochettes. | MVP |
| 3. Recherche | Index OpenSearch, tolérance aux fautes (fuzziness auto), translittération (« bjork » → « Björk »), recherche par paroles, recherche par crédit (« produit par Pharrell »), filtres à facettes. Résultats dès la 2ᵉ frappe, budget 80 ms P95. Un index SQLite FTS5 miroir assure la recherche hors-ligne sur la bibliothèque locale. | MVP |
| 4. Recommandations | Voir §11. Six étagères d'accueil recalculées quotidiennement + une étagère temps réel qui réagit à la session en cours. | V1 |
| 5. Radio | Radio à partir d'un titre, d'un artiste, d'une playlist ou d'une humeur. Génération kNN sur embeddings + rééquilibrage MMR pour la diversité, avec des règles dures : jamais deux titres du même artiste à moins de trois positions, 20 % de titres jamais écoutés, respect du filtre enfant. Flux infini, jusqu'à 50 titres pré-chargés hors-ligne. | V1 |
| 6. Téléchargement | Titre, album, playlist, podcast, livre audio. Qualité de téléchargement indépendante de la qualité de streaming. Auto-download des nouveaux épisodes. Reprise après coupure, téléchargement uniquement en Wi-Fi (option), purge automatique du plus ancien non écouté quand l'espace manque. Plafonds : 10 000 titres × 5 appareils. | MVP |
| 7. Qualité audio | Normal AAC-LC 128 kbps · Haute AAC-LC 256 kbps · HiFi FLAC 16 bit / 44,1 kHz · Lossless Master FLAC 24 bit / 96 kHz. Réglage séparé pour Wi-Fi, données mobiles et téléchargement. Bascule automatique à la baisse si le buffer descend sous 4 s. Passeport audio affichant la chaîne réelle jusqu'à la sortie matérielle. | MVP |
| 8. Paroles synchronisées | Format LRC enrichi, synchronisation au mot pour le catalogue prioritaire, à la ligne ailleurs. Vue plein écran, défilement fluide, clic sur une ligne pour y sauter, traduction, translittération pour le japonais et le coréen, mode karaoké en Party. Sources : fournisseur (Musixmatch / LyricFind) + contributions modérées. | V1 |
| 9. Partage | Lien universel lyro.app/t/{id} avec page web légère et bouton d'ouverture dans l'app. Visuel carré ou story généré côté client (pochette, dégradé extrait de la pochette, extrait de paroles), export MP4 de 15 s pour les stories, code Lyro scannable. | V1 |
| 10. Mode Party | Un hôte ouvre une session, les invités rejoignent par QR ou lien. Deux modes : Sync (tous les appareils jouent le même son en simultané, dérive corrigée en continu par un protocole d'horloge inspiré de NTP) et File partagée (chacun ajoute, l'hôte arbitre, vote optionnel). WebSocket + CRDT sur la file pour tolérer les déconnexions. | V2 |
| 11. Égaliseur | 10 bandes paramétriques (biquad en cascade), presets d'usine + presets utilisateur, réglages par appareil de sortie mémorisés, correction physiologique optionnelle, limiteur en sortie pour éviter l'écrêtage. DSP en Rust sur desktop, AudioEffect/AVAudioUnitEQ sur mobile. | V1 |
| 12. Podcasts & livres audio | Ingestion RSS, transcriptions cherchables, vitesse 0,5× à 3,5×, suppression des silences, saut de 15/30 s, chapitrage, minuteur de sommeil, reprise à la seconde près multi-appareils. Livres audio : chapitres, marque-pages, notes. | V2 |
| 13. Profils multiples | Jusqu'à 6 profils par compte, chacun avec sa bibliothèque, son historique et ses recommandations. Profils enfants : catalogue filtré (pas de explicit), pas de recherche libre, playlists validées par le parent, limite de temps quotidienne, code parental à 4 chiffres. | V2 |
| 14. Thèmes | Sombre (défaut), clair, système, et Immersif — l'interface adopte la teinte dominante de la pochette en cours. Six accents au choix. Contraste renforcé et réduction des animations pour l'accessibilité. | MVP |
| 15. Raccourcis | Voir §13. Entièrement remappables, plus des raccourcis globaux actifs application en arrière-plan. | MVP |
Inventaire des écrans — 54
Groupés par flux, pas numérotés : ce n'est pas une séquence, c'est un inventaire.
Entrée · 7
- Splash / restauration
- Accueil déconnecté
- Inscription (e-mail, passkey, OAuth)
- Connexion
- Onboarding : 5 genres
- Onboarding : 3 artistes
- Onboarding : qualité + hors-ligne
Navigation · 6
- Accueil (étagères)
- Recherche — état vide
- Recherche — résultats
- Explorer / Genres
- Nouveautés
- Palmarès
Bibliothèque · 8
- Bibliothèque — racine
- Titres likés
- Playlists
- Albums suivis
- Artistes suivis
- Podcasts
- Livres audio
- Fichiers locaux importés
Détail · 8
- Album
- Artiste
- Discographie
- Playlist
- Podcast (émission)
- Épisode + transcription
- Livre audio + chapitres
- Crédits d'un titre
Lecture · 7
- Mini-lecteur (barre)
- Lecteur plein écran
- File d'attente
- Paroles synchronisées
- Égaliseur
- Passeport audio
- Sortie audio / appareils
Radio & Party · 5
- Stations
- Radio en lecture
- Party — créer / rejoindre
- Party — salon & file
- Party — QR d'invitation
Hors-ligne · 3
- Téléchargements
- Gestion du stockage
- Bandeau mode hors-ligne
Compte · 10
- Sélecteur de profil
- Profil / statistiques
- Paramètres — racine
- Qualité audio
- Lecture (crossfade, gapless)
- Apparence & thèmes
- Raccourcis clavier
- Appareils liés
- Confidentialité & données
- Contrôle parental
Enfants · 3
- Accueil Kids
- Catégories Kids
- Verrou parental
Abonnement · 4
- Comparateur d'offres
- Paiement
- Gestion d'abonnement
- Factures
Système · 5
- Erreur réseau
- État vide générique
- Mise à jour disponible
- Import en cours
- Partage / visuel généré
Wireframes
┌────────────────────────────────────────────────────────────────────────────────┐
│ ‹ › ⌕ Titres, artistes, podcasts, paroles… ⬇ 3 ⦿ ◉ Léa ▾ │
├──────────────────┬─────────────────────────────────────────────────────────────┤
│ │ │
│ L Y R O │ Bonsoir Léa │
│ │ │
│ ⌂ Accueil │ ┌───────────┐┌───────────┐┌───────────┐┌───────────┐ │
│ ⌕ Recherche │ │ Reprendre ││ Mix du ││ Radio ││ Hier soir │ │
│ ▤ Bibliothèque │ │ ▶ 12:04 ││ jour #1 ││ Ambient ││ 23 tit. │ │
│ ⬇ Hors-ligne 3 │ └───────────┘└───────────┘└───────────┘└───────────┘ │
│ ⦿ Party │ │
│ │ Vos mix du jour Tout voir › │
│ ────────────────│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ + Playlist │ │ ▦ │ │ ▦ │ │ ▦ │ │ ▦ │ │ ▦ │ │ ▦ │ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ ♥ Titres likés │ Mix 1 Mix 2 Chill Focus Rap FR 2010s │
│ ▦ Nuits blanch.│ │
│ ▦ Focus deep │ Parce que vous avez écouté « Homework » Tout voir › │
│ ▦ Trajet 8h12 │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ ⬛ Mes FLAC 412 │ │ ▦ │ │ ▦ │ │ ▦ │ │ ▦ │ │ ▦ │ │ ▦ │ │
│ ◍ Podcasts 6 │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │ │
├──────────────────┴─────────────────────────────────────────────────────────────┤
│ ┌───┐ Voyager ⤫ ⏮ ⏸ ⏭ ⇄ 1:42 ━━━━━●───────── 3:47 │
│ │ ▦ │ Daft Punk · R.A.M. │
│ └───┘ ♥ ⊕ ≡ File ♫ Paroles ⊞ EQ ▣ FLAC 16/44 🔊━━━─ │
└────────────────────────────────────────────────────────────────────────────────┘
Zone 1 : navigation persistante · Zone 2 : contenu scrollable · Zone 3 : lecteur ancré
Le badge ▣ FLAC 16/44 ouvre le Passeport audio — c'est notre signature visuelle.
┌────────────────────────────────────────────────────────────────────────────────┐
│ ⌄ Lecture depuis · Random Access Memories ⋯ ⤢ │
│ │
│ ┌──────────────────────┐ Doin' it right │
│ │ │ ───────────────────────────────── │
│ │ │ Everybody will be dancing │
│ │ POCHETTE │ and we'll feeling it right │
│ │ 480 × 480 │ │
│ │ │ ▸ Everybody will be dancing │
│ │ │ and we'll doin' it right ← ligne│
│ └──────────────────────┘ active │
│ Everybody will be dancing │
│ Doin' it right and we'll feeling it right │
│ Daft Punk, Panda Bear │
│ ♥ ⊕ ⇪ Partager ⌄ Traduction ⌄ Crédits (7) │
│ │
│ 2:12 ━━━━━━━━━━━━●────────────────────── 4:11 │
│ ⤫ ⏮ ⏸ ⏭ ⇄ │
│ │
│ ▣ Passeport audio · FLAC 16 bit / 44,1 kHz · 987 kbps · WASAPI exclusif │
│ Focusrite Scarlett 2i2 · ReplayGain −3,2 dB · EQ off │
└────────────────────────────────────────────────────────────────────────────────┘
Fond : dégradé extrait des 3 couleurs dominantes de la pochette, flouté et désaturé
à 18 % — jamais assez pour que le texte perde son contraste (vérif. WCAG à l'exécution).
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Bonsoir ⚙ ⬇3 ◉ │ │ ⌄ LECTURE ⋯ │ │ ⌄ PARTY ⇪ QR│
│ │ │ │ │ │
│ ┌────┐┌────┐ │ │ ┌──────────────┐ │ │ Salon de Léa │
│ │ ▦ ││ ▦ │ │ │ │ │ │ │ ● 4 connectés │
│ │Repr││Mix1│ │ │ │ │ │ │ ◉ Léa (hôte) │
│ └────┘└────┘ │ │ │ POCHETTE │ │ │ ◉ Sam ◉ Ana │
│ ┌────┐┌────┐ │ │ │ │ │ │ ◉ Tom │
│ │ ▦ ││ ▦ │ │ │ │ │ │ │ │
│ │Radi││Hier│ │ │ └──────────────┘ │ │ Sync ●━━ 0 ms │
│ └────┘└────┘ │ │ │ │ │
│ │ │ Voyager ♥ │ │ EN COURS │
│ Vos mix du jour │ │ Daft Punk │ │ ┌──┐ Voyager │
│ ┌───┐┌───┐┌───┐ │ │ │ │ │▦ │ ajouté: Sam │
│ │ ▦ ││ ▦ ││ ▦ │ │ │ 1:42 ━━●─── 3:47 │ │ └──┘ │
│ └───┘└───┘└───┘ │ │ │ │ FILE PARTAGÉE │
│ Mix1 Mix2 Chil │ │ ⤫ ⏮ ⏸ ⏭ ⇄ │ │ ┌──┐ Around… ▲7 │
│ │ │ │ │ │▦ │ Ana │
│ Reprendre │ │ ▣FLAC ⊞EQ ≡ │ │ └──┘ │
│ ┌───┐┌───┐┌───┐ │ │ │ │ ┌──┐ One More ▲4 │
│ │ ▦ ││ ▦ ││ ▦ │ │ │ ♫ Paroles ⌃ │ │ │▦ │ Tom │
│ └───┘└───┘└───┘ │ │ │ │ └──┘ │
│ │ │ │ │ │
│┌────────────────┐│ │ │ │ [ + Ajouter ] │
││▦ Voyager ⏸ ⏭ ││ │ │ │ │
│└────────────────┘│ │ │ │ │
│ ⌂ ⌕ ▤ ⬇ │ │ │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Bottom nav 4 onglets Geste : glisser ↓ Sync affichée en ms :
+ mini-lecteur ancré pour réduire la preuve que ça marche
┌────────────────────────────────────────────────────────────────────────────────┐
│ Hors-ligne ⚙ Gérer │
│ │
│ ████████████████████████████░░░░░░░░░░░░░░░░ 38,2 Go / 64 Go │
│ ▣ Musique 24,1 Go ▣ Podcasts 9,4 Go ▣ Livres 4,7 Go ░ Libre 25,8 Go │
│ │
│ EN COURS ───────────────────────────────────────────────────────────────── │
│ ▦ Random Access Memories · Daft Punk │
│ FLAC 16/44 · 8 / 13 titres · 412 Mo ━━━━━━━━━━━●────── 62 % ⏸ ✕ │
│ │
│ TÉLÉCHARGÉ ──────────────────────────────────── Trier : Récent ▾ Filtre ▾ │
│ ▦ Titres likés 1 204 titres FLAC 16/44 11,9 Go ⬇ ✓ ⋯ │
│ ▦ Trajet 8h12 38 titres AAC 256 184 Mo ⬇ ✓ ⋯ │
│ ◍ Le Code a changé 12 épisodes AAC 128 340 Mo ⬇ ✓ ⋯ │
│ ▦ Mes FLAC (import local) 412 titres Source 6,8 Go ⬛ local │
│ │
│ RÈGLES ─────────────────────────────────────────────────────────────────── │
│ ☑ Télécharger uniquement en Wi-Fi │
│ ☑ Supprimer les épisodes écoutés après 7 jours │
│ ☑ Libérer de l'espace automatiquement sous 3 Go (le plus ancien non écouté) │
│ ☐ Télécharger automatiquement les titres likés │
│ │
│ Licence hors-ligne valide 30 j · renouvelée en silence · appareils 3 / 5 │
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────┐ ┌───────────────────────────────────┐
│ Égaliseur ⏻ Activé ⋯ │ │ ◉ Nina, 7 ans 🔒 Sortir │
│ │ │ │
│ Preset : Casque ouvert ▾ Enreg. │ │ Salut Nina ! │
│ │ │ ┌─────────┐ ┌─────────┐ │
│ +12 ┼ ▉ │ │ │ 🎵 │ │ 🐻 │ │
│ │ ▉ ▉ ▉ │ │ │ Mes │ │ Histoi- │ │
│ +6 ┼ ▉ ▉ ▉ ▉ ▉ ▉ │ │ │ chansons│ │ res │ │
│ │ ▉ ▉ ▉ ▉ ▉ ▉ ▉ ▉ │ │ └─────────┘ └─────────┘ │
│ 0 ┼─▉──▉──▉──▉──▉──▉──▉──▉──▉─ │ │ ┌─────────┐ ┌─────────┐ │
│ │ ▉ ▉ ▉ ▉ ▉ ▉ ▉ ▉ ▉ │ │ │ 🚀 │ │ 💤 │ │
│ −6 ┼ ▉ ▉ ▉ ▉ │ │ │ Danser │ │ Dormir │ │
│ │ │ │ └─────────┘ └─────────┘ │
│ −12 ┼ │ │ │
│ 32 64 125 250 500 1k 2k 4k 8k │ │ Continuer │
│ 16k │ │ ┌────┐ ┌────┐ ┌────┐ │
│ │ │ │ ▦ │ │ ▦ │ │ ▦ │ │
│ ☑ Correction physiologique │ │ └────┘ └────┘ └────┘ │
│ ☑ Limiteur (anti-écrêtage) │ │ │
│ Réglage mémorisé pour : │ │ ⏱ 42 min restantes aujourd'hui │
│ ▸ Focusrite Scarlett 2i2 │ │ │
│ │ │ ┌───────────────────────────────┐ │
│ [ Réinitialiser ] [ A/B compar. ]│ │ │ ▦ Le Roi Lion ⏸ ⏭ │ │
└────────────────────────────────────┘ │ └───────────────────────────────┘ │
Presets mémorisés par sortie audio : │ ⌂ ⌕ (désactivée) ♥ │
casque ≠ enceintes ≠ voiture. └───────────────────────────────────┘
Pas de recherche libre. Grandes cibles
tactiles. Compteur de temps visible.
Design system de l'application
Un seul fichier de tokens dans packages/ui alimente Tauri, React Native et le web.
Sombre par défaut, parce que 71 % des sessions de streaming musical ont lieu après 18 h.
Palette de base — thème sombre
fond
surface
élevée
accent
info
texte
secondaire
| Domaine | Règle |
|---|---|
| Typographie | Une seule famille variable, une grotesque à chasse légèrement resserrée (Söhne, Aeonik ou General Sans — jamais Inter, trop identifiable). Échelle : 32 / 24 / 19 / 16 / 14 / 12,5 / 11. Titres à −2 % d'interlettrage, corps à 0. Chiffres tabulaires partout où il y a des durées. |
| Grille | Base 4 px. Gouttières 16 px mobile, 24 px desktop. Rayons : 4 px (contrôles), 8 px (cartes), 12 px (feuilles modales), plein rond (avatars, pastilles). |
| Pochettes | Jamais rognées, toujours carrées, ombre portée douce, réservation d'espace avant chargement pour supprimer tout décalage. Le lecteur plein écran monte à 480 px, la carte d'accueil à 180 px, la ligne de liste à 48 px. |
| Thème Immersif | Extraction des 3 couleurs dominantes de la pochette côté client, conversion en OKLCH, puis contrainte : la luminosité du fond est plafonnée à L < 0,22 et le contraste texte/fond revérifié à chaque titre. Si le ratio descend sous 4,5:1, on retombe sur le thème neutre. La beauté ne casse jamais la lisibilité. |
| Mouvement | Durées 120 ms (retour immédiat), 220 ms (transition d'écran), 400 ms (ouverture du lecteur). Courbe cubic-bezier(.2,.8,.2,1). Transition partagée sur la pochette entre la barre et le plein écran. Tout est coupé net sous prefers-reduced-motion. |
| Micro-interactions | Le cœur du like se remplit avec un léger dépassement d'échelle. La forme d'onde du titre en cours pulse à 8 fps (assez pour vivre, assez peu pour ne pas coûter de batterie). Le bouton de téléchargement se remplit par le bas. Retour haptique léger sur mobile pour like, ajout à la file et fin de téléchargement. |
| Accessibilité | Cibles tactiles ≥ 44 px, contraste AA minimum sur tous les thèmes, navigation clavier complète au desktop, étiquettes VoiceOver/TalkBack sur chaque contrôle du lecteur, mode contraste renforcé, taille de texte respectant le réglage système jusqu'à 200 %. |
Architecture backend
CLIENTS Tauri (Win/mac/Linux) React Native (iOS/Android) Web léger
│ │ │
└────────────┬───────────┘──────────────────────┘
│ HTTPS/2 · REST + WebSocket
┌────────────▼────────────┐
│ API Gateway / BFF │ Kong · rate-limit, JWT,
│ Node 22 · NestJS │ agrégation d'étagères
└────────────┬────────────┘
┌──────────┬─────────┬────┴────┬─────────┬──────────┬──────────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌───────┐ ┌───────┐ ┌────────┐ ┌───────┐ ┌────────┐
│identity│ │catalog │ │playbk │ │entitl.│ │perso │ │social │ │billing │
│ Go │ │ Go │ │ Go │ │ Go │ │ Python │ │ Go/WS │ │ TS │
└───┬────┘ └───┬────┘ └───┬───┘ └───┬───┘ └───┬────┘ └───┬───┘ └───┬────┘
│ │ │ │ │ │ │
┌────────┴──────────┴──────────┴─────────┴────────┴──────────┴─────────┴──────┐
│ Postgres 16 (primaire + 2 réplicas) Redis 7 (cache, présence, verrous) │
│ OpenSearch (recherche) ClickHouse (événements) Qdrant (vecteurs) │
│ Kafka (bus d'événements) S3 / R2 (objets) │
└──────────────────────────────────────────────────────────────────────────────┘
│
INGESTION ──────────────────────────┴─────────────────────────────────────────
Livraison label (DDEX ERN) → validation → transcodage (FFmpeg sur k8s jobs)
→ empreinte audio + embedding (CLAP) → packaging CMAF → S3 → invalidation CDN
DIFFUSION ─────────────────────────────────────────────────────────────────────
S3/R2 (origine) → Multi-CDN (CloudFront + Bunny, bascule par mesure RUM)
→ URL signée 5 min, liée à l'appareil → client
Choix de langages, et pourquoi
| Service | Techno | Justification |
|---|---|---|
| Gateway / BFF | NestJS (TS) | Partage les types TypeScript avec les clients via un schéma OpenAPI généré. Un seul contrat, zéro dérive entre front et back. |
| identity | Go + Ory Hydra | OIDC certifié plutôt que réécrit. Passkeys WebAuthn, PKCE, tokens liés à l'appareil. |
| catalog | Go | Chemin le plus lu du système. Cache Redis agressif, réponses < 15 ms P95. |
| playback | Go | Génère les manifestes HLS et les URL signées. Sans état, autoscalable, latence critique. |
| entitlement | Go | Répond à une seule question, très souvent : « ce profil, sur cet appareil, a-t-il droit à ce palier de qualité pour ce titre dans ce pays ? » |
| personalization | Python / FastAPI | L'écosystème ML est en Python. Sert les modèles, interroge Qdrant, applique le ranker. |
| social / party | Go + WebSocket | Des connexions longues par milliers : les goroutines coûtent bien moins cher que des threads Node. |
| billing | TS + Stripe | Peu de trafic, beaucoup de règles métier. On ne réinvente pas la facturation. |
| ingestion | Python + FFmpeg | Jobs k8s : transcodage parallèle, calcul de loudness R128, empreinte, embedding. |
Pragmatisme MVP
Ne démarrez pas avec neuf services. Le MVP est un monolithe modulaire NestJS découpé en modules qui correspondent exactement à ces frontières, plus un service Python séparé pour le ML. Les frontières étant déjà tracées, l'extraction en Go se fait service par service en V1, sans réécriture. Infrastructure : k8s managé (OVH ou Scaleway pour un ancrage RGPD européen), ArgoCD, Terraform, OpenTelemetry → Grafana / Tempo / Loki.
Chaîne audio & diffusion
| Palier | Codec | Débit | Mo / heure | Conteneur | Accès |
|---|---|---|---|---|---|
| Normal | AAC-LC | 128 kbps | 56 Mo | CMAF / HLS | Free, Premium |
| Haute | AAC-LC | 256 kbps | 112 Mo | CMAF / HLS | Premium, HiFi |
| HiFi | FLAC 16 bit / 44,1 kHz | ~900 kbps | ~400 Mo | fMP4 chiffré | HiFi |
| Master | FLAC 24 bit / 96 kHz | ~2 800 kbps | ~1,2 Go | fMP4 chiffré | HiFi (si master dispo.) |
Du fichier source à la carte son
- Ingestion. Le master WAV/FLAC 24/96 arrive, on calcule le loudness intégré EBU R128, l'empreinte acoustique (déduplication) et l'embedding CLAP (cold-start des recommandations).
- Transcodage. Quatre rendus, segmentés en morceaux de 4 s alignés, packagés en CMAF pour qu'un même segment serve HLS et DASH.
- Manifeste. Le client demande
/v1/playback/{track_id}.playbackinterrogeentitlement, choisit le palier maximal autorisé, renvoie un manifeste dont chaque URL est signée pour 5 minutes et liée à l'empreinte de l'appareil. - Buffer adaptatif. Cible 30 s d'avance, plancher 4 s. Sous le plancher, on descend d'un palier ; au-dessus de 20 s stables pendant 30 s, on remonte. Le prochain titre de la file est pré-chargé sur 15 s dès que le titre courant passe 60 %.
- Décodage. Rust
symphoniasur desktop,Media3/AVAudioEnginesur mobile. Le flux entre dans un ring buffer sans allocation. - Traitement. Normalisation (gain R128 pré-calculé, jamais recalculé à la lecture), puis EQ 10 bandes en biquads cascadés, puis limiteur.
- Sortie. WASAPI exclusif / CoreAudio hog mode / ALSA hw quand l'utilisateur l'active — la fréquence de la carte son suit celle du fichier, aucun rééchantillonnage. C'est ce que le Passeport audio affiche, et c'est vérifiable.
Gapless et crossfade
Le gapless exige de connaître les délais d'encodage. On stocke encoder_delay et
padding par rendu et on rogne à l'échantillon près à la jonction. Le crossfade, lui,
est fait à l'étage de mixage : deux flux décodés en parallèle, courbe de gain égale-puissance sur
la durée choisie. Les deux sont incompatibles par nature — quand le crossfade est actif, le gapless
est ignoré, et l'interface le dit.
Hors-ligne & protection du contenu
La contrainte que personne ne documente
Widevine et FairPlay ne savent pas protéger du FLAC. Le chiffrement commun (CENC) couvre AAC et quelques autres, pas le FLAC en HLS. Tout service qui propose du lossless a donc dû construire une protection maison pour ce palier. Il faut le budgéter dès le départ, et il faut l'expliquer aux ayants droit lors de la négociation.
| Palier | Protection en streaming | Protection hors-ligne |
|---|---|---|
| Normal / Haute | Widevine L3 (Android, Windows, Linux) · FairPlay (Apple) | Licence persistante DRM, 30 jours |
| HiFi / Master | AES-128-CTR, clé délivrée par segment via jeton signé lié à l'appareil | Ré-enveloppe AES-256-GCM, clé stockée dans le Keychain / Keystore / DPAPI |
Moteur de synchronisation
Chaque client tient une base SQLite complète : catalogue des éléments connus, bibliothèque,
playlists, historique, file d'attente, réglages. Toute écriture locale est appliquée immédiatement
puis déposée dans une outbox. Au retour du réseau, l'outbox se rejoue avec un
identifiant d'idempotence par opération ; le serveur renvoie un delta depuis le dernier
sync_cursor. Les conflits se résolvent par « dernière écriture gagne » sur l'horodatage
serveur, sauf pour l'ordre des playlists qui utilise un LSEQ (index fractionnaires) — deux
personnes peuvent réordonner la même playlist collaborative hors-ligne sans que rien ne se perde.
Demande utilisateur
└─▶ Vérification entitlement (palier, pays, quota appareil, quota titres)
└─▶ Attribution d'un slot dans la file (3 téléchargements simultanés max)
└─▶ GET segments (URL signées, reprise par Range HTTP)
└─▶ Réassemblage en fichier unique
└─▶ Chiffrement AES-256-GCM · clé aléatoire par titre
└─▶ Clé enveloppée par la clé d'appareil (Keychain/
Keystore/DPAPI) et écrite dans SQLite
└─▶ Enregistrement offline_license (expire J+30)
J+23 ─▶ renouvellement silencieux en tâche de fond
J+30 ─▶ sans réseau depuis 30 j : lecture bloquée, fichier conservé,
message explicite « Reconnectez-vous une fois pour réactiver »
Résiliation ─▶ révocation immédiate, purge au prochain démarrage
Quotas : 5 appareils · 10 000 titres / appareil · délier un appareil = 1× / 30 j
Moteur de recommandation
Quatre couches. Chacune corrige une faiblesse de la précédente.
Couche 1 — Filtrage collaboratif
ALS sur retour implicite (Spark), matrice profil × titre pondérée par le taux d'achèvement d'écoute, pas par le simple clic. Réentraînement nocturne. Produit des embeddings de 128 dimensions. Faiblesse : aveugle aux nouveautés.
Couche 2 — Contenu audio
Embedding CLAP calculé à l'ingestion pour chaque titre. Résout le démarrage à froid : un titre publié il y a une heure, zéro écoute, est déjà plaçable. Faiblesse : ignore le goût.
Couche 3 — Deux tours
Tour utilisateur (historique, genres, contexte) et tour titre (embeddings des couches 1 et 2, métadonnées) projetés dans un même espace. Recherche approximative dans Qdrant : 500 candidats en moins de 10 ms.
Couche 4 — Ranker
LightGBM sur les 500 candidats, avec l'heure, le jour, l'appareil, la nouveauté, le taux de skip historique, la fatigue (un titre trop servi est pénalisé). Puis MMR pour la diversité, puis les règles dures.
Exploration
Les étagères d'accueil sont sélectionnées par un bandit contextuel (Thompson sampling) : chaque étagère est un bras, la récompense est le taux de lecture > 30 s. Une étagère qui ne marche pas descend d'elle-même, sans intervention humaine. 15 % du trafic est réservé à l'exploration pure pour ne jamais s'enfermer dans une bulle.
Chaîne de données
Client ──▶ Kafka ──▶ Flink ──┬─▶ ClickHouse (analytique, tableaux de bord)
événements topic fenêtres ├─▶ Feast/Redis (features temps réel, < 5 ms)
de lecture "plays" glissantes└─▶ S3 Parquet (entraînement nocturne)
Événements collectés : play_start, play_progress (25/50/75/95 %), skip (+ position),
like, add_to_playlist, download, search_query, shelf_impression, shelf_click.
Un skip avant 15 s est un signal négatif fort. Une écoute complétée à 95 % vaut
plus qu'un like — les gens likent peu et écoutent beaucoup.
Objectifs mesurables
| Indicateur | Référence marché | Cible V1 | Cible V2 |
|---|---|---|---|
| Taux de skip sur recommandation | ~32 % | < 30 % | < 25 % |
| Écoutes issues d'une reco | ~30 % | 25 % | 40 % |
| Artistes distincts / mois / profil | ~40 | 45 | 60 |
| Latence de génération d'une radio | — | < 400 ms | < 250 ms |
Structure de base de données
| Magasin | Rôle | Pourquoi pas Postgres |
|---|---|---|
| Postgres 16 | Comptes, catalogue, bibliothèque, playlists, abonnements, licences | — |
| ClickHouse | Événements d'écoute (~2 Md/mois à l'échelle cible), analytique, royalties | Postgres s'effondre au-delà de quelques centaines de millions de lignes append-only |
| OpenSearch | Recherche plein texte, fautes de frappe, facettes, paroles | Le tsvector ne gère ni la fuzziness ni le scoring métier |
| Redis 7 | Cache catalogue, sessions, présence Party, limitation de débit, verrous | Latence sub-milliseconde requise |
| Qdrant | Embeddings titres et profils, recherche ANN | pgvector suffit sous 5 M vecteurs, pas au-delà |
| S3 / R2 | Audio segmenté, pochettes, avatars, exports | Objets binaires |
| SQLite | Réplique locale sur chaque client (voir §10) | Embarqué |
Schéma relationnel principal
-- ══ IDENTITÉ ═════════════════════════════════════════════════════════════
CREATE TABLE accounts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email citext UNIQUE NOT NULL,
password_hash text, -- NULL si passkey/OAuth seulement
country char(2) NOT NULL, -- gouverne les droits de diffusion
status text NOT NULL DEFAULT 'active',
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE profiles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
account_id uuid NOT NULL REFERENCES accounts ON DELETE CASCADE,
display_name text NOT NULL,
avatar_url text,
kind text NOT NULL DEFAULT 'adult', -- adult | kid
birth_year smallint,
pin_hash text, -- verrou parental
explicit_ok boolean NOT NULL DEFAULT true,
daily_limit_min smallint, -- profils enfants
theme jsonb NOT NULL DEFAULT '{"mode":"dark","accent":"amber"}',
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT kid_needs_pin CHECK (kind <> 'kid' OR pin_hash IS NOT NULL)
);
CREATE INDEX ON profiles (account_id);
CREATE TABLE devices (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
account_id uuid NOT NULL REFERENCES accounts ON DELETE CASCADE,
platform text NOT NULL, -- windows|macos|linux|ios|android
name text NOT NULL,
fingerprint text NOT NULL,
public_key bytea, -- enveloppe des clés hors-ligne
last_seen_at timestamptz,
unlinked_at timestamptz,
UNIQUE (account_id, fingerprint)
);
-- ══ CATALOGUE ════════════════════════════════════════════════════════════
CREATE TABLE artists (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
sort_name text NOT NULL,
bio text,
image_url text,
verified boolean NOT NULL DEFAULT false,
external_ids jsonb NOT NULL DEFAULT '{}' -- musicbrainz, isni, spotify…
);
CREATE TABLE albums (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
title text NOT NULL,
primary_artist uuid NOT NULL REFERENCES artists,
album_type text NOT NULL, -- album|single|ep|compilation
release_date date,
label text,
upc text,
cover_url text,
cover_palette jsonb, -- 3 couleurs OKLCH, thème Immersif
total_tracks smallint
);
CREATE INDEX ON albums (primary_artist, release_date DESC);
CREATE TABLE tracks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
album_id uuid REFERENCES albums ON DELETE CASCADE,
title text NOT NULL,
disc_no smallint NOT NULL DEFAULT 1,
track_no smallint NOT NULL,
duration_ms integer NOT NULL,
isrc char(12),
explicit boolean NOT NULL DEFAULT false,
bpm numeric(5,2),
musical_key smallint, -- 0-11, pour l'enchaînement DJ
loudness_lufs numeric(5,2), -- EBU R128, normalisation
peak_dbfs numeric(5,2),
popularity smallint NOT NULL DEFAULT 0,
is_local boolean NOT NULL DEFAULT false, -- import utilisateur
owner_profile uuid REFERENCES profiles, -- non NULL si is_local
available_in char(2)[] NOT NULL DEFAULT '{}', -- restriction territoriale
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ON tracks (album_id, disc_no, track_no);
CREATE INDEX ON tracks (isrc) WHERE isrc IS NOT NULL;
CREATE INDEX ON tracks (owner_profile) WHERE is_local;
-- Un rendu encodé par palier de qualité
CREATE TABLE track_assets (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
track_id uuid NOT NULL REFERENCES tracks ON DELETE CASCADE,
tier text NOT NULL, -- normal|high|hifi|master
codec text NOT NULL, -- aac_lc | flac
bitrate_kbps integer,
sample_rate integer NOT NULL,
bit_depth smallint,
channels smallint NOT NULL DEFAULT 2,
size_bytes bigint NOT NULL,
manifest_key text NOT NULL, -- clé S3 du manifeste CMAF
encoder_delay integer NOT NULL DEFAULT 0, -- échantillons — gapless
encoder_padding integer NOT NULL DEFAULT 0,
drm_scheme text, -- widevine|fairplay|aes128|null
UNIQUE (track_id, tier)
);
-- Crédits complets : notre différenciateur
CREATE TABLE credits (
track_id uuid NOT NULL REFERENCES tracks ON DELETE CASCADE,
artist_id uuid NOT NULL REFERENCES artists,
role text NOT NULL, -- performer|producer|composer|mixing|
-- mastering|featured|engineer|studio
PRIMARY KEY (track_id, artist_id, role)
);
CREATE INDEX ON credits (artist_id, role);
-- ══ BIBLIOTHÈQUE ═════════════════════════════════════════════════════════
CREATE TABLE library_items (
profile_id uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
item_type text NOT NULL, -- track|album|artist|playlist|show|book
item_id uuid NOT NULL,
added_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (profile_id, item_type, item_id)
);
CREATE INDEX ON library_items (profile_id, added_at DESC);
CREATE TABLE playlists (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_profile uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
name text NOT NULL,
description text,
cover_url text,
visibility text NOT NULL DEFAULT 'private', -- private|unlisted|public
collaborative boolean NOT NULL DEFAULT false,
share_slug text UNIQUE,
updated_at timestamptz NOT NULL DEFAULT now()
);
-- position : index fractionnaire LSEQ → réordonnancement hors-ligne sans conflit
CREATE TABLE playlist_tracks (
playlist_id uuid NOT NULL REFERENCES playlists ON DELETE CASCADE,
track_id uuid NOT NULL REFERENCES tracks ON DELETE CASCADE,
position numeric(20,10) NOT NULL,
added_by uuid REFERENCES profiles,
added_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (playlist_id, track_id)
);
CREATE INDEX ON playlist_tracks (playlist_id, position);
-- ══ PAROLES ══════════════════════════════════════════════════════════════
CREATE TABLE lyrics (
track_id uuid PRIMARY KEY REFERENCES tracks ON DELETE CASCADE,
language char(2) NOT NULL,
sync_level text NOT NULL, -- none|line|word
provider text NOT NULL,
lines jsonb NOT NULL, -- [{t:12340,d:2100,x:"…",w:[…]}]
translations jsonb NOT NULL DEFAULT '{}',
updated_at timestamptz NOT NULL DEFAULT now()
);
-- ══ HORS-LIGNE ═══════════════════════════════════════════════════════════
CREATE TABLE downloads (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
profile_id uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
device_id uuid NOT NULL REFERENCES devices ON DELETE CASCADE,
track_id uuid NOT NULL REFERENCES tracks ON DELETE CASCADE,
tier text NOT NULL,
size_bytes bigint NOT NULL,
state text NOT NULL, -- queued|active|done|failed|revoked
completed_at timestamptz,
UNIQUE (device_id, track_id, tier)
);
CREATE INDEX ON downloads (profile_id, state);
CREATE TABLE offline_licenses (
device_id uuid NOT NULL REFERENCES devices ON DELETE CASCADE,
track_id uuid NOT NULL REFERENCES tracks ON DELETE CASCADE,
wrapped_key bytea NOT NULL, -- clé titre chiffrée par la clé appareil
issued_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL, -- issued_at + 30 j
revoked_at timestamptz,
PRIMARY KEY (device_id, track_id)
);
CREATE INDEX ON offline_licenses (expires_at) WHERE revoked_at IS NULL;
-- ══ ABONNEMENT ═══════════════════════════════════════════════════════════
CREATE TABLE subscriptions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
account_id uuid NOT NULL REFERENCES accounts ON DELETE CASCADE,
plan text NOT NULL, -- free|premium|hifi|duo|family|student
status text NOT NULL, -- trialing|active|past_due|canceled
seats smallint NOT NULL DEFAULT 1,
provider text NOT NULL, -- stripe|apple|google
provider_ref text,
current_period_end timestamptz,
cancel_at_period_end boolean NOT NULL DEFAULT false
);
CREATE UNIQUE INDEX ON subscriptions (account_id) WHERE status IN ('trialing','active');
-- ══ PARTY ════════════════════════════════════════════════════════════════
CREATE TABLE party_sessions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
host_profile uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
join_code char(6) UNIQUE NOT NULL,
mode text NOT NULL DEFAULT 'sync', -- sync | shared_queue
voting_enabled boolean NOT NULL DEFAULT true,
clock_epoch_ms bigint NOT NULL, -- référence de synchronisation
ended_at timestamptz
);
CREATE TABLE party_queue (
session_id uuid NOT NULL REFERENCES party_sessions ON DELETE CASCADE,
track_id uuid NOT NULL REFERENCES tracks,
added_by uuid NOT NULL REFERENCES profiles,
position numeric(20,10) NOT NULL,
votes integer NOT NULL DEFAULT 0,
played_at timestamptz,
PRIMARY KEY (session_id, track_id)
);
CREATE TABLE play_events (
ts DateTime64(3),
profile_id UUID,
account_id UUID,
device_id UUID,
track_id UUID,
artist_id UUID,
event LowCardinality(String), -- start|p25|p50|p75|p95|complete|skip
position_ms UInt32,
tier LowCardinality(String),
source LowCardinality(String), -- shelf|search|radio|playlist|library
source_id Nullable(UUID),
shuffle UInt8,
offline UInt8,
country LowCardinality(FixedString(2))
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (profile_id, ts)
TTL ts + INTERVAL 26 MONTH; -- au-delà : agrégats seulement (RGPD)
-- Vue matérialisée alimentant le calcul de royalties centré utilisateur
CREATE MATERIALIZED VIEW mv_monthly_streams
ENGINE = SummingMergeTree ORDER BY (month, account_id, artist_id) AS
SELECT toStartOfMonth(ts) AS month, account_id, artist_id, count() AS streams
FROM play_events WHERE event = 'p95' GROUP BY month, account_id, artist_id;
Raccourcis clavier — desktop
Ctrl sur Windows et Linux, ⌘ sur macOS. Tout est remappable.
| Raccourci | Action | Raccourci | Action |
|---|---|---|---|
| Espace | Lecture / pause | Ctrl+L | Focus sur la recherche |
| Ctrl+→ | Titre suivant | Ctrl+F | Filtrer la vue courante |
| Ctrl+← | Titre précédent | Ctrl+K | Palette de commandes |
| ← / → | Reculer / avancer 5 s | Ctrl+Q | Ouvrir la file d'attente |
| Maj+←/→ | Reculer / avancer 30 s | Ctrl+P | Paroles plein écran |
| ↑ / ↓ | Volume ± 5 % | Ctrl+E | Égaliseur |
| M | Couper le son | Ctrl+I | Passeport audio |
| S | Shuffle | Ctrl+D | Télécharger la sélection |
| R | Repeat (cycle) | Ctrl+⇧+P | Démarrer une Party |
| H | Like / unlike | Ctrl+N | Nouvelle playlist |
| F | Lecteur plein écran | Ctrl+, | Paramètres |
| Q | Ajouter à la file | Ctrl+⇧+T | Changer de thème |
| 1–5 | Aller à l'onglet n | ? | Aide des raccourcis |
| Alt+⇧+Espace | Global — lecture / pause | Alt+⇧+→ | Global — suivant |
La palette de commandes Ctrl+K est le raccourci central : elle cherche à la fois dans le catalogue, dans la bibliothèque et dans les actions de l'application (« télécharger en FLAC », « minuteur 30 min », « thème clair »). C'est ce qui fait qu'une application desktop est une application et pas un site.
Différenciateurs face à Spotify, Deezer, Napster
Impact estimé sur l'acquisition et la rétention, sur cinq crans.
| Fonctionnalité | Ce que ça change | État chez les concurrents | Impact |
|---|---|---|---|
| Bibliothèque unifiée catalogue + vos fichiers |
Vos FLAC, vos bootlegs, vos enregistrements introuvables cohabitent avec le catalogue. Même recherche, mêmes playlists, mêmes paroles, mêmes recommandations. | Spotify a retiré les fichiers locaux du mobile. Deezer et Napster ne le font pas. | |
| Passeport audio | Codec, débit réel, fréquence, sortie matérielle, gain appliqué — affichés en direct. Rend le HiFi vérifiable au lieu d'être une promesse. | Aucun ne l'expose. Tidal s'en approche avec un simple badge. | |
| Hors-ligne intégral | L'app entière fonctionne sans réseau, pas seulement la lecture des fichiers déjà là. Recherche, tri, playlists, statistiques, EQ. | Tous dégradent fortement l'expérience hors ligne. | |
| Party synchronisée | Plusieurs appareils jouent le même son à la même milliseconde. La dérive mesurée est affichée — c'est la preuve visible que ça fonctionne. | Spotify Jam partage la file mais pas la synchronisation multi-appareils. | |
| Crédits complets | Producteur, ingénieur, studio, instrumentistes, samples — cliquables. « Trouver tout ce qu'a mixé Manny Marroquin » devient une requête. | Spotify affiche 3 lignes. Tidal fait mieux mais ne rend pas cherchable. | |
| Rémunération centrée utilisateur | Votre abonnement se répartit entre vos artistes, pas dans un pot commun proportionnel. Chaque mois, vous voyez qui vous avez payé et combien. | Deezer a commencé, sans le rendre visible à l'utilisateur. Personne ne le montre. | |
| Scènes d'écoute | Un contexte = un preset complet (EQ + crossfade + normalisation + qualité + source). « Voiture », « Casque », « Sommeil », « Soirée » s'activent seuls selon l'appareil de sortie et l'heure. | Inexistant. Les EQ concurrents sont globaux et oubliés après trois jours. | |
| Enchaînement DJ | Détection BPM et tonalité à l'ingestion, transitions beat-matchées et harmoniques dans les radios et playlists de soirée. | Le crossfade concurrent est un fondu aveugle. | |
| Rétrospective permanente | Vos statistiques d'écoute, tout le temps, pas une fois par an en décembre. Exportables en JSON et CSV. | Wrapped une fois l'an. Deezer et Napster, presque rien. | |
| Import de playlists | Migration en un clic depuis Spotify, Deezer, Apple Music et YouTube Music, avec rapport des titres introuvables. Le coût de sortie de la concurrence tombe à zéro. | Confié à des services tiers payants. |
Monétisation
| Palier | Prix / mois | Qualité max | Hors-ligne | Publicité | Comptes | Cible |
|---|---|---|---|---|---|---|
| Free | 0 € | AAC 128 | Non | 4 min / heure | 1 | Acquisition, Yanis |
| Premium | 10,99 € | AAC 256 | Illimité | Aucune | 1 | Cœur de marché, Sofia |
| HiFi | 16,99 € | FLAC 24/96 | Illimité | Aucune | 1 | Audiophiles, Marc |
| Duo | 14,99 € | AAC 256 | Illimité | Aucune | 2 | Couples |
| Famille | 17,99 € | AAC 256 | Illimité | Aucune | 6 + Kids | Famille Diallo |
| Étudiant | 5,99 € | AAC 256 | Illimité | Aucune | 1 | Léa |
| HiFi Famille | 24,99 € | FLAC 24/96 | Illimité | Aucune | 6 + Kids | Foyers audiophiles |
1 — Freemium avec publicité
Trois formats : audio de 15 à 30 s entre les titres (jamais pendant), bannière sur l'accueil, et playlist sponsorisée clairement étiquetée. Plafond de 4 minutes par heure — la moitié de la radio classique. Le Free est bridé où ça pousse à l'abonnement, pas où ça rend le produit pénible : pas de téléchargement, pas de lossless, pas de mode Party, saut limité à 6 titres par heure sur mobile, mais recherche et lecture à la demande complètes sur desktop. Le desktop généreux est un choix : c'est là que se recrutent les mélomanes.
ARPU cible Free : 1,40–1,80 € / mois (CPM 6–12 €, ~22 impressions/h d'écoute) · Coût CDN : ~0,25 € / mois
2 — Premium
Le moteur économique. 30 jours d'essai, résiliation en deux clics sans page de rétention culpabilisante. Paiement par Stripe sur le web et sur desktop (0 % de commission plateforme), par achat in-app sur iOS et Android quand c'est imposé — d'où une incitation permanente à s'abonner depuis le desktop, où la marge est de 30 points supérieure.
3 — HiFi / Lossless
Le palier qui porte l'identité de marque. Il coûte réellement plus cher (7 à 10× le volume CDN), donc le surcoût de 6 € est justifié et défendable. Il inclut le Passeport audio, l'EQ paramétrique avancé, la sortie exclusive bit-perfect, l'import illimité de fichiers locaux et l'accès anticipé aux nouveautés du produit. Les audiophiles sont peu nombreux mais ils sont prescripteurs : ce sont eux qui écrivent les tests et animent les forums.
Économie unitaire — abonné Premium
| Poste | Premium 10,99 € | HiFi 16,99 € | Note |
|---|---|---|---|
| Recette HT | 9,16 € | 14,16 € | TVA 20 % |
| Droits d'auteur et voisins | −6,41 € | −9,91 € | ~70 % de la recette HT |
| Diffusion CDN | −0,38 € | −2,60 € | 60 h/mois, 0,004 $/Go négocié |
| Paiement | −0,42 € | −0,62 € | Stripe ; ×5 en achat in-app |
| Infrastructure et support | −0,55 € | −0,55 € | Calcul, stockage, humains |
| Marge brute | 1,40 € · 15,3 % | 0,48 € · 3,4 % | Conforme au secteur |
La leçon est nette : le streaming pur est un métier à marge faible. Le HiFi ne s'auto-finance qu'à peine — il se justifie par la rétention et l'image de marque, pas par sa marge. La rentabilité viendra du volume et des revenus adjacents, pas du prix de l'abonnement.
Revenus adjacents — V3
- Billetterie. Concerts des artistes suivis, géolocalisés. Commission de 5 à 8 %, marge nettement supérieure au streaming.
- Pourboires artistes. Don direct depuis la page artiste, 90 % reversés. Alimente le récit de la rémunération équitable.
- Lyro pour les pros. Licence B2B pour commerces et restaurants : catalogue autorisé en diffusion publique, 29 €/mois par établissement. Marge élevée, ventes récurrentes.
- Partenariats matériel. Certification « Lyro Master Ready » pour DAC et casques, en marque blanche dans les applications constructeurs.
Roadmap — 30 mois
Prouver la lecture
Mois 0 → 6- Desktop Tauri, les 3 OS
- Moteur audio Rust : gapless, R128
- Compte, profil unique, OAuth + passkey
- Catalogue, recherche, album, artiste
- Lecteur, file, shuffle, repeat
- Playlists et likes
- Import de fichiers locaux
- Téléchargement hors-ligne chiffré
- 4 paliers de qualité + Passeport audio
- Thèmes sombre / clair, raccourcis
- Backend : monolithe modulaire
Sortir sur les stores
Mois 6 → 13 · lancement public- Mobile iOS + Android complets
- Synchronisation multi-appareils, reprise
- Recommandations : les 4 couches
- Radio et stations intelligentes
- Paroles synchronisées
- Égaliseur 10 bandes
- Partage, visuels, liens universels
- Import depuis Spotify / Deezer / Apple
- Abonnements Stripe + achats in-app
- Palier Free avec publicité
- Extraction Go de catalog et playback
Élargir les usages
Mois 13 → 21- Podcasts + transcriptions cherchables
- Livres audio, chapitrage, marque-pages
- Profils multiples et mode Kids
- Mode Party synchronisé
- Thème Immersif, accents
- Scènes d'écoute contextuelles
- Rétrospective permanente
- CarPlay, Android Auto, Wear, watchOS
- Chromecast, AirPlay 2, Sonos
- Rémunération centrée utilisateur
Devenir une plateforme
Mois 21 → 30- Espace artiste : statistiques, revenus
- Enchaînement DJ beat-matché
- Audio spatial et binaural
- Billetterie concerts
- Pourboires artistes
- Lyro Pro (B2B commerces)
- API publique et SDK partenaires
- TV : Apple TV, Android TV
- Certification matériel
- Ouverture internationale, 12 langues
Jalons et indicateurs
| Mois | Jalon | Critère de passage |
|---|---|---|
| M6 | Alpha desktop interne | Latence de démarrage < 150 ms · gapless validé à l'oreille et à l'analyseur · zéro coupure sur 8 h |
| M9 | Bêta fermée, 500 testeurs | Crash-free > 99,3 % · 4 sessions / semaine / testeur |
| M13 | Lancement France | 30 000 inscrits M+1 · conversion Free → Premium > 4 % |
| M18 | Bénélux, Suisse, Canada | Rétention J30 > 42 % · désabonnement mensuel < 5 % |
| M24 | 300 000 abonnés payants | Marge brute positive · CAC remboursé en moins de 9 mois |
| M30 | 1 M d'abonnés payants | Ouverture de l'API · premiers partenariats matériel |
Risques & stratégie de droits
Le vrai obstacle n'est pas technique
Tout ce qui précède est réalisable par une bonne équipe. Ce qui ne l'est pas sans capital, c'est la licence du catalogue. Universal, Sony et Warner exigent une avance non récupérable de plusieurs millions d'euros par territoire, plus un minimum garanti par abonné et par mois — que vous soyez rentable ou non. Merlin couvre les indépendants, la SACEM et la SDRM couvrent les droits d'auteur en France. Un lancement français avec catalogue complet se budgète entre 3 et 8 M€ avant le premier abonné.
Le chemin de contournement
Il existe, et il est même stratégiquement plus intéressant que d'attaquer le catalogue de front.
- Phase 1 — Votre musique. Lyro se lance comme le meilleur lecteur au monde pour votre propre collection : import local, serveurs personnels, qualité irréprochable, hors-ligne parfait. Zéro licence à négocier, zéro coût de royalties, un produit vendable immédiatement (le marché des lecteurs audiophiles paie 30 à 60 € pour bien moins que ça).
- Phase 2 — Catalogue indépendant. Accord avec des agrégateurs (Merlin, CD Baby, DistroKid) et des labels indépendants. Quelques millions de titres, des conditions négociables, et un vrai discours : « nous payons mieux les indépendants ».
- Phase 3 — Majors. Avec une base d'abonnés payants et une preuve d'usage, la négociation change de nature. On n'arrive plus en demandeur, on arrive avec des chiffres.
Cette progression a un effet secondaire heureux : elle construit exactement le positionnement « bibliothèque unifiée » décrit au §02. Le contournement est le produit.
Autres risques
| Risque | Grav. | Parade |
|---|---|---|
| Commission de 30 % sur iOS et Android | Abonnement pris sur le web ou le desktop, lien externe autorisé par le DMA en Europe, tarif in-app majoré pour compenser | |
| Coût CDN du lossless | Multi-CDN avec bascule au tarif, cache navigateur agressif, incitation forte au téléchargement (un titre téléchargé ne coûte qu'une fois) | |
| Refus du DRM maison FLAC par les ayants droit | Audit de sécurité par un tiers reconnu, watermarking par abonné, journal de révocation — dossier préparé avant la négociation | |
| Coût d'acquisition face aux budgets marketing des géants | Ne pas les affronter en publicité de masse. Communautés audiophiles, forums, tests matériel, prescripteurs. Import en un clic pour supprimer le coût du changement | |
| Conformité RGPD sur l'historique d'écoute | Hébergement européen, TTL de 26 mois puis agrégation seule, export et suppression en un clic, consentement granulaire |