- app-player-menu : stopPropagation wheel/touchstart/touchend sur le host — les handlers window:wheel et window:touch* de /shorts ne volaient plus les gestes sur le panneau (impossible de scroller jusqu'en bas du menu, tap = play/pause, swipe = changement de short) - /shorts : ArrowUp/Down ignore quand le menu est ouvert - /shorts : le FAB passe en haut a DROITE du cadre (la fleche retour mobile z-20 passait par-dessus le FAB piege dans la scene z-10) - Look/taille : FAB 40->36px, panneau 288->240px, items compacts, max-height 70vh/480 -> 65vh/430 (plus de rognage par overflow:hidden), pastille en degrade + relief, panneau rounded-xl + highlight interne
NewTube — Votre hub vidéo multi-plateformes 🎬🌐
Agrégez, explorez et regardez des vidéos depuis
- YouTube 🔴
- Dailymotion 🔵
- Twitch 🟣
- PeerTube 🟢 (multi-instances)
- Odysee 🟡
- Rumble 🟠
✨ Pourquoi NewTube ? (Objectifs clés)
- 🔎 Recherche unifiée & tendances : un seul champ de recherche, des sections par fournisseur, filtres cohérents.
- 🧭 Navigation claire : thèmes (Trending, Live, Gaming, News…), onglets Shorts, pages Playlists/History/Liked.
- 👥 Multi-utilisateur : playlists publiques/privées, préférences, région/qualité par défaut.
- ⚙️ Prod-ready : API Node/Express, SQLite intégré, rate-limit, logs, tests ciblés.
- 📦 Ops friendly : image Docker, script
maj.sh, variables d’env, astucesdaemon.jsonpour registres HTTP.
🧩 Stack
- 🅰️ Angular 20 (standalone), RxJS, TailwindCSS
- 🟩 Node/Express (sert
dist/+ API) - 🐳 Docker/Compose (déploiement)
- 💾 SQLite (par défaut) – simple, portable
🎥 Fournisseurs supportés
| Plateforme | Recherche | Lecture | Shorts | Live | Playlists* |
|---|---|---|---|---|---|
| YouTube 🔴 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Dailymotion 🔵 | ✅ | ✅ | ⏳ | ✅ | ⏳ |
| Twitch 🟣 | ✅ | ✅ | N/A | ✅ | ⏳ |
| 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.
🔎 Recherche unifiée (multi-providers)
Un seul champ, tous les fournisseurs — avec filtres, raccourcis et deep-links :
- Panneau de filtres : bouton Filtres (ou Ctrl/⌘+Maj+F) — un seul écran pour toutes les dimensions : sources (multi), type de contenu (vidéos / shorts-clips / en direct / chaînes), période (dernière heure, aujourd’hui, semaine, mois, année), durée (courte < 4 min, moyenne 4-20 min, longue > 20 min) et tri. Pastilles, roving tabindex (←/→ dans un groupe, ↑/↓ entre groupes, Entrée pour choisir, Échap pour fermer), focus trap, « Réinitialiser », « Mémoriser comme sources par défaut » et les recherches récentes en raccourci
- Filtres appliqués côté serveur :
?type=…&duration=…&period=…&sort=…voyagent dans l’URL et dans/api/search— YouTube filtre nativement (InnerTubeupload_date/type/duration/features, Data APItype/videoDuration/publishedAfter, yt-dlp--dateafter), les autres providers sont affinés en post-traitement (server/search-filters.mjs) - Opérateurs en clair :
linux live:,tuto today: long:,concert shorts:… tapés dans la barre sont convertis en filtres et retirés de la requête (autocomplétion après le:) - Autocomplete
@: tapez@ytdans le champ pour cocher/décocher une source (↑/↓/Entrée, Échap pour fermer) — raccourcis Alt+1..6 - Deep-links :
/#/search?q=…&providers=yt,ru&period=week&type=liverelance la recherche filtrée — partageable - Fallback préférence : URL sans
providers→ préférencedefaultProvidersde l’utilisateur → provider actif - Accessibilité : focus trap dans le panneau, Esc pour fermer, aria-combobox +
aria-activedescendantsur le champ, focus restauré à la fermeture - Panneau de suggestions : sous l’input, ligne « Rechercher
<q>» puis suggestions (recherches récentes 🕘 + groupes par providerYT/DM/…, sous-chaîne surlignée) —GET /api/search/suggest?q=…&providers=…&limit=…(min 2 caractères, debounce 250 ms, cache 5 min, dégradation[]par provider) ; ↑/↓ (bouclants),Home/End,PageUp/PageDown,Entrée(valide la ligne surlignée, sinon lance la recherche),Tab(complète sans chercher),Échap(ferme puis vide), priorité au popover@ - Focus : le panneau se referme dès que le focus quitte la barre (
focusout+relatedTarget, clic neutralisé sur les lignes pour garder le focus dans l’input, pas de scintillement au re-clic)
Endpoints API concernés
GET /api/search?q=…&providers=yt,dm&type=live&duration=short&period=week&sort=date— fan-out parallèle, réponse groupée par provider (page/pageSize/sort+filters; YT sans quota via InnerTube + continuations)GET /api/search/suggest?q=…&providers=yt,dm&limit=10— typeahead{ q, groups: { yt: string[], … } }(cache 5 min, rate-limit)GET /api/details/youtube/:videoId— métadonnées +related[](watch-next InnerTube,?related=0pour désactiver)GET /api/trending?provider=yt&limit=…— tendances YT sans cléGET /healthz(alias/api/healthz) — mode YT, binaire yt-dlpbinOk, cache, métriques quota/jour, clésGET /api/transcript/:provider/:videoId?lang=&instance=&slug=&sourceUrl=— transcript{ lang, available, languages, lines: [{ t, dur, text }] }(cache 24 h, rate-limit 10/min ; absent → 200{ available: false }, échec → 502 ; YouTube : découverte des pistes via InnerTube,YT_TRANSCRIPT_SOURCE)GET/PATCH /api/user/preferences—defaultProviders(tableau JSON, sanitizé serveur)POST /api/telemetry/events— événements UX anonymes (whitelist :search_submit,provider_picker_open,provider_apply,at_autocomplete_use,quick_menu_open,filter_panel_open,filter_apply,suggest_shown,suggest_used)
Tests
npm run test:search # unitaires SearchService + clavier/focus du panneau de suggestions + panneau de filtres
npm run test:search-e2e # scénarios e2e (serveur réel isolé)
npm run test:filters # filtres de recherche : normalisation, bornes, mapping providers (offline)
npm run test:suggest # typeahead : parsing/dédup + contrat /api/search/suggest
npm run test:transcript # transcripts : parseurs json3/vtt + contrat /api/transcript
npm run test:preferences # persistance defaultProviders
npm run test:telemetry # télémétrie minimale
🖥️ Fonctionnalités (vue d’ensemble)
- 🧭 Accueil “Tendances & Viral” par fournisseurs
- 🧩 Thèmes : Trending, Live, Gaming, Sports, News, Finance, Tech, Science, Health, Music, Podcasts, Movies/TV, Education, Travel, Food, DIY, Auto…
- 🎯 Shorts : affichage dédié (séparé des vidéos longues)
- ❤️ Liked videos avec recherche serveur
- 🕘 History (recherches + visionnage)
- 📚 Playlists publiques/privées (CRUD, compteurs, dates MAJ)
- 🔐 Auth légère (JWT), rate-limit API
- ⚙️ Préférences : langue, thème (système / dark / light / blue / black), région, qualité par défaut
- 🧩 PeerTube multi-instances : activer/désactiver, choisir l’instance active
🗂️ Structure du repo
-
docker-compose/docker-compose.yml— servicenewtube(image, ports, env, volumes, restart).env.example— modèle d’environnementmaj.sh— pull image + restart stackinit.sh— init des dossiers/volumes &.env
-
docker/Dockerfile— build Node/Express (sertdist/+ API)scripts/env-dump.sh— génèreassets/config.jsdepuis l’env (option NGINX)config/nginx.conf— exemple (si variante NGINX)
-
server/index.mjs— routes API, statiquesdist/, downloadsdb.mjs— SQLite + migrations légèrestests/playlist_visibility.test.mjs— tests playlists
-
db/—schema.sql+migrations/ -
assets/—config.local.example.js -
src/,app/,public/— front Angular -
package.json— scripts (dev,build,api,api:watch,test:playlists)
🧱 Prérequis
- 🐧 Linux (recommandé) / macOS / Windows (WSL2 ok)
- 🐳 Docker Engine ≥ 24, Compose v2
- 🟩 Node.js ≥ 20 (dev local)
- 🔐 Accès au registre d’images (ex.
docker-registry.dev.home:5000)
🚀 Installation
Option A — Docker (recommandé)
cp docker-compose/.env.example docker-compose/.env
# Éditez docker-compose/.env (hostnames, clés API, secrets…)
cd docker-compose
docker compose up -d
Application : http://localhost:8080 (mappage 8080:4000)
Option B — Dev local
npm install
cp assets/config.local.example.js assets/config.local.js # (optionnel)
npm run dev # front + api dev proxy
# API seule :
npm run api
npm run api:watch
# Build prod :
npm run build
🔁 Déploiement & mises à jour (maj.sh)
chmod +x docker-compose/maj.sh
./docker-compose/maj.sh
Ce script fait :
docker image pull <registre>/newtube-angular:latestdocker compose downdocker compose up -d
Vérifications : docker compose ps, docker logs newtube --tail=200, curl -I http://localhost:8080
🧰 Astuce Ops — Docker /etc/docker/daemon.json (registres HTTP/insecure)
À configurer sur la machine qui exécute
docker compose/maj.sh.
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json >/dev/null <<'JSON'
{
"insecure-registries": ["docker-registry.dev.home:5000"],
"registry-mirrors": [],
"debug": false
}
JSON
# debian linux
sudo systemctl daemon-reload
sudo systemctl restart docker
# Alpine linux
sudo rc-service docker restart
sudo rc-update add docker
docker info | grep -i registry
Ajoutez au besoin log-driver, log-opts, default-address-pools, etc.
🔐 Variables d’environnement (exemples)
Service / compose
NGINX_HOSTNAME,DIR_NEWTUBE,TZ
App / serveur
PORT(4000),NODE_ENV,JWT_SECRETACCESS_TTL_MIN,REFRESH_TTL_DAYS,REMEMBER_TTL_DAYSYT_CACHE_TTL_MS- Clés API :
GEMINI_API_KEY,YOUTUBE_API_KEYouYOUTUBE_API_KEYS(CSV),VIMEO_ACCESS_TOKEN,TWITCH_CLIENT_ID,TWITCH_CLIENT_SECRET NEWTUBE_DB_FILE(chemin SQLite alternatif)
Voir
docker-compose/.env.examplepour un point de départ.
🩺 Troubleshooting (rapide)
- 🧾 Certif/registre :
x509… unknown authority→ configurezdaemon.json(ci-dessus) puissystemctl restart docker - 🔌 Port 8080 occupé : changez le mappage dans
docker-compose.yml - 🗄️ Permissions volumes : vérifiez que
DIR_NEWTUBEexiste et est accessible par Docker - 🧩 Variables manquantes : complétez
.env(clés API,JWT_SECRET, etc.) - 🌐 CORS en dev : utilisez le
proxy.conf.jsonetnpm run dev
🗺️ Roadmap
- ✅ Dist statique + API Node/Express
- ✅ Playlists publiques/privées (tests de visibilité)
- ✅ Barre de recherche Liked videos (filtrage serveur)
- ✅ PeerTube multi-instances (activer/désactiver, set active)
- ✅ Thème (système / dark / light / blue / black)
- ✅ 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érateurslive:/today:, deep-links?providers=…&type=…&period=…, préférencedefaultProviderspersisté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érateurslive:/today:, deep-links?providers=…&type=…&period=…, préférencedefaultProviderspersistée, filtres serveur, télémétrie minimale, a11y (focus trap, Esc, clavier complet sur le panneau de suggestions) - ✅ 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
- ✅ 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êtreDOWNLOAD_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)
- ✅ Sous-titres & transcripts —
GET /api/transcript/:provider/:videoId(yt-dlpsubtitles/automatic_captions, parsingjson3/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.jspinné, chaîneinnertube → scrape yt-dlp → API officielle(YT_SEARCH_MODE, défautinnertube-first), pagination illimitée via continuations (scroll infini,page=2,3…), vidéos connexes watch-next dansGET /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, binairebinOk, cache, quota jour, clés) - ⏳ 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
- ⏳ Theming avancé (polices, densité, accents)
🔒 Notes de sécurité
- ❌ Ne jamais versionner de secrets réels
- 🔑 Utiliser
.envhors VCS + restrictions par referer côté fournisseurs - 🚧 Limiter l’exposition publique de l’API, activer rate-limit (déjà en place)
- 🧪 Ajouter des tests e2e/units pour les zones critiques (auth, playlists, downloads)
🤝 Contribuer
Issues & PR bienvenues ! Proposez des connecteurs, des règles de parsing plus robustes ou des idées d’UX. Astuce : ouvrez une PR “Draft” tôt pour discussion/feedback.
📜 Licence
MIT (voir LICENSE)
💡 Tip produit : gardez l’UX simple — Thèmes fixes sous le header, Shorts séparés, CTA clairs (“View All”, “Add to playlist”), et utilisez les états vides (empty states) sur History/Liked/Playlists pour guider l’utilisateur.
🧩 Ajouter un provider en 5 minutes
- Front — Registry
- Éditez
src/app/core/providers/provider-registry.tset ajoutez une entréeProviderSpecavec:id(ex."yt"),displayName,shortLabel,icon,colorClasssupports(search/shorts/live/playlists)buildSearchUrl(q)(facultatif côté front)
- Back — Handler
- Créez un fichier
server/providers/<provider>.mjsqui exportedefaultavec:id,labelasync search(q, { limit, page })→Promise<Suggestion[]>
async search(q, { limit, page, sort, filters })→Promise<Suggestion[]>(filters={ type, duration, period, sort }normalisé parserver/search-filters.mjs; ignoré par défaut, affiné en post‑traitement si besoin).- Enregistrez-le dans
server/providers/registry.mjspour être éligible au fan‑out/api/search.
- API — Recherche multi‑providers
- L’endpoint
GET /api/search?q=...&providers=yt,dm,ru&limit=...valide les providers et déclenche les recherches en parallèle (timeouts), puis retourne:
{
"q": "documentary",
"providers": ["yt","dm","ru"],
"groups": {
"yt": [{ "title": "...", "id": "...", "duration": 192, "isShort": false }],
"dm": [],
"ru": [{ "title": "...", "id": "...", "isShort": true }]
}
}
- UI — Panneau de filtres
- Le
SearchBoxComponent(standalone) rend le champ + le bouton Filtres (SearchFilterPanelComponent: sources, type, période, durée, tri, recherches récentes) et les pastilles des filtres actifs. - Le composant émet
(submitted)avec{ q, providers, filters }et(filtersChange)avec les filtres seuls ;SearchService(RxJS state) portefilters$et l’envoie à chaque adapter. - Le modèle de filtres est partagé :
src/app/search/filters.ts(front) etserver/search-filters.mjs(API) implémentent les mêmes règles.
- Deep‑link
- Les URLs du type
?q=…&providers=yt,rurelancent la même recherche. Assurez-vous de propager le paramètreproviderslors des navigations.
- Tests
- Unit (
npm run test:search) : parsing@yt, clavier/focus du panneau de suggestions, opérateurslive:, brouillon + apply du panneau de filtres, composition et fan-out deSearchService.npm run test:filters` : normalisation/bornes/post-filtrage/mapping providers (offline). - e2e (
npm run test:search-e2e) :providers=yt,dm→ réponse ciblée sur [yt,dm]; deep-linkproviders=pt; fallback registry complet; préférence persistée; route SPA.
Astuce: l’ajout d’un provider ne nécessite pas de modifier les composants — il suffit d’ajouter une entrée dans le registry front + un handler API.