diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt index 1d796e2a..d9291425 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt @@ -285,13 +285,13 @@ class RadioAudioEngine { check(trackMin > 0) { "AudioTrack minimum buffer invalid group=$group value=$trackMin" } val trackBuf = (trackMin * 2).coerceAtLeast(FRAME_SAMPLES * PLAY_CHANNELS * 4) val track = AudioTrack( - AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) - .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH).build(), - AudioFormat.Builder().setSampleRate(SAMPLE_RATE) - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build(), - trackBuf, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE) + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH).build(), + AudioFormat.Builder().setSampleRate(SAMPLE_RATE) + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build(), + trackBuf, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE) audioTrack = track @@ -525,10 +525,12 @@ class RadioAudioEngine { /** Дождаться своего worker до остановки DSP; interruption не разрешает освободить используемые им ресурсы. */ private fun joinWorker(worker: Thread?, kind: String): Boolean { var interrupted = Thread.interrupted() + if (worker != null) LogManager.addLog("DEBUG", "RadioAudio", "joining $kind worker=${worker.name} group=$audioGroup") while (worker?.isAlive == true) try { worker.join() } catch (e: InterruptedException) { interrupted = true LogManager.addLog("WARN", "RadioAudio", "$kind join interrupted group=$audioGroup; finishing cleanup") } + if (worker != null) LogManager.addLog("DEBUG", "RadioAudio", "$kind worker joined group=$audioGroup") return interrupted } diff --git a/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioAudioLifecycleTest.kt b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioAudioLifecycleTest.kt index 21c96254..1f2db894 100644 --- a/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioAudioLifecycleTest.kt +++ b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioAudioLifecycleTest.kt @@ -305,4 +305,25 @@ class RadioAudioLifecycleTest { assertTrue(engine.isActive()); assertFalse(track.released); assertEquals(1, AudioTrack.created.size) diagnostic("zero group ID") } + + @Test fun failedRestoreAfterCallDisablesNativeGroupMembershipBeforeClearingThePublishedGroup() { + context.configProvider.failRadioSettings = true + engine.restoreAfterCall(7); drain(); main.dispatchAll(); drain() + val activation = synchronized(NativeLib.radioCommands) { NativeLib.radioCommands.filter { it.kind.startsWith("active=") } } + assertEquals(listOf("active=true", "active=false"), activation.map { it.kind }) + assertFalse(engine.isActive()); assertNull(engine.activeGroup.value) + } + + @Test fun reentrantStartFromFailedReadinessCannotBeClearedByTheOldCallback() { + context.configProvider.failRadioSettings = true + val results = mutableListOf>() + engine.start(7) { ready -> + results.add(7L to ready) + context.configProvider.failRadioSettings = false + engine.start(8) { results.add(8L to it) } + } + drain(); main.dispatchAll(); drain(); main.dispatchAll() + assertEquals(listOf(7L to false, 8L to true), results) + assertEquals(8L, engine.activeGroup.value); assertTrue(engine.isActive()) + } }