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