From b1390a9542d9c7baa57e88485a28a61aa66c0083 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 15 Oct 2026 00:48:12 +0200 Subject: [PATCH] Ignore stale Android call surfaces during activity replacement --- .../app/src/main/java/com/utun/chat/data/CallVideoEngine.kt | 1 + .../app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt | 6 +++++- .../main/java/com/utun/chat/ui/screens/CallVideoPanel.kt | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt index eacb344e..e7cb2b41 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt @@ -105,6 +105,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) { } fun surface(surface: Surface?, release: () -> Unit = {}) = player.surface(surface, release) + fun detachSurface(surface: Surface) = player.detach(surface) fun select(settings: CallVideoSettings) { handler.post { if (!stopped) { wanted = settings; excluded = emptySet(); mutable.value = mutable.value.copy(settings = settings) } } } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt index 7b60af47..9211e542 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt @@ -9,7 +9,7 @@ import java.util.concurrent.ConcurrentLinkedQueue /** Один RX worker; удерживаем output buffer до фактического audio PTS. Новое поколение ждёт drain старого. */ internal class CallVideoPlayer(private val id: Long, private val failure: (String) -> Unit) { @Volatile private var running = true - private data class Output(val surface: Surface?, val release: () -> Unit) + private data class Output(val surface: Surface?, val release: () -> Unit, val detach: Surface? = null) private val outputs = ConcurrentLinkedQueue() private val displayed = MutableStateFlow(null) val display: StateFlow = displayed @@ -18,6 +18,9 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin @Synchronized fun surface(surface: Surface?, release: () -> Unit = {}) { if (running) outputs.add(Output(surface, release)) else release() } + @Synchronized fun detach(surface: Surface) { + if (running) outputs.add(Output(null, {}, surface)) + } fun decoders(value: Map) { verified = value } fun stop() { synchronized(this) { running = false }; worker.join() } @@ -39,6 +42,7 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin val now = System.nanoTime() while (true) { val next = outputs.poll() ?: break + if (next.detach != null && output?.surface !== next.detach) continue close(); output?.release?.invoke(); output = next; surface = next.surface pending = null; displayed.value = null; CallVideoNative.requestKey(id) } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt index 93f68073..5f1398f8 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt @@ -91,7 +91,7 @@ internal fun CallVideoPreview(video: CallVideoEngine, modifier: Modifier = Modif } private class CallVideoTexture(context: Context, private val video: CallVideoEngine) : TextureView(context) { - private var attached = false + private var attached: Surface? = null private var closed = false var format: CallVideoFormat? = null set(value) { field = value; fit() } @@ -100,7 +100,7 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng surfaceTextureListener = object : SurfaceTextureListener { override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) { if (closed) return - val surface = Surface(texture); attached = true + val surface = Surface(texture); attached = surface // RX worker освобождает Surface/texture только после остановки MediaCodec. video.surface(surface) { surface.release(); texture.release() }; fit() } @@ -117,7 +117,7 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng val scale = minOf(width.toFloat() / w, height.toFloat() / h) setTransform(Matrix().apply { setScale(w * scale / width, h * scale / height, width / 2f, height / 2f) }) } - private fun closeOutput() { if (attached) { attached = false; video.surface(null) } } + private fun closeOutput() { attached?.let(video::detachSurface); attached = null } fun close() { closed = true; closeOutput() } }