Skip to content

On-device SpeechRecognition silently hangs in "downloading" state, no SODA component ever installs #55414

Description

@jantimon

Description

SpeechRecognition.available({ langs: ["en-US"], processLocally: true }) returns "downloading" but nothing actually downloads. SpeechRecognition.install(...) returns a promise that never resolves. No SODA or Speech Recognition components show up in brave://components. The API surface is exposed but the SODA component fetcher does not seem wired up

Steps to reproduce

  1. Open DevTools console on any https page
  2. Run await SpeechRecognition.available({ langs: ["en-US"], processLocally: true })
  3. Run await SpeechRecognition.install({ langs: ["en-US"], processLocally: true })
  4. Open brave://components in another tab and search for SODA or Speech Recognition

Actual result

  • Step 2 returns "downloading" instantly
  • Step 3 returns a Promise { <pending> } that never resolves
  • Step 4 shows no SODA or Speech Recognition entries among the ~24 components
  • No matching network activity at the OS level
  • Polling available() every 2s for 10 minutes keeps returning "downloading":
async function waitForLang(lang, { intervalMs = 2000, timeoutMs = 10 * 60_000 } = {}) {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    const s = await SpeechRecognition.available({ langs: [lang], processLocally: true });
    if (s === "available") return true;
    if (s === "unavailable") return false;
    await new Promise(r => setTimeout(r, intervalMs));
  }
  return false;
}
await waitForLang("en-US"); // → false after 10 min, status stays "downloading" the whole time

Expected result

One of two reasonable outcomes:

  • install() actually triggers the SODA component fetch (whether proxied through go-updater.brave.com or via a bypass), resolves within reasonable time, the component appears in brave://components, and available() flips to "available" (matches Chrome stable behavior)
  • If Brave has not wired up on-device speech recognition yet, available() should return "unavailable" so feature detection works correctly

The current state is the worst of both worlds: feature detection passes, but the install path is a dead end. There is no client-side way to distinguish "actually downloading a large model" from "silently stuck" short of an arbitrary timeout

Reproduces how often

Easily reproduced

Brave version (brave://version info)

  • Brave: 1.89.145 Chromium: 147.0.7727.137 (Official Build) (arm64)
  • Revision: 888db4a4613788705fd9850bc0a36cd253fa2260
  • OS: macOS Version 26.3 (Build 25D125)

Channel information

  • release (stable)
  • beta
  • nightly

Reproducibility

  • with Brave Shields disabled
  • with Brave Rewards disabled
  • in the latest version of Chrome

Miscellaneous information

Root cause appears to be that Brave disables the Live Caption / on-device speech support layer in the build (which is what hosts SODA in Chromium) while leaving the Blink Web Speech API surface untouched. So SpeechRecognition, available, install, and processLocally are all reachable from JS, feature detection passes, but no install path can complete because the backing component pipeline is gated off at the C++ level.

Context from #3725: @iefremov mentioned that the plan was to switch to on-device recognition once Chromium 139 shipped the relevant Web Speech improvements which happened in August 2025 with Chrome 139. About nine months later, the API surface is reachable in Brave stable but the backing stack is disabled, producing the half-exposed state described above

Code evidence from brave-core (asked Copilot to search the repo with the diagnosis as a hypothesis):

  1. Live Caption (the Chromium subsystem that hosts SODA) is hard-disabled
    chromium_src/components/live_caption/caption_util.cc forces the support check to false:
bool IsLiveCaptionFeatureSupported() {
  return false;
}

https://github.com/brave/brave-core/blob/44ee9ede9332d3289c848b342684765effa2b94b/chromium_src/components/live_caption/caption_util.cc#L14-L18

  1. kLiveCaption is set to disabled-by-default in chromium_src/media/base/media_switches.cc:
OVERRIDE_FEATURE_DEFAULT_STATES({{
    {kLiveCaption, base::FEATURE_DISABLED_BY_DEFAULT},
    {kEnableTabMuting, base::FEATURE_ENABLED_BY_DEFAULT},
}});

https://github.com/brave/brave-core/blob/44ee9ede9332d3289c848b342684765effa2b94b/chromium_src/media/base/media_switches.cc#L10-L15

  1. Settings UI for captions is explicitly removed in browser/resources/settings/br/a11y_page.ts, which explains why brave://settings/?search=live caption is empty

https://github.com/brave/brave-core/blob/44ee9ede9332d3289c848b342684765effa2b94b/browser/resources/settings/br/a11y_page.ts#L10-L29

Smallest possible fix to help web developers right now, even if full SODA support is not on the roadmap: make available({ processLocally: true }) return "unavailable" while the backing layer is disabled, so feature detection can route around the API on Brave instead of hanging client code

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions