From d292e5160086a56be928a36ea30694a37e567202 Mon Sep 17 00:00:00 2001 From: isaiahb Date: Wed, 17 Jun 2026 15:53:17 -0700 Subject: [PATCH] Fallback when glasses mic stops streaming --- .../com/mentra/bluetoothsdk/DeviceManager.kt | 31 +++++++++++++++-- .../ios/Source/DeviceManager.swift | 33 +++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt b/mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt index 3a0dc897bb..d346509cb2 100644 --- a/mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +++ b/mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt @@ -203,6 +203,7 @@ class DeviceManager { private var lastReadyHandledAtMs: Long = 0L private var lastReadyHandledKey: String = "" private var lastSystemTimeSyncConnectionKey: String = "" + private var glassesMicDegradedUntilMs: Long = 0L private var systemMicUnavailable: Boolean get() = DeviceStore.store.get("bluetooth", "systemMicUnavailable") as? Boolean ?: false @@ -354,8 +355,19 @@ class DeviceManager { // actually attempt recovery (was 0 before, which made the watchdog a no-op). val timeSinceLastLc3Event = System.currentTimeMillis() - (lastLc3Event ?: 0L) if (timeSinceLastLc3Event > 5000) { - Bridge.log("MAN: No audio activity in the last 5 seconds from glasses, reinitializing glasses mic") - sgc?.setMicEnabled(true) + if (currentMic == MicTypes.GLASSES_CUSTOM && + preferredMic != "glasses" && + micRanking.any { it != MicTypes.GLASSES_CUSTOM } + ) { + glassesMicDegradedUntilMs = System.currentTimeMillis() + 15_000 + Bridge.log( + "MAN: No audio activity in the last 5 seconds from glasses, temporarily falling back from glasses mic" + ) + updateMicState() + } else { + Bridge.log("MAN: No audio activity in the last 5 seconds from glasses, reinitializing glasses mic") + sgc?.setMicEnabled(true) + } } } @@ -708,6 +720,7 @@ class DeviceManager { */ fun handleGlassesMicData(rawLC3Data: ByteArray, frameSize: Int = 40) { lastLc3Event = System.currentTimeMillis() + glassesMicDegradedUntilMs = 0L val pcmData: ByteArray? synchronized(lc3Lock) { if (lc3DecoderPtr == 0L) { @@ -809,6 +822,20 @@ class DeviceManager { } if (micMode == MicTypes.GLASSES_CUSTOM) { + val glassesConnected = + DeviceStore.get("glasses", "connected") as? Boolean ?: false + if (!glassesConnected) { + Bridge.log("MAN: glasses mic skipped because glasses are not connected") + continue + } + + if (glassesMicDegradedUntilMs > System.currentTimeMillis() && + preferredMic != "glasses" && + micRanking.any { it != MicTypes.GLASSES_CUSTOM } + ) { + Bridge.log("MAN: glasses mic temporarily skipped due to missing LC3 frames") + continue + } if (sgc?.hasMic == true) { // enable the mic if it's not already on: if (sgc?.micEnabled == false) { diff --git a/mobile/modules/bluetooth-sdk/ios/Source/DeviceManager.swift b/mobile/modules/bluetooth-sdk/ios/Source/DeviceManager.swift index 87f873e662..0c040c493c 100644 --- a/mobile/modules/bluetooth-sdk/ios/Source/DeviceManager.swift +++ b/mobile/modules/bluetooth-sdk/ios/Source/DeviceManager.swift @@ -296,6 +296,7 @@ struct ViewState { /// Last time we received an LC3 frame from the glasses (used by the mic /// inactivity watchdog). private var lastLc3Event: Date? + private var glassesMicDegradedUntil: Date? private var micReinitTimer: Timer? /// STT: @@ -394,6 +395,7 @@ struct ViewState { */ func handleGlassesMicData(_ lc3Data: Data, _ frameSize: Int = 20) { lastLc3Event = Date() + glassesMicDegradedUntil = nil guard let lc3Converter = lc3Converter else { Bridge.log("MAN: LC3 converter not initialized") return @@ -464,6 +466,20 @@ struct ViewState { } if micMode == MicTypes.GLASSES_CUSTOM { + let glassesConnected = DeviceStore.shared.get("glasses", "connected") as? Bool ?? false + if !glassesConnected { + Bridge.log("MAN: glasses mic skipped because glasses are not connected") + continue + } + + if let degradedUntil = glassesMicDegradedUntil, + degradedUntil > Date(), + preferredMic != "glasses", + micRanking.contains(where: { $0 != MicTypes.GLASSES_CUSTOM }) + { + Bridge.log("MAN: glasses mic temporarily skipped due to missing LC3 frames") + continue + } // Bridge.log( // "MAN: glasses custom mic found - hasMic: \(sgc?.hasMic ?? false), micEnabled: \(sgc?.micEnabled ?? false)" // ) @@ -775,10 +791,21 @@ struct ViewState { return } - let timeSinceLastLc3Event = Date().timeIntervalSince(lastLc3Event ?? Date()) + let timeSinceLastLc3Event = lastLc3Event.map { Date().timeIntervalSince($0) } ?? .greatestFiniteMagnitude if timeSinceLastLc3Event > 5 { - Bridge.log("MAN: No audio activity in the last 5 seconds from glasses, reinitializing glasses mic") - sgc?.setMicEnabled(true) + if currentMic == MicTypes.GLASSES_CUSTOM, + preferredMic != "glasses", + micRanking.contains(where: { $0 != MicTypes.GLASSES_CUSTOM }) + { + glassesMicDegradedUntil = Date().addingTimeInterval(15) + Bridge.log( + "MAN: No audio activity in the last 5 seconds from glasses, temporarily falling back from glasses mic" + ) + updateMicState() + } else { + Bridge.log("MAN: No audio activity in the last 5 seconds from glasses, reinitializing glasses mic") + sgc?.setMicEnabled(true) + } } }