Dossier de conception produit · Application native desktop & mobile

LYRO

« Toute votre musique. Même sans réseau. »

Cible Spotify · Deezer · Napster Plateformes Win / macOS / Linux / iOS / Android Horizon 30 mois, 4 phases Version 1.0 — 14 août 2026
Icône Lyro
L'application existe déjà — Phase 1 du plan Lecteur local hors-ligne, installable sur Windows, macOS, Linux, Android et iOS. Importez vos fichiers, ils ne quittent jamais votre appareil.
Télécharger Ouvrir l'app

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.

01

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.

LYRORetenu
« Toute votre musique. Même sans réseau. »

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.

SILLON
« Suivez le sillon. »

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).

KADENCE
« Trouvez votre tempo. »

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.

NOCTURNE
« La musique ne dort jamais. »

É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.

ONDA
« Chaque onde vous ressemble. »

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.

02

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

PersonaContexteBesoin dominantPalier visé
Léa, 24
étudiante
Mobile 90 %, transports, colocationPlaylists, découverte, mode Party en soiréeÉtudiant 5,99 €
Marc, 38
ingénieur
Casque filaire + DAC, 4 To de FLACLossless vérifiable, import de sa collection, créditsHiFi 16,99 €
Famille Diallo
2 + 3 enfants
Enceinte cuisine, tablette, voitureProfils enfants filtrés, 6 comptes, hors-ligne pour les trajetsFamille 17,99 €
Sofia, 31
commerciale
2 h de route par jour, zones blanchesPodcasts et livres audio téléchargés, reprise multi-appareilsPremium 10,99 €
Yanis, 19
lycéen
Android entrée de gamme, forfait limitéGratuit, faible consommation data, découverteFree (pubs)
03

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èreTauri 2 (retenu)Electron
Poids de l'installeur~12 Mo~180 Mo
RAM au repos~90 Mo~320 Mo
Sortie audio bit-perfectOui — WASAPI exclusif, CoreAudio hog mode, ALSA direct via cpalNon — passe par le mixeur du navigateur, rééchantillonné en 48 kHz
Décodage FLAC / lecture gaplessNatif via symphonia, contrôle du ring bufferDépend du moteur média de Chromium
Widevine / FairPlayÀ intégrer soi-mêmeFourni par Chromium (avantage réel)
Maturité écosystèmeCorrecte depuis Tauri 2Supé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.

Monorepo — répartition du code
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

CibleFormatCanalMise à jour
Windows.msi + .exe (NSIS)Site + Microsoft StoreTauri updater, delta signé
macOS.dmg universal2Site (notarisé) + Mac App StoreTauri updater / Sparkle
Linux.deb, .rpm, AppImage, FlatpakSite + FlathubGestionnaire de paquets
Android.aabPlay Store + APK directPlay + CodePush pour le JS
iOS / iPadOS.ipaApp Store + TestFlightApp Store
04

Les 15 fonctionnalités, spécifiées

FonctionSpécification opérationnellePhase
1. LecteurPlay / 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èqueTitres 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. RechercheIndex 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. RecommandationsVoir §11. Six étagères d'accueil recalculées quotidiennement + une étagère temps réel qui réagit à la session en cours.V1
5. RadioRadio à 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échargementTitre, 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é audioNormal 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éesFormat 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. PartageLien 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 PartyUn 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. Égaliseur10 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 audioIngestion 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 multiplesJusqu'à 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èmesSombre (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. RaccourcisVoir §13. Entièrement remappables, plus des raccourcis globaux actifs application en arrière-plan.MVP
05

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

  1. Splash / restauration
  2. Accueil déconnecté
  3. Inscription (e-mail, passkey, OAuth)
  4. Connexion
  5. Onboarding : 5 genres
  6. Onboarding : 3 artistes
  7. Onboarding : qualité + hors-ligne

Navigation · 6

  1. Accueil (étagères)
  2. Recherche — état vide
  3. Recherche — résultats
  4. Explorer / Genres
  5. Nouveautés
  6. Palmarès

Bibliothèque · 8

  1. Bibliothèque — racine
  2. Titres likés
  3. Playlists
  4. Albums suivis
  5. Artistes suivis
  6. Podcasts
  7. Livres audio
  8. Fichiers locaux importés

Détail · 8

  1. Album
  2. Artiste
  3. Discographie
  4. Playlist
  5. Podcast (émission)
  6. Épisode + transcription
  7. Livre audio + chapitres
  8. Crédits d'un titre

