From 7ec46278afa8aebf120e97f7f05d74e8bb7712d8 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 03:24:19 +0100 Subject: [PATCH] Keep video gesture callbacks current and forbid reads during cleanup --- .../java/com/utun/chat/data/VideoFileMetadata.kt | 3 +++ .../java/com/utun/chat/data/VideoFilePlayer.kt | 2 +- .../com/utun/chat/ui/screens/VideoPlayerScreen.kt | 14 +++++++------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFileMetadata.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFileMetadata.kt index 06b6c501..1d179c46 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFileMetadata.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFileMetadata.kt @@ -20,11 +20,13 @@ internal class VideoFileMetadata : AutoCloseable { private val main = Handler(Looper.getMainLooper()) private var delay = 250L private var scheduled = false + private var closing = false private val retry = Runnable { synchronized(this) { scheduled = false } try { close() } catch (e: Exception) { LogManager.addLog("WARN", "VideoPlayer", "Metadata cleanup pending: $e") } } @Synchronized fun read(path: String): VideoInfo { + check(!closing) { "Metadata reader closing" } val current = checkNotNull(retriever) { "Metadata reader closed" } current.setDataSource(path) val width = current.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0 @@ -34,6 +36,7 @@ internal class VideoFileMetadata : AutoCloseable { return VideoInfo(width, height, rotation) } @Synchronized override fun close() { + closing = true val current = retriever ?: return try { current.release(); retriever = null diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFilePlayer.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFilePlayer.kt index 3041d95b..5b9f0408 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFilePlayer.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/VideoFilePlayer.kt @@ -68,7 +68,7 @@ internal class VideoFilePlayer(onCompleted: () -> Unit) : AutoCloseable { fun attach(texture: SurfaceTexture) { check(Looper.myLooper() == Looper.getMainLooper()) if (uiTargets.containsKey(texture)) return - val target = StreamPreviewTarget(texture) + val target = try { StreamPreviewTarget(texture) } catch (e: Exception) { fail("create Surface", e); return } uiTargets[texture] = target if (disposed) { target.release(); return } if (retiring) return // UI сохраняет собственную texture до destroy/close даже после ошибки player. diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/VideoPlayerScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/VideoPlayerScreen.kt index f577a28a..7492f827 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/VideoPlayerScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/VideoPlayerScreen.kt @@ -259,13 +259,13 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) { if (error == null) { Box( modifier = Modifier.fillMaxSize() - .pointerInput(Unit) { + .pointerInput(player) { detectTapGestures { hideTick++ - if (isPlaying) controlsVisible = !controlsVisible + if (player.state.value.playing) controlsVisible = !controlsVisible } } - .pointerInput(screenW, screenH, videoW, videoH) { + .pointerInput(player, screenW, screenH, videoW, videoH) { detectTransformGestures { centroid, pan, factor, _ -> onInteraction() zoom.transform(centroid, pan, factor, Size(videoW, videoH), Size(screenW, screenH)) @@ -377,7 +377,7 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) { modifier = Modifier .weight(1f) .height(32.dp) - .pointerInput(durationMs, playback.prepared, error) { + .pointerInput(player, durationMs, playback.prepared, error) { detectTapGestures { offset -> if (!playback.prepared || error != null) return@detectTapGestures onInteraction() @@ -385,11 +385,11 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) { player.seek(pos) } } - .pointerInput(durationMs, playback.prepared, error) { + .pointerInput(player, durationMs, playback.prepared, error) { detectDragGestures( onDragStart = { offset -> - wasPlayingBeforeDrag = isPlaying - if (playback.prepared && error == null && (!isPlaying || player.pause())) { + wasPlayingBeforeDrag = player.state.value.playing + if (playback.prepared && error == null && (!wasPlayingBeforeDrag || player.pause())) { dragging = true dragPosition = (offset.x / size.width * durationMs).toInt().coerceIn(0, durationMs) player.seek(dragPosition)