Skip to content

iOS app: WebView microphone capture is muted when the app goes to the background or the screen locks #5698

Open

Description

@lucascastellanibr

Summary

In the bb iOS app, microphone capture inside the WebView stops the moment the app goes to the background or the screen locks. The MediaStreamTrack from getUserMedia gets a system mute event while the AudioContext keeps running and the WebSocket stays open. I expected capture to continue while the page holds an active play-and-record audio session, as it does in apps that declare background audio.

This hits every audio feature that runs in the WebView: the built-in voice input in the prompt box, and plugins that do realtime audio (I found it while building a plugin that runs voice calls with agents).

Versions and environment

  • bb iOS app (App Store build, rendering bb in WKWebView via react-native-webview), iPhone on iOS 18.7. User agent: iPhone OS 18_7 ... AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148.
  • bb server 0.46.1 (npm bb-app), macOS.
  • Code read at get-bb/bb@6a639cfc0d66e0c789a1bd7309266973228d1a80 and WebKit/WebKit@2d1c22173231e23bb5dbfb1485a85f482fa0b993.

Steps to reproduce

  1. Open bb in the iOS app.
  2. Start microphone capture in the page: the prompt box voice input, or any plugin that calls navigator.mediaDevices.getUserMedia({ audio: true }). Optionally set navigator.audioSession.type = "play-and-record" first; it does not change the result.
  3. Add listeners: track.onmute, track.onunmute, document.onvisibilitychange, and log audioContext.state.
  4. Switch to another app (or lock the screen) for a few seconds, then come back.

Expected vs actual

Actual (times relative to the start of capture):
+0 s  navigator.audioSession.type = "play-and-record"; capture started, track live
+3 s  visibilitychange: document.hidden = true (switched apps)
+3 s  MediaStreamTrack "mute" with document.hidden = true (system mute; the page did not mute it)
      AudioContext.state stays "running"; WebSocket stays open
+9 s  visibilitychange: document.hidden = false (back in bb)
+9 s  MediaStreamTrack "unmute" by itself

Expected:
The track stays unmuted while the app is in the background, and audio keeps flowing.

Only capture is cut, and it is cut by the system. Nothing in the page asked for it.

Evidence

Root cause is in WebKit, driven by the host app's Info.plist:

  1. When the document is hidden, WebKit mutes microphone capture if the interruptAudioOnPageVisibilityChangeEnabled setting is on: Document.cpp#L6500-L6509.
  2. The UI process (the host app) sets that preference from RealtimeMediaSourceCenter::shouldInterruptAudioOnPageVisibilityChange(): WebPreferencesCocoa.mm#L149.
  3. On iOS that function returns true unless the main bundle's UIBackgroundModes contains audio: RealtimeMediaSourceCenterCocoa.mm#L35-L51. The same check also gates muting of speech recognition when the page is hidden: WebProcessProxy.cpp#L3266-L3275.
  4. WebKit's own test covers this behavior: LayoutTests/platform/ios/mediastream/audio-muted-in-background-tab.html. It was added in WebKit bug 198307, "Add an option to mute audio capture automatically when page is not visible".

bb's iOS app declares only remote-notification: apps/mobile/app.json#L28. So WebKit mutes capture every time the app is backgrounded. The app does not configure AVAudioSession itself; WebKit manages the session for the page. react-native-webview 13.16.1 only touches audio when ignoreSilentHardwareSwitch is set, and bb does not set it.

What you ruled out

  • Not the page muting itself: the mute arrives with document.hidden = true and no page code calls enabled = false or stops the track.
  • Not the audio graph or the network: the AudioContext stays running and the WebSocket stays open during the mute.
  • navigator.audioSession.type = "play-and-record" does not help. The mute decision in WebKit does not look at it.
  • I did not find an existing issue or PR for UIBackgroundModes or background microphone capture.
  • I did not reproduce on a device build from main. I observed it in the App Store build. main still declares only remote-notification, so the same check applies.

Suggested priority and effort

Medium. It affects every iPhone user of voice input or audio plugins whenever they leave the app. No data is lost, but the only workaround is to keep bb in the foreground with the screen on. Effort: low, a one-line Info.plist change. PR follows.

AGENT GENERATED

Activity

  1. added theissue type on Oct 17, 2026
  2. added
    mobileMobile web: iOS Safari, touch, layout
    partial-reproBug partially reproduced; some claims unverified; see linked report
    on Oct 17, 2026
  3. bb-slop-cop commented on Oct 17, 2026

    @bb-slop-cop
    Contributor

    🚨 SLOP COP 🚨 · new-issue-autopilot

    • Classification: Bug · Priority: Medium · Effort: Low · area: mobile (all previously unset).
    • Report: Reproduction and root-cause report.
    • Verdict: PARTIALLY REPRODUCED · root-cause confidence: medium · reproduction label: partial-repro.
    • Evidence: The report's Expo configuration probe produces the same result in two clean checkouts of trusted main 6a639cfc0: both production and dev iOS plists omit background audio while permitting microphone use. Frozen installs and all 67 full-build tasks pass.
    • Root cause: A confirmed native background-audio declaration gap plausibly explains interrupted capture; the exact WebKit mute mechanism and whether adding the capability is sufficient remain unverified without an iOS device.
    • No automatic PR: Native capture was not reproduced, and changing packaged background capabilities falls outside this rule's simple-fix boundary. The report includes the probe and the next real-device experiment.
  4. lucascastellanibr commented on Oct 17, 2026

    @lucascastellanibr
    Author

    The fix for this issue is ready in #5700: one line in apps/mobile/app.json that adds "audio" to UIBackgroundModes. It builds for the iOS Simulator, and the generated Info.plist contains the key.

    The contributor gate closed #5700 automatically because I'm not on the allow list yet. Could a maintainer /approve @lucascastellanibr on #5700 and reopen it? If you'd rather apply the one-line change yourselves, that works just as well. I'm happy to test on a device.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    mobileMobile web: iOS Safari, touch, layoutpartial-reproBug partially reproduced; some claims unverified; see linked report

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions