diff --git a/app/src/main/java/com/futo/platformplayer/AdvancedOrientationListener.kt b/app/src/main/java/com/futo/platformplayer/AdvancedOrientationListener.kt new file mode 100644 index 00000000..acc0baca --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/AdvancedOrientationListener.kt @@ -0,0 +1,114 @@ +import android.app.Activity +import android.content.Context +import android.content.pm.ActivityInfo +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.logging.Logger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + + +class AdvancedOrientationListener(private val activity: Activity, private val lifecycleScope: CoroutineScope) { + private val sensorManager: SensorManager = activity.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + private val magnetometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) + + private var lastOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + private var lastStableOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + private var lastOrientationChangeTime = 0L + private val debounceTime = 200L + private val stabilityThresholdTime = 800L + private var deviceAspectRatio: Float = 1.0f + + private val gravity = FloatArray(3) + private val geomagnetic = FloatArray(3) + private val rotationMatrix = FloatArray(9) + private val orientationAngles = FloatArray(3) + + val onOrientationChanged = Event1() + + private val sensorListener = object : SensorEventListener { + override fun onSensorChanged(event: SensorEvent) { + when (event.sensor.type) { + Sensor.TYPE_ACCELEROMETER -> { + System.arraycopy(event.values, 0, gravity, 0, gravity.size) + } + Sensor.TYPE_MAGNETIC_FIELD -> { + System.arraycopy(event.values, 0, geomagnetic, 0, geomagnetic.size) + } + } + + if (gravity.isNotEmpty() && geomagnetic.isNotEmpty()) { + val success = SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic) + if (success) { + SensorManager.getOrientation(rotationMatrix, orientationAngles) + + val azimuth = Math.toDegrees(orientationAngles[0].toDouble()).toFloat() + val pitch = Math.toDegrees(orientationAngles[1].toDouble()).toFloat() + val roll = Math.toDegrees(orientationAngles[2].toDouble()).toFloat() + + val newOrientation = when { + roll in -155f .. -15f && isWithinThreshold(pitch, 0f, 30.0) -> { + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } + roll in 15f .. 155f && isWithinThreshold(pitch, 0f, 30.0) -> { + ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + } + isWithinThreshold(pitch, -90f, 30.0 * deviceAspectRatio) && roll in -15f .. 15f -> { + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + isWithinThreshold(pitch, 90f, 30.0 * deviceAspectRatio) && roll in -15f .. 15f -> { + ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT + } + else -> lastOrientation + } + + //Logger.i("AdvancedOrientationListener", "newOrientation = ${newOrientation}, roll = ${roll}, pitch = ${pitch}, azimuth = ${azimuth}") + + if (newOrientation != lastStableOrientation) { + val currentTime = System.currentTimeMillis() + if (currentTime - lastOrientationChangeTime > debounceTime) { + lastOrientationChangeTime = currentTime + lastStableOrientation = newOrientation + + lifecycleScope.launch(Dispatchers.Main) { + delay(stabilityThresholdTime) + if (newOrientation == lastStableOrientation) { + lastOrientation = newOrientation + onOrientationChanged.emit(newOrientation) + } + } + } + } + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} + } + + private fun isWithinThreshold(value: Float, target: Float, threshold: Double): Boolean { + return Math.abs(value - target) <= threshold + } + + init { + sensorManager.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_GAME) + sensorManager.registerListener(sensorListener, magnetometer, SensorManager.SENSOR_DELAY_GAME) + + val metrics = activity.resources.displayMetrics + deviceAspectRatio = (metrics.heightPixels.toFloat() / metrics.widthPixels.toFloat()) + if (deviceAspectRatio == 0.0f) + deviceAspectRatio = 1.0f + + lastOrientation = activity.resources.configuration.orientation + } + + fun stopListening() { + sensorManager.unregisterListener(sensorListener) + } +} diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index 18cd87d6..60e1b240 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -1,17 +1,22 @@ package com.futo.platformplayer.fragment.mainactivity.main +import AdvancedOrientationListener import android.content.pm.ActivityInfo import android.content.res.Configuration +import android.graphics.drawable.GradientDrawable.Orientation import android.os.Bundle import android.os.Handler import android.util.Log import android.view.LayoutInflater +import android.view.OrientationEventListener +import android.view.OrientationListener import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowInsetsController import androidx.constraintlayout.motion.widget.MotionLayout import androidx.core.view.WindowCompat +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs @@ -27,6 +32,10 @@ import com.futo.platformplayer.models.PlatformVideoWithTime import com.futo.platformplayer.models.UrlVideoWithTime import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + class VideoDetailFragment : MainFragment { override val isMainView : Boolean = false; @@ -39,6 +48,8 @@ class VideoDetailFragment : MainFragment { private var _viewDetail : VideoDetailView? = null; private var _view : SingleViewTouchableMotionLayout? = null; private lateinit var _autoRotateChangeListener: AutoRotateChangeListener + private lateinit var _orientationListener: AdvancedOrientationListener + private var _currentOrientation = 0 var isFullscreen : Boolean = false; val onFullscreenChanged = Event1(); @@ -89,37 +100,37 @@ class VideoDetailFragment : MainFragment { } private fun updateOrientation() { + val a = activity ?: return val isMaximized = state == State.MAXIMIZED; val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait; val isAutoRotateAllowed = Settings.instance.playback.isAutoRotate(); + val currentOrientation = _currentOrientation; + var desiredOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT - if (isFullscreen && isMaximized) { - if (isFullScreenPortraitAllowed) { - activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR - } else if (isAutoRotateAllowed) { - activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR + if (isMaximized) { + if (isFullscreen) { + val isCurrentlyPortrait = currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + if (isAutoRotateAllowed && isCurrentlyPortrait) { + _viewDetail?.setFullscreen(false) + return + } else { + desiredOrientation = if (isFullScreenPortraitAllowed) ActivityInfo.SCREEN_ORIENTATION_SENSOR else ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + } } else { - activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - } - } else { - if (isMaximized && isAutoRotateAllowed) { - activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR - } else { - activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + val isCurrentlyLandscape = currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + if (isAutoRotateAllowed && isCurrentlyLandscape) { + _viewDetail?.setFullscreen(true) + return + } } } - Log.i(TAG, "updateOrientation (isMaximized = ${isMaximized}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}, isAutoRotateAllowed = ${isAutoRotateAllowed}) resulted in requested orientation ${activity?.requestedOrientation}"); - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE && !isFullscreen && Settings.instance.playback.isAutoRotate()) { - _viewDetail?.setFullscreen(true) - } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT && isFullscreen && Settings.instance.playback.isAutoRotate() && !Settings.instance.playback.fullscreenPortrait) { - _viewDetail?.setFullscreen(false) + if (a.requestedOrientation != desiredOrientation) { + Log.i(TAG, "onConfigurationChanged setting requestedOrientation (desiredOrientation = ${desiredOrientation}, requestedOrientation = ${activity?.requestedOrientation})"); + a.requestedOrientation = desiredOrientation } + + Log.i(TAG, "updateOrientation (isMaximized = ${isMaximized}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}, isMaximized = ${isMaximized}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}, isAutoRotateAllowed = ${isAutoRotateAllowed}) resulted in requested orientation ${activity?.requestedOrientation}"); } override fun onShownWithView(parameter: Any?, isBack: Boolean) { @@ -294,6 +305,12 @@ class VideoDetailFragment : MainFragment { updateOrientation() } + _orientationListener = AdvancedOrientationListener(requireActivity(), lifecycleScope) + _orientationListener.onOrientationChanged.subscribe { + _currentOrientation = it + Logger.i(TAG, "Current orientation changed (_currentOrientation = ${_currentOrientation})") + updateOrientation() + } return _view!!; } @@ -393,6 +410,7 @@ class VideoDetailFragment : MainFragment { super.onDestroyMainView(); Logger.v(TAG, "onDestroyMainView"); _autoRotateChangeListener?.unregister() + _orientationListener.stopListening() SettingsActivity.settingsActivityClosed.remove(this) StatePlayer.instance.onRotationLockChanged.remove(this)