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
- Pair Ray-Ban Meta glasses via Bluetooth as an HFP audio device.
- Start an audio-only recording session (
capture_mode="audio_only"). - Allow recording to run for ~5–10 seconds with phone screen on (real audio).
- Lock the phone screen (press power button or auto-lock).
- Wait 30 seconds and unlock.
- Stop the recording.
- 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-accessory — audio 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
5c38a89eevidence: 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 (
UIBackgroundModesmissingaudio; 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
selectGlassesHFPInputunit tests unaffected; background mode is a build-time config) - [x] Verified no duplicate solved-bug log exists for same root cause