Live: lo sfocato dello sfondo fallisce sempre in Brave Browser ("Impossibile avviare la sfocatura dello sfondo") - .play() di <video> staccato viene interrotto

Riepilogo

FAO: @Falco :smiling_face_with_sunglasses:
Dichiarazione sull’AI: Questo bug è stato indagato in modo autonomo e causa e rimedio sono stati suggeriti da Opus 5.5.

In Brave Browser, l’attivazione della sfocatura dello sfondo in una stanza Live mostra sempre un toast di errore: “Impossibile avviare la sfocatura dello sfondo, quindi è stata disattivata.” Lo stesso utente, forum e stanza funzionano correttamente in Google Chrome sulla stessa macchina.

error: "Background blur couldn't be started, so it was turned off."

La causa è il <video> nascosto che BackgroundBlurManager#setup() crea ma non inserisce mai nel documento. Brave rifiuta play() su di esso con:

AbortError: The play() request was interrupted because the media was removed from the document.

L’inserimento dell’elemento nel documento (invisibilmente) prima di play() lo fa funzionare in Brave.

Ambiente

  • Discourse 2026.10.0-latest.1 (568a006ad8), plugin Live incluso, discourse_voice_assets 0.2.0
  • Brave (Chromium 155) su Linux, Wayland, GPU Intel; WebGL2 e WebGPU accelerati via hardware
  • Google Chrome sulla stessa macchina: funziona

Passi per riprodurre

  1. Apri una stanza Live in Brave e accendi la fotocamera.
  2. Abilita la sfocatura dello sfondo.
  3. Compare il toast e la sfocatura viene disattivata.

Diagnosi

Ho eseguito la pipeline di sfocatura del plugin passo dopo passo nella console di Brave (stesso percorso asset, modello, delegate e modalità di esecuzione come background-blur.js), con la protezione contro le impronte digitali di Brave attiva e poi disattiva:

Passo Brave, protezione impronte digitali attiva Brave, protezione impronte digitali disattiva
Import di vision_bundle.js, caricamento WASM ok ok
ImageSegmenter.createFromOptions, delegate GPU e CPU ok ok
getUserMedia({ video: true }), stato della traccia ok, live ok, live
play() su un <video> mutato distaccato con lo stream della fotocamera (ciò che fa il plugin) AbortError AbortError
play() sullo stesso tipo di <video> aggiunto a document.body (1px, trasparente) ok ok

Quindi MediaPipe, WebGL e la fotocamera vanno tutti bene. Solo il play() distaccato fallisce, e la protezione contro le impronte digitali di Brave non fa differenza. Chromium headless riproduce un <video> distaccato senza errori, il che è coerente con il funzionamento di Chrome.

Correzione suggerita

In plugins/live/assets/javascripts/discourse/lib/live/background-blur.js, setup(): aggiungi il video nascosto al documento prima di video.play() e rimuovilo in teardown(). Ad esempio:

video.setAttribute("aria-hidden", "true");
video.style.cssText =
  "position:fixed;width:1px;height:1px;opacity:0;pointer-events:none";
document.body.appendChild(video);
// ... await video.play() come ora ...

// teardown():
this.#video?.remove();

Per quanto posso vedere, questo è l’unico punto nel plugin in cui viene riprodotto un elemento multimediale distaccato.

Due osservazioni correlate

  1. L’errore reale viene ignorato. #createBackgroundBlur() in local-video-manager.js usa un semplice catch {} e chiama liveLog.warn("[live] failed to start background blur") senza l’errore. Anche con live_verbose_logging abilitato, la console non dice mai perché la sfocatura è fallita. Passare l’errore catturato a liveLog.warn (che già lo sanifica) avrebbe fatto emergere l’errore più rapidamente.

  2. La sfocatura viene offerta dove non può mai funzionare. BackgroundBlurManager.isSupported() controlla solo canvas.captureStream e WebAssembly. In un browser senza WebGL, il segmentatore di MediaPipe fallisce con entrambi i delegate GPU e CPU (Service "kGpuService" ... cannot be created: emscripten_webgl_create_context() returned error 0), perché il post-processore di segmentazione ha bisogno di un contesto GL in ogni caso. L’aggiunta di un controllo WebGL2 a isSupported() nasconderebbe o disabiliterebbe il controllo della sfocatura invece di fallire dopo che l’utente l’ha attivata. (Riprodotta in Chromium headless facendo uno stub di getContext("webgl*"), non vista in ambiente reale.)

Grazie per Live - a parte questo, funziona bene per noi.