diff --git a/README.md b/README.md index fa280fe..de1c3bc 100644 --- a/README.md +++ b/README.md @@ -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_` / `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/

.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_` 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/

*.test.mjs` hors ligne (fixtures HTML/JSON, zéro spawn yt-dlp, zéro appel réseau) + script `test:

` 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: