From 2bcc7321aaaf1f7f0f942b47e72e107b3bd2ab24 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Wed, 30 Sep 2026 21:48:06 -0400 Subject: [PATCH] fix(dailymotion): Player ID obligatoire dans l'URL d'embed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dailymotion exige désormais un Player ID (fév. 2026) : player.html?video=... sans Player ID répond HTTP 403 'Forbidden'. L'URL correcte est geo.dailymotion.com/player/{PLAYER_ID}.html?video=... - helper dailymotionPlayerId()/dailymotionEmbedBase() (config window.DAILYMOTION_PLAYER_ID, repli demo x8lr5) - /watch et /shorts utilisent le nouvel endpoint avec Player ID - config.local.example.js documente la config --- assets/config.local.example.js | 7 ++++ .../shared/providers/dailymotion-player.ts | 33 +++++++++++++++++++ .../shorts/watch-short.component.ts | 16 +++------ src/components/watch/watch.component.ts | 8 ++--- 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 src/app/shared/providers/dailymotion-player.ts diff --git a/assets/config.local.example.js b/assets/config.local.example.js index bc7f545..c66fa43 100644 --- a/assets/config.local.example.js +++ b/assets/config.local.example.js @@ -11,6 +11,13 @@ // YouTube Data API v3 key (browser-safe only if properly restricted by HTTP referrer) window.YOUTUBE_API_KEY = 'PUT_YOUR_YOUTUBE_API_KEY_HERE'; +// Dailymotion Player ID (required since Feb 2026: embeds without a Player ID +// return HTTP 403 "Forbidden"). Create a Player in Dailymotion Studio +// (https://www.dailymotion.com/partner/embed/players) — ideally a vertical +// player (aspect_ratio 9:16) for Shorts — and set its ID here. +// If unset, the app falls back to a public demo player ID (x8lr5). +// window.DAILYMOTION_PLAYER_ID = 'x8lr5'; + // Optional: you can also provide multiple keys (CSV) via server env YOUTUBE_API_KEYS. // Rumble API key (optional — Rumble now works via server-side scraping) diff --git a/src/app/shared/providers/dailymotion-player.ts b/src/app/shared/providers/dailymotion-player.ts new file mode 100644 index 0000000..e374324 --- /dev/null +++ b/src/app/shared/providers/dailymotion-player.ts @@ -0,0 +1,33 @@ +/** + * Dailymotion : Player ID obligatoire (fév. 2026). + * + * Dailymotion a rendu le Player ID obligatoire sur le nouvel endpoint + * d'embed : `https://geo.dailymotion.com/player.html?video=…` SANS Player ID + * répond désormais HTTP 403 « Forbidden ». L'URL correcte est + * `https://geo.dailymotion.com/player/{PLAYER_ID}.html?video=…`. + * + * Configurable via `window.DAILYMOTION_PLAYER_ID` (assets/config.local.js). + * Repli sur un Player ID de démonstration public (`x8lr5`, fourni par la + * documentation Dailymotion) pour que l'embed fonctionne sans configuration. + * En production, créez votre propre Player (Dailymotion Studio → Players) et + * renseignez son ID — idéalement un player vertical (aspect_ratio 9:16) pour + * les Shorts. + */ + +const DM_PLAYER_ID_DEFAULT = 'x8lr5'; + +/** Player ID Dailymotion effectif (config explicite sinon démonstration). */ +export function dailymotionPlayerId(): string { + try { + const g = globalThis as any; + const raw = g?.DAILYMOTION_PLAYER_ID; + const v = typeof raw === 'string' ? raw.trim() : ''; + if (v) return v; + } catch {} + return DM_PLAYER_ID_DEFAULT; +} + +/** Base de l'URL d'embed Dailymotion (nouveau endpoint + Player ID). */ +export function dailymotionEmbedBase(videoId: string): string { + return `https://geo.dailymotion.com/player/${encodeURIComponent(dailymotionPlayerId())}.html?video=${encodeURIComponent(videoId)}`; +} diff --git a/src/components/shorts/watch-short.component.ts b/src/components/shorts/watch-short.component.ts index 5f0edfe..ec3f866 100644 --- a/src/components/shorts/watch-short.component.ts +++ b/src/components/shorts/watch-short.component.ts @@ -15,6 +15,7 @@ import { AuthService } from '../../services/auth.service'; import { Video } from '../../models/video.model'; import { shortsFeedAccepts, isPlayable as isPlayablePolicy, type PlayableContext } from '../../app/shared/utils/section-policy'; import { SHORT_TO_LONG, type ProviderId } from '../../app/shared/providers/provider-ids'; +import { dailymotionEmbedBase } from '../../app/shared/providers/dailymotion-player'; import { SearchService } from '../../app/search/search.service'; import { ChannelProviderFactory } from '../../app/core/providers/channel/channel-provider.factory'; import { @@ -375,18 +376,11 @@ export class WatchShortComponent implements OnDestroy { return this.sanitizer.bypassSecurityTrustResourceUrl(u); } if (p === 'dailymotion' && v.videoId) { - // Nouveau player obligatoire depuis fév. 2026 (l'ancien /embed est - // coupé et répond "Forbidden"). Seuls mute/startTime/loop passent - // en query sans Player ID ; autoplay est tenté au mieux. - // Correctif bug "Forbidden" systématique en Shorts alors que /watch - // (toujours muet) joue la même vidéo : on démarre MUET comme /watch - // (même URL, mute=1) et on ne passe au son QUE sur action explicite - // du bouton Son — le clic EST le geste qui autorise le son (même - // motif que le player Odysee, cf. soundTouched). L'autoplay sonore - // sans geste préalable est bloqué et le player par défaut l'affiche - // comme "Forbidden". + // Nouveau player + Player ID obligatoire depuis fév. 2026 (player.html + // sans Player ID répond "Forbidden"). Démarrage MUET (comme /watch), + // son uniquement sur action explicite du bouton Son. const withSound = !this.muted() && this.soundTouched(); - const u = `https://geo.dailymotion.com/player.html?video=${encodeURIComponent(v.videoId)}&mute=${withSound ? 0 : 1}&autoplay=1`; + const u = `${dailymotionEmbedBase(v.videoId)}&mute=${withSound ? 0 : 1}&autoplay=1`; return this.sanitizer.bypassSecurityTrustResourceUrl(u); } if (p === 'twitch' && v.videoId) { diff --git a/src/components/watch/watch.component.ts b/src/components/watch/watch.component.ts index 2694a02..f2ea975 100644 --- a/src/components/watch/watch.component.ts +++ b/src/components/watch/watch.component.ts @@ -17,6 +17,7 @@ import { ChannelsService } from '../../services/channels.service'; import { SubscriptionsService } from '../../services/subscriptions.service'; import type { ChannelMeta, ProviderId } from '../../app/shared/models/channel.model'; import { toShortProviderId } from '../../app/shared/providers/provider-ids'; +import { dailymotionEmbedBase } from '../../app/shared/providers/dailymotion-player'; import { SubscribeButtonComponent } from '../../app/shared/components/subscribe-button/subscribe-button.component'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { IframeProgressService } from '../../services/iframe-progress.service'; @@ -402,10 +403,9 @@ export class WatchComponent implements OnDestroy, AfterViewInit { return this.sanitizer.bypassSecurityTrustResourceUrl(u); } if (p === 'dailymotion') { - // Nouveau player obligatoire depuis fév. 2026 (l'ancien /embed est - // coupé et répond "Forbidden"). Seuls mute/startTime/loop passent - // en query sans Player ID. - let u = `https://geo.dailymotion.com/player.html?video=${encodeURIComponent(id)}&mute=1&autoplay=1`; + // Nouveau player + Player ID obligatoire depuis fév. 2026 (l'ancien + // /embed et player.html sans Player ID répondent "Forbidden"). + let u = `${dailymotionEmbedBase(id)}&mute=1&autoplay=1`; if (start > 0) u += `&startTime=${start}`; return this.sanitizer.bypassSecurityTrustResourceUrl(u); }