mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-09-17 06:52:27 +00:00
Added zoom pan snapping.
This commit is contained in:
parent
5cc8488d94
commit
2c7f02a24d
5 changed files with 131 additions and 14 deletions
|
@ -9,6 +9,7 @@ import android.graphics.Matrix
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
@ -85,6 +86,10 @@ class GestureControlView : LinearLayout {
|
||||||
private val _layoutControlsZoom: FrameLayout
|
private val _layoutControlsZoom: FrameLayout
|
||||||
private val _textZoom: TextView
|
private val _textZoom: TextView
|
||||||
private var _isZooming = false
|
private var _isZooming = false
|
||||||
|
private var _isZoomPanEnabled = false
|
||||||
|
private var _surfaceView: View? = null
|
||||||
|
private var _layoutIndicatorFill: FrameLayout;
|
||||||
|
private var _layoutIndicatorFit: FrameLayout;
|
||||||
|
|
||||||
private val _gestureController: GestureDetectorCompat;
|
private val _gestureController: GestureDetectorCompat;
|
||||||
|
|
||||||
|
@ -113,20 +118,16 @@ class GestureControlView : LinearLayout {
|
||||||
_textZoom = findViewById(R.id.text_zoom)
|
_textZoom = findViewById(R.id.text_zoom)
|
||||||
_progressBrightness = findViewById(R.id.progress_brightness);
|
_progressBrightness = findViewById(R.id.progress_brightness);
|
||||||
_layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen);
|
_layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen);
|
||||||
|
_layoutIndicatorFill = findViewById(R.id.layout_indicator_fill);
|
||||||
|
_layoutIndicatorFit = findViewById(R.id.layout_indicator_fit);
|
||||||
|
|
||||||
_scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
_scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||||
if (!_isFullScreen || !Settings.instance.gestureControls.zoom) {
|
if (!_isZoomPanEnabled || !_isFullScreen || !Settings.instance.gestureControls.zoom) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var newScaleFactor = (_scaleFactor * detector.scaleFactor).coerceAtLeast(1.0f).coerceAtMost(5.0f)
|
val newScaleFactor = (_scaleFactor * detector.scaleFactor).coerceAtLeast(1.0f).coerceAtMost(10.0f)
|
||||||
|
|
||||||
//Make original zoom sticky
|
|
||||||
if (newScaleFactor - 1.0f < 0.01f) {
|
|
||||||
newScaleFactor = 1.0f
|
|
||||||
}
|
|
||||||
|
|
||||||
val scaleFactorChange = newScaleFactor / _scaleFactor
|
val scaleFactorChange = newScaleFactor / _scaleFactor
|
||||||
_scaleFactor = newScaleFactor
|
_scaleFactor = newScaleFactor
|
||||||
onZoom.emit(_scaleFactor)
|
onZoom.emit(_scaleFactor)
|
||||||
|
@ -149,6 +150,25 @@ class GestureControlView : LinearLayout {
|
||||||
_layoutControlsZoom.visibility = View.VISIBLE
|
_layoutControlsZoom.visibility = View.VISIBLE
|
||||||
_textZoom.text = "${String.format("%.1f", _scaleFactor)}x"
|
_textZoom.text = "${String.format("%.1f", _scaleFactor)}x"
|
||||||
_isZooming = true
|
_isZooming = true
|
||||||
|
|
||||||
|
if (willSnapFill()) {
|
||||||
|
_layoutIndicatorFill.visibility = View.VISIBLE
|
||||||
|
_layoutIndicatorFit.visibility = View.GONE
|
||||||
|
} else if (willSnapFit()) {
|
||||||
|
_layoutIndicatorFill.visibility = View.GONE
|
||||||
|
_layoutIndicatorFit.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
_surfaceView?.let {
|
||||||
|
val lp = _layoutIndicatorFit.layoutParams
|
||||||
|
lp.width = it.width
|
||||||
|
lp.height = it.height
|
||||||
|
_layoutIndicatorFit.layoutParams = lp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_layoutIndicatorFill.visibility = View.GONE
|
||||||
|
_layoutIndicatorFit.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -201,7 +221,7 @@ class GestureControlView : LinearLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (_isFullScreen && !_isZooming && Settings.instance.gestureControls.pan) {
|
} else if (_isZoomPanEnabled && _isFullScreen && !_isZooming && Settings.instance.gestureControls.pan) {
|
||||||
stopAllGestures()
|
stopAllGestures()
|
||||||
pan(_translationX - distanceX, _translationY - distanceY)
|
pan(_translationX - distanceX, _translationY - distanceY)
|
||||||
}
|
}
|
||||||
|
@ -244,6 +264,19 @@ class GestureControlView : LinearLayout {
|
||||||
isClickable = true
|
isClickable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setZoomPanEnabled(view: View) {
|
||||||
|
_isZoomPanEnabled = true
|
||||||
|
_surfaceView = view
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetZoomPan() {
|
||||||
|
_scaleFactor = 1.0f
|
||||||
|
onZoom.emit(_scaleFactor)
|
||||||
|
_translationX = 0f
|
||||||
|
_translationY = 0f
|
||||||
|
onPan.emit(_translationX, _translationY)
|
||||||
|
}
|
||||||
|
|
||||||
private fun pan(translationX: Float, translationY: Float) {
|
private fun pan(translationX: Float, translationY: Float) {
|
||||||
val xc = width / 2.0f
|
val xc = width / 2.0f
|
||||||
val yc = height / 2.0f
|
val yc = height / 2.0f
|
||||||
|
@ -258,6 +291,8 @@ class GestureControlView : LinearLayout {
|
||||||
_translationY = translationY.coerceAtLeast(ymin).coerceAtMost(ymax)
|
_translationY = translationY.coerceAtLeast(ymin).coerceAtMost(ymax)
|
||||||
|
|
||||||
onPan.emit(_translationX, _translationY)
|
onPan.emit(_translationX, _translationY)
|
||||||
|
|
||||||
|
Log.i(TAG, "surfaceView (width: ${_surfaceView?.width}, height: ${_surfaceView?.height}, x: ${_surfaceView?.x}, y: ${_surfaceView?.y}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupTouchArea(layoutControls: ViewGroup? = null, background: View? = null) {
|
fun setupTouchArea(layoutControls: ViewGroup? = null, background: View? = null) {
|
||||||
|
@ -306,7 +341,26 @@ class GestureControlView : LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isZooming && ev.action == MotionEvent.ACTION_UP) {
|
if (_isZooming && ev.action == MotionEvent.ACTION_UP) {
|
||||||
|
val surfaceView = _surfaceView
|
||||||
|
if (surfaceView != null && willSnapFill()) {
|
||||||
|
_scaleFactor = calculateZoomScaleFactor()
|
||||||
|
onZoom.emit(_scaleFactor)
|
||||||
|
|
||||||
|
_translationX = 0f
|
||||||
|
_translationY = 0f
|
||||||
|
onPan.emit(_translationX, _translationY)
|
||||||
|
} else if (willSnapFit()) {
|
||||||
|
_scaleFactor = 1f
|
||||||
|
onZoom.emit(_scaleFactor)
|
||||||
|
|
||||||
|
_translationX = 0f
|
||||||
|
_translationY = 0f
|
||||||
|
onPan.emit(_translationX, _translationY)
|
||||||
|
}
|
||||||
|
|
||||||
_layoutControlsZoom.visibility = View.GONE
|
_layoutControlsZoom.visibility = View.GONE
|
||||||
|
_layoutIndicatorFill.visibility = View.GONE
|
||||||
|
_layoutIndicatorFit.visibility = View.GONE
|
||||||
_isZooming = false
|
_isZooming = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +371,34 @@ class GestureControlView : LinearLayout {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun calculateZoomScaleFactor(): Float {
|
||||||
|
val w = _surfaceView?.width?.toFloat() ?: return 1.0f;
|
||||||
|
val h = _surfaceView?.height?.toFloat() ?: return 1.0f;
|
||||||
|
if (w == 0.0f || h == 0.0f) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(width / w, height / h)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun willSnapFill(): Boolean {
|
||||||
|
//TODO: Make sure pan is not too far away from 0, 0
|
||||||
|
val surfaceView = _surfaceView
|
||||||
|
if (surfaceView != null) {
|
||||||
|
val zoomScaleFactor = calculateZoomScaleFactor()
|
||||||
|
if (Math.abs(_scaleFactor - zoomScaleFactor) < 0.05f) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun willSnapFit(): Boolean {
|
||||||
|
//TODO: Make sure pan is not too far away from 0, 0
|
||||||
|
return Math.abs(_scaleFactor - 1.0f) < 0.05f
|
||||||
|
}
|
||||||
|
|
||||||
fun cancelHideJob() {
|
fun cancelHideJob() {
|
||||||
_jobHideControls?.cancel();
|
_jobHideControls?.cancel();
|
||||||
_jobHideControls = null;
|
_jobHideControls = null;
|
||||||
|
@ -646,11 +728,7 @@ class GestureControlView : LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFullscreen(isFullScreen: Boolean) {
|
fun setFullscreen(isFullScreen: Boolean) {
|
||||||
_scaleFactor = 1.0f
|
resetZoomPan()
|
||||||
onZoom.emit(_scaleFactor)
|
|
||||||
_translationX = 0f
|
|
||||||
_translationY = 0f
|
|
||||||
onPan.emit(_translationX, _translationY)
|
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
val c = context
|
val c = context
|
||||||
|
|
|
@ -272,6 +272,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
_videoView.scaleY = it
|
_videoView.scaleY = it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gestureControl.setZoomPanEnabled(_videoView.videoSurfaceView!!)
|
||||||
|
|
||||||
if(!isInEditMode) {
|
if(!isInEditMode) {
|
||||||
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||||
val player = StatePlayer.instance.getPlayerOrCreate(context);
|
val player = StatePlayer.instance.getPlayerOrCreate(context);
|
||||||
|
@ -600,6 +602,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||||
|
gestureControl.resetZoomPan()
|
||||||
_lastSourceFit = null;
|
_lastSourceFit = null;
|
||||||
if(isFullScreen)
|
if(isFullScreen)
|
||||||
fillHeight();
|
fillHeight();
|
||||||
|
@ -763,4 +766,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
fun setGestureSoundFactor(soundFactor: Float) {
|
fun setGestureSoundFactor(soundFactor: Float) {
|
||||||
gestureControl.setSoundFactor(soundFactor);
|
gestureControl.setSoundFactor(soundFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceSizeChanged(width: Int, height: Int) {
|
||||||
|
gestureControl.resetZoomPan()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -104,6 +104,11 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
super.onPlaybackSuppressionReasonChanged(playbackSuppressionReason)
|
super.onPlaybackSuppressionReasonChanged(playbackSuppressionReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceSizeChanged(width: Int, height: Int) {
|
||||||
|
super.onSurfaceSizeChanged(width, height)
|
||||||
|
this@FutoVideoPlayerBase.onSurfaceSizeChanged(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
super.onIsPlayingChanged(isPlaying);
|
super.onIsPlayingChanged(isPlaying);
|
||||||
this@FutoVideoPlayerBase.onIsPlayingChanged(isPlaying);
|
this@FutoVideoPlayerBase.onIsPlayingChanged(isPlaying);
|
||||||
|
@ -592,6 +597,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
exoPlayer?.setVolume(volume);
|
exoPlayer?.setVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun onSurfaceSizeChanged(width: Int, height: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
protected open fun onPlayerError(error: PlaybackException) {
|
protected open fun onPlayerError(error: PlaybackException) {
|
||||||
Logger.i(TAG, "onPlayerError error=$error error.errorCode=${error.errorCode} connectivityLoss");
|
Logger.i(TAG, "onPlayerError error=$error error.errorCode=${error.errorCode} connectivityLoss");
|
||||||
|
|
5
app/src/main/res/drawable/background_primary_border.xml
Normal file
5
app/src/main/res/drawable/background_primary_border.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<stroke android:color="#992D63ED" android:width="5dp" />
|
||||||
|
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||||
|
</shape>
|
|
@ -177,4 +177,22 @@
|
||||||
android:textSize="16dp"/>
|
android:textSize="16dp"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/layout_indicator_fill"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/background_primary_border"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/layout_indicator_fit"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:background="@drawable/background_primary_border"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Add table
Add a link
Reference in a new issue