Fit fullscreen call video along the screen without rotating controls

This commit is contained in:
evgeny committed 2026-10-20 00:50:05 +02:00
1 parent bea8f1f901
commit 8b438f26a5
3 files changed
+33 -7

No files matched your search

@@ -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() })
@@ -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()) }
@@ -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)
}