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 2475732a..e2af14f9 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 @@ -16,6 +16,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.geometry.Rect import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset @@ -179,9 +180,12 @@ internal fun CallVideoStage(video: CallVideoEngine, expanded: CallVideoSource?, CallVideoPlayback(video, click, frameModifier, description, large) } else BoxWithConstraints(frameModifier, contentAlignment = Alignment.Center) { val aspect = local.format?.aspect(displayDegrees) ?: 1f - val width = minOf(maxWidth, maxHeight * aspect) + val fit = fitVideoFrame(maxWidth.value, maxHeight.value, aspect, large) + LaunchedEffect(fit, large) { + LogManager.addLog("DEBUG", "call", "video local fit expanded=$large aspect=$aspect fit=$fit") + } AndroidView(factory = { CameraPreviewView(it, video.preview, keepAwake = false) }, - modifier = Modifier.requiredSize(width, width / aspect), update = { view -> + modifier = Modifier.requiredSize(fit.width.dp, fit.height.dp).graphicsLayer { rotationZ = fit.rotation }, update = { view -> view.update(local.format, displayDegrees); view.alpha = if (local.active) 1f else 0f view.contentDescription = description view.setOnClickListener { if (local.active) click() }; view.isClickable = local.active @@ -200,9 +204,17 @@ internal fun CallVideoStage(video: CallVideoEngine, expanded: CallVideoSource?, @Composable private fun CallVideoPlayback(video: CallVideoEngine, onTap: () -> Unit, modifier: Modifier, description: String, large: Boolean) { val format by video.display.collectAsState() - Box(modifier) { + BoxWithConstraints(modifier) { + val rotated = format?.rotation?.rem(180) != 0 && format != null + val aspect = format?.let { if (rotated) it.height.toFloat() / it.width else it.width.toFloat() / it.height } ?: 1f + val fit = fitVideoFrame(maxWidth.value, maxHeight.value, aspect, large) + LaunchedEffect(fit, format, large) { + LogManager.addLog("INFO", "call", "video remote fit expanded=$large viewport=${maxWidth.value}x${maxHeight.value} " + + "format=$format fit=$fit; interface orientation retained") + } key(video) { - AndroidView(factory = { CallVideoTexture(it, video) }, modifier = Modifier.matchParentSize(), + AndroidView(factory = { CallVideoTexture(it, video) }, modifier = Modifier.requiredSize(fit.width.dp, fit.height.dp) + .align(Alignment.Center).graphicsLayer { rotationZ = fit.rotation }, update = { view -> view.update(format, large); view.alpha = if (format == null) 0f else 1f view.contentDescription = description view.setOnClickListener { if (format != null) onTap() }; view.isClickable = format != null }, onRelease = { it.close() }) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt index 545f227b..2ff9eec3 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt @@ -100,9 +100,9 @@ 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 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 fit = fitVideoFrame(maxWidth.value, maxHeight.value, ratio, aspect > 0f) + val turn = fit.rotation != 0f + val width = fit.width.dp; val height = fit.height.dp val density = LocalDensity.current // Жесты работают в координатах окна, поэтому учитывают размеры после поворота видео. val videoSize = with(density) { if (turn) Size(height.toPx(), width.toPx()) else Size(width.toPx(), height.toPx()) } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/VideoFrameFit.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/VideoFrameFit.kt new file mode 100644 index 00000000..ecc76f1f --- /dev/null +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/VideoFrameFit.kt @@ -0,0 +1,14 @@ +package com.utun.chat.ui.screens + +/** Размер слоя ДО rotation; поворот относится только к видео, не к окну/кнопкам. */ +internal data class VideoFrameFit(val width: Float, val height: Float, val rotation: Float) + +/** Вписать целый кадр без растяжения; fullscreen выбирает 0/90° по максимальной площади. */ +internal fun fitVideoFrame(width: Float, height: Float, aspect: Float, allowTurn: Boolean): VideoFrameFit { + val ratio = aspect.takeIf { it.isFinite() && it > 0f } ?: 1f + val normal = minOf(width, height * ratio) + val rotated = minOf(height, width * ratio) + val turn = allowTurn && rotated > normal + val fittedWidth = if (turn) rotated else normal + return VideoFrameFit(fittedWidth, fittedWidth / ratio, if (turn) 90f else 0f) +}