Lecture · 7

  1. Mini-lecteur (barre)
  2. Lecteur plein écran
  3. File d'attente
  4. Paroles synchronisées
  5. Égaliseur
  6. Passeport audio
  7. Sortie audio / appareils

Radio & Party · 5

  1. Stations
  2. Radio en lecture
  3. Party — créer / rejoindre
  4. Party — salon & file
  5. Party — QR d'invitation

Hors-ligne · 3

  1. Téléchargements
  2. Gestion du stockage
  3. Bandeau mode hors-ligne

Compte · 10

  1. Sélecteur de profil
  2. Profil / statistiques
  3. Paramètres — racine
  4. Qualité audio
  5. Lecture (crossfade, gapless)
  6. Apparence & thèmes
  7. Raccourcis clavier
  8. Appareils liés
  9. Confidentialité & données
  10. Contrôle parental

Enfants · 3

  1. Accueil Kids
  2. Catégories Kids
  3. Verrou parental

Abonnement · 4

  1. Comparateur d'offres
  2. Paiement
  3. Gestion d'abonnement
  4. Factures

Système · 5

  1. Erreur réseau
  2. État vide générique
  3. Mise à jour disponible
  4. Import en cours
  5. Partage / visuel généré
06

Wireframes

Desktop — Accueil, shell à trois zones
┌────────────────────────────────────────────────────────────────────────────────┐
│ ‹ ›   ⌕  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.
Desktop — Lecteur plein écran & paroles synchronisées
┌────────────────────────────────────────────────────────────────────────────────┐
│  ⌄  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).
Mobile — Accueil, Lecteur, Party
┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│ 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
Téléchargements & qualité — l'écran qui porte le positionnement
┌────────────────────────────────────────────────────────────────────────────────┐
│  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 & Accueil Kids
┌────────────────────────────────────┐   ┌───────────────────────────────────┐
│  É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.
07

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

#08090C
fond
#101218
surface
#1A1D26
élevée
#F0A93C
accent
#57ADC0
info
#EDEFF5
texte
#8B91A3
secondaire
DomaineRègle
TypographieUne 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.
GrilleBase 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).
PochettesJamais 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 ImmersifExtraction 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é.
MouvementDuré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-interactionsLe 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 %.
08

Architecture backend

Vue d'ensemble
  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

ServiceTechnoJustification
Gateway / BFFNestJS (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.
identityGo + Ory HydraOIDC certifié plutôt que réécrit. Passkeys WebAuthn, PKCE, tokens liés à l'appareil.
catalogGoChemin le plus lu du système. Cache Redis agressif, réponses < 15 ms P95.
playbackGoGénère les manifestes HLS et les URL signées. Sans état, autoscalable, latence critique.
entitlementGoRé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 ? »
personalizationPython / FastAPIL'écosystème ML est en Python. Sert les modèles, interroge Qdrant, applique le ranker.
social / partyGo + WebSocketDes connexions longues par milliers : les goroutines coûtent bien moins cher que des threads Node.
billingTS + StripePeu de trafic, beaucoup de règles métier. On ne réinvente pas la facturation.
ingestionPython + FFmpegJobs 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.

09

Chaîne audio & diffusion

PalierCodecDébitMo / heureConteneurAccès
NormalAAC-LC128 kbps56 MoCMAF / HLSFree, Premium
HauteAAC-LC256 kbps112 MoCMAF / HLSPremium, HiFi
HiFiFLAC 16 bit / 44,1 kHz~900 kbps~400 MofMP4 chiffréHiFi
MasterFLAC 24 bit / 96 kHz~2 800 kbps~1,2 GofMP4 chiffréHiFi (si master dispo.)

Du fichier source à la carte son

  1. 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).
  2. 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.
  3. Manifeste. Le client demande /v1/playback/{track_id}. playback interroge entitlement, 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.
  4. 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 %.
  5. Décodage. Rust symphonia sur desktop, Media3/AVAudioEngine sur mobile. Le flux entre dans un ring buffer sans allocation.
  6. Traitement. Normalisation (gain R128 pré-calculé, jamais recalculé à la lecture), puis EQ 10 bandes en biquads cascadés, puis limiteur.
  7. 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.

10

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.

PalierProtection en streamingProtection hors-ligne
Normal / HauteWidevine L3 (Android, Windows, Linux) · FairPlay (Apple)Licence persistante DRM, 30 jours
HiFi / MasterAES-128-CTR, clé délivrée par segment via jeton signé lié à l'appareilRé-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.

