Share video zoom geometry between Android viewers
This commit is contained in:
1 parent
7ff95623c1
commit
c39610f079
3 files changed
+91
-26
No files matched your search
+10
-26
@@ -36,7 +36,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
@@ -67,12 +67,6 @@ private tailrec fun Context.findActivity(): Activity? = when (this) {
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun clampOffset(scale: Float, o: Offset, vW: Float, vH: Float, sW: Float, sH: Float): Offset {
|
||||
val mx = ((vW * scale - sW) / 2f).coerceAtLeast(0f)
|
||||
val my = ((vH * scale - sH) / 2f).coerceAtLeast(0f)
|
||||
return Offset(o.x.coerceIn(-mx, mx), o.y.coerceIn(-my, my))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
|
||||
val player = remember(filePath) { MediaPlayer() }
|
||||
@@ -88,8 +82,7 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
|
||||
var dragPosition by remember { mutableStateOf(0) }
|
||||
var wasPlayingBeforeDrag by remember { mutableStateOf(false) }
|
||||
var videoInfo by remember { mutableStateOf<VideoInfo?>(null) }
|
||||
var zoomScale by remember(filePath) { mutableStateOf(1f) }
|
||||
var zoomOffset by remember(filePath) { mutableStateOf(Offset.Zero) }
|
||||
val zoom = remember(filePath) { VideoZoomState() }
|
||||
var hideTick by remember { mutableStateOf(0) }
|
||||
|
||||
val context = LocalContext.current
|
||||
@@ -290,8 +283,8 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
|
||||
val videoH = with(density) { h.toPx() }
|
||||
|
||||
/* re-clamp zoom offset when screen size changes (e.g. flip 180) */
|
||||
LaunchedEffect(screenW, screenH) {
|
||||
zoomOffset = clampOffset(zoomScale, zoomOffset, videoW, videoH, screenW, screenH)
|
||||
LaunchedEffect(screenW, screenH, videoW, videoH) {
|
||||
zoom.constrain(Size(videoW, videoH), Size(screenW, screenH))
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
@@ -317,10 +310,10 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
|
||||
}
|
||||
},
|
||||
modifier = videoModifier.graphicsLayer {
|
||||
scaleX = zoomScale
|
||||
scaleY = zoomScale
|
||||
translationX = zoomOffset.x
|
||||
translationY = zoomOffset.y
|
||||
scaleX = zoom.scale
|
||||
scaleY = zoom.scale
|
||||
translationX = zoom.offset.x
|
||||
translationY = zoom.offset.y
|
||||
}
|
||||
)
|
||||
|
||||
@@ -344,18 +337,9 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
|
||||
}
|
||||
}
|
||||
.pointerInput(screenW, screenH, videoW, videoH) {
|
||||
detectTransformGestures { centroid, pan, zoom, _ ->
|
||||
detectTransformGestures { centroid, pan, factor, _ ->
|
||||
onInteraction()
|
||||
val old = zoomScale
|
||||
val new = (zoomScale * zoom).coerceIn(1f, 4f)
|
||||
val z = new / old
|
||||
val rel = centroid - Offset(screenW / 2f, screenH / 2f)
|
||||
val raw = Offset(
|
||||
zoomOffset.x * z + rel.x * (1f - z) + pan.x,
|
||||
zoomOffset.y * z + rel.y * (1f - z) + pan.y
|
||||
)
|
||||
zoomScale = new
|
||||
zoomOffset = clampOffset(new, raw, videoW, videoH, screenW, screenH)
|
||||
zoom.transform(centroid, pan, factor, Size(videoW, videoH), Size(screenW, screenH))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.utun.chat.ui.screens
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
|
||||
/** Общая геометрия увеличения для файла и live: точка под пальцами сохраняется, края ограничивают сдвиг. */
|
||||
internal class VideoZoomState {
|
||||
var scale by mutableStateOf(1f)
|
||||
private set
|
||||
var offset by mutableStateOf(Offset.Zero)
|
||||
private set
|
||||
|
||||
fun transform(centroid: Offset, pan: Offset, factor: Float, video: Size, viewport: Size) {
|
||||
val next = (scale * factor).coerceIn(1f, 4f)
|
||||
val ratio = next / scale
|
||||
val relative = centroid - Offset(viewport.width / 2f, viewport.height / 2f)
|
||||
offset = offset * ratio + relative * (1f - ratio) + pan
|
||||
scale = next
|
||||
constrain(video, viewport)
|
||||
}
|
||||
|
||||
fun constrain(video: Size, viewport: Size) {
|
||||
val x = ((video.width * scale - viewport.width) / 2f).coerceAtLeast(0f)
|
||||
val y = ((video.height * scale - viewport.height) / 2f).coerceAtLeast(0f)
|
||||
offset = Offset(offset.x.coerceIn(-x, x), offset.y.coerceIn(-y, y))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user