diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/AudioRecorderManager.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/AudioRecorderManager.kt index 84f0e88b..096f6530 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/AudioRecorderManager.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/AudioRecorderManager.kt @@ -16,12 +16,14 @@ import java.util.concurrent.atomic.AtomicReference /** * Запись голосового сообщения/тест микрофона и отдельный плеер native Opus-файла. * Запись и тест делят один native recorder: успешный voiceStart даёт владение до stop/cancel. - * Lifecycle сериализован монитором менеджера; stopRecording/cancelRecording вызываются также из IO worker. + * Capture lifecycle сериализован монитором менеджера; playback controls/callbacks имеют отдельный короткий lock. * Захват — PCM16 48 кГц mono, чтение без блокировки; stop сначала останавливает reader, ждёт join и освобождает Android. * Ошибка захвата/очистки запрещает публикацию частичной записи: stopRecording вернёт -1 и отменит native recorder. * После отказа worker владелец вызывает stop/cancel; неосвобождённый Android device сохраняется для повтора cleanup. * Плеер владеет своим decoder/AudioTrack в worker; прогресс/завершение — быстрые callbacks этого потока. * Pause/stop меняют поколение и прерывают worker, старое поколение больше не уведомляет UI. + * Process registry и cleanup worker сохраняют владельцев до release ACK также после ViewModel.onCleared. + * Stop core/запуск звонка ждут всех владельцев; file decoder освобождается независимо от отказа AudioTrack. */ class AudioRecorderManager { companion object { diff --git a/tools/chatgui-android/capture-tests/src/test/kotlin/com/utun/chat/data/AudioRecorderManagerTest.kt b/tools/chatgui-android/capture-tests/src/test/kotlin/com/utun/chat/data/AudioRecorderManagerTest.kt index c5119f66..ab7dbbdf 100644 --- a/tools/chatgui-android/capture-tests/src/test/kotlin/com/utun/chat/data/AudioRecorderManagerTest.kt +++ b/tools/chatgui-android/capture-tests/src/test/kotlin/com/utun/chat/data/AudioRecorderManagerTest.kt @@ -191,7 +191,7 @@ class AudioRecorderManagerTest { @Test fun nativeCancelFailureRetainsTheCaptureUntilARealAcknowledgement() { plan(); assertTrue(manager.startRecording("7")); NativeLib.failCancel = true - assertEquals(-1, manager.stopRecording()); assertTrue(NativeLib.active) + manager.cancelRecording(); assertTrue(NativeLib.active) val closed = manager.requestClose(); assertFalse(closed.isDone) assertThrows(IllegalStateException::class.java) { AudioRecorderManager.requireReleasedForCore() } NativeLib.failCancel = false; closed.get(2, TimeUnit.SECONDS) @@ -239,6 +239,7 @@ class AudioRecorderManagerTest { val closed = manager.requestClose() try { assertFalse(closed.isDone); assertFalse(manager.startRecording("8")) + manager.pausePlayback(); manager.stopPlayback(); manager.cancelRecording(); manager.stopMicTest() assertThrows(IllegalStateException::class.java) { AudioRecorderManager.requireReleasedForCore() } } finally { leave.countDown() } closed.get(2, TimeUnit.SECONDS); assertTrue(device.released); assertFalse(NativeLib.active)