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
|
* auquel cas l'utilisateur tape une fois sur la vidéo (geste qui débloque
|
||||||
* les suivantes).
|
* les suivantes).
|
||||||
*
|
*
|
||||||
* Exceptions (muet forcé, autoplay garanti) :
|
* Cas particuliers :
|
||||||
* - Twitch clips : l'embed `clips.twitch.tv` n'expose AUCUNE API de son —
|
* - 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
|
* - Rumble : `autoplay=1` (son) par défaut, `autoplay=2` (muet) si
|
||||||
* l'utilisateur coupe le son.
|
* l'utilisateur coupe le son.
|
||||||
*/
|
*/
|
||||||
@@ -454,11 +455,9 @@ export class WatchShortComponent implements OnDestroy {
|
|||||||
// Les Shorts Twitch = clips uniquement (jamais de live/channel).
|
// Les Shorts Twitch = clips uniquement (jamais de live/channel).
|
||||||
const clipId = (v as any).kind === 'clip' ? v.videoId : null;
|
const clipId = (v as any).kind === 'clip' ? v.videoId : null;
|
||||||
if (clipId) {
|
if (clipId) {
|
||||||
// `muted=true` FORCÉ (indépendant du bouton Son) : l'embed clips
|
// Son piloté par le bouton Son du menu (`muted`, défaut = son ON) —
|
||||||
// n'expose aucune API de volume — muet est la seule combinaison
|
// le rechargement de l'embed applique le nouvel état.
|
||||||
// qui démarre sans geste. Le son reste disponible via « Ouvrir
|
const u = `https://clips.twitch.tv/embed?clip=${encodeURIComponent(clipId)}&${parentParams}&autoplay=true&muted=${this.muted()}${nudge}`;
|
||||||
// chez le fournisseur » / Watch.
|
|
||||||
const u = `https://clips.twitch.tv/embed?clip=${encodeURIComponent(clipId)}&${parentParams}&autoplay=true&muted=true${nudge}`;
|
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
||||||
}
|
}
|
||||||
// Repli : player channel/VOD (ne devrait pas arriver, les lives
|
// 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 base = 'https://player.twitch.tv/';
|
||||||
const chan = !/^\d+$/.test(String(v.videoId)) ? v.videoId : null;
|
const chan = !/^\d+$/.test(String(v.videoId)) ? v.videoId : null;
|
||||||
const qs = chan
|
const qs = chan
|
||||||
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true&muted=true${nudge}`
|
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true&muted=${this.muted()}${nudge}`
|
||||||
: `?video=${encodeURIComponent(v.videoId)}&${parentParams}&autoplay=true&muted=true${nudge}`;
|
: `?video=${encodeURIComponent(v.videoId)}&${parentParams}&autoplay=true&muted=${this.muted()}${nudge}`;
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(base + qs);
|
return this.sanitizer.bypassSecurityTrustResourceUrl(base + qs);
|
||||||
}
|
}
|
||||||
if (p === 'peertube' && v.videoId) {
|
if (p === 'peertube' && v.videoId) {
|
||||||
@@ -805,9 +804,10 @@ export class WatchShortComponent implements OnDestroy {
|
|||||||
} catch {}
|
} catch {}
|
||||||
this.autoAdvanceKey = '';
|
this.autoAdvanceKey = '';
|
||||||
if (!v?.videoId) return;
|
if (!v?.videoId) return;
|
||||||
// Embed Twitch en attente d'un geste : il n'y a rien à l'écran, inutile de
|
// Twitch : l'embed clips est cross-origin et n'expose AUCUNE API d'état —
|
||||||
// faire défiler le flux (sinon les videos « switchent » sans jamais jouer).
|
// on ne peut pas savoir si la vidéo est en pause. Pas d'avance auto pour
|
||||||
if (this.providerOf(v) === 'twitch' && !this.twitchCanLoad()) return;
|
// 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 known = Number((v as Video).duration || 0);
|
||||||
const secs = (Number.isFinite(known) && known > 0) ? known : this.estimatedDuration(v);
|
const secs = (Number.isFinite(known) && known > 0) ? known : this.estimatedDuration(v);
|
||||||
if (!Number.isFinite(secs) || secs <= 0) return;
|
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).
|
// 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));
|
const clipId = clip || (/^\d+$/.test(String(id || '')) ? null : ((this.video() as any)?.kind === 'clip' ? id : null));
|
||||||
if (clipId) {
|
if (clipId) {
|
||||||
// autoplay+muet, comme les autres fournisseurs de la page (YouTube
|
// Son PAR DÉFAUT (demande utilisateur) : le chargement a lieu dans un
|
||||||
// mute=1, Dailymotion mute=1, Odysee muted=1) : le lecteur démarre
|
// geste (voir twitchCanLoad), donc l'autoplay sonore est autorisé.
|
||||||
// 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${nudge}`;
|
||||||
const u = `https://clips.twitch.tv/embed?clip=${encodeURIComponent(clipId)}&${parentParams}&autoplay=true&muted=true${nudge}`;
|
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
||||||
}
|
}
|
||||||
const base = 'https://player.twitch.tv/';
|
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).
|
// Les VOD acceptent time=1h2m3s (seek au chargement, ex. transcript).
|
||||||
const timeParam = !chan && start > 0 ? `&time=${encodeURIComponent(this.formatTwitchTime(start))}` : '';
|
const timeParam = !chan && start > 0 ? `&time=${encodeURIComponent(this.formatTwitchTime(start))}` : '';
|
||||||
const qs = chan
|
const qs = chan
|
||||||
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true&muted=true${nudge}`
|
? `?channel=${encodeURIComponent(chan)}&${parentParams}&autoplay=true${nudge}`
|
||||||
: `?video=${encodeURIComponent(id)}&${parentParams}&autoplay=true&muted=true${timeParam}${nudge}`;
|
: `?video=${encodeURIComponent(id)}&${parentParams}&autoplay=true${timeParam}${nudge}`;
|
||||||
const u = base + qs;
|
const u = base + qs;
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
return this.sanitizer.bypassSecurityTrustResourceUrl(u);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user