REVAMP : Firefox handling
All checks were successful
release / build (push) Successful in 25s
release / verify-windows (push) Successful in 1m15s

This commit is contained in:
jeanotx32
2026-08-12 01:46:14 +02:00
parent 3bd4d5f2ba
commit 77bfa0b8c5
14 changed files with 1223 additions and 108 deletions

130
README.md
View File

@@ -42,8 +42,9 @@ IP publique nécessaire, et obs-websocket reste sur `127.0.0.1`.
- Node.js **22+** sur le serveur et sur chaque VM (le serveur utilise `node:sqlite`).
- OBS **28+** sur chaque VM, avec *Outils → Paramètres du serveur WebSocket* activé.
Note le port (4455 par défaut) et le mot de passe.
- Pour le rappel du plein écran sous Ubuntu : `xdotool` et une session X11
(voir [Prérequis pour le rappel du plein écran](#prérequis-pour-le-rappel-du-plein-écran)).
- Pour le rappel du plein écran sous Ubuntu : Firefox, en mode de pilotage « WebDriver BiDi »
(voir [Deux façons de piloter le navigateur](#deux-façons-de-piloter-le-navigateur)).
Le mode historique exige en plus `xdotool` et une session X11.
## Démarrage rapide (développement)
@@ -157,9 +158,75 @@ moment coûte plus cher que quelques secondes d'écran d'attente enregistrées :
Un passage `hors-ligne` (`idle`) ne provoque ni pause ni reprise : seul le retour effectif
du flux public relance l'enregistrement.
### Prérequis pour le rappel du plein écran
### Deux façons de piloter le navigateur
L'envoi de touche se fait au niveau du système, pas via OBS.
Le rappel du plein écran dépend entièrement de ce choix, qui se règle par agent dans
« Pilotage du navigateur ».
| | **WebDriver BiDi** (recommandé) | **Lancement simple** (historique) |
| --- | --- | --- |
| Ouverture d'une page | commande dans l'instance pilotée | un processus lancé par page |
| Plein écran | commande WebDriver adressée à Firefox | touche envoyée au serveur d'affichage |
| Effet vérifié ? | oui — `document.fullscreenElement` est relu | non |
| Session Wayland | **fonctionne** | impossible (voir plus bas) |
| Dépendances | Firefox | xdotool + session X11 |
#### WebDriver BiDi
Firefox expose ce protocole dès qu'on le lance avec `--remote-debugging-port` : ni
geckodriver, ni Selenium, une simple WebSocket JSON sur la boucle locale. L'agent
implémente le strict nécessaire (`bidi.ts`, `firefox.ts`).
Le renversement est là : **l'agent possède le navigateur** au lieu de lui envoyer des URL
en espérant. Il parle à Firefox, pas au serveur d'affichage — d'où le fonctionnement
identique sous Wayland, où xdotool ne voit rien.
Séquence du rappel de plein écran, dans cet ordre volontaire :
1. `browsingContext.activate` — sans quoi la touche partirait vers un onglet d'arrière-plan ;
2. `input.performActions` — la touche configurée (`f` par défaut), délivrée à la page comme
un vrai évènement (`isTrusted: true`), donc traitée par le lecteur du site ;
3. relecture de `document.fullscreenElement` ;
4. si la page n'a pas bougé : `requestFullscreen()` sur l'élément `<video>`, avec
`userActivation` ;
5. relecture, et verdict rapporté dans l'historique de la VM.
**Un profil Firefox dédié est obligatoire**, pas cosmétique : le port de pilotage ne
s'ouvre qu'au démarrage du processus, et deux instances ne peuvent pas partager un profil.
L'agent en gère un sous `~/.stream-control/firefox-profile` et y réécrit un `user.js` à
chaque lancement — chaque préférence y supprime quelque chose qui finirait dans le fichier
enregistré, ou qui empêcherait un démarrage sans surveillance :
| Préférence | Pourquoi |
| --- | --- |
| `full-screen-api.warning.timeout = 0` | le bandeau « … est maintenant en plein écran » se retrouvait dans les premières secondes de chaque fichier |
| `media.autoplay.default = 0` | sans lecture automatique, l'agent enregistre une image fixe sans que rien ne le signale |
| `browser.aboutwelcome.enabled = false`, `browser.startup.page = 0`, … | tout onglet d'accueil passerait devant la page du streamer |
| `browser.sessionstore.resume_from_crash = false` | un dialogue modal bloquerait toute commande |
| `app.update.auto = false` | une mise à jour fermerait la fenêtre que capture OBS, en plein enregistrement |
**Au premier passage en BiDi, une nouvelle fenêtre Firefox s'ouvre** : pointe la source
« capture de fenêtre » d'OBS dessus une fois. Ensuite elle survit aux enregistrements
comme aux redémarrages de l'agent — le processus est lancé détaché, et l'agent se
rattache au port plutôt que de relancer.
#### Si le plein écran est refusé
Firefox refuse le plein écran à un document **dont la fenêtre n'a pas le focus**. C'est la
première cause d'échec sur une VM : il suffit qu'OBS ou un terminal l'ait pris. L'agent
relit alors trois conditions depuis la page et nomme celle qui manque, plutôt que de
répercuter un « Fullscreen request denied » qui ne dit rien :
| Message | Cause |
| --- | --- |
| `la fenêtre Firefox n'a pas le focus` | une autre fenêtre est active dans la session |
| `aucun élément vidéo dans la page` | le lecteur n'a pas fini de charger — augmente le délai avant plein écran |
| `l'API plein écran est désactivée dans ce profil` | `full-screen-api.enabled` forcé à faux |
#### Lancement simple
Conservé comme défaut pour ne pas changer le comportement d'un agent existant à la mise à
jour. L'envoi de touche se fait au niveau du système, pas via OBS.
| OS | Mécanisme | À prévoir |
| --- | --- | --- |
@@ -168,13 +235,8 @@ L'envoi de touche se fait au niveau du système, pas via OBS.
| Windows | `SetForegroundWindow` + `SendKeys` | L'agent doit tourner dans la session interactive — d'où la tâche planifiée plutôt qu'un service |
| macOS | AppleScript System Events | Autorisation Accessibilité (prévu pour le développement) |
Dans les deux cas la fenêtre du lecteur passe **au premier plan** : les navigateurs
ignorent les évènements clavier synthétiques envoyés sans focus (`XSendEvent`). Sans
conséquence sur une VM d'enregistrement dédiée, gênant si quelqu'un s'en sert en même
temps.
Le bouton ⛶ sur la fiche de l'agent renvoie la touche à la demande, et ⟳ force une sonde
immédiate — les deux servent à valider le titre de fenêtre sans attendre un vrai show privé.
La fenêtre du lecteur passe **au premier plan** : les navigateurs ignorent les évènements
clavier synthétiques envoyés sans focus (`XSendEvent`).
Le **titre de fenêtre** à renseigner est celui de la fenêtre, pas le nom du processus. Sous
Firefox il vaut `<titre de la page> — Mozilla Firefox`, et le titre d'une page Stripchat se
@@ -182,9 +244,6 @@ termine par `| Stripchat` : `Stripchat` comme `Firefox` conviennent donc. En cas
l'agent liste les fenêtres qu'il voit réellement, et `agent.cjs --check` fait de même sans
rien déclencher.
Trois causes distinctes produisaient autrefois le même message « aucune fenêtre ne
correspond » ; elles sont maintenant séparées :
| Symptôme | Cause |
| --- | --- |
| `Authorization required` / `Invalid MIT-MAGIC-COOKIE-1 key` | cookie X introuvable ou périmé |
@@ -192,6 +251,9 @@ correspond » ; elles sont maintenant séparées :
| `Aucune fenêtre visible sur DISPLAY=:0` | navigateur lancé hors de la session de l'agent |
| Liste des fenêtres ouvertes | titre mal renseigné — recopier un fragment de la liste |
Le bouton ⛶ sur la fiche de l'agent renvoie la touche à la demande, et ⟳ force une sonde
immédiate.
### Wayland
Ubuntu démarre en session Wayland par défaut, et Firefox y tourne en client Wayland natif.
@@ -199,18 +261,14 @@ Une telle fenêtre est **invisible à xdotool** : ni activation, ni envoi de tou
symptôme est net — côté X11, seule `mutter guard window` apparaît, la fenêtre technique du
compositeur. L'agent reconnaît cette signature et le dit explicitement.
Deux issues :
**La réponse est le mode WebDriver BiDi**, qui ne passe pas par le serveur d'affichage.
- **Rester en Wayland, passer Firefox sous XWayland.** L'agent pose `MOZ_ENABLE_WAYLAND=0`
en lançant le navigateur, ce qui suffit — *à condition qu'aucune instance Firefox ne
tourne déjà*. Firefox délègue l'URL à l'instance existante, qui garde ses propres
variables d'environnement : ferme toutes ses fenêtres avant de lancer la capture.
- **Ouvrir une session Xorg.** Sur l'écran de connexion GDM, roue dentée en bas à droite →
« Ubuntu sur Xorg ». Tout redevient pilotable, y compris une fenêtre ouverte à la main.
Les contournements d'avant sont conservés ici pour mémoire, mais ils coûtent tous quelque
chose et aucun n'est à préférer :
Si le rappel du plein écran s'avère fragile sur ta VM, l'alternative sans clavier est de
lancer le navigateur en mode kiosque (`chromium --kiosk`) : il n'y a alors plus de plein
écran à restaurer.
- forcer Firefox sous XWayland (`MOZ_ENABLE_WAYLAND=0`) ajoute une copie d'image par trame ;
- basculer la session en Xorg fait chuter les performances de capture (voir
« Xorg, Wayland et le coût de la capture »).
## Presets d'enregistrement
@@ -271,8 +329,9 @@ Sur une VM sans accélération 3D, la méthode de capture pèse lourd :
- **Xorg** : la capture d'écran XSHM recopie tout le tampon à chaque image. La capture de
fenêtre XComposite est nettement plus économe — à préférer systématiquement.
Xorg n'est nécessaire que pour le rappel du plein écran par xdotool. Si le cadrage se fait
côté OBS, Wayland est le meilleur choix en performance.
Xorg n'était nécessaire que pour le rappel du plein écran par xdotool. Le mode de pilotage
« WebDriver BiDi » supprime cette contrainte : **Wayland est le bon choix**, en performance
comme en simplicité.
Un preset trop lourd pour la VM fait chuter les images par seconde : la qualité perçue
baisse alors malgré un meilleur CRF. Après un changement, surveille « FPS » et les deux
@@ -369,7 +428,12 @@ La source « capture de fenêtre » d'OBS mémorise un identifiant de fenêtre X
fenêtre du navigateur, ou en ouvrir une nouvelle, invalide cet identifiant : la source
devient noire et il faut la repointer à la main.
L'agent évite donc les deux :
En mode **WebDriver BiDi** le problème disparaît : l'agent ne relance jamais de processus,
il navigue dans l'onglet qu'il pilote. La fenêtre est ouverte une fois et survit à tout,
y compris à un redémarrage de l'agent — celui-ci se rattache au port au lieu de relancer.
La seule fois où il faut repointer la source OBS est le passage initial en BiDi.
En mode **lancement simple**, l'agent évite les deux pièges :
- **aucun argument** par défaut (`--new-window` est proscrit) : Firefox confie l'URL à la
fenêtre déjà ouverte ;
@@ -381,8 +445,10 @@ arguments ou leur comportement d'arrêt ont été personnalisés.
### « Firefox est déjà ouvert »
Ce dialogue signifie que le processus lancé par l'agent n'a pas trouvé l'instance déjà en
cours : il bute alors sur le verrou de profil au lieu de lui confier l'URL.
Propre au mode **lancement simple** : ce dialogue signifie que le processus lancé par
l'agent n'a pas trouvé l'instance déjà en cours, et bute sur le verrou de profil au lieu de
lui confier l'URL. Le mode BiDi ne peut pas le rencontrer — il n'y a qu'une instance, sur
un profil qui n'appartient qu'à l'agent.
Le passage de relais se fait par le **bus de session D-Bus** — le seul mécanisme disponible
sous Wayland, le protocole X de remoting n'y existant pas. Or un service systemd « system »
@@ -395,13 +461,17 @@ n'hérite pas de ce bus, pas plus qu'il n'hérite du cookie X. L'agent résout d
| `XAUTHORITY` | `$XAUTHORITY` s'il existe, puis GDM, Xwayland, `~/.Xauthority` |
| `XDG_RUNTIME_DIR` | valeur héritée, sinon `/run/user/<uid>` |
| `DBUS_SESSION_BUS_ADDRESS` | valeur héritée, sinon la socket `$XDG_RUNTIME_DIR/bus` |
| `WAYLAND_DISPLAY` | valeur héritée, sinon la socket `wayland-<n>` du répertoire d'exécution |
`agent.cjs --check` affiche les quatre. Si le bus ressort en avertissement, vérifie que
`agent.cjs --check` les affiche toutes. Si le bus ressort en avertissement, vérifie que
l'agent tourne bien sous **le même compte** que la session graphique : l'utilisateur est
choisi par `--user` à l'installation.
### Éviter l'accumulation d'onglets
Propre lui aussi au mode **lancement simple** ; en BiDi l'agent navigue toujours dans le
même onglet.
Avec les réglages d'usine de Firefox, un lien venu de l'extérieur ouvre un **nouvel
onglet** : chaque cycle d'enregistrement en laisse donc derrière lui, et les anciens
continuent de décoder leur page. Sur une VM d'enregistrement, règle une fois pour toutes