Live: background blur always fails in Brave Browser ("Background blur couldn't be started") - detached `<video>`.play() is aborted

AI-generated summary

pacharanero reported that enabling background blur in Live rooms consistently fails in Brave Browser with the error “Background blur couldn’t be started.” While the feature works correctly in Google Chrome on the same machine, Brave throws an AbortError when attempting to play a detached <video> element used internally by the BackgroundBlurManager. The user’s investigation confirmed that MediaPipe, WebGL, and camera access function normally; the issue is strictly related to Brave’s handling of media playback on elements not attached to the DOM. Inserting the hidden video element into document.body before calling play() resolves the issue.

The report included a suggested code fix for background-blur.js to append the video element invisibly during setup and remove it during teardown. Additionally, pacharanero noted two related improvements: first, the plugin currently swallows the actual error message, making debugging difficult without passing the error object to the logger; second, the isSupported() check should include a WebGL validation to prevent users from enabling blur in environments where it cannot function, rather than letting it fail silently after activation.

pmusaraj reviewed the analysis and requested a Pull Request. pacharanero subsequently submitted PR #45366. pmusaraj confirmed that the PR was merged, resolving the Brave Browser compatibility issue.

Summary

FAO: @Falco :smiling_face_with_sunglasses:
AI disclosure: This bug was investigated agentically and the cause and remedy were suggested by Opus 5.5.

In Brave Browser, turning on background blur in a Live room always shows an error toast: “Background blur couldn’t be started, so it was turned off.” The same user, forum and room work fine in Google Chrome on the same machine.

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

The cause is the hidden <video> that BackgroundBlurManager#setup() creates but never inserts into the document. Brave rejects play() on it with:

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

Inserting the element into the document (invisibly) before play() makes it work in Brave.

Environment

  • Discourse 2026.10.0-latest.1 (568a006ad8), bundled Live plugin, discourse_voice_assets 0.2.0
  • Brave (Chromium 155) on Linux, Wayland, Intel GPU; WebGL2 and WebGPU hardware accelerated
  • Google Chrome on the same machine: works

Steps to reproduce

  1. Open a Live room in Brave and turn the camera on.
  2. Enable background blur.
  3. The toast appears and blur is switched off.

Diagnosis

I ran the plugin’s own blur pipeline step by step in the Brave console (same asset path, model, delegate, and running mode as background-blur.js), with Brave’s fingerprinting protection on and then off:

Step Brave, fingerprinting protection on Brave, fingerprinting protection off
vision_bundle.js import, WASM load ok ok
ImageSegmenter.createFromOptions, GPU and CPU delegates ok ok
getUserMedia({ video: true }), track state ok, live ok, live
play() on a detached muted <video> with the camera stream (what the plugin does) AbortError AbortError
play() on the same kind of <video> appended to document.body (1px, transparent) ok ok

So MediaPipe, WebGL and the camera are all fine. Only the detached play() fails, and Brave’s fingerprinting protection makes no difference. Headless Chromium plays a detached <video> without error, which matches Chrome working.

Suggested fix

In plugins/live/assets/javascripts/discourse/lib/live/background-blur.js, setup(): append the hidden video to the document before video.play(), and remove it in teardown(). For example:

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() as now ...

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

As far as I can see, this is the only place in the plugin that plays a detached media element.

Two related observations

  1. The real error is swallowed. #createBackgroundBlur() in local-video-manager.js uses a bare catch {} and calls liveLog.warn("[live] failed to start background blur") without the error. Even with live_verbose_logging enabled, the console never says why blur failed. Passing the caught error to liveLog.warn (which already sanitises it) would have msurfaced the error more rapidly.

  2. Blur is offered where it can never work. BackgroundBlurManager.isSupported() checks only canvas.captureStream and WebAssembly. In a browser without WebGL, MediaPipe’s segmenter fails with both the GPU and CPU delegates (Service "kGpuService" ... cannot be created: emscripten_webgl_create_context() returned error 0), because the segmentation post-processor needs a GL context either way. Adding a WebGL2 check to isSupported() would hide or disable the blur control instead of failing after the user turns it on. (Reproduced in headless Chromium by stubbing getContext("webgl*"), not seen in the wild.)

Thanks for Live - apart from this, it’s working nicely for us.

2 Likes

This seems good from a quick look. Did you want to submit a PR with the fix?

1 Like

Incoming!

3 Likes

Thanks, PR is now merged!

3 Likes