Browse Source

Rotate call video independently of Android call controls

master
evgeny 5 days ago
parent
commit
0c214dfca0
  1. 28
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallScreen.kt
  2. 29
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt

28
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") } if (videoActive) { view.keepScreenOn = previous; LogManager.addLog("INFO", "call", "video screen wake policy restored") }
} }
} }
// Ориентация принадлежит экрану звонка; краткое переподключение Surface не должно поворачивать его обратно. // Геометрию видео меняет TextureView; Activity сохраняет ориентацию окна звонка.
DisposableEffect(video, phase == CallPhase.ACTIVE, selectedVideo) { DisposableEffect(context) {
val activity = context.findActivity(); val previous = activity?.requestedOrientation 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 { onDispose {
if (activity != null && previous != null && activity.requestedOrientation != previous) { if (activity != null && previous != null && activity.requestedOrientation != previous) {
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 || val hasBtPermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED PackageManager.PERMISSION_GRANTED

29
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.Modifier
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp 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 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) } val click = { if (large) onTap() else onSelect(source) }
if (source == CallVideoSource.REMOTE) { if (source == CallVideoSource.REMOTE) {
CallVideoPlayback(video, click, frameModifier, description) CallVideoPlayback(video, click, frameModifier, description, large)
} else BoxWithConstraints(frameModifier, contentAlignment = Alignment.Center) { } else BoxWithConstraints(frameModifier, contentAlignment = Alignment.Center) {
val aspect = local.format?.aspect(displayDegrees) ?: 1f 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) }, 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.update(local.format, displayDegrees); view.alpha = if (local.active) 1f else 0f
view.contentDescription = description view.contentDescription = description
view.setOnClickListener { if (local.active) click() }; view.isClickable = local.active view.setOnClickListener { if (local.active) click() }; view.isClickable = local.active
@ -111,11 +113,11 @@ internal fun CallVideoStage(video: CallVideoEngine, expanded: CallVideoSource?,
} }
@Composable @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() val format by video.display.collectAsState()
key(video) { key(video) {
AndroidView(factory = { CallVideoTexture(it, video) }, modifier = modifier, 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.contentDescription = description
view.setOnClickListener { if (format != null) onTap() }; view.isClickable = format != null }, onRelease = { it.close() }) 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 class CallVideoTexture(context: Context, private val video: CallVideoEngine) : TextureView(context) {
private var attached: Surface? = null private var attached: Surface? = null
private var closed = false private var closed = false
var format: CallVideoFormat? = null private var format: CallVideoFormat? = null
set(value) { field = value; fit() } private var large = false
fun update(next: CallVideoFormat?, expanded: Boolean) {
if (format == next && large == expanded) return
format = next; large = expanded; fit()
}
init { init {
isOpaque = false isOpaque = false
surfaceTextureListener = object : SurfaceTextureListener { surfaceTextureListener = object : SurfaceTextureListener {
@ -146,7 +152,14 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng
val rotated = f.rotation % 180 != 0 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 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) 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 } private fun closeOutput() { attached?.let(video::detachSurface); attached = null }
fun close() { closed = true; closeOutput() } fun close() { closed = true; closeOutput() }

Loading…
Cancel
Save