mirror of
https://github.com/jellyfin/jellyfin-android.git
synced 2026-09-02 21:04:10 +03:00
Revert "Remove automatic landscape mode"
This reverts commit b9db694529.
This commit is contained in:
@@ -8,6 +8,7 @@ import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.provider.Settings
|
||||
import android.view.OrientationEventListener
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.SystemBarStyle
|
||||
@@ -31,6 +32,7 @@ import org.jellyfin.mobile.utils.BackPressInterceptor
|
||||
import org.jellyfin.mobile.utils.BluetoothPermissionHelper
|
||||
import org.jellyfin.mobile.utils.Constants
|
||||
import org.jellyfin.mobile.utils.PermissionRequestHelper
|
||||
import org.jellyfin.mobile.utils.SmartOrientationListener
|
||||
import org.jellyfin.mobile.utils.extensions.replaceFragment
|
||||
import org.jellyfin.mobile.utils.isWebViewSupported
|
||||
import org.jellyfin.mobile.webapp.RemotePlayerService
|
||||
@@ -59,6 +61,8 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private val orientationListener: OrientationEventListener by lazy { SmartOrientationListener(this) }
|
||||
|
||||
/**
|
||||
* Passes back press events onto the currently visible [Fragment] if it implements the [BackPressInterceptor] interface.
|
||||
*
|
||||
@@ -141,6 +145,11 @@ class MainActivity : AppCompatActivity() {
|
||||
chromecast.initializePlugin(this)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
orientationListener.enable()
|
||||
}
|
||||
|
||||
private fun handleServerState(state: ServerState) {
|
||||
with(supportFragmentManager) {
|
||||
val currentFragment = findFragmentById(R.id.fragment_container)
|
||||
@@ -190,6 +199,11 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
orientationListener.disable()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unbindService(serviceConnection)
|
||||
chromecast.destroy()
|
||||
|
||||
@@ -96,6 +96,9 @@ class AppPreferences(context: Context) {
|
||||
val videoPlayerType: String
|
||||
get() = sharedPreferences.getString(Constants.PREF_VIDEO_PLAYER_TYPE, VideoPlayerType.EXO_PLAYER)!!
|
||||
|
||||
val exoPlayerStartLandscapeVideoInLandscape: Boolean
|
||||
get() = sharedPreferences.getBoolean(Constants.PREF_EXOPLAYER_START_LANDSCAPE_VIDEO_IN_LANDSCAPE, false)
|
||||
|
||||
val exoPlayerAllowSwipeGestures: Boolean
|
||||
get() = sharedPreferences.getBoolean(Constants.PREF_EXOPLAYER_ALLOW_SWIPE_GESTURES, true)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.jellyfin.mobile.events
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -47,9 +48,12 @@ class ActivityEventHandler(
|
||||
is ActivityEvent.ChangeFullscreen -> {
|
||||
val fullscreenHelper = PlayerFullscreenHelper(window)
|
||||
if (event.isFullscreen) {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
fullscreenHelper.enableFullscreen()
|
||||
window.setBackgroundDrawable(null)
|
||||
} else {
|
||||
// Reset screen orientation
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
fullscreenHelper.disableFullscreen()
|
||||
// Reset window background color
|
||||
window.setBackgroundDrawableResource(R.color.theme_background)
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.jellyfin.mobile.player.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
@@ -9,6 +10,7 @@ import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.OrientationEventListener
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||
@@ -41,6 +43,7 @@ import org.jellyfin.mobile.utils.Constants
|
||||
import org.jellyfin.mobile.utils.Constants.DEFAULT_CONTROLS_TIMEOUT_MS
|
||||
import org.jellyfin.mobile.utils.Constants.PIP_MAX_RATIONAL
|
||||
import org.jellyfin.mobile.utils.Constants.PIP_MIN_RATIONAL
|
||||
import org.jellyfin.mobile.utils.SmartOrientationListener
|
||||
import org.jellyfin.mobile.utils.brightness
|
||||
import org.jellyfin.mobile.utils.extensions.aspectRational
|
||||
import org.jellyfin.mobile.utils.extensions.getParcelableCompat
|
||||
@@ -76,6 +79,16 @@ class PlayerFragment : Fragment(), BackPressInterceptor {
|
||||
private val currentVideoStream: MediaStream?
|
||||
get() = viewModel.mediaSourceOrNull?.selectedVideoStream
|
||||
|
||||
/**
|
||||
* Listener that watches the current device orientation.
|
||||
* It makes sure that the orientation sensor can still be used (if enabled)
|
||||
* after toggling the orientation through the fullscreen button.
|
||||
*
|
||||
* If the requestedOrientation was reset directly after setting it in the fullscreenSwitcher click handler,
|
||||
* the orientation would get reverted before the user had any chance to rotate the device to the desired position.
|
||||
*/
|
||||
private val orientationListener: OrientationEventListener by lazy { SmartOrientationListener(requireActivity()) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -106,6 +119,9 @@ class PlayerFragment : Fragment(), BackPressInterceptor {
|
||||
if (mediaSource.selectedVideoStream?.isLandscape == false) {
|
||||
// For portrait videos, immediately enable fullscreen
|
||||
playerFullscreenHelper.enableFullscreen()
|
||||
} else if (appPreferences.exoPlayerStartLandscapeVideoInLandscape) {
|
||||
// Auto-switch to landscape for landscape videos if enabled
|
||||
requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
}
|
||||
|
||||
// Update title and player menus
|
||||
@@ -193,7 +209,7 @@ class PlayerFragment : Fragment(), BackPressInterceptor {
|
||||
// Disable controller animations
|
||||
playerView.setControllerAnimationEnabled(false)
|
||||
|
||||
playerLockScreenHelper = PlayerLockScreenHelper(this, playerBinding)
|
||||
playerLockScreenHelper = PlayerLockScreenHelper(this, playerBinding, orientationListener)
|
||||
playerGestureHelper = PlayerGestureHelper(this, playerBinding, playerLockScreenHelper)
|
||||
|
||||
// Handle fullscreen switcher
|
||||
@@ -202,6 +218,11 @@ class PlayerFragment : Fragment(), BackPressInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
orientationListener.enable()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
@@ -238,10 +259,25 @@ class PlayerFragment : Fragment(), BackPressInterceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle fullscreen
|
||||
* Toggle fullscreen.
|
||||
*
|
||||
* If playing a portrait video, this just hides the status and navigation bars.
|
||||
* For landscape videos, additionally the screen gets rotated.
|
||||
*/
|
||||
private fun toggleFullscreen() {
|
||||
playerFullscreenHelper.toggleFullscreen()
|
||||
val videoTrack = currentVideoStream
|
||||
if (videoTrack == null || videoTrack.isLandscape) {
|
||||
val current = resources.configuration.orientation
|
||||
requireActivity().requestedOrientation = when (current) {
|
||||
Configuration.ORIENTATION_PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
else -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
// No need to call playerFullscreenHelper in this case,
|
||||
// since the configuration change triggers updateFullscreenState,
|
||||
// which does it for us.
|
||||
} else {
|
||||
playerFullscreenHelper.toggleFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,6 +413,11 @@ class PlayerFragment : Fragment(), BackPressInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
orientationListener.disable()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
// Detach player from PlayerView
|
||||
@@ -391,6 +432,8 @@ class PlayerFragment : Fragment(), BackPressInterceptor {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
with(requireActivity()) {
|
||||
// Reset screen orientation
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
playerFullscreenHelper.disableFullscreen()
|
||||
// Reset screen brightness
|
||||
window.brightness = BRIGHTNESS_OVERRIDE_NONE
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package org.jellyfin.mobile.player.ui
|
||||
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.view.OrientationEventListener
|
||||
import android.widget.ImageButton
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.media3.ui.PlayerView
|
||||
import org.jellyfin.mobile.databinding.FragmentPlayerBinding
|
||||
import org.jellyfin.mobile.utils.AndroidVersion
|
||||
import org.jellyfin.mobile.utils.Constants
|
||||
import org.jellyfin.mobile.utils.extensions.lockOrientation
|
||||
import org.jellyfin.mobile.utils.isAutoRotateOn
|
||||
|
||||
class PlayerLockScreenHelper(
|
||||
private val playerFragment: PlayerFragment,
|
||||
private val playerBinding: FragmentPlayerBinding,
|
||||
private val orientationListener: OrientationEventListener,
|
||||
) {
|
||||
private val playerView: PlayerView by playerBinding::playerView
|
||||
private val unlockScreenButton: ImageButton by playerBinding::unlockScreenButton
|
||||
@@ -30,12 +35,18 @@ class PlayerLockScreenHelper(
|
||||
|
||||
fun lockScreen() {
|
||||
playerView.useController = false
|
||||
orientationListener.disable()
|
||||
playerFragment.requireActivity().lockOrientation()
|
||||
peekUnlockButton()
|
||||
}
|
||||
|
||||
private fun unlockScreen() {
|
||||
hideUnlockButton()
|
||||
val activity = playerFragment.requireActivity()
|
||||
if (activity.isAutoRotateOn()) {
|
||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
orientationListener.enable()
|
||||
if (!AndroidVersion.isAtLeastN || !activity.isInPictureInPictureMode) {
|
||||
playerView.useController = true
|
||||
playerView.apply {
|
||||
|
||||
@@ -132,6 +132,10 @@ class SettingsFragment : Fragment(), BackPressInterceptor {
|
||||
externalPlayerChoicePreference.enabled = selection == VideoPlayerType.EXTERNAL_PLAYER
|
||||
}
|
||||
}
|
||||
startLandscapeVideoInLandscapePreference = checkBox(Constants.PREF_EXOPLAYER_START_LANDSCAPE_VIDEO_IN_LANDSCAPE) {
|
||||
titleRes = R.string.pref_exoplayer_start_landscape_video_in_landscape
|
||||
enabled = appPreferences.videoPlayerType == VideoPlayerType.EXO_PLAYER
|
||||
}
|
||||
swipeGesturesPreference = checkBox(Constants.PREF_EXOPLAYER_ALLOW_SWIPE_GESTURES) {
|
||||
titleRes = R.string.pref_exoplayer_allow_brightness_volume_gesture
|
||||
enabled = appPreferences.videoPlayerType == VideoPlayerType.EXO_PLAYER
|
||||
|
||||
@@ -32,6 +32,7 @@ object Constants {
|
||||
const val PREF_DOWNLOAD_METHOD = "pref_download_method"
|
||||
const val PREF_MUSIC_NOTIFICATION_ALWAYS_DISMISSIBLE = "pref_music_notification_always_dismissible"
|
||||
const val PREF_VIDEO_PLAYER_TYPE = "pref_video_player_type"
|
||||
const val PREF_EXOPLAYER_START_LANDSCAPE_VIDEO_IN_LANDSCAPE = "pref_exoplayer_start_landscape_video_in_landscape"
|
||||
const val PREF_EXOPLAYER_ALLOW_SWIPE_GESTURES = "pref_exoplayer_allow_swipe_gestures"
|
||||
const val PREF_EXOPLAYER_ALLOW_PRESS_SPEED_UP = "pref_exoplayer_allow_press_speed_up"
|
||||
const val PREF_EXOPLAYER_REMEMBER_BRIGHTNESS = "pref_exoplayer_remember_brightness"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.jellyfin.mobile.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.view.OrientationEventListener
|
||||
|
||||
/**
|
||||
* Listener that watches the current device orientation.
|
||||
* It makes sure that the orientation sensor can still be used (if enabled)
|
||||
* after toggling the orientation manually.
|
||||
*/
|
||||
class SmartOrientationListener(private val activity: Activity) : OrientationEventListener(activity) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
if (!activity.isAutoRotateOn()) return
|
||||
|
||||
val isAtTarget = when (activity.requestedOrientation) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT -> orientation in Constants.ORIENTATION_PORTRAIT_RANGE
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE -> orientation in Constants.ORIENTATION_LANDSCAPE_RANGE
|
||||
else -> false
|
||||
}
|
||||
if (isAtTarget) {
|
||||
// Reset to unspecified orientation
|
||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.jellyfin.mobile.utils.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Point
|
||||
import android.view.Surface
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun Activity.lockOrientation() {
|
||||
val display = windowManager.defaultDisplay
|
||||
val size = Point().also(display::getSize)
|
||||
val height = size.y
|
||||
val width = size.x
|
||||
requestedOrientation = when (display.rotation) {
|
||||
Surface.ROTATION_90 -> if (width > height) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
||||
Surface.ROTATION_180 -> if (height > width) ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT else ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
Surface.ROTATION_270 -> if (width > height) ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
else -> if (height > width) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,7 @@
|
||||
<string name="video_player_web_description">The default HTML video player from the Web UI</string>
|
||||
<string name="video_player_native_description">Based on ExoPlayer, supports more video formats and codecs, and is more integrated into the OS</string>
|
||||
<string name="video_player_external_description">External video playback apps like MX Player and VLC</string>
|
||||
<string name="pref_exoplayer_start_landscape_video_in_landscape">Start landscape mode videos in landscape orientation</string>
|
||||
<string name="pref_exoplayer_allow_brightness_volume_gesture">Brightness and volume gestures</string>
|
||||
<string name="pref_exoplayer_remember_brightness">Remember display brightness</string>
|
||||
<string name="pref_exoplayer_allow_press_speed_up">Hold to speed up</string>
|
||||
|
||||
Reference in New Issue
Block a user