feat(twitch): son par défaut + pas d'avance auto sur les clips
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
- /watch : muted=true supprime — son actif dès le démarrage (le chargement de l'embed a lieu dans un geste, donc l'autoplay sonore est autorisé). - /shorts : muted piloté par le bouton Son du menu (défaut : son ON) — ce bouton était jusqu'ici sans effet sur Twitch. - /shorts : plus d'avance auto pour Twitch. L'embed clips est cross-origin et n'expose AUCUNE API d'état : impossible de savoir si la vidéo est en pause, donc on ne change jamais de vidéo tout seul (swipe / flèches / molette). - Verifs instance locale : 4/4 (URLs + aucune avance après 75 s d'un clip de 49 s) + 6/6 (nudge) + 8/8 (chargement au premier geste).
This commit is contained in:
@@ -131,9 +131,10 @@ export class WatchShortComponent implements OnDestroy {
|
||||
* auquel cas l'utilisateur tape une fois sur la vidéo (geste qui débloque
|
||||
* les suivantes).
|
||||
*
|
||||
* Exceptions (muet forcé, autoplay garanti) :
|
||||
* Cas particuliers :
|
||||
* - Twitch clips : l'embed `clips.twitch.tv` n'expose AUCUNE API de son —
|
||||
* `muted=true` est la seule combinaison qui démarre sans geste ;
|
||||
* l'état muet passe par le paramètre `muted` de l'URL (défaut : son ON,
|
||||
* piloté par le bouton Son du menu) ;
|
||||
* - Rumble : `autoplay=1` (son) par défaut, `autoplay=2` (muet) si
|
||||
* l'utilisateur coupe le son.
|
||||
*/
|
||||
@@ -454,11 +455,9 @@ export class WatchShortComponent implements OnDestroy {
|
||||
// Les Shorts Twitch = clips uniquement (jamais de live/channel).
|
||||
const clipId = (v as any).kind === 'clip' ? v.videoId : null;
|
||||
if (clipId) {
|
||||
// `muted=true` FORCÉ (indépendant du bouton Son) : l'embed clips
|
||||
// n'expose aucune API de volume — muet est la seule combinaison
|
||||
// qui démarre sans geste. Le son reste disponible via « Ouvrir
|
||||
// chez le fournisseur » / Watch.
|
||||
const u = `https://clips.twitch.tv/embed?clip=${encodeURIComponent(clipId)}&${parentParams}&autoplay=true&muted=true${nudge}`;
|
||||
// Son piloté par le bouton Son du menu (`muted`, défaut = son ON) —
|
||||
// le rechargement de l'embed applique le nouvel état.
|
||||
const u = `https://clips.twitch.tv/embed?clip=${encodeURIComponent(clipId)}&${parentParams}&autoplay=true&muted=${this.muted()}${nudge}`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
||||
}
|
||||
// Repli : player channel/VOD (ne devrait pas arriver, les lives
|
||||
@@ -466,8 +465,8 @@ export class WatchShortComponent implements OnDestroy {
|
||||
const base = 'https://player.twitch.tv/';
|
||||
const chan = !/^\d+$/.test(String(v.videoId)) ? v.videoId : null;
|
||||
const qs = chan
|
||||
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true&muted=true${nudge}`
|
||||
: `?video=${encodeURIComponent(v.videoId)}&${parentParams}&autoplay=true&muted=true${nudge}`;
|
||||
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true&muted=${this.muted()}${nudge}`
|
||||
: `?video=${encodeURIComponent(v.videoId)}&${parentParams}&autoplay=true&muted=${this.muted()}${nudge}`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(base + qs);
|
||||
}
|
||||
if (p === 'peertube' && v.videoId) {
|
||||
@@ -805,9 +804,10 @@ export class WatchShortComponent implements OnDestroy {
|
||||
} catch {}
|
||||
this.autoAdvanceKey = '';
|
||||
if (!v?.videoId) return;
|
||||
// Embed Twitch en attente d'un geste : il n'y a rien à l'écran, inutile de
|
||||
// faire défiler le flux (sinon les videos « switchent » sans jamais jouer).
|
||||
if (this.providerOf(v) === 'twitch' && !this.twitchCanLoad()) return;
|
||||
// Twitch : l'embed clips est cross-origin et n'expose AUCUNE API d'état —
|
||||
// on ne peut pas savoir si la vidéo est en pause. Pas d'avance auto pour
|
||||
// ne jamais changer de vidéo pendant une pause (navigation manuelle).
|
||||
if (this.providerOf(v) === 'twitch') return;
|
||||
const known = Number((v as Video).duration || 0);
|
||||
const secs = (Number.isFinite(known) && known > 0) ? known : this.estimatedDuration(v);
|
||||
if (!Number.isFinite(secs) || secs <= 0) return;
|
||||
|
||||
@@ -484,10 +484,9 @@ export class WatchComponent implements OnDestroy, AfterViewInit {
|
||||
// Clips : player dédié clips.twitch.tv (le player VOD rejette les slugs).
|
||||
const clipId = clip || (/^\d+$/.test(String(id || '')) ? null : ((this.video() as any)?.kind === 'clip' ? id : null));
|
||||
if (clipId) {
|
||||
// autoplay+muet, comme les autres fournisseurs de la page (YouTube
|
||||
// mute=1, Dailymotion mute=1, Odysee muted=1) : le lecteur démarre
|
||||
// tout seul, le son se réactive via les commandes du player.
|
||||
const u = `https://clips.twitch.tv/embed?clip=${encodeURIComponent(clipId)}&${parentParams}&autoplay=true&muted=true${nudge}`;
|
||||
// Son PAR DÉFAUT (demande utilisateur) : le chargement a lieu dans un
|
||||
// geste (voir twitchCanLoad), donc l'autoplay sonore est autorisé.
|
||||
const u = `https://clips.twitch.tv/embed?clip=${encodeURIComponent(clipId)}&${parentParams}&autoplay=true${nudge}`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
||||
}
|
||||
const base = 'https://player.twitch.tv/';
|
||||
@@ -497,8 +496,8 @@ export class WatchComponent implements OnDestroy, AfterViewInit {
|
||||
// Les VOD acceptent time=1h2m3s (seek au chargement, ex. transcript).
|
||||
const timeParam = !chan && start > 0 ? `&time=${encodeURIComponent(this.formatTwitchTime(start))}` : '';
|
||||
const qs = chan
|
||||
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true&muted=true${nudge}`
|
||||
: `?video=${encodeURIComponent(id)}&${parentParams}&autoplay=true&muted=true${timeParam}${nudge}`;
|
||||
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true${nudge}`
|
||||
: `?video=${encodeURIComponent(id)}&${parentParams}&autoplay=true${timeParam}${nudge}`;
|
||||
const u = base + qs;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user