Feat : detection de fullscreen
This commit is contained in:
@@ -33,7 +33,7 @@ import { ObsController } from './obs.ts';
|
||||
import { StreamWatcher } from './watcher.ts';
|
||||
import { currentBuildId, runningBundlePath, selfUpdate } from './updater.ts';
|
||||
import { blankPage, closeWindow, delay as sleep, openUrl } from './browser.ts';
|
||||
import { FirefoxController, type StreamQualityOutcome } from './firefox.ts';
|
||||
import { FirefoxController, describeMetrics, type StreamQualityOutcome } from './firefox.ts';
|
||||
import { describeFullscreen, type FullscreenOutcome } from './fullscreen.ts';
|
||||
import { sendHotkey } from './hotkey.ts';
|
||||
import { cpuUsagePercent, diskUsage, memoryUsage } from './system.ts';
|
||||
@@ -252,6 +252,9 @@ async function startCapture(params: Record<string, unknown>): Promise<unknown> {
|
||||
}
|
||||
|
||||
const url = requireUrl(params);
|
||||
// Une nouvelle capture repart avec ses propres chances : un abandon décidé sur
|
||||
// la précédente ne doit pas laisser celle-ci sans surveillance.
|
||||
fullscreenMisses = 0;
|
||||
const opened = await openPage(url);
|
||||
|
||||
const wait = Number(params.readyDelayMs ?? browserSettings.readyDelayMs);
|
||||
@@ -473,6 +476,93 @@ async function restoreFullscreen(settings: FullscreenSettings): Promise<Fullscre
|
||||
return fullscreenInFlight;
|
||||
}
|
||||
|
||||
// --- Surveillance du plein écran ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Fréquence de relecture du plein écran pendant une capture.
|
||||
*
|
||||
* Espacée : la mesure est une évaluation dans la page, et rien ne justifie de
|
||||
* la refaire au rythme du statut (2 s). Une perte de plein écran gâche au pire
|
||||
* cette demi-minute de vidéo, là où une relecture serrée pèserait en continu.
|
||||
*/
|
||||
const FULLSCREEN_WATCH_MS = 30_000;
|
||||
|
||||
/**
|
||||
* Rappels de surveillance restés sans effet, à la suite. Au-delà, on cesse
|
||||
* d'insister : réappuyer toutes les 30 s sur une touche qui ne produit rien
|
||||
* noierait l'historique sans rien corriger. Le compteur repart à chaque
|
||||
* nouvelle capture, et à chaque rappel qui aboutit.
|
||||
*/
|
||||
let fullscreenMisses = 0;
|
||||
const MAX_FULLSCREEN_MISSES = 3;
|
||||
let fullscreenTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
/**
|
||||
* Vérifie que le lecteur occupe toujours la fenêtre, et le rétablit sinon.
|
||||
*
|
||||
* C'est le pendant du rappel programmé par {@link StreamWatcher} : celui-ci ne
|
||||
* réagit qu'aux interruptions du flux, alors que le plein écran peut se perdre
|
||||
* sans que le statut bouge — un clic malheureux, un ré-affichage du lecteur, une
|
||||
* publicité qui reprend la main. Rien ne le signalait jusqu'ici, d'où des
|
||||
* captures qui en sortaient sans qu'on sache ni quand ni pourquoi ; la mesure
|
||||
* est maintenant relue régulièrement, et l'écart journalisé.
|
||||
*/
|
||||
async function watchFullscreen(): Promise<void> {
|
||||
const bidi = driver();
|
||||
const settings = watcher.snapshotSettings.fullscreen;
|
||||
if (!bidi || !settings.enabled || fullscreenMisses >= MAX_FULLSCREEN_MISSES) return;
|
||||
|
||||
// Uniquement pendant une capture réellement en cours. Hors capture il n'y a
|
||||
// rien à cadrer ; en pause (show privé), l'overlay du site occupe le lecteur
|
||||
// et insister entrerait en conflit avec le rappel que la surveillance
|
||||
// programme déjà pour le retour du flux public.
|
||||
const record = await obs.recordState().catch(() => null);
|
||||
if (!record?.active || record.paused) {
|
||||
fullscreenMisses = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// `undefined` couvre aussi bien « pas de lecteur dans la page » que « rien ne
|
||||
// permet de trancher » : dans les deux cas, agir se ferait à l'aveugle.
|
||||
const { fullscreen, metrics, baseline } = await bidi.checkFullscreen();
|
||||
if (fullscreen !== false) {
|
||||
fullscreenMisses = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
fullscreenMisses += 1;
|
||||
// La mesure part avec le constat : c'est elle qui dit ce que le lecteur occupe
|
||||
// réellement au moment où on le déclare sorti du plein écran.
|
||||
report(
|
||||
'warn',
|
||||
`Plein écran perdu en cours de capture (${describeMetrics(metrics, baseline)}) — rappel envoyé`,
|
||||
'fullscreen.failed',
|
||||
);
|
||||
|
||||
const outcome = await restoreFullscreen(settings).catch((err: Error) => {
|
||||
report('warn', `Rappel du plein écran impossible : ${err.message}`, 'fullscreen.failed');
|
||||
return null;
|
||||
});
|
||||
if (!outcome) return;
|
||||
|
||||
// Un envoi ignoré (rappel trop rapproché) ne prouve rien : il ne compte ni
|
||||
// comme réussite ni comme échec, et la relecture suivante tranchera.
|
||||
if (outcome.skipped) return;
|
||||
if (outcome.confirmed === true) {
|
||||
fullscreenMisses = 0;
|
||||
report('info', describeFullscreen(outcome, settings.key), 'fullscreen.restored');
|
||||
return;
|
||||
}
|
||||
if (fullscreenMisses >= MAX_FULLSCREEN_MISSES) {
|
||||
report(
|
||||
'warn',
|
||||
`Plein écran non rétabli après ${MAX_FULLSCREEN_MISSES} tentatives : surveillance ` +
|
||||
'suspendue jusqu\'au prochain enregistrement.',
|
||||
'fullscreen.failed',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne la meilleure qualité offerte par le lecteur Stripchat.
|
||||
*
|
||||
@@ -619,11 +709,23 @@ function startStatusLoop(): void {
|
||||
void pushStatus();
|
||||
statusTimer = setInterval(() => void pushStatus(), statusIntervalMs);
|
||||
statusTimer.unref?.();
|
||||
|
||||
// Horloge distincte de celle du statut : la relecture du plein écran est bien
|
||||
// plus espacée, et la caler sur un cycle de 2 s reviendrait à évaluer du code
|
||||
// dans la page en permanence.
|
||||
fullscreenTimer = setInterval(() => {
|
||||
void watchFullscreen().catch((err: unknown) => {
|
||||
console.error('Surveillance du plein écran en échec :', err);
|
||||
});
|
||||
}, FULLSCREEN_WATCH_MS);
|
||||
fullscreenTimer.unref?.();
|
||||
}
|
||||
|
||||
function stopStatusLoop(): void {
|
||||
if (statusTimer) clearInterval(statusTimer);
|
||||
statusTimer = null;
|
||||
if (fullscreenTimer) clearInterval(fullscreenTimer);
|
||||
fullscreenTimer = null;
|
||||
}
|
||||
|
||||
// --- Cycle de vie ------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user