docs(readme): roadmap restructuré (livré / partiel / à faire) + plan des nouveaux fournisseurs
CI / build-and-test (push) Successful in 15m0s

- Légende ✅/🟡/⏳ et section « Pas encore démarré » regroupant les vrais restes
- Abonnements et page Shorts unifiée passés en ✅ (vérifiés câblés: routes /api/subscriptions + groupes, flux /shorts 6 fournisseurs)
- Partiellement livré: tags, observabilité (healthz+metrics faits, Admin non), cache TTL, qualité vidéo, i18n
- Matrice fournisseurs: Shorts ✅ pour Dailymotion/PeerTube/Odysee/Rumble, Twitch = Clips, notes de lecture (filtres durée par provider, rumble natif)
- Nouvelle section candidats Kick/Vimeo/SoundCloud/TikTok + chemin d'implémentation standard en 8 étapes
- Dédoublonnage du doublon « Recherche unifiée », cross-référence avec « Ajouter un provider en 5 minutes »
This commit is contained in:
2026-10-02 17:25:27 -04:00
parent f99004f459
commit 5f1bb78c3b
+56 -20
View File
@@ -35,13 +35,17 @@ Agrégez, explorez et regardez des vidéos depuis
| Plateforme | Recherche | Lecture | Shorts | Live | Playlists\* |
| ----------------------------- | :-------: | :-----: | :----: | :--: | :---------: |
| YouTube 🔴 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Dailymotion 🔵 | ✅ | ✅ | ⏳ | ✅ | ⏳ |
| Twitch 🟣 | ✅ | ✅ | N/A | ✅ | ⏳ |
| PeerTube 🟢 (multi-instances) | ✅ | ✅ | ⏳ | ⏳ | ⏳ |
| Odysee 🟡 | ✅ | ✅ | ⏳ | ⏳ | ⏳ |
| Rumble 🟠 | ✅ | ✅ | ⏳ | ⏳ | ⏳ |
| Dailymotion 🔵 | ✅ | ✅ | ✅ | ✅ | ⏳ |
| Twitch 🟣 | ✅ | ✅ | Clips ✅ | ✅ | ⏳ |
| PeerTube 🟢 (multi-instances) | ✅ | ✅ | ✅ | ⏳ | ⏳ |
| Odysee 🟡 | ✅ | ✅ | ✅ | ⏳ | ⏳ |
| Rumble 🟠 | ✅ | ✅ | ✅ | ⏳ | ⏳ |
\* Playlists = intégration locale NewTube (création/gestion); la synchro native dépend de l’API publique de chaque fournisseur.
\* Playlists = intégration locale NewTube (création/gestion) ; la synchro native (ajout/suppression côté fournisseur) dépend de l’API publique de chaque fournisseur → ⏳ pour tous sauf YouTube.
Notes sur les colonnes :
* **Shorts** : le flux `/#/shorts` tourne sur les 6 fournisseurs — catalogue `/api/search?type=shorts` (lot affiché s’il compte ≥ 3 résultats), sinon repli recherche avec requêtes dédiées (`SHORTS_QUERIES`). Filtre de durée côté serveur : YouTube (`videoDuration=short`), Dailymotion (`shorter_than`), PeerTube (`durationMax`) ; filtre client pour Odysee et Rumble ; **Rumble** : flux natif `rumble.com/shorts` ; **Twitch** : pas de format vertical natif, le flux sert les **clips**.
* **Live** : lecture des streams live prouvée pour YouTube, Dailymotion et Twitch ; PeerTube / Odysee / Rumble ⏳ (voir « Fournisseurs existants — lacunes à combler »).
* **Rumble 🟠** : scraping maison derrière Cloudflare (fetch navigateur + helper `curl_cffi`). Si le réseau du serveur est challengé, l’API répond `503 { error: "rumble_cloudflare_challenge" }` (« bloqué temporairement ») au lieu d’un résultat vide : Rumble peut donc être temporairement absent selon le réseau — voir `server/providers/rumble.mjs`.
@@ -239,6 +243,10 @@ Ajoutez au besoin `log-driver`, `log-opts`, `default-address-pools`, etc.
## 🗺️ Roadmap
Légende : **✅ livré** (câblé de bout en bout) · **🟡 partiellement livré** (détail sous chaque item) · **⏳ pas encore démarré**
### ✅ Livré
* ✅ Dist statique + API Node/Express
* ✅ Playlists **publiques/privées** (tests de visibilité)
* ✅ Barre de recherche **Liked videos** (filtrage serveur)
@@ -249,28 +257,54 @@ Ajoutez au besoin `log-driver`, `log-opts`, `default-address-pools`, etc.
* ✅ Préférences (langue, thème, région, qualité par défaut)
* ✅ Auth légère (JWT), rate-limit API
* ✅ **Recherche unifiée multi-providers** — panneau de filtres (sources / type / période / durée / tri, Ctrl/⌘+Maj+F), autocomplete `@` + opérateurs `live:`/`today:`, deep-links `?providers=…&type=…&period=…`, préférence `defaultProviders` persistée, filtres serveur, télémétrie minimale, a11y (focus trap, Esc, clavier complet sur le panneau de suggestions)
* ✅ **Recherche unifiée multi-providers** — panneau de filtres (sources / type / période / durée / tri, Ctrl/⌘+Maj+F), autocomplete `@` + opérateurs `live:`/`today:`, deep-links `?providers=…&type=…&period=…`, préférence `defaultProviders` persistée, filtres serveur, télémétrie minimale, a11y (focus trap, Esc, clavier complet sur le panneau de suggestions)
* ✅ **Filtres & tri unifiés** — barre partagée **[Filtres] [Tri]** sur la page de recherche ET sur les pages thèmes (pastilles de type, ligne Sources **Toutes / Aucune**, période / durée / langue, filtres actifs en pastilles) ; bouton « filtres rapides » supprimé, tout est sous [Filtres]. Tri : Pertinence, Date de mise en ligne, Vues, **Note (likes)**, **Durée** (les deux derniers réordonnent côté client)
* ✅ Navigation par thèmes (Trending, Live, Gaming, News, Finance, Tech, Science, Health, Music, Podcasts, Movies/TV, Education, Travel, Food, DIY, Auto…)
* ⏳ **Abonnements** (routes + DB)
* ⏳ **Tags** & recherche par tags
* ⏳ **Page “Shorts”** unifiée (tous fournisseurs) + badges de durée
* ✅ **UI/UX des Shorts** — le rail d'actions quitte les bords (les flèches latérales sont supprimées) et s'ancre au bord **droit** de la vidéo : **informations**, **flèches ↑/↓**, j'aime, **enregistrer** (liste « À regarder plus tard »), s'abonner (pastille = avatar), commentaires, partager, **lecture auto** (interrupteur persisté, respecté par le minuteur ET la fin de vidéo YouTube) ; panneau **ⓘ** (titre, vues, engagement, chaîne, **description et vues demandées à la source à l'ouverture** via `/api/details` — squelette de chargement, échec silencieux —, renvoi vers la page complète, aucune source de commentaires dans l'app) en colonne à droite au bureau et en tiroir plein écran sur mobile, la vidéo rétrécissant sans jamais être masquée ; **transition glissée** d'un short à l'autre (le cadre suit le doigt, résistance aux extrémités, ressort sous 25 % de la hauteur puis éjection/emboîtement, `prefers-reduced-motion` respecté) ; FAB menu conservé (déplaçable, au-dessus de la vidéo). Le tap sur un contrôle du rail ne déclenche plus play/pause
* ✅ **Abonnements (routes + DB)** — `GET/POST/DELETE /api/subscriptions` (+ `POST /api/subscriptions/batch`), groupes façon PocketTube (`/api/subscription-groups` CRUD + affectation de membres), tables SQLite `subscriptions` / groupes, page `/#/library/subscriptions`, bouton « S'abonner » partagé (`subscribe-button`) + entrée barre latérale ; alimentés par l'import OAuth (YouTube/Twitch). Tests : `npm run test:subscriptions`
* ✅ **Page « Shorts » unifiée (tous fournisseurs)** — flux immersif `/#/shorts` sur les 6 fournisseurs (catalogue `/api/search?type=shorts` sinon repli recherche, dédup centralisée), durées : filtres côté serveur (YouTube `videoDuration=short`, Dailymotion `shorter_than`, PeerTube `durationMax`) + filtre client ailleurs, **pastille de durée** sur les cartes de la recherche filtrée et durée affichée dans le panneau ⓘ, clips Twitch, mode « Pour toi »
* ✅ **UI/UX des Shorts** — le rail d'actions quitte les bords (les flèches latérales sont supprimées) et s'ancre au bord **droit** de la vidéo : **informations**, flèches ↑/↓, j'aime, **enregistrer** (liste « À regarder plus tard »), s'abonner (pastille = avatar), commentaires, partager, **lecture auto** (interrupteur persisté, respecté par le minuteur ET la fin de vidéo YouTube) ; panneau **ⓘ** (titre, vues, engagement, chaîne, **description et vues demandées à la source à l'ouverture** via `/api/details` — squelette de chargement, échec silencieux —, renvoi vers la page complète, aucune source de commentaires dans l'app) en colonne à droite au bureau et en tiroir plein écran sur mobile, la vidéo rétrécissant sans jamais être masquée ; **transition glissée** d'un short à l'autre (le cadre suit le doigt, résistance aux extrémités, ressort sous 25 % de la hauteur puis éjection/emboîtement, `prefers-reduced-motion` respecté) ; FAB menu conservé (déplaçable, au-dessus de la vidéo). Le tap sur un contrôle du rail ne déclenche plus play/pause
* ✅ **Métadonnées Odysee sans yt-dlp** — `resolve` de l'API LBRY (**~0,2 s** contre **~45 s** mesurées avec l'extracteur `lbry` de yt-dlp, qui finissait sur « No video formats found! ») : titre, description, vignette, durée et date arrivent enfin sur `/watch` ; `views` volontairement omis (LBRY n'expose aucun compteur pour un claim isolé). Au passage : **cache `/api/details` 6 h** (LRU 500, `DETAILS_CACHE_TTL_MS`) et `views`/`duration` ne sont plus émis quand ils valent 0 (un 0 écrasait la valeur du flux : « 0 vue » sur les fournisseurs muets). Tests : `npm run test:odysee`
* ✅ **Téléchargements** intégrés — file d'attente **persistée en SQLite** (survit aux redémarrages API), jobs **par utilisateur** (répertoires isolés, ownership sur status/fichier/cancel), **quota de stockage** configurable (`DOWNLOAD_STORAGE_QUOTA_BYTES`, fenêtre `DOWNLOAD_QUOTA_WINDOW_MS`), **reprise** des jobs échoués/interrrompus (bouton Réessayer), **page Bibliothèque > Téléchargements** (filtres par état, progression live, quota), nettoyage auto des fichiers orphelins au boot
* 🔧 Variables : `DOWNLOAD_MAX_CONCURRENT` (2), `DOWNLOAD_STORAGE_QUOTA_BYTES` (5 GiB), `DOWNLOAD_QUOTA_WINDOW_MS` (30 j), `DOWNLOAD_PROVIDERS` (peertube,odysee)
* ⏳ **Import/Export** playlists (JSON / OPML-like)
* ✅ **Téléchargements** intégrés — file d'attente **persistée en SQLite** (survit aux redémarrages API), jobs **par utilisateur** (répertoires isolés, ownership sur status/fichier/cancel), **quota de stockage** configurable (`DOWNLOAD_STORAGE_QUOTA_BYTES`, fenêtre `DOWNLOAD_QUOTA_WINDOW_MS`), **reprise** des jobs échoués/interrrompus (bouton Réessayer), **page Bibliothèque > Téléchargements** (filtres par état, progression live, quota), nettoyage auto des fichiers orphelins au boot. Variables : `DOWNLOAD_MAX_CONCURRENT` (2), `DOWNLOAD_STORAGE_QUOTA_BYTES` (5 GiB), `DOWNLOAD_QUOTA_WINDOW_MS` (30 j), `DOWNLOAD_PROVIDERS` (peertube,odysee)
* ✅ **Sous-titres & transcripts** — `GET /api/transcript/:provider/:videoId` (yt-dlp `subtitles`/`automatic_captions`, parsing `json3`/`vtt`, cache 24 h, rate-limit), panneau **Transcript** sur la page Watch (sélecteur de langue, dégradation propre si indisponible)
* ✅ **YouTube sans quota (InnerTube façon SmartTube)** — `youtubei.js` pinné, chaîne `innertube → scrape yt-dlp → API officielle` (`YT_SEARCH_MODE`, défaut `innertube-first`), **pagination illimitée** via continuations (scroll infini, `page=2,3…`), **vidéos connexes** watch-next dans `GET /api/details/youtube/:videoId` → sidebar Watch, `GET /api/trending?provider=yt`, cache mémoire + SQLite, anti-ban (`YT_COOKIES_FILE`, `YT_PO_TOKEN`, `YT_EGRESS_PROXY`), observabilité `/healthz` (mode, binaire `binOk`, cache, quota jour, clés)
* ✅ **OAuth** (Google/Twitch) pour import favoris/abonnements — `/library/import` : abonnements + likes YouTube (lecture seule), follows Twitch → abonnements, preview + import, `GET /api/oauth/status|connections|preview`, `POST /api/oauth/:provider/import`
### 🟡 Partiellement livré
* 🟡 **Tags** — ✅ tables `tags` / `video_tags` (stockage des listes « Aimés » et « À regarder plus tard », recherche serveur dans ces listes) ; ⏳ tags **libres** créés par l'utilisateur sur n'importe quelle vidéo + recherche de vidéos par tag
* 🟡 **Observabilité** — ✅ `GET /healthz` (mode `YT_SEARCH_MODE`, binaire yt-dlp, cache, quota du jour, clés masquées — testé par `test:api`) + `GET /api/providers/metrics` ; ⏳ page **Admin** (clés OK/KO, logs, versions) + exposition métriques au format Prometheus
* 🟡 **Cache serveur configurable** — ✅ TTL réglable par variable d'environnement sur **tous** les caches, la recherche étant déjà **par provider** (`SEARCH_CACHE_TTL_MS_<PROVIDER>` / `SEARCH_CACHE_TTL_MS_DEFAULT`), plus `YT_CACHE_TTL_MS`, `YT_RELATED_TTL_MS`, `DETAILS_CACHE_TTL_MS`, `TRANSCRIPT_CACHE_TTL`, `SUGGEST_CACHE_TTL_MS`, `CHANNEL_TTL_MS` ; ⏳ étendre le TTL **par provider** aux caches details / transcripts / suggest (aujourd'hui un seul TTL global chacun)
* 🟡 **Qualité vidéo** — ✅ sélecteur de qualité sur `/#/watch` (formats yt-dlp, tri décroissant `1080p → 144p`, reprise de la lecture sur le flux choisi) ; ⏳ mode **« auto » intelligent** (choix automatique du flux selon la bande passante)
* 🟡 **Traduction UI (i18n)** — ✅ socle FR/EN (`src/services/i18n.service.ts`, chaque clé déclarée des deux côtés) couvrant barre de recherche, barre de filtres, pages thèmes ; ⏳ couverture **élargie** aux écrans encore en texte FR hardcodé (`/watch`, `/shorts`, bibliothèque)
### ⏳ Pas encore démarré
* ⏳ **Import/Export** playlists (JSON / OPML-like)
* ⏳ **PWA** (installable, offline cache des métadonnées)
* ⏳ **Chromecast / AirPlay**
* ⏳ Mode “TV”
* ⏳ **Observabilité** : healthcheck `/healthz`, métriques, page **Admin** (clés OK/KO, logs, versions)
* ✅ **OAuth** (Google/Twitch) pour import favoris/abonnements — `/library/import` : abonnements + likes YouTube (lecture seule), follows Twitch → abonnements, preview + import, `GET /api/oauth/status|connections|preview`, `POST /api/oauth/:provider/import`
* ⏳ **Cache serveur** configurable (TTL par provider)
* ⏳ **Qualité vidéo** : sélecteur et “auto” intelligent
* ⏳ **Traduction UI** (i18n) élargie
* ⏳ Mode **« TV »**
* ⏳ **Theming avancé** (polices, densité, accents)
* ⏳ **Fournisseurs existants — lacunes à combler** (détail dans la matrice en haut) : lecture **Live** sur PeerTube, Odysee, Rumble ; **playlists natives** (synchro côté fournisseur) pour Dailymotion, Twitch, PeerTube, Odysee, Rumble
* ⏳ **Nouveaux fournisseurs** — plan détaillé ci-dessous
### 🆕 Nouveaux fournisseurs — plan d'implémentation
**Candidats** (par rapport / effort ; endpoints et quotas à re-valider au démarrage de chacun) :
1. **Kick** — API JSON publique (sans quota clé à confirmer), modèle identique à Twitch (live / vod / clip) : recherche + métadonnées via l'API, lecture via l'embed officiel, feature-flag `FF_KICK`. Priorité haute : seul candidat avec un vrai live concurrent de Twitch.
2. **Vimeo** — recherche et détails via l'API Data (clé de développeur), formats/téléchargement via yt-dlp (extracteur Vimeo mature), lecture via l'embed officiel `player.vimeo.com`.
3. **SoundCloud** — recherche via yt-dlp, lecture via le widget iframe officiel ; audio seul → carte avec pastille « audio », **hors flux Shorts**.
4. **TikTok** — recherche via yt-dlp + embed officiel ; **risque élevé** (anti-bot de la même classe que Cloudflare/Rumble) : ne commencer qu'après les 1-3, avec contrat d'erreur strict (`503` = challenge, `404` = introuvable prouvé — jamais l'inverse).
**Chemin d'implémentation standard** (identique pour chaque fournisseur, c'est celui des 6 existants ; la procédure détaillée côté recherche/filtres est aussi dans « Ajouter un provider en 5 minutes » en fin de README) :
1. **IDs & URLs** — id court dans `src/app/shared/providers/provider-ids.ts` + couleur du badge fournisseur ; brancher `providerUrlFrom()` (`server/index.mjs`) — jamais d'URL provider écrite à la main ; transmettre tels quels `instance` / `slug` / `sourceUrl`.
2. **Métadonnées** — privilégier l'API native du provider quand elle existe (modèle Odysee : `resolve` LBRY, ~0,2 s), sinon tuyau yt-dlp `youtubedl(url, {dumpSingleJson, skipDownload})` qui alimente gratuitement `/api/details/:provider/:videoId` et `/api/download/:provider/:videoId/formats`.
3. **Recherche** — `server/providers/<p>.mjs` = **un seul cœur** fetch + parse ; enregistrer `search()` dans `registry.mjs` (fan-out), les chaînes dans `channel-registry.mjs` ; rate-limit + cache dans un routeur fin dédié si besoin (modèle `server/rumble.mjs`). Ne jamais créer un second parseur.
4. **Capacités & robustesse** — déclarer `suggest / live / channelMeta / channelContent` dans le bloc capacités de `registry.mjs` ; feature-flag `FF_<ID>` automatique via `feature-flags.mjs` ; dégradation **toujours** `200 + available: false` + état « pas de données » dans l'UI, jamais une erreur ; contrat d'erreur net : `404` = introuvable **prouvé** (HTML reçu), `503` = blocage/rate-limit.
5. **Shorts** — requêtes dédiées dans `SHORTS_QUERIES` + filtre de durée (serveur si l'API le supporte, client sinon), branche catalogue `type=shorts` ; tout chemin d'alimentation du flux passe par `dedup()` (sinon feed vide en silence) ; cadre vidéo avec attribut `data-video-provider` (contour 1 px).
6. **UI** — cartes, `/watch` et `/shorts` réutilisent les composants existants, **aucun composant par fournisseur** ; texte FR hardcodé sur `/watch` et `/shorts` (pas de clés i18n), ailleurs `| t: 'repli FR'` déclaré des **deux côtés** de `src/services/i18n.service.ts` (dictionnaire EN et dictionnaire FR).
7. **Tests** — `server/tests/<p>*.test.mjs` hors ligne (fixtures HTML/JSON, zéro spawn yt-dlp, zéro appel réseau) + script `test:<p>` dans `package.json` ; passer aussi `test:contract` et `test:shapes`.
8. **Livraison** — tests verts **avant** commit ; matrice fournisseurs + ce roadmap mis à jour (✅ uniquement une fois câblé de bout en bout) ; vérif navigateur desktop **1280×900** ET mobile **390×844** (dont `/watch` et `/shorts`, FAB menu visible sur tous les états) ; instance locale rebuildée, puis image prod `docker/build-img.ps1` + déploiement `docker/deploy-img.ps1`.
---
@@ -302,6 +336,8 @@ MIT (voir `LICENSE`)
## 🧩 Ajouter un provider en 5 minutes
> Complète le plan général « 🆕 Nouveaux fournisseurs » du [roadmap](#️-roadmap) : cette section détaille le côté **recherche / filtres / deep-link**, le plan du roadmap couvre IDs, URLs, métadonnées, shorts, capacités et livraison.
1) Front — Registry
- Éditez `src/app/core/providers/provider-registry.ts` et ajoutez une entrée `ProviderSpec` avec: