fix bla
This commit is contained in:
@@ -103,14 +103,17 @@ export class FirefoxController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rappelle le plein écran, puis le vérifie.
|
||||
* Rappelle le plein écran en envoyant la touche du lecteur, et seulement elle.
|
||||
*
|
||||
* Deux tentatives, dans cet ordre volontaire. La touche d'abord : c'est le
|
||||
* geste de l'opérateur, elle passe par le gestionnaire du lecteur et donne
|
||||
* exactement le cadrage attendu par le site. L'appel direct à
|
||||
* `requestFullscreen` ensuite, seulement si la première n'a rien produit —
|
||||
* il fonctionne toujours mais met en plein écran l'élément vidéo brut, sans
|
||||
* l'habillage du lecteur.
|
||||
* Volontairement une seule voie, sans repli. Une version antérieure appelait
|
||||
* `requestFullscreen()` sur l'élément vidéo brut quand la touche ne semblait
|
||||
* pas avoir agi — mais « ne semblait pas » se mesurait à
|
||||
* `document.fullscreenElement`, qui ne dit rien du bouton « théâtre » de
|
||||
* Stripchat : ce mode-là garde la barre du haut visible et n'a souvent rien
|
||||
* à voir avec l'API plein écran native du navigateur. Le repli déclenchait
|
||||
* alors un vrai plein écran natif — vidéo seule, tout le reste disparu —
|
||||
* précisément l'effet que la touche du lecteur, elle, évite. Mieux vaut
|
||||
* envoyer le geste attendu par le site et ne pas le corriger à l'aveugle.
|
||||
*/
|
||||
async restoreFullscreen(settings: FullscreenSettings): Promise<FullscreenOutcome> {
|
||||
await this.ensureReady();
|
||||
@@ -139,57 +142,18 @@ export class FirefoxController {
|
||||
// Le passage en plein écran est animé : relire trop tôt renverrait faux
|
||||
// alors que la touche a bien été prise en compte.
|
||||
await delay(1200);
|
||||
if (await this.isFullscreen()) {
|
||||
this.fullscreen = true;
|
||||
return { method: 'webdriver (touche)', target: this.url ?? undefined, confirmed: true };
|
||||
}
|
||||
|
||||
this.log(
|
||||
'info',
|
||||
`La touche « ${settings.key} » n'a pas basculé la page ; tentative directe sur l'élément vidéo`,
|
||||
);
|
||||
const detail = await this.requestFullscreenOnVideo();
|
||||
await delay(800);
|
||||
const confirmed = await this.isFullscreen();
|
||||
|
||||
this.fullscreen = confirmed;
|
||||
if (confirmed) {
|
||||
return { method: 'webdriver (élément vidéo)', target: this.url ?? undefined, confirmed };
|
||||
}
|
||||
|
||||
// « Fullscreen request denied » ne dit rien à personne. Les trois conditions
|
||||
// que Gecko exige sont lisibles depuis la page : on les relit pour nommer
|
||||
// celle qui manque, plutôt que de laisser l'opérateur deviner.
|
||||
const why = await this.explainRefusal(detail);
|
||||
this.lastError = why;
|
||||
return { method: `webdriver — ${why}`, target: this.url ?? undefined, confirmed: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Nomme la condition manquante après un refus de plein écran.
|
||||
*
|
||||
* Le focus est de loin la première cause : Firefox refuse le plein écran à un
|
||||
* document dont la fenêtre n'est pas celle qu'a sélectionnée le gestionnaire
|
||||
* de fenêtres — et sur une VM, il suffit qu'OBS ou un terminal l'ait pris.
|
||||
*/
|
||||
private async explainRefusal(detail: string): Promise<string> {
|
||||
const raw = await this.evaluate(
|
||||
'JSON.stringify({ focus: document.hasFocus(), api: document.fullscreenEnabled, video: !!document.querySelector("video") })',
|
||||
).catch(() => null);
|
||||
|
||||
const page = typeof raw === 'string' ? (JSON.parse(raw) as Record<string, boolean>) : null;
|
||||
if (!page) return detail;
|
||||
|
||||
if (!page.video) return 'aucun élément vidéo dans la page — le lecteur a-t-il fini de charger ?';
|
||||
if (!page.focus) {
|
||||
return (
|
||||
"la fenêtre Firefox n'a pas le focus : Firefox refuse le plein écran à un onglet " +
|
||||
"en arrière-plan. Ferme les autres fenêtres de la session, ou laisse Firefox seul " +
|
||||
"au premier plan sur cette VM"
|
||||
);
|
||||
}
|
||||
if (!page.api) return "l'API plein écran est désactivée dans ce profil Firefox";
|
||||
return detail;
|
||||
// `confirmed` reste `undefined`, pas `false`, quand l'API native ne signale
|
||||
// rien : c'est le cas attendu pour un mode « théâtre » propre au site, pas
|
||||
// un échec. Le distinguer évite de journaliser en avertissement une touche
|
||||
// qui a très bien pu faire son effet.
|
||||
return {
|
||||
method: 'webdriver (touche)',
|
||||
target: this.url ?? undefined,
|
||||
confirmed: confirmed ? true : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/** Décharge le lecteur sans détruire la fenêtre — donc sans casser la source OBS. */
|
||||
@@ -494,30 +458,6 @@ export class FirefoxController {
|
||||
return result === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demande le plein écran sur l'élément vidéo.
|
||||
*
|
||||
* `userActivation` fait croire à Firefox à un geste utilisateur : sans lui,
|
||||
* l'API refuse l'appel. Le profil pose aussi
|
||||
* `full-screen-api.allow-trusted-requests-only=false`, pour les versions de
|
||||
* Firefox qui ne connaissent pas encore ce paramètre de commande.
|
||||
*/
|
||||
private async requestFullscreenOnVideo(): Promise<string> {
|
||||
const expression = `(async () => {
|
||||
const video = document.querySelector('video');
|
||||
if (!video) return 'aucune vidéo dans la page';
|
||||
try {
|
||||
await (video.requestFullscreen ? video.requestFullscreen() : Promise.reject(new Error('API absente')));
|
||||
return 'ok';
|
||||
} catch (err) {
|
||||
return 'refus : ' + (err && err.message ? err.message : err);
|
||||
}
|
||||
})()`;
|
||||
|
||||
const result = await this.evaluate(expression, true).catch((err: Error) => err.message);
|
||||
return typeof result === 'string' ? result : 'ok';
|
||||
}
|
||||
|
||||
private async evaluate(expression: string, userActivation = false): Promise<unknown> {
|
||||
const response = await this.send<{
|
||||
type: string;
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
/**
|
||||
* Résultat d'un rappel de plein écran, quelle que soit la méthode employée.
|
||||
*
|
||||
* `confirmed` est le champ qui compte. xdotool ne peut jamais le renseigner :
|
||||
* il envoie une touche et n'a aucun moyen de savoir ce que la page en a fait.
|
||||
* BiDi, lui, relit `document.fullscreenElement` — donc un `false` ici est une
|
||||
* information, pas une incertitude, et c'est ce qui permet d'enchaîner sur une
|
||||
* seconde tentative plutôt que de laisser l'enregistrement en fenêtré.
|
||||
* `confirmed` distingue trois cas, pas deux. xdotool ne peut jamais le
|
||||
* renseigner (`undefined`) : il envoie une touche et n'a aucun moyen de
|
||||
* savoir ce que la page en a fait. BiDi relit `document.fullscreenElement`,
|
||||
* mais son absence ne vaut pas `false` pour autant : beaucoup de lecteurs —
|
||||
* celui de Stripchat compris — implémentent leur propre mode « théâtre » en
|
||||
* CSS, barre du haut toujours visible, sans jamais toucher à l'API plein
|
||||
* écran native. `undefined` couvre donc aussi bien « aucun moyen de vérifier »
|
||||
* que « vérifié négatif, mais rien ne prouve un échec » ; `false` ne
|
||||
* s'emploie que là où l'échec est positivement établi.
|
||||
*/
|
||||
export interface FullscreenOutcome {
|
||||
/** `webdriver`, `xdotool`, `SendKeys`, `osascript`… */
|
||||
method: string;
|
||||
/** Titre de fenêtre ou URL, selon ce que la méthode a pu identifier. */
|
||||
target?: string;
|
||||
/** Vérifié dans la page. `undefined` = la méthode ne sait pas vérifier. */
|
||||
/** Vrai si confirmé, faux si l'échec est positivement établi, sinon indéterminé. */
|
||||
confirmed?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -276,6 +276,12 @@ async function startCapture(params: Record<string, unknown>): Promise<unknown> {
|
||||
});
|
||||
}
|
||||
|
||||
// Dernière chance d'écrire un preset en attente : une fois l'enregistrement
|
||||
// commencé, OBS refuse toute modification jusqu'à l'arrêt. Sans cet appel
|
||||
// explicite, l'écriture dépendait du hasard d'un cycle de statut (toutes les
|
||||
// 2 s) tombant avant le démarrage — perdu, le preset restait en attente
|
||||
// jusqu'au prochain arrêt, et cet enregistrement tournait sur l'ancien réglage.
|
||||
await drainPresetApply();
|
||||
await obs.execute('record.start');
|
||||
report('info', `Capture démarrée pour ${url}`, 'capture.started');
|
||||
|
||||
@@ -348,6 +354,12 @@ async function runAction(action: AgentAction, params: Record<string, unknown>):
|
||||
return startCapture(params);
|
||||
case 'capture.stop':
|
||||
return stopCapture();
|
||||
case 'record.start':
|
||||
// Même raison que dans startCapture() : sans ce passage explicite, un
|
||||
// preset changé juste avant restait en attente jusqu'au prochain arrêt,
|
||||
// et cette capture démarrait sur l'ancien réglage.
|
||||
await drainPresetApply();
|
||||
return obs.execute('record.start');
|
||||
case 'preset.apply':
|
||||
return applyPresetNow(params);
|
||||
case 'agent.update':
|
||||
|
||||
Reference in New Issue
Block a user