fix(android): unify voice speaker gating and config refresh

This commit is contained in:
Ayaan Zaidi
2026-02-28 19:33:42 +05:30
committed by Ayaan Zaidi
parent 72e135083a
commit 3daed77ba9
2 changed files with 41 additions and 8 deletions

View File

@@ -249,7 +249,12 @@ class NodeRuntime(context: Context) {
applyMainSessionKey(mainSessionKey)
updateStatus()
micCapture.onGatewayConnectionChanged(true)
scope.launch { refreshBrandingFromGateway() }
scope.launch {
refreshBrandingFromGateway()
if (voiceReplySpeakerLazy.isInitialized()) {
voiceReplySpeaker.refreshConfig()
}
}
},
onDisconnected = { message ->
operatorConnected = false
@@ -319,7 +324,7 @@ class NodeRuntime(context: Context) {
json = json,
supportsChatSubscribe = false,
)
private val voiceReplySpeaker: TalkModeManager by lazy {
private val voiceReplySpeakerLazy: Lazy<TalkModeManager> = lazy {
// Reuse the existing TalkMode speech engine (ElevenLabs + deterministic system-TTS fallback)
// without enabling the legacy talk capture loop.
TalkModeManager(
@@ -328,8 +333,12 @@ class NodeRuntime(context: Context) {
session = operatorSession,
supportsChatSubscribe = false,
isConnected = { operatorConnected },
)
).also { speaker ->
speaker.setPlaybackEnabled(prefs.speakerEnabled.value)
}
}
private val voiceReplySpeaker: TalkModeManager
get() = voiceReplySpeakerLazy.value
private val micCapture: MicCaptureManager by lazy {
MicCaptureManager(
@@ -349,9 +358,7 @@ class NodeRuntime(context: Context) {
parseChatSendRunId(response) ?: idempotencyKey
},
speakAssistantReply = { text ->
if (prefs.speakerEnabled.value) {
voiceReplySpeaker.speakAssistantReply(text)
}
voiceReplySpeaker.speakAssistantReply(text)
},
)
}
@@ -641,6 +648,9 @@ class NodeRuntime(context: Context) {
fun setSpeakerEnabled(value: Boolean) {
prefs.setSpeakerEnabled(value)
if (voiceReplySpeakerLazy.isInitialized()) {
voiceReplySpeaker.setPlaybackEnabled(value)
}
}
fun refreshGatewayConnection() {

View File

@@ -146,6 +146,8 @@ class TalkModeManager(
private var pendingRunId: String? = null
private var pendingFinal: CompletableDeferred<Boolean>? = null
private var chatSubscribedSessionKey: String? = null
private var configLoaded = false
@Volatile private var playbackEnabled = true
private var player: MediaPlayer? = null
private var streamingSource: StreamingMediaDataSource? = null
@@ -194,8 +196,21 @@ class TalkModeManager(
}
}
suspend fun speakAssistantReply(text: String) {
fun setPlaybackEnabled(enabled: Boolean) {
playbackEnabled = enabled
if (!enabled) {
stopSpeaking()
}
}
suspend fun refreshConfig() {
reloadConfig()
}
suspend fun speakAssistantReply(text: String) {
if (!playbackEnabled) return
ensureConfigLoaded()
if (!playbackEnabled) return
playAssistant(text)
}
@@ -347,7 +362,7 @@ class TalkModeManager(
lastTranscript = ""
lastHeardAtMs = null
reloadConfig()
ensureConfigLoaded()
val prompt = buildPrompt(transcript)
if (!isConnected()) {
_statusText.value = "Gateway not connected"
@@ -855,6 +870,12 @@ class TalkModeManager(
return true
}
private suspend fun ensureConfigLoaded() {
if (!configLoaded) {
reloadConfig()
}
}
private suspend fun reloadConfig() {
val envVoice = System.getenv("ELEVENLABS_VOICE_ID")?.trim()
val sagVoice = System.getenv("SAG_VOICE_ID")?.trim()
@@ -907,6 +928,7 @@ class TalkModeManager(
} else if (selection?.normalizedPayload == true) {
Log.d(tag, "talk config provider=elevenlabs")
}
configLoaded = true
} catch (_: Throwable) {
defaultVoiceId = envVoice?.takeIf { it.isNotEmpty() } ?: sagVoice?.takeIf { it.isNotEmpty() }
defaultModelId = defaultModelIdFallback
@@ -914,6 +936,7 @@ class TalkModeManager(
apiKey = envKey?.takeIf { it.isNotEmpty() }
voiceAliases = emptyMap()
defaultOutputFormat = defaultOutputFormatFallback
configLoaded = true
}
}