Revert "Remove automatic landscape mode"

This reverts commit b9db694529.
This commit is contained in:
Niels van Velzen
2026-07-25 14:28:48 +02:00
parent f597a276e2
commit 63b7c22931
10 changed files with 130 additions and 3 deletions
@@ -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
}
}
+1
View File
@@ -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>