diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallScreen.kt index 6d7764db..5a527e66 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallScreen.kt @@ -159,9 +159,11 @@ fun CallScreen( if (videoActive) { view.keepScreenOn = previous; LogManager.addLog("INFO", "call", "video screen wake policy restored") } } } - // Ориентация принадлежит экрану звонка; краткое переподключение Surface не должно поворачивать его обратно. - DisposableEffect(video, phase == CallPhase.ACTIVE, selectedVideo) { + // Геометрию видео меняет TextureView; Activity сохраняет ориентацию окна звонка. + DisposableEffect(context) { val activity = context.findActivity(); val previous = activity?.requestedOrientation + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED + LogManager.addLog("INFO", "call", "call screen orientation locked; video rotates independently") onDispose { if (activity != null && previous != null && activity.requestedOrientation != previous) { activity.requestedOrientation = previous @@ -169,28 +171,6 @@ fun CallScreen( } } } - val rotated = remote?.rotation?.rem(180) != 0 - val localFormat = local?.format - val localRotated = (localFormat?.camera?.sensor ?: 0) % 180 != 0 - val videoWidth = if (expandedVideo == CallVideoSource.LOCAL) { - if (localRotated) localFormat?.height else localFormat?.width - } - else if (rotated) remote?.height else remote?.width - val videoHeight = if (expandedVideo == CallVideoSource.LOCAL) { - if (localRotated) localFormat?.width else localFormat?.height - } - else if (rotated) remote?.width else remote?.height - LaunchedEffect(video, phase, expandedVideo, videoWidth, videoHeight) { - val activity = context.findActivity() - if (viewingVideo && videoWidth != null && videoHeight != null && activity != null) { - val orientation = if (videoWidth > videoHeight) ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - else ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT - if (activity.requestedOrientation != orientation) { - activity.requestedOrientation = orientation - LogManager.addLog("INFO", "call", "video screen orientation=$orientation source=$expandedVideo display=${videoWidth}x$videoHeight") - } - } - } val hasBtPermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED 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 c80d4a4f..dc415bd1 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.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -94,12 +95,13 @@ internal fun CallVideoStage(video: CallVideoEngine, expanded: CallVideoSource?, val description = stringResource(if (source == CallVideoSource.LOCAL) R.string.call_video_local else R.string.call_video_remote) val click = { if (large) onTap() else onSelect(source) } if (source == CallVideoSource.REMOTE) { - CallVideoPlayback(video, click, frameModifier, description) + 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 turn = large && minOf(maxHeight.value, maxWidth.value * aspect) > minOf(maxWidth.value, maxHeight.value * aspect) + val width = if (turn) minOf(maxHeight, maxWidth * aspect) else minOf(maxWidth, maxHeight * aspect) AndroidView(factory = { CameraPreviewView(it, video.preview, keepAwake = false) }, - modifier = Modifier.size(width, width / aspect), update = { view -> + modifier = Modifier.requiredSize(width, width / aspect).graphicsLayer { rotationZ = if (turn) 90f else 0f }, 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 @@ -111,11 +113,11 @@ internal fun CallVideoStage(video: CallVideoEngine, expanded: CallVideoSource?, } @Composable -private fun CallVideoPlayback(video: CallVideoEngine, onTap: () -> Unit, modifier: Modifier, description: String) { +private fun CallVideoPlayback(video: CallVideoEngine, onTap: () -> Unit, modifier: Modifier, description: String, large: Boolean) { val format by video.display.collectAsState() key(video) { AndroidView(factory = { CallVideoTexture(it, video) }, modifier = modifier, - update = { view -> view.format = format; view.alpha = if (format == null) 0f else 1f + 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() }) } @@ -124,8 +126,12 @@ private fun CallVideoPlayback(video: CallVideoEngine, onTap: () -> Unit, modifie private class CallVideoTexture(context: Context, private val video: CallVideoEngine) : TextureView(context) { private var attached: Surface? = null private var closed = false - var format: CallVideoFormat? = null - set(value) { field = value; fit() } + private var format: CallVideoFormat? = null + private var large = false + fun update(next: CallVideoFormat?, expanded: Boolean) { + if (format == next && large == expanded) return + format = next; large = expanded; fit() + } init { isOpaque = false surfaceTextureListener = object : SurfaceTextureListener { @@ -146,7 +152,14 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng val rotated = f.rotation % 180 != 0 val w = if (rotated) f.height else f.width; val h = if (rotated) f.width else f.height val scale = minOf(width.toFloat() / w, height.toFloat() / h) - setTransform(Matrix().apply { setScale(w * scale / width, h * scale / height, width / 2f, height / 2f) }) + val turnedScale = minOf(width.toFloat() / h, height.toFloat() / w) + val turn = large && turnedScale > scale + val fitted = if (turn) turnedScale else scale + setTransform(Matrix().apply { + setScale(w * fitted / width, h * fitted / height, width / 2f, height / 2f) + if (turn) postRotate(90f, width / 2f, height / 2f) + }) + com.utun.chat.data.LogManager.addLog("DEBUG", "call", "video fit view=${width}x$height frame=${w}x$h expanded=$large turn=$turn") } private fun closeOutput() { attached?.let(video::detachSurface); attached = null } fun close() { closed = true; closeOutput() }