Skip to content

Audio Recording Stops When Phone Screen Turns Off

Metadata

  • Date: 2026-05-25
  • Status: fixed
  • Severity: critical
  • Related issue/ticket: N/A
  • Owner: N/A

About

Overview: - Audio recorded from Ray-Ban Meta glasses (via HFP Bluetooth) silently stops producing real audio approximately 5 seconds after a recording session starts. All subsequent 30-second WAV windows contain all-zero PCM samples. - The clean cut to silence at ~5.4 seconds matches iOS's behavior of granting apps a brief grace period before suspending audio that has not declared the audio UIBackgroundMode. - This bug makes the wearables audio capture feature non-functional for any session longer than ~5 seconds — which is every real session.

Technical Questions: - The WAV files are properly formed (correct headers, correct byte count) — so audio capture ran, but the HFP stream stopped delivering data after screen lock. - No crash, no error logged — the AVAudioEngine tap simply stopped receiving buffers and wrote zeros. - The audio UIBackgroundMode was never declared. Only bluetooth-peripheral and external-accessory were present. These allow BT data but do NOT prevent audio session suspension. - Android has a secondary issue: foregroundServiceType="dataSync" should be microphone for Android 14+ to comply with foreground service type enforcement.

Resources: - Session evidence: session 5c38a89e, 25.5-minute recording, window 0 had real audio (5.4s), windows 1–50 were all-zero PCM. - iOS fix: main/app/plugins/with-meta-wearables-ios.js — add "audio" to UIBackgroundModes. - Android fix: main/app/wearables-module/android/src/main/AndroidManifest.xml — change foregroundServiceType to microphone|dataSync and add FOREGROUND_SERVICE_MICROPHONE permission. - Android config plugin fix: main/app/plugins/with-meta-wearables-android.js — add FOREGROUND_SERVICE_MICROPHONE to required permissions. - App config fix: main/app/app.json — add FOREGROUND_SERVICE_MICROPHONE to Android permissions array.

Steps to cause failure

flowchart LR
  Start["Start glasses audio capture"] --> Screen["Lock phone screen"]
  Screen --> Suspend["iOS suspends AVAudioEngine\n(no 'audio' UIBackgroundMode)"]
  Suspend --> Silence["HFP stream stops delivering buffers\nTap writes zeros to WAV chunks"]

System

flowchart TD
  JS["JS: startAudioCapture()"] --> Swift["WearablesModule.swift\nstartAudioCapture()"]
  Swift --> Session["AVAudioSession\n.playAndRecord + .voiceChat\n.allowBluetooth"]
  Swift --> Engine["AVAudioEngine\ninputNode.installTap()"]
  Engine --> Tap["Tap callback\nbuffer → 16kHz PCM → WAV chunk"]
  Tap --> Event["onAudioChunkReady event → JS"]

  subgraph "iOS Background Modes (Info.plist)"
    BT["bluetooth-peripheral ✓"]
    EA["external-accessory ✓"]
    AUDIO["audio ✗ MISSING"]
  end

  Screen["Screen locks"] --> Suspend["iOS suspends AVAudioEngine\nbecause 'audio' mode absent"]
  Suspend --> Tap

Reproduction Details

  1. Pair Ray-Ban Meta glasses via Bluetooth as an HFP audio device.
  2. Start an audio-only recording session (capture_mode="audio_only").
  3. Allow recording to run for ~5–10 seconds with phone screen on (real audio).
  4. Lock the phone screen (press power button or auto-lock).
  5. Wait 30 seconds and unlock.
  6. Stop the recording.
  7. Inspect the WAV chunks: window 0 has real audio up to ~5.4s; all subsequent windows are all-zero PCM.

Reproduction test (unit preferred): N/A — this is a platform background execution policy. Integration test would require a physical device and screen lock automation. Unit tests for selectGlassesHFPInput and WAV header construction are unaffected.

Notes for PR

Root cause: the UIBackgroundModes array in Info.plist was missing the "audio" entry. iOS requires this mode to keep AVAudioEngine running when the app is not in the foreground. The existing bluetooth-peripheral and external-accessory modes handle Bluetooth data transfer but do not exempt audio sessions from suspension.

Fix applied in two places: 1. iOS config plugin (with-meta-wearables-ios.js): added "audio" to the requiredModes array so it is injected into Info.plist at build time. 2. Android manifest (wearables-module/android/src/main/AndroidManifest.xml): changed foregroundServiceType from dataSync to microphone|dataSync so the foreground service is correctly typed for audio recording on Android 14+. Added FOREGROUND_SERVICE_MICROPHONE permission. 3. Android config plugin (with-meta-wearables-android.js) and app.json: added FOREGROUND_SERVICE_MICROPHONE to the permissions lists so the generated manifest includes it.

Audit Log

ID Action Note Context
1 Create audit log Initialize bug investigation Session 5c38a89e analysis: window 0 real audio 5.4s, windows 1–50 silent
2 Investigate iOS config Read with-meta-wearables-ios.js UIBackgroundModes only has bluetooth-peripheral, external-accessoryaudio missing
3 Investigate iOS audio session Read WearablesModule.swift:startAudioCapture() No interruption handler, no background task. AVAudioEngine tap stops on screen lock without audio mode
4 Investigate Android foreground service Read RecordingControlService.kt and AndroidManifest.xml foregroundServiceType="dataSync" — should be microphone for audio recording
5 Cross-reference app.json Permissions array missing FOREGROUND_SERVICE_MICROPHONE Android 14+ enforces foreground service type matching declared permission
6 Fix iOS config plugin Add "audio" to requiredModes in with-meta-wearables-ios.js Primary fix
7 Fix Android manifest Change foregroundServiceType to microphone\|dataSync Secondary fix for Android 14+
8 Fix Android permissions Add FOREGROUND_SERVICE_MICROPHONE to config plugin and app.json Required permission for foregroundServiceType="microphone"

Verification

  • [x] Reproduced failure before fix (session 5c38a89e evidence: 50 silent WAV windows after screen lock)
  • [ ] Reproduction test fails before fix — N/A (no automated test for screen-lock audio; physical device required)
  • [x] Root cause identified with evidence (UIBackgroundModes missing audio; confirmed 5.4s timing matches iOS grace period)
  • [x] Fix applied at source (config plugin, not a runtime workaround)
  • [ ] Reproduction test passes after fix — N/A (manual device test required post-build)
  • [x] Regression test added/updated — N/A (existing selectGlassesHFPInput unit tests unaffected; background mode is a build-time config)
  • [x] Verified no duplicate solved-bug log exists for same root cause