Fix Android stream layout and show measured publisher bitrate

This commit is contained in:
evgeny committed 2026-10-18 18:24:30 +02:00
1 parent e246357f92
commit b8bc9b409a
7 files changed
+46 -24

No files matched your search

@@ -2,7 +2,6 @@ package com.utun.chat.ui.screens
import android.app.Activity
import android.content.ContextWrapper
import android.content.pm.ActivityInfo
import android.graphics.SurfaceTexture
import android.view.Surface
import android.view.TextureView
@@ -42,14 +41,13 @@ import com.utun.chat.data.LogManager
import com.utun.chat.data.StreamPlayerEngine
import kotlinx.coroutines.delay
/** Полноэкранный viewer: геометрия видео не зависит от видимости управления. */
/** Полноэкранный viewer: видео разворачивается вдоль окна, ориентацию экрана не меняем. */
@Composable
internal fun StreamViewerScreen(group: Long, source: Long, epoch: Long, title: String, name: String, onClose: () -> Unit) {
val context = LocalContext.current
val activity = remember(context) {
generateSequence(context) { (it as? ContextWrapper)?.baseContext }.filterIsInstance<Activity>().firstOrNull()
}
val previousOrientation = remember(activity) { activity?.requestedOrientation }
val owner = LocalLifecycleOwner.current
var visible by remember(owner) { mutableStateOf(owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) }
var aspect by remember { mutableStateOf(0f) }
@@ -75,25 +73,13 @@ internal fun StreamViewerScreen(group: Long, source: Long, epoch: Long, title: S
val behavior = controller?.systemBarsBehavior
controller?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
controller?.hide(WindowInsetsCompat.Type.systemBars())
LogManager.addLog("INFO", "stream", "viewer fullscreen source=$source epoch=$epoch orientation=$previousOrientation")
LogManager.addLog("INFO", "stream", "viewer fullscreen source=$source epoch=$epoch " +
"screenOrientation=${activity?.requestedOrientation}")
onDispose {
if (previousOrientation != null) activity?.requestedOrientation = previousOrientation
if (behavior != null) controller?.systemBarsBehavior = behavior
if (statusVisible) controller?.show(WindowInsetsCompat.Type.statusBars()) else controller?.hide(WindowInsetsCompat.Type.statusBars())
if (navigationVisible) controller?.show(WindowInsetsCompat.Type.navigationBars()) else controller?.hide(WindowInsetsCompat.Type.navigationBars())
LogManager.addLog("INFO", "stream", "viewer window restored source=$source orientation=$previousOrientation")
}
}
LaunchedEffect(activity, aspect) {
// Формат уже учитывает rotation источника. До первого KEY ориентацию не угадываем.
val orientation = when {
aspect > 1f -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
aspect > 0f && aspect < 1f -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
else -> null
}
if (activity != null && orientation != null && activity.requestedOrientation != orientation) {
activity.requestedOrientation = orientation
LogManager.addLog("INFO", "stream", "viewer orientation source=$source aspect=$aspect requested=$orientation")
LogManager.addLog("INFO", "stream", "viewer window restored source=$source")
}
}
LaunchedEffect(status) {
@@ -114,14 +100,21 @@ internal fun StreamViewerScreen(group: Long, source: Long, epoch: Long, title: S
BoxWithConstraints(Modifier.fillMaxSize().background(Color.Black).clipToBounds()) {
val ratio = aspect.takeIf { it > 0f } ?: 16f / 9f
val width = minOf(maxWidth, maxHeight * ratio)
val turn = aspect > 0f && minOf(maxHeight, maxWidth * ratio) > minOf(maxWidth, maxHeight * ratio)
val width = if (turn) minOf(maxHeight, maxWidth * ratio) else minOf(maxWidth, maxHeight * ratio)
val height = width / ratio
val density = LocalDensity.current
val videoSize = with(density) { Size(width.toPx(), height.toPx()) }
// Жесты работают в координатах окна, поэтому учитывают размеры после поворота видео.
val videoSize = with(density) { if (turn) Size(height.toPx(), width.toPx()) else Size(width.toPx(), height.toPx()) }
val viewport = with(density) { Size(maxWidth.toPx(), maxHeight.toPx()) }
LaunchedEffect(aspect, turn, videoSize, viewport) {
LogManager.addLog("INFO", "stream", "viewer fit source=$source aspect=$aspect viewport=$viewport " +
"video=$videoSize videoRotation=${if (turn) 90 else 0}")
}
LaunchedEffect(videoSize, viewport) { zoom.constrain(videoSize, viewport) }
if (visible) StreamViewerPlayback(group, source, epoch, { aspect = it }, { status = it },
Modifier.size(width, height).align(Alignment.Center).graphicsLayer {
Modifier.requiredSize(width, height).align(Alignment.Center).graphicsLayer {
rotationZ = if (turn) 90f else 0f
scaleX = zoom.scale; scaleY = zoom.scale
translationX = zoom.offset.x; translationY = zoom.offset.y
})
@@ -136,7 +129,7 @@ internal fun StreamViewerScreen(group: Long, source: Long, epoch: Long, title: S
}
})
if (controls) {
Row(Modifier.fillMaxWidth().align(Alignment.TopCenter).background(Color.Black.copy(alpha = 0.65f))
Row(Modifier.fillMaxWidth().align(Alignment.TopCenter).background(Color.Black.copy(alpha = 0.325f))
.windowInsetsPadding(WindowInsets.safeDrawing).padding(start = 16.dp, end = 4.dp, top = 4.dp, bottom = 4.dp),
verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f)) {