mirror of
https://github.com/yattee/yattee.git
synced 2024-12-14 22:30:32 +05:30
Orientation improvements
This commit is contained in:
parent
868e5fcbc7
commit
d93e9294db
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
#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 {
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: OrientationTracker.deviceOrientationChangedNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { _ in
|
||||
guard !Defaults[.honorSystemOrientationLock], player.presentingPlayer, !player.playingInPictureInPicture else {
|
||||
return
|
||||
}
|
||||
|
||||
motionManager = CMMotionManager()
|
||||
motionManager.accelerometerUpdateInterval = 0.2
|
||||
motionManager.startAccelerometerUpdates(to: OperationQueue()) { data, _ in
|
||||
guard player.presentingPlayer, !player.playingInPictureInPicture, !data.isNil 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) {
|
||||
DispatchQueue.main.async {
|
||||
guard Defaults[.enterFullscreenInLandscape] else {
|
||||
return
|
||||
}
|
||||
|
||||
if orientation.isLandscape {
|
||||
player.enterFullScreen()
|
||||
|
||||
let orientationLockMask = orientation == .landscapeLeft ?
|
||||
UIInterfaceOrientationMask.landscapeLeft : .landscapeRight
|
||||
|
||||
Orientation.lockOrientation(orientationLockMask, andRotateTo: orientation)
|
||||
|
||||
guard Defaults[.lockOrientationInFullScreen] else {
|
||||
return
|
||||
}
|
||||
|
||||
player.lockedOrientation = orientation
|
||||
}
|
||||
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
|
||||
} else {
|
||||
guard abs(acceleration.z) <= 0.74,
|
||||
player.lockedOrientation.isNil,
|
||||
Defaults[.enterFullscreenInLandscape],
|
||||
!Defaults[.lockOrientationInFullScreen]
|
||||
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()
|
||||
}
|
||||
if !player.playingFullScreen {
|
||||
player.exitFullScreen()
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
player.exitFullScreen()
|
||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
||||
player.exitFullScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 */,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
92
iOS/OrientationTracker.swift
Normal file
92
iOS/OrientationTracker.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user