diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPreview.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPreview.kt index a6dc0fb9..924dc610 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPreview.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPreview.kt @@ -46,6 +46,7 @@ class StreamPreview { private var target: StreamPreviewTarget? = null private var owner: ((StreamPreviewTarget?) -> Unit)? = null private var closed = false + private var visible = true private val mutable = MutableStateFlow(StreamPreviewState()) val state: StateFlow = mutable @@ -53,7 +54,7 @@ class StreamPreview { val next = StreamPreviewTarget(texture) if (closed) { LogManager.addLog("WARN", "video", "direct preview attach after capture stopped"); return next } val previous = target - target = next.retain(); owner?.invoke(next.retain()); previous?.release() + target = next.retain(); owner?.invoke(if (visible) next.retain() else null); previous?.release() LogManager.addLog("INFO", "video", "direct preview attached target=${System.identityHashCode(next)} bound=${owner != null}") return next // Первую ссылку освобождает UI в onSurfaceTextureDestroyed, возвращая false. } @@ -67,7 +68,15 @@ class StreamPreview { @Synchronized internal fun bind(listener: (StreamPreviewTarget?) -> Unit) { check(!closed && owner == null) - owner = listener; listener(target?.retain()) + owner = listener; listener(if (visible) target?.retain() else null) + } + + /** Lifecycle отключает output сразу, даже когда Compose уже приостановил recomposition. */ + @Synchronized fun setVisible(value: Boolean) { + if (visible == value || closed) return + visible = value; owner?.invoke(if (visible) target?.retain() else null) + if (!visible) mutable.value = mutable.value.copy(active = false) + LogManager.addLog("INFO", "video", "direct preview visibility=$visible target=${target?.let(System::identityHashCode)}") } internal fun update(format: StreamPreviewFormat?, active: Boolean = false, failed: Boolean = false) { diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt index 0f15955b..4db67579 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt @@ -298,13 +298,14 @@ private fun StreamRotationMark(top: Int) { private fun LiveStreamView(group: Long, source: Long, epoch: Long, localPreview: StreamPreview?) { val owner = LocalLifecycleOwner.current var visible by remember(owner) { mutableStateOf(owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) } - DisposableEffect(owner) { + DisposableEffect(owner, localPreview) { + localPreview?.setVisible(visible) val observer = LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_START) visible = true - if (event == Lifecycle.Event.ON_STOP) visible = false + if (event == Lifecycle.Event.ON_START) { localPreview?.setVisible(true); visible = true } + if (event == Lifecycle.Event.ON_STOP) { localPreview?.setVisible(false); visible = false } } owner.lifecycle.addObserver(observer) - onDispose { owner.lifecycle.removeObserver(observer) } + onDispose { owner.lifecycle.removeObserver(observer); localPreview?.setVisible(false) } } val context = LocalContext.current val configuration = LocalConfiguration.current