Cycle de vie d'un téléchargement
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
11

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

IndicateurRéférence marchéCible V1Cible V2
Taux de skip sur recommandation~32 %< 30 %< 25 %
Écoutes issues d'une reco~30 %25 %40 %
Artistes distincts / mois / profil~404560
Latence de génération d'une radio< 400 ms< 250 ms
12

Structure de base de données

MagasinRôlePourquoi pas Postgres
Postgres 16Comptes, catalogue, bibliothèque, playlists, abonnements, licences
ClickHouseÉvénements d'écoute (~2 Md/mois à l'échelle cible), analytique, royaltiesPostgres s'effondre au-delà de quelques centaines de millions de lignes append-only
OpenSearchRecherche plein texte, fautes de frappe, facettes, parolesLe tsvector ne gère ni la fuzziness ni le scoring métier
Redis 7Cache catalogue, sessions, présence Party, limitation de débit, verrousLatence sub-milliseconde requise
QdrantEmbeddings titres et profils, recherche ANNpgvector suffit sous 5 M vecteurs, pas au-delà
S3 / R2Audio segmenté, pochettes, avatars, exportsObjets binaires
SQLiteRéplique locale sur chaque client (voir §10)Embarqué

Schéma relationnel principal

Postgres 16 — extrait significatif
-- ══ 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)
);
ClickHouse — table des événements d'écoute
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;
13

Raccourcis clavier — desktop

Ctrl sur Windows et Linux, sur macOS. Tout est remappable.

RaccourciActionRaccourciAction
EspaceLecture / pauseCtrl+LFocus sur la recherche
Ctrl+Titre suivantCtrl+FFiltrer la vue courante
Ctrl+Titre précédentCtrl+KPalette de commandes
/ Reculer / avancer 5 sCtrl+QOuvrir la file d'attente
Maj+/Reculer / avancer 30 sCtrl+PParoles plein écran
/ Volume ± 5 %Ctrl+EÉgaliseur
MCouper le sonCtrl+IPasseport audio
SShuffleCtrl+DTélécharger la sélection
RRepeat (cycle)Ctrl++PDémarrer une Party
HLike / unlikeCtrl+NNouvelle playlist
FLecteur plein écranCtrl+,Paramètres
QAjouter à la fileCtrl++TChanger de thème
15Aller à l'onglet n?Aide des raccourcis
Alt++EspaceGlobal — lecture / pauseAlt++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.

14

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 concurrentsImpact
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.
15

Monétisation

PalierPrix / moisQualité maxHors-lignePublicitéComptesCible
Free0 €AAC 128Non4 min / heure1Acquisition, Yanis
Premium10,99 €AAC 256IllimitéAucune1Cœur de marché, Sofia
HiFi16,99 €FLAC 24/96IllimitéAucune1Audiophiles, Marc
Duo14,99 €AAC 256IllimitéAucune2Couples
Famille17,99 €AAC 256IllimitéAucune6 + KidsFamille Diallo
Étudiant5,99 €AAC 256IllimitéAucune1Léa
HiFi Famille24,99 €FLAC 24/96IllimitéAucune6 + KidsFoyers 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

PostePremium 10,99 €HiFi 16,99 €Note
Recette HT9,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 brute1,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.
16

Roadmap — 30 mois

Phase 1 · MVP

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
8 pers.Effort
Phase 2 · V1

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
16 pers.Effort
Phase 3 · V2

É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
26 pers.Effort
Phase 4 · V3

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
40 pers.Effort

Jalons et indicateurs

MoisJalonCritère de passage
M6Alpha desktop interneLatence de démarrage < 150 ms · gapless validé à l'oreille et à l'analyseur · zéro coupure sur 8 h
M9Bêta fermée, 500 testeursCrash-free > 99,3 % · 4 sessions / semaine / testeur
M13Lancement France30 000 inscrits M+1 · conversion Free → Premium > 4 %
M18Bénélux, Suisse, CanadaRétention J30 > 42 % · désabonnement mensuel < 5 %
M24300 000 abonnés payantsMarge brute positive · CAC remboursé en moins de 9 mois
M301 M d'abonnés payantsOuverture de l'API · premiers partenariats matériel
17

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.

  1. 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).
  2. 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 ».
  3. 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

RisqueGrav.Parade
Commission de 30 % sur iOS et AndroidAbonnement 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 losslessMulti-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 droitAudit 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éantsNe 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'écouteHébergement européen, TTL de 26 mois puis agrégation seule, export et suppression en un clic, consentement granulaire