simplified fullscreen and orientation handling

- iPad: rotate to device orientation on startup
- fixed controls in portrait fullscreen
- iOS: don’t call setNeedsDrawing multiple times
- On iOS we call set needs drawing only once.
- Added cooldown time to MPV.Client setNeedsDrawing to avoid multiple successive calls
- make fullscreen animation smoother
- dragGesture now calls toggleFullScreenAction
- fix tvOS and macOS build

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
This commit is contained in:
Toni Förster
2024-09-01 12:42:31 +02:00
parent 9bf3df1a29
commit 322a550666
16 changed files with 213 additions and 177 deletions

View File

@@ -1,16 +1,17 @@
import AVFoundation
import Defaults
import Foundation
import Logging
import UIKit
final class AppDelegate: UIResponder, UIApplicationDelegate {
var orientationLock = UIInterfaceOrientationMask.all
var orientationLock = UIInterfaceOrientationMask.allButUpsideDown
private var logger = Logger(label: "stream.yattee.app.delegalate")
private var logger = Logger(label: "stream.yattee.app.delegate")
private(set) static var instance: AppDelegate!
func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask {
orientationLock
return orientationLock
}
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // swiftlint:disable:this discouraged_optional_collection
@@ -19,6 +20,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
#if !os(macOS)
UIViewController.swizzleHomeIndicatorProperty()
OrientationTracker.shared.startDeviceOrientationTracking()
OrientationModel.shared.startOrientationUpdates()
// Configure the audio session for playback
do {

View File

@@ -1,10 +1,12 @@
import Defaults
import Foundation
import Logging
import Repeat
import SwiftUI
final class OrientationModel {
static var shared = OrientationModel()
let logger = Logger(label: "stream.yattee.orientation.model")
var orientation = UIInterfaceOrientation.portrait
var lastOrientation: UIInterfaceOrientation?
@@ -13,79 +15,69 @@ final class OrientationModel {
private var player = PlayerModel.shared
func configureOrientationUpdatesBasedOnAccelerometer() {
let currentOrientation = OrientationTracker.shared.currentInterfaceOrientation
if currentOrientation.isLandscape,
Defaults[.enterFullscreenInLandscape],
!Defaults[.honorSystemOrientationLock],
!player.playingFullScreen,
!player.currentItem.isNil,
player.lockedOrientation.isNil || player.lockedOrientation!.contains(.landscape),
!player.playingInPictureInPicture,
player.presentingPlayer
{
DispatchQueue.main.async {
self.player.controls.presentingControls = false
self.player.enterFullScreen(showControls: false)
}
player.onPresentPlayer.append {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: currentOrientation)
}
}
func startOrientationUpdates() {
// Ensure the orientation observer is active
orientationObserver = NotificationCenter.default.addObserver(
forName: OrientationTracker.deviceOrientationChangedNotification,
object: nil,
queue: .main
) { _ in
guard !Defaults[.honorSystemOrientationLock],
self.player.presentingPlayer,
!self.player.playingInPictureInPicture,
self.player.lockedOrientation.isNil
self.logger.info("Notification received: Device orientation changed.")
// We only allow .portrait and are not showing the player
guard (!self.player.presentingPlayer && !Defaults[.lockPortraitWhenBrowsing]) || self.player.presentingPlayer
else {
return
}
let orientation = OrientationTracker.shared.currentInterfaceOrientation
self.logger.info("Current interface orientation: \(orientation)")
guard self.lastOrientation != orientation else {
// Always update lastOrientation to keep track of the latest state
if self.lastOrientation != orientation {
self.lastOrientation = orientation
self.logger.info("Orientation changed to: \(orientation)")
} else {
self.logger.info("Orientation has not changed.")
}
// Only take action if the player is active and presenting
guard (!self.player.isOrientationLocked && !self.player.playingInPictureInPicture) || (!Defaults[.lockPortraitWhenBrowsing] && !self.player.presentingPlayer) || (!Defaults[.lockPortraitWhenBrowsing] && self.player.presentingPlayer && !self.player.isOrientationLocked)
else {
self.logger.info("Only updating orientation without actions.")
return
}
self.lastOrientation = orientation
DispatchQueue.main.async {
guard Defaults[.enterFullscreenInLandscape],
self.player.presentingPlayer
else {
return
}
self.orientationDebouncer.callback = {
DispatchQueue.main.async {
if orientation.isLandscape {
self.player.controls.presentingControls = false
self.player.enterFullScreen(showControls: false)
if Defaults[.enterFullscreenInLandscape], self.player.presentingPlayer {
self.logger.info("Entering fullscreen because orientation is landscape.")
self.player.controls.presentingControls = false
self.player.enterFullScreen(showControls: false)
}
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
} else {
self.player.exitFullScreen(showControls: false)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
self.logger.info("Exiting fullscreen because orientation is portrait.")
if self.player.playingFullScreen {
self.player.exitFullScreen(showControls: false)
}
if Defaults[.lockPortraitWhenBrowsing] {
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
} else {
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
}
}
}
}
self.orientationDebouncer.call()
}
}
}
func stopOrientationUpdates() {
guard let observer = orientationObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation? = nil) {
logger.info("Locking orientation to: \(orientation), rotating to: \(String(describing: rotateOrientation)).")
if let rotateOrientation {
self.orientation = rotateOrientation
lastOrientation = rotateOrientation