Orientation improvements

This commit is contained in:
Arkadiusz Fal 2022-07-10 00:29:13 +02:00
parent 6c71cd72b1
commit 5f858bc6d4
8 changed files with 144 additions and 138 deletions

View File

@ -92,7 +92,6 @@ extension Defaults.Keys {
#if os(iOS)
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: UIDevice.current.userInterfaceIdiom == .phone)
static let lockOrientationInFullScreen = Key<Bool>("lockOrientationInFullScreen", default: false)
#endif
static let showMPVPlaybackStats = Key<Bool>("showMPVPlaybackStats", default: false)

View File

@ -136,40 +136,13 @@ extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
func playerViewController(
_: AVPlayerViewController,
willBeginFullScreenPresentationWithAnimationCoordinator context: UIViewControllerTransitionCoordinator
) {
#if os(iOS)
if !context.isCancelled, Defaults[.lockOrientationInFullScreen] {
Orientation.lockOrientation(.landscape, andRotateTo: UIDevice.current.orientation.isLandscape ? nil : .landscapeRight)
}
#endif
}
willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
) {}
func playerViewController(
_: AVPlayerViewController,
willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
) {
let wasPlaying = playerModel.isPlaying
coordinator.animate(alongsideTransition: nil) { context in
#if os(iOS)
if wasPlaying {
self.playerModel.play()
}
#endif
if !context.isCancelled {
#if os(iOS)
self.playerModel.lockedOrientation = nil
if Defaults[.enterFullscreenInLandscape] {
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
}
if wasPlaying {
self.playerModel.play()
}
#endif
}
}
}
willEndFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
) {}
func playerViewController(
_: AVPlayerViewController,

View File

@ -38,7 +38,6 @@ struct VideoPlayerView: View {
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
@State private var motionManager: CMMotionManager!
@State private var orientation = UIInterfaceOrientation.portrait
@State private var lastOrientation: UIInterfaceOrientation?
#elseif os(macOS)
@ -94,7 +93,7 @@ struct VideoPlayerView: View {
playerSize = geometry.size
}
}
// .ignoresSafeArea(.all, edges: playerEdgesIgnoringSafeArea)
.ignoresSafeArea(.all, edges: playerEdgesIgnoringSafeArea)
.onChange(of: geometry.size) { size in
self.playerSize = size
}
@ -102,9 +101,6 @@ struct VideoPlayerView: View {
player.backend.setNeedsDrawing(!value)
}
#if os(iOS)
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
handleOrientationDidChangeNotification()
}
.onChange(of: player.presentingPlayer) { newValue in
if newValue {
viewVerticalOffset = 0
@ -120,9 +116,6 @@ struct VideoPlayerView: View {
} else {
Orientation.lockOrientation(.allButUpsideDown)
}
motionManager?.stopAccelerometerUpdates()
motionManager = nil
viewVerticalOffset = Self.hiddenOffset
}
}
@ -203,7 +196,6 @@ struct VideoPlayerView: View {
#endif
}
}
// .ignoresSafeArea(.all, edges: fullScreenLayout ? .bottom : Edge.Set())
.frame(maxWidth: fullScreenLayout ? .infinity : nil, maxHeight: fullScreenLayout ? .infinity : nil)
.onHover { hovering in
hoveringPlayer = hovering
@ -253,7 +245,7 @@ struct VideoPlayerView: View {
.background(Color.black)
#if !os(tvOS)
if !player.playingFullScreen {
if !fullScreenLayout {
VStack(spacing: 0) {
#if os(iOS)
VideoDetails(sidebarQueue: sidebarQueue, fullScreen: $fullScreenDetails)
@ -281,7 +273,7 @@ struct VideoPlayerView: View {
#if os(macOS)
.frame(minWidth: 650)
#endif
if !player.playingFullScreen {
if !fullScreenLayout {
#if os(iOS)
if sidebarQueue {
PlayerQueueView(sidebarQueue: true, fullScreen: $fullScreenDetails)
@ -297,7 +289,7 @@ struct VideoPlayerView: View {
}
}
#if os(iOS)
.statusBar(hidden: player.playingFullScreen)
.statusBar(hidden: fullScreenLayout)
#endif
}
@ -339,18 +331,26 @@ struct VideoPlayerView: View {
PlayerGestures()
PlayerControls(player: player, thumbnails: thumbnails)
#if os(iOS)
.padding(.top, fullScreenLayout ? (safeAreaInsets.top.isZero ? safeAreaInsets.bottom : safeAreaInsets.top) : 0)
.padding(.top, controlsTopPadding)
.padding(.bottom, fullScreenLayout ? safeAreaInsets.bottom : 0)
#endif
#endif
}
.ignoresSafeArea(.all, edges: fullScreenLayout ? .vertical : Edge.Set())
#if os(iOS)
.statusBarHidden(fullScreenLayout)
.statusBarHidden(fullScreenLayout)
#endif
}
#if os(iOS)
var controlsTopPadding: Double {
guard fullScreenLayout else { return 0 }
let idiom = UIDevice.current.userInterfaceIdiom
guard idiom == .pad else { return safeAreaInsets.top }
return safeAreaInsets.top.isZero ? safeAreaInsets.bottom : safeAreaInsets.top
}
var safeAreaInsets: UIEdgeInsets {
UIApplication.shared.windows.first?.safeAreaInsets ?? .init()
}
@ -432,7 +432,7 @@ struct VideoPlayerView: View {
#if os(iOS)
private func configureOrientationUpdatesBasedOnAccelerometer() {
if UIDevice.current.orientation.isLandscape,
if OrientationTracker.shared.currentInterfaceOrientation.isLandscape,
Defaults[.enterFullscreenInLandscape],
!player.playingFullScreen,
!player.playingInPictureInPicture
@ -442,32 +442,16 @@ struct VideoPlayerView: View {
}
}
guard !Defaults[.honorSystemOrientationLock], motionManager.isNil else {
return
}
motionManager = CMMotionManager()
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdates(to: OperationQueue()) { data, _ in
guard player.presentingPlayer, !player.playingInPictureInPicture, !data.isNil else {
NotificationCenter.default.addObserver(
forName: OrientationTracker.deviceOrientationChangedNotification,
object: nil,
queue: .main
) { _ in
guard !Defaults[.honorSystemOrientationLock], player.presentingPlayer, !player.playingInPictureInPicture else {
return
}
guard let acceleration = data?.acceleration else {
return
}
var orientation = UIInterfaceOrientation.unknown
if acceleration.x >= 0.65 {
orientation = .landscapeLeft
} else if acceleration.x <= -0.65 {
orientation = .landscapeRight
} else if acceleration.y <= -0.65 {
orientation = .portrait
} else if acceleration.y >= 0.65 {
orientation = .portraitUpsideDown
}
let orientation = OrientationTracker.shared.currentInterfaceOrientation
guard lastOrientation != orientation else {
return
@ -475,67 +459,21 @@ struct VideoPlayerView: View {
lastOrientation = orientation
if orientation.isLandscape {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
guard Defaults[.enterFullscreenInLandscape] else {
return
}
player.enterFullScreen()
let orientationLockMask = orientation == .landscapeLeft ?
UIInterfaceOrientationMask.landscapeLeft : .landscapeRight
Orientation.lockOrientation(orientationLockMask, andRotateTo: orientation)
guard Defaults[.lockOrientationInFullScreen] else {
return
}
player.lockedOrientation = orientation
}
} else {
guard abs(acceleration.z) <= 0.74,
player.lockedOrientation.isNil,
Defaults[.enterFullscreenInLandscape],
!Defaults[.lockOrientationInFullScreen]
else {
DispatchQueue.main.async {
guard Defaults[.enterFullscreenInLandscape] else {
return
}
Orientation.lockOrientation(.portrait)
}
}
}
private func handleOrientationDidChangeNotification() {
viewVerticalOffset = viewVerticalOffset == 0 ? 0 : Self.hiddenOffset
let newOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
if newOrientation?.isLandscape ?? false,
player.presentingPlayer,
Defaults[.lockOrientationInFullScreen],
!player.lockedOrientation.isNil
{
Orientation.lockOrientation(.landscape, andRotateTo: newOrientation)
return
}
guard player.presentingPlayer, Defaults[.enterFullscreenInLandscape], Defaults[.honorSystemOrientationLock] else {
return
}
if UIDevice.current.orientation.isLandscape {
DispatchQueue.main.async {
player.lockedOrientation = newOrientation
player.enterFullScreen()
}
} else {
DispatchQueue.main.async {
player.exitFullScreen()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
player.exitFullScreen()
if orientation.isLandscape {
player.enterFullScreen()
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
} else {
if !player.playingFullScreen {
player.exitFullScreen()
} else {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
}
}
}
}

View File

@ -13,7 +13,6 @@ struct PlayerSettings: View {
@Default(.closeLastItemOnPlaybackEnd) private var closeLastItemOnPlaybackEnd
#if os(iOS)
@Default(.honorSystemOrientationLock) private var honorSystemOrientationLock
@Default(.lockOrientationInFullScreen) private var lockOrientationInFullScreen
@Default(.enterFullscreenInLandscape) private var enterFullscreenInLandscape
#endif
@Default(.closePiPOnNavigation) private var closePiPOnNavigation
@ -93,7 +92,6 @@ struct PlayerSettings: View {
enterFullscreenInLandscapeToggle
}
honorSystemOrientationLockToggle
lockOrientationInFullScreenToggle
}
#endif
}
@ -186,11 +184,6 @@ struct PlayerSettings: View {
private var enterFullscreenInLandscapeToggle: some View {
Toggle("Enter fullscreen in landscape", isOn: $enterFullscreenInLandscape)
}
private var lockOrientationInFullScreenToggle: some View {
Toggle("Lock orientation in fullscreen", isOn: $lockOrientationInFullScreen)
.disabled(!enterFullscreenInLandscape)
}
#endif
private var closePiPOnNavigationToggle: some View {

View File

@ -511,6 +511,7 @@
379775932689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; };
379775942689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; };
379775952689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; };
379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379B0252287A1CDF001015B5 /* OrientationTracker.swift */; };
37A3B15A27255E7F000FB5EE /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A3B15927255E7F000FB5EE /* SafariWebExtensionHandler.swift */; };
37A3B15F27255E7F000FB5EE /* images in Resources */ = {isa = PBXBuildFile; fileRef = 37A3B15E27255E7F000FB5EE /* images */; };
37A3B16127255E7F000FB5EE /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 37A3B16027255E7F000FB5EE /* manifest.json */; };
@ -1073,6 +1074,7 @@
3797758A2689345500DD52A8 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
379775922689365600DD52A8 /* Array+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Next.swift"; sourceTree = "<group>"; };
37992DC726CC50BC003D4C27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
379B0252287A1CDF001015B5 /* OrientationTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrientationTracker.swift; sourceTree = "<group>"; };
37A3B15727255E7F000FB5EE /* Open in Yattee - macOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Open in Yattee - macOS.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
37A3B15927255E7F000FB5EE /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; };
37A3B15E27255E7F000FB5EE /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = images; sourceTree = "<group>"; };
@ -1775,6 +1777,7 @@
children = (
37B4E802277D0A72004BF56A /* AppDelegate.swift */,
37B4E804277D0AB4004BF56A /* Orientation.swift */,
379B0252287A1CDF001015B5 /* OrientationTracker.swift */,
3784B23A272894DA00B09468 /* ShareSheet.swift */,
3749BF9227ADA142000480FF /* BridgingHeader.h */,
37992DC726CC50BC003D4C27 /* Info.plist */,
@ -2832,6 +2835,7 @@
37E70923271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */,
379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */,
370B79C9286279810045DB77 /* NSObject+Swizzle.swift in Sources */,
37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */,
37C3A241272359900087A57A /* Double+Format.swift in Sources */,

View File

@ -19,6 +19,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().isTranslucent = true
UITabBar.appearance().backgroundColor = .clear
OrientationTracker.shared.startDeviceOrientationTracking()
#endif
return true
}

View File

@ -21,11 +21,16 @@ struct Orientation {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation? = nil) {
lockOrientation(orientation)
guard !rotateOrientation.isNil else {
guard let rotateOrientation = rotateOrientation else {
return
}
UIDevice.current.setValue(rotateOrientation!.rawValue, forKey: "orientation")
let orientationString = rotateOrientation == .portrait ? "portrait" : rotateOrientation == .landscapeLeft ? "landscapeLeft" :
rotateOrientation == .landscapeRight ? "landscapeRight" : rotateOrientation == .portraitUpsideDown ? "portraitUpsideDown" : "allButUpsideDown"
logger.info("rotating to \(orientationString)")
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
}
}

View File

@ -0,0 +1,92 @@
import CoreMotion
import UIKit
public class OrientationTracker {
public static let shared = OrientationTracker()
public static let deviceOrientationChangedNotification = NSNotification.Name("DeviceOrientationChangedNotification")
public var currentDeviceOrientation: UIDeviceOrientation = .portrait
public var currentInterfaceOrientation: UIInterfaceOrientation {
switch currentDeviceOrientation {
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
default:
return .portrait
}
}
public var currentInterfaceOrientationMask: UIInterfaceOrientationMask {
switch currentInterfaceOrientation {
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
default:
return .portrait
}
}
public var affineTransform: CGAffineTransform {
var angleRadians: Double
switch currentDeviceOrientation {
case .portrait:
angleRadians = 0
case .landscapeLeft:
angleRadians = -0.5 * .pi
case .landscapeRight:
angleRadians = 0.5 * .pi
case .portraitUpsideDown:
angleRadians = .pi
default:
return .identity
}
return CGAffineTransform(rotationAngle: angleRadians)
}
private let motionManager: CMMotionManager
private let queue: OperationQueue
private init() {
motionManager = CMMotionManager()
motionManager.accelerometerUpdateInterval = 0.1
queue = OperationQueue()
}
public func startDeviceOrientationTracking() {
motionManager.startAccelerometerUpdates(to: queue) { accelerometerData, error in
guard error == nil else { return }
guard let accelerometerData = accelerometerData else { return }
let newDeviceOrientation = self.deviceOrientation(forAccelerometerData: accelerometerData)
guard newDeviceOrientation != self.currentDeviceOrientation else { return }
self.currentDeviceOrientation = newDeviceOrientation
NotificationCenter.default.post(name: Self.deviceOrientationChangedNotification,
object: nil,
userInfo: nil)
}
}
public func stopDeviceOrientationTracking() {
motionManager.stopAccelerometerUpdates()
}
private func deviceOrientation(forAccelerometerData accelerometerData: CMAccelerometerData) -> UIDeviceOrientation {
let treshold = 0.55
if accelerometerData.acceleration.x >= treshold {
return .landscapeLeft
} else if accelerometerData.acceleration.x <= -treshold {
return .landscapeRight
} else if accelerometerData.acceleration.y <= -treshold {
return .portrait
} else if accelerometerData.acceleration.y >= treshold {
return .portraitUpsideDown
} else {
return currentDeviceOrientation
}
}
}