Compare commits

..

12 Commits

Author SHA1 Message Date
Arkadiusz Fal
b0264aaabe Bump build number to 195 2024-09-05 23:01:29 +02:00
Arkadiusz Fal
035f3503c4 Update CHANGELOG 2024-09-05 23:01:19 +02:00
Arkadiusz Fal
e3ac11c172 Merge pull request #786 from stonerl/simplified-fullscreen-and-orientation
iOS: Simplified fullscreen and orientation
2024-09-05 22:59:54 +02:00
Arkadiusz Fal
7aed6ac0d9 Merge pull request #799 from stonerl/controls-background
player controls: add background opacity selection
2024-09-05 22:54:30 +02:00
Arkadiusz Fal
457c0ce7b3 Merge pull request #797 from stonerl/shorts-resolutions
add missing Shorts resolutions
2024-09-05 22:53:42 +02:00
Arkadiusz Fal
747baf3edd Merge pull request #801 from stonerl/O2-for-macOS
use -O1 on macOS
2024-09-05 22:53:26 +02:00
Arkadiusz Fal
cd24a0322f Merge pull request #802 from stonerl/buttons-interfere-with-search
macOS: only apply player shortcuts when window is active
2024-09-05 22:53:16 +02:00
Toni Förster
d525a22215 macOS only apply player shortcuts when window is active 2024-09-05 21:53:25 +02:00
Toni Förster
322a550666 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>
2024-09-05 18:17:14 +02:00
Toni Förster
98fa0b98e5 use -O1 on macOS
On macOS optimisation level -O3 seems to be a bit aggressive and can cause crashes when opening MPV.

- fixes #783

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-05 17:35:52 +02:00
Toni Förster
5313e4ead0 player controls: add background opacity selection
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-05 15:14:39 +02:00
Toni Förster
fa7b897e76 add missing Shorts resolutions
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-04 12:44:43 +02:00
21 changed files with 275 additions and 204 deletions

View File

@@ -1,11 +1,9 @@
## Build 194 ## Build 195
* Gestures: swipe up toggles fullscreen by @stonerl in https://github.com/yattee/yattee/pull/778 * iOS: Simplified fullscreen and orientation by @stonerl in https://github.com/yattee/yattee/pull/786
* dont open video when dismissing context menu by @stonerl in https://github.com/yattee/yattee/pull/780 * macOS: only apply player shortcuts when window is active by @stonerl in https://github.com/yattee/yattee/pull/802
* mpv: remove video layer when entering background by @stonerl in https://github.com/yattee/yattee/pull/793 * player controls: add background opacity selection by @stonerl in https://github.com/yattee/yattee/pull/799
* hi-res invidious logos by @stonerl in https://github.com/yattee/yattee/pull/791 * add missing Shorts resolutions by @stonerl in https://github.com/yattee/yattee/pull/797
* enable -O3 by @stonerl in https://github.com/yattee/yattee/pull/794 * use -O1 on macOS by @stonerl in https://github.com/yattee/yattee/pull/801
* Better audio ducking by @stonerl in https://github.com/yattee/yattee/pull/779
* fix picture in picture by @stonerl in https://github.com/yattee/yattee/pull/789
## Previous builds ## Previous builds
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein) * Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
@@ -24,6 +22,13 @@
* Add import export of missing settings * Add import export of missing settings
* macOS: Fix settings windows layout * macOS: Fix settings windows layout
* Fix seek OSD layout on tvOS, revert OSD position * Fix seek OSD layout on tvOS, revert OSD position
* Gestures: swipe up toggles fullscreen by @stonerl in https://github.com/yattee/yattee/pull/778
* dont open video when dismissing context menu by @stonerl in https://github.com/yattee/yattee/pull/780
* mpv: remove video layer when entering background by @stonerl in https://github.com/yattee/yattee/pull/793
* hi-res invidious logos by @stonerl in https://github.com/yattee/yattee/pull/791
* enable -O3 by @stonerl in https://github.com/yattee/yattee/pull/794
* Better audio ducking by @stonerl in https://github.com/yattee/yattee/pull/779
* fix picture in picture by @stonerl in https://github.com/yattee/yattee/pull/789
* Invidious: propper HTTP basic auth support by @stonerl in https://github.com/yattee/yattee/pull/762 * Invidious: propper HTTP basic auth support by @stonerl in https://github.com/yattee/yattee/pull/762
* Apply correct orientation by @stonerl in https://github.com/yattee/yattee/pull/770 * Apply correct orientation by @stonerl in https://github.com/yattee/yattee/pull/770
* Circular Invidious logo by @stonerl in https://github.com/yattee/yattee/pull/769 * Circular Invidious logo by @stonerl in https://github.com/yattee/yattee/pull/769

View File

@@ -10,6 +10,7 @@ final class ConstrolsSettingsGroupExporter: SettingsGroupExporter {
"seekGestureSpeed": Defaults[.seekGestureSpeed], "seekGestureSpeed": Defaults[.seekGestureSpeed],
"playerControlsLayout": Defaults[.playerControlsLayout].rawValue, "playerControlsLayout": Defaults[.playerControlsLayout].rawValue,
"fullScreenPlayerControlsLayout": Defaults[.fullScreenPlayerControlsLayout].rawValue, "fullScreenPlayerControlsLayout": Defaults[.fullScreenPlayerControlsLayout].rawValue,
"playerControlsBackgroundOpacity": Defaults[.playerControlsBackgroundOpacity],
"systemControlsCommands": Defaults[.systemControlsCommands].rawValue, "systemControlsCommands": Defaults[.systemControlsCommands].rawValue,
"buttonBackwardSeekDuration": Defaults[.buttonBackwardSeekDuration], "buttonBackwardSeekDuration": Defaults[.buttonBackwardSeekDuration],
"buttonForwardSeekDuration": Defaults[.buttonForwardSeekDuration], "buttonForwardSeekDuration": Defaults[.buttonForwardSeekDuration],

View File

@@ -44,7 +44,7 @@ final class PlayerSettingsGroupExporter: SettingsGroupExporter {
#endif #endif
#if os(iOS) #if os(iOS)
export["honorSystemOrientationLock"].bool = Defaults[.honorSystemOrientationLock] export["isOrientationLocked"].bool = Defaults[.isOrientationLocked]
export["enterFullscreenInLandscape"].bool = Defaults[.enterFullscreenInLandscape] export["enterFullscreenInLandscape"].bool = Defaults[.enterFullscreenInLandscape]
export["rotateToLandscapeOnEnterFullScreen"].string = Defaults[.rotateToLandscapeOnEnterFullScreen].rawValue export["rotateToLandscapeOnEnterFullScreen"].string = Defaults[.rotateToLandscapeOnEnterFullScreen].rawValue
#endif #endif

View File

@@ -33,6 +33,10 @@ struct ConstrolsSettingsGroupImporter {
Defaults[.fullScreenPlayerControlsLayout] = fullScreenPlayerControlsLayout Defaults[.fullScreenPlayerControlsLayout] = fullScreenPlayerControlsLayout
} }
if let playerControlsBackgroundOpacity = json["playerControlsBackgroundOpacity"].double {
Defaults[.playerControlsBackgroundOpacity] = playerControlsBackgroundOpacity
}
if let systemControlsCommandsString = json["systemControlsCommands"].string, if let systemControlsCommandsString = json["systemControlsCommands"].string,
let systemControlsCommands = SystemControlsCommands(rawValue: systemControlsCommandsString) let systemControlsCommands = SystemControlsCommands(rawValue: systemControlsCommandsString)
{ {

View File

@@ -97,8 +97,8 @@ struct PlayerSettingsGroupImporter {
#endif #endif
#if os(iOS) #if os(iOS)
if let honorSystemOrientationLock = json["honorSystemOrientationLock"].bool { if let isOrientationLocked = json["isOrientationLocked"].bool {
Defaults[.honorSystemOrientationLock] = honorSystemOrientationLock Defaults[.isOrientationLocked] = isOrientationLocked
} }
if let enterFullscreenInLandscape = json["enterFullscreenInLandscape"].bool { if let enterFullscreenInLandscape = json["enterFullscreenInLandscape"].bool {

View File

@@ -14,6 +14,8 @@ final class MPVClient: ObservableObject {
} }
private var logger = Logger(label: "mpv-client") private var logger = Logger(label: "mpv-client")
private var needsDrawingCooldown = false
private var needsDrawingWorkItem: DispatchWorkItem?
var mpv: OpaquePointer! var mpv: OpaquePointer!
var mpvGL: OpaquePointer! var mpvGL: OpaquePointer!
@@ -389,10 +391,30 @@ final class MPVClient: ObservableObject {
} }
func setNeedsDrawing(_ needsDrawing: Bool) { func setNeedsDrawing(_ needsDrawing: Bool) {
// Check if we are currently in a cooldown period
guard !needsDrawingCooldown else {
logger.info("Not drawing, cooldown in progress")
return
}
logger.info("needs drawing: \(needsDrawing)") logger.info("needs drawing: \(needsDrawing)")
// Set the cooldown flag to true and cancel any existing work item
needsDrawingCooldown = true
needsDrawingWorkItem?.cancel()
#if !os(macOS) #if !os(macOS)
glView?.needsDrawing = needsDrawing glView?.needsDrawing = needsDrawing
#endif #endif
// Create a new DispatchWorkItem to reset the cooldown flag after 0.1 seconds
let workItem = DispatchWorkItem { [weak self] in
self?.needsDrawingCooldown = false
}
needsDrawingWorkItem = workItem
// Schedule the cooldown reset after 0.1 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: workItem)
} }
func command( func command(

View File

@@ -56,7 +56,6 @@ final class PlayerModel: ObservableObject {
@Published var presentingPlayer = false { didSet { handlePresentationChange() } } @Published var presentingPlayer = false { didSet { handlePresentationChange() } }
@Published var activeBackend = PlayerBackendType.mpv @Published var activeBackend = PlayerBackendType.mpv
@Published var forceBackendOnPlay: PlayerBackendType? @Published var forceBackendOnPlay: PlayerBackendType?
@Published var wasFullscreen = false
var avPlayerBackend = AVPlayerBackend() var avPlayerBackend = AVPlayerBackend()
var mpvBackend = MPVBackend() var mpvBackend = MPVBackend()
@@ -131,6 +130,12 @@ final class PlayerModel: ObservableObject {
#if os(iOS) #if os(iOS)
@Published var lockedOrientation: UIInterfaceOrientationMask? @Published var lockedOrientation: UIInterfaceOrientationMask?
@Published var isOrientationLocked: Bool {
didSet {
Defaults[.isOrientationLocked] = isOrientationLocked
}
}
@Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen @Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
#endif #endif
@@ -201,6 +206,16 @@ final class PlayerModel: ObservableObject {
#endif #endif
init() { init() {
#if os(iOS)
isOrientationLocked = Defaults[.isOrientationLocked]
if isOrientationLocked, Defaults[.lockPortraitWhenBrowsing] {
lockedOrientation = UIInterfaceOrientationMask.portrait
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
} else if isOrientationLocked {
lockOrientationAction()
}
#endif
#if !os(macOS) #if !os(macOS)
mpvBackend.controller = mpvController mpvBackend.controller = mpvController
mpvBackend.client = mpvController.client mpvBackend.client = mpvController.client
@@ -517,7 +532,10 @@ final class PlayerModel: ObservableObject {
} }
private func handlePresentationChange() { private func handlePresentationChange() {
backend.setNeedsDrawing(presentingPlayer) #if !os(iOS)
// TODO: Check whether this is neede on tvOS and macOS
backend.setNeedsDrawing(presentingPlayer)
#endif
#if os(iOS) #if os(iOS)
if presentingPlayer, activeBackend == .appleAVPlayer, avPlayerUsesSystemControls, Constants.isIPhone { if presentingPlayer, activeBackend == .appleAVPlayer, avPlayerUsesSystemControls, Constants.isIPhone {
@@ -551,8 +569,6 @@ final class PlayerModel: ObservableObject {
} else { } else {
Orientation.lockOrientation(.allButUpsideDown) Orientation.lockOrientation(.allButUpsideDown)
} }
OrientationModel.shared.stopOrientationUpdates()
#endif #endif
} }
} }
@@ -659,32 +675,37 @@ final class PlayerModel: ObservableObject {
} }
func closeCurrentItem(finished: Bool = false) { func closeCurrentItem(finished: Bool = false) {
pause() guard !closing else { return }
videoBeingOpened = nil
advancing = false
forceBackendOnPlay = nil
closing = true closing = true
controls.presentingControls = false
self.prepareCurrentItemForHistory(finished: finished) if playingFullScreen { exitFullScreen() }
self.hide() Delay.by(0.3) { [weak self] in
Delay.by(0.8) { [weak self] in
guard let self else { return } guard let self else { return }
self.closePiP() pause()
videoBeingOpened = nil
advancing = false
forceBackendOnPlay = nil
withAnimation { controls.presentingControls = false
self.currentItem = nil
self.prepareCurrentItemForHistory(finished: finished)
self.hide()
Delay.by(0.7) { [weak self] in
guard let self else { return }
if playingInPictureInPicture { self.closePiP() }
withAnimation {
self.currentItem = nil
}
self.updateNowPlayingInfo()
self.backend.closeItem()
self.aspectRatio = VideoPlayerView.defaultAspectRatio
self.resetAutoplay()
self.closing = false
} }
self.updateNowPlayingInfo()
self.backend.closeItem()
self.aspectRatio = VideoPlayerView.defaultAspectRatio
self.resetAutoplay()
self.closing = false
self.playingFullScreen = false
} }
} }
@@ -773,7 +794,7 @@ final class PlayerModel: ObservableObject {
} }
func toggleFullScreenAction() { func toggleFullScreenAction() {
toggleFullscreen(playingFullScreen, showControls: false) toggleFullscreen(playingFullScreen, showControls: false, initiatedByButton: true)
} }
func togglePiPAction() { func togglePiPAction() {
@@ -786,20 +807,21 @@ final class PlayerModel: ObservableObject {
#if os(iOS) #if os(iOS)
var lockOrientationImage: String { var lockOrientationImage: String {
lockedOrientation.isNil ? "lock.rotation.open" : "lock.rotation" isOrientationLocked ? "lock.rotation" : "lock.rotation.open"
} }
func lockOrientationAction() { func lockOrientationAction() {
if lockedOrientation.isNil { // This makes toggling orientation lock more robust
if lockedOrientation.isNil || !isOrientationLocked {
isOrientationLocked = true
let orientationMask = OrientationTracker.shared.currentInterfaceOrientationMask let orientationMask = OrientationTracker.shared.currentInterfaceOrientationMask
lockedOrientation = orientationMask lockedOrientation = orientationMask
let orientation = OrientationTracker.shared.currentInterfaceOrientation let orientation = OrientationTracker.shared.currentInterfaceOrientation
Orientation.lockOrientation(orientationMask, andRotateTo: .landscapeLeft) Orientation.lockOrientation(orientationMask, andRotateTo: playingFullScreen ? nil : orientation)
// iOS 16 workaround
Orientation.lockOrientation(orientationMask, andRotateTo: orientation)
} else { } else {
isOrientationLocked = false
lockedOrientation = nil lockedOrientation = nil
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation) Orientation.lockOrientation(.allButUpsideDown)
} }
} }
#endif #endif
@@ -985,7 +1007,14 @@ final class PlayerModel: ObservableObject {
} }
#else #else
func handleEnterForeground() { func handleEnterForeground() {
setNeedsDrawing(presentingPlayer) #if os(iOS)
OrientationTracker.shared.startDeviceOrientationTracking()
#endif
#if os(tvOS)
// TODO: Not sure if this is realy needed on tvOS, maybe it can be removed.
setNeedsDrawing(presentingPlayer)
#endif
if !musicMode, activeBackend == .mpv { if !musicMode, activeBackend == .mpv {
mpvBackend.addVideoTrackFromStream() mpvBackend.addVideoTrackFromStream()
@@ -995,17 +1024,6 @@ final class PlayerModel: ObservableObject {
avPlayerBackend.bindPlayerToLayer() avPlayerBackend.bindPlayerToLayer()
} }
#if os(iOS)
if wasFullscreen {
wasFullscreen = false
DispatchQueue.main.async { [weak self] in
Delay.by(0.3) {
self?.enterFullScreen()
}
}
}
#endif
guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else { guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else {
return return
} }
@@ -1018,6 +1036,10 @@ final class PlayerModel: ObservableObject {
} }
func handleEnterBackground() { func handleEnterBackground() {
#if os(iOS)
OrientationTracker.shared.stopDeviceOrientationTracking()
#endif
if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode { if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode {
pause() pause()
} else if !playingInPictureInPicture, activeBackend == .appleAVPlayer { } else if !playingInPictureInPicture, activeBackend == .appleAVPlayer {
@@ -1025,15 +1047,6 @@ final class PlayerModel: ObservableObject {
} else if activeBackend == .mpv, !musicMode { } else if activeBackend == .mpv, !musicMode {
mpvBackend.setVideoToNo() mpvBackend.setVideoToNo()
} }
#if os(iOS)
guard playingFullScreen else { return }
wasFullscreen = playingFullScreen
DispatchQueue.main.async { [weak self] in
Delay.by(0.3) {
self?.exitFullScreen(showControls: false)
}
}
#endif
} }
#endif #endif
@@ -1124,7 +1137,7 @@ final class PlayerModel: ObservableObject {
task.resume() task.resume()
} }
func toggleFullscreen(_ isFullScreen: Bool, showControls: Bool = true) { func toggleFullscreen(_ isFullScreen: Bool, showControls: Bool = true, initiatedByButton: Bool = false) {
controls.presentingControls = showControls && isFullScreen controls.presentingControls = showControls && isFullScreen
#if os(macOS) #if os(macOS)
@@ -1139,15 +1152,13 @@ final class PlayerModel: ObservableObject {
avPlayerBackend.controller.enterFullScreen(animated: true) avPlayerBackend.controller.enterFullScreen(animated: true)
return return
} }
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return } let lockOrientation = rotateToLandscapeOnEnterFullScreen.interfaceOrientation
if currentVideoIsLandscape { if currentVideoIsLandscape {
let delay = activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0 if initiatedByButton {
// not sure why but first rotation call is ignore so doing rotate to same orientation first Orientation.lockOrientation(self.isOrientationLocked ? (lockOrientation == .landscapeRight ? .landscapeRight : .landscapeLeft) : .landscape)
Delay.by(delay) {
let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interaceOrientation
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation)
} }
let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interfaceOrientation
Orientation.lockOrientation(self.isOrientationLocked ? (lockOrientation == .landscapeRight ? .landscapeRight : .landscapeLeft) : .landscape, andRotateTo: orientation)
} }
} else { } else {
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls { if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
@@ -1155,10 +1166,12 @@ final class PlayerModel: ObservableObject {
avPlayerBackend.controller.dismiss(animated: true) avPlayerBackend.controller.dismiss(animated: true)
return return
} }
let rotationOrientation = Constants.isIPhone ? UIInterfaceOrientation.portrait : nil if Defaults[.lockPortraitWhenBrowsing] {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: rotationOrientation) lockedOrientation = UIInterfaceOrientationMask.portrait
}
let rotationOrientation = Defaults[.lockPortraitWhenBrowsing] ? UIInterfaceOrientation.portrait : nil
Orientation.lockOrientation(Defaults[.lockPortraitWhenBrowsing] ? .portrait : .allButUpsideDown, andRotateTo: rotationOrientation)
} }
#endif #endif
} }
@@ -1286,7 +1299,10 @@ final class PlayerModel: ObservableObject {
#if os(macOS) #if os(macOS)
private func assignKeyPressMonitor() { private func assignKeyPressMonitor() {
keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { keyEvent -> NSEvent? in keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] keyEvent -> NSEvent? in
// Check if the player window is the key window
guard let self, let window = Windows.playerWindow, window.isKeyWindow else { return keyEvent }
switch keyEvent.keyCode { switch keyEvent.keyCode {
case 124: case 124:
if !self.liveStreamInAVPlayer { if !self.liveStreamInAVPlayer {

View File

@@ -139,10 +139,14 @@ class Stream: Equatable, Hashable, Identifiable {
case sd428p30 case sd428p30
case sd428p25 case sd428p25
case sd426p30
case sd426p25
case sd360p30 case sd360p30
case sd360p25 case sd360p25
case sd320p30 case sd320p30
case sd320p25 case sd320p25
case sd256p30
case sd256p25
case sd240p30 case sd240p30
case sd240p25 case sd240p25
case sd214p30 case sd214p30
@@ -253,7 +257,7 @@ class Stream: Equatable, Hashable, Identifiable {
case .sd480p30, .sd480p25: case .sd480p30, .sd480p25:
return 2_500_000 // 2.5 Mbit/s return 2_500_000 // 2.5 Mbit/s
case .sd428p30, .sd428p25: case .sd428p30, .sd428p25, .sd426p30, .sd426p25:
return 2_000_000 // 2 Mbit/s return 2_000_000 // 2 Mbit/s
case .sd360p30, .sd360p25: case .sd360p30, .sd360p25:
@@ -262,7 +266,7 @@ class Stream: Equatable, Hashable, Identifiable {
case .sd320p30, .sd320p25: case .sd320p30, .sd320p25:
return 1_200_000 // 1.2 Mbit/s return 1_200_000 // 1.2 Mbit/s
case .sd240p30, .sd240p25: case .sd256p30, .sd256p25, .sd240p30, .sd240p25:
return 1_000_000 // 1 Mbit/s return 1_000_000 // 1 Mbit/s
case .sd214p30, .sd214p25: case .sd214p30, .sd214p25:

View File

@@ -93,12 +93,9 @@ extension Defaults.Keys {
static let enableReturnYouTubeDislike = Key<Bool>("enableReturnYouTubeDislike", default: false) static let enableReturnYouTubeDislike = Key<Bool>("enableReturnYouTubeDislike", default: false)
#if os(iOS) #if os(iOS)
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true) static let isOrientationLocked = Key<Bool>("isOrientationLocked", default: Constants.isIPhone)
static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: Constants.isIPhone) static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: Constants.isIPhone)
static let rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>( static let rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>("rotateToLandscapeOnEnterFullScreen", default: .landscapeRight)
"rotateToLandscapeOnEnterFullScreen",
default: Constants.isIPhone ? .landscapeRight : .disabled
)
#endif #endif
static let closePiPOnNavigation = Key<Bool>("closePiPOnNavigation", default: false) static let closePiPOnNavigation = Key<Bool>("closePiPOnNavigation", default: false)
@@ -134,6 +131,7 @@ extension Defaults.Keys {
static let playerControlsLayout = Key<PlayerControlsLayout>("playerControlsLayout", default: playerControlsLayoutDefault) static let playerControlsLayout = Key<PlayerControlsLayout>("playerControlsLayout", default: playerControlsLayoutDefault)
static let fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault) static let fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault)
static let playerControlsBackgroundOpacity = Key<Double>("playerControlsBackgroundOpacity", default: 0.2)
static let systemControlsCommands = Key<SystemControlsCommands>("systemControlsCommands", default: .restartAndAdvanceToNext) static let systemControlsCommands = Key<SystemControlsCommands>("systemControlsCommands", default: .restartAndAdvanceToNext)
@@ -612,26 +610,19 @@ enum PlayerTapGestureAction: String, CaseIterable, Defaults.Serializable {
} }
enum FullScreenRotationSetting: String, CaseIterable, Defaults.Serializable { enum FullScreenRotationSetting: String, CaseIterable, Defaults.Serializable {
case disabled
case landscapeLeft case landscapeLeft
case landscapeRight case landscapeRight
#if os(iOS) #if os(iOS)
var interaceOrientation: UIInterfaceOrientation { var interfaceOrientation: UIInterfaceOrientation {
switch self { switch self {
case .landscapeLeft: case .landscapeLeft:
return .landscapeLeft return .landscapeLeft
case .landscapeRight: case .landscapeRight:
return .landscapeRight return .landscapeRight
default:
return .portrait
} }
} }
#endif #endif
var isRotating: Bool {
self != .disabled
}
} }
struct WidgetSettings: Defaults.Serializable { struct WidgetSettings: Defaults.Serializable {

View File

@@ -17,12 +17,11 @@ import SwiftUI
#if os(iOS) #if os(iOS)
func playerViewController(_: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator) { func playerViewController(_: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator) {
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
if PlayerModel.shared.currentVideoIsLandscape { if PlayerModel.shared.currentVideoIsLandscape {
let delay = PlayerModel.shared.activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0 let delay = PlayerModel.shared.activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0
// not sure why but first rotation call is ignore so doing rotate to same orientation first // not sure why but first rotation call is ignore so doing rotate to same orientation first
Delay.by(delay) { Delay.by(delay) {
let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interaceOrientation let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interfaceOrientation
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation) Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation) Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation)
} }
@@ -37,8 +36,6 @@ import SwiftUI
} }
if !context.isCancelled { if !context.isCancelled {
#if os(iOS) #if os(iOS)
self.player.lockedOrientation = nil
if Constants.isIPhone { if Constants.isIPhone {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait) Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
} }

View File

@@ -29,6 +29,7 @@ struct PlayerControls: View {
@Default(.playerControlsLayout) private var regularPlayerControlsLayout @Default(.playerControlsLayout) private var regularPlayerControlsLayout
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
@Default(.playerControlsBackgroundOpacity) private var playerControlsBackgroundOpacity
@Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration @Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration
@Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration @Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration
@@ -270,6 +271,9 @@ struct PlayerControls: View {
} }
} else if player.videoForDisplay == nil { } else if player.videoForDisplay == nil {
Color.black Color.black
} else if model.presentingControls {
Color.black.opacity(playerControlsBackgroundOpacity)
.edgesIgnoringSafeArea(.all)
} }
} }
} }
@@ -389,7 +393,7 @@ struct PlayerControls: View {
#if os(iOS) #if os(iOS)
private var lockOrientationButton: some View { private var lockOrientationButton: some View {
button("Lock Rotation", systemImage: player.lockOrientationImage, active: !player.lockedOrientation.isNil, action: player.lockOrientationAction) button("Lock Rotation", systemImage: player.lockOrientationImage, active: player.isOrientationLocked, action: player.lockOrientationAction)
} }
#endif #endif

View File

@@ -64,11 +64,7 @@ extension VideoPlayerView {
// Toggle fullscreen on upward drag only when not disabled // Toggle fullscreen on upward drag only when not disabled
if verticalDrag < -50 { if verticalDrag < -50 {
if player.playingFullScreen { player.toggleFullScreenAction()
player.exitFullScreen(showControls: false)
} else {
player.enterFullScreen()
}
disableGestureTemporarily() disableGestureTemporarily()
return return
} }

View File

@@ -158,7 +158,7 @@ struct VideoActions: View {
actionButton("PiP", systemImage: player.pipImage, active: player.playingInPictureInPicture, action: player.togglePiPAction) actionButton("PiP", systemImage: player.pipImage, active: player.playingInPictureInPicture, action: player.togglePiPAction)
#if os(iOS) #if os(iOS)
case .lockOrientation: case .lockOrientation:
actionButton("Lock", systemImage: player.lockOrientationImage, active: player.lockedOrientation != nil, action: player.lockOrientationAction) actionButton("Lock", systemImage: player.lockOrientationImage, active: player.isOrientationLocked, action: player.lockOrientationAction)
#endif #endif
case .restart: case .restart:
actionButton("Replay", systemImage: "backward.end.fill", action: player.replayAction) actionButton("Replay", systemImage: "backward.end.fill", action: player.replayAction)

View File

@@ -111,9 +111,6 @@ struct VideoPlayerView: View {
.onChange(of: geometry.size) { _ in .onChange(of: geometry.size) { _ in
self.playerSize = geometry.size self.playerSize = geometry.size
} }
.onChange(of: fullScreenDetails) { value in
player.backend.setNeedsDrawing(!value)
}
#if os(iOS) #if os(iOS)
.onChange(of: player.presentingPlayer) { newValue in .onChange(of: player.presentingPlayer) { newValue in
if newValue { if newValue {
@@ -127,19 +124,6 @@ struct VideoPlayerView: View {
} }
#endif #endif
viewDragOffset = 0 viewDragOffset = 0
Delay.by(0.2) {
orientationModel.configureOrientationUpdatesBasedOnAccelerometer()
if let orientationMask = player.lockedOrientation {
Orientation.lockOrientation(
orientationMask,
andRotateTo: orientationMask == .landscapeLeft ? .landscapeLeft : orientationMask == .landscapeRight ? .landscapeRight : .portrait
)
} else {
Orientation.lockOrientation(.allButUpsideDown)
}
}
} }
.onAnimationCompleted(for: viewDragOffset) { .onAnimationCompleted(for: viewDragOffset) {
guard !dragGestureState else { return } guard !dragGestureState else { return }
@@ -313,11 +297,14 @@ struct VideoPlayerView: View {
playerSize: player.playerSize, playerSize: player.playerSize,
fullScreen: fullScreenDetails fullScreen: fullScreenDetails
)) ))
#if os(macOS)
// TODO: Check whether this is needed on macOS.
.onDisappear { .onDisappear {
if player.presentingPlayer { if player.presentingPlayer {
player.setNeedsDrawing(true) player.setNeedsDrawing(true)
} }
} }
#endif
.id(player.currentVideo?.cacheKey) .id(player.currentVideo?.cacheKey)
.transition(.opacity) .transition(.opacity)
} else { } else {

View File

@@ -10,6 +10,7 @@ struct BrowsingSettings: View {
@Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges @Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges
@Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop @Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop
#if os(iOS) #if os(iOS)
@Default(.enterFullscreenInLandscape) private var enterFullscreenInLandscape
@Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing @Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing
@Default(.showDocuments) private var showDocuments @Default(.showDocuments) private var showDocuments
#endif #endif
@@ -161,14 +162,18 @@ struct BrowsingSettings: View {
#if os(iOS) #if os(iOS)
Toggle("Show Documents", isOn: $showDocuments) Toggle("Show Documents", isOn: $showDocuments)
Toggle("Lock portrait mode", isOn: $lockPortraitWhenBrowsing) if Constants.isIPad {
.onChange(of: lockPortraitWhenBrowsing) { lock in Toggle("Lock portrait mode", isOn: $lockPortraitWhenBrowsing)
if lock { .onChange(of: lockPortraitWhenBrowsing) { lock in
Orientation.lockOrientation(.portrait, andRotateTo: .portrait) if lock {
} else { enterFullscreenInLandscape = true
Orientation.lockOrientation(.allButUpsideDown) Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
} else {
enterFullscreenInLandscape = false
Orientation.lockOrientation(.allButUpsideDown)
}
} }
} }
#endif #endif
if !accounts.isEmpty { if !accounts.isEmpty {

View File

@@ -38,6 +38,7 @@ struct PlayerControlsSettings: View {
@Default(.playerControlsAdvanceToNextEnabled) private var playerControlsAdvanceToNextEnabled @Default(.playerControlsAdvanceToNextEnabled) private var playerControlsAdvanceToNextEnabled
@Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled @Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled
@Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled @Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled
@Default(.playerControlsBackgroundOpacity) private var playerControlsBackgroundOpacity
private var player = PlayerModel.shared private var player = PlayerModel.shared
@@ -76,6 +77,8 @@ struct PlayerControlsSettings: View {
playerControlsLayoutPicker playerControlsLayoutPicker
SettingsHeader(text: "Fullscreen size".localized(), secondary: true) SettingsHeader(text: "Fullscreen size".localized(), secondary: true)
fullScreenPlayerControlsLayoutPicker fullScreenPlayerControlsLayoutPicker
SettingsHeader(text: "Background opacity".localized(), secondary: true)
playerControlsBackgroundOpacityPicker
} }
#endif #endif
@@ -202,6 +205,15 @@ struct PlayerControlsSettings: View {
.modifier(SettingsPickerModifier()) .modifier(SettingsPickerModifier())
} }
private var playerControlsBackgroundOpacityPicker: some View {
Picker("Background opacity", selection: $playerControlsBackgroundOpacity) {
ForEach(Array(stride(from: 0.0, through: 1.0, by: 0.1)), id: \.self) { value in
Text("\(Int(value * 100))%").tag(value)
}
}
.modifier(SettingsPickerModifier())
}
@ViewBuilder private var seekingSection: some View { @ViewBuilder private var seekingSection: some View {
seekingDurationSetting("System controls", $systemControlsSeekDuration) seekingDurationSetting("System controls", $systemControlsSeekDuration)
.foregroundColor(systemControlsCommands == .restartAndAdvanceToNext ? .secondary : .primary) .foregroundColor(systemControlsCommands == .restartAndAdvanceToNext ? .secondary : .primary)

View File

@@ -18,8 +18,8 @@ struct PlayerSettings: View {
@Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer @Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer
@Default(.closeVideoOnEOF) private var closeVideoOnEOF @Default(.closeVideoOnEOF) private var closeVideoOnEOF
#if os(iOS) #if os(iOS)
@Default(.honorSystemOrientationLock) private var honorSystemOrientationLock
@Default(.enterFullscreenInLandscape) private var enterFullscreenInLandscape @Default(.enterFullscreenInLandscape) private var enterFullscreenInLandscape
@Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing
@Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen @Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
#endif #endif
@Default(.closePiPOnNavigation) private var closePiPOnNavigation @Default(.closePiPOnNavigation) private var closePiPOnNavigation
@@ -87,7 +87,7 @@ struct PlayerSettings: View {
} }
pauseOnHidingPlayerToggle pauseOnHidingPlayerToggle
closeVideoOnEOFToggle closeVideoOnEOFToggle
#if !os(tvOS) #if os(macOS)
exitFullscreenOnEOFToggle exitFullscreenOnEOFToggle
#endif #endif
#if !os(macOS) #if !os(macOS)
@@ -202,11 +202,12 @@ struct PlayerSettings: View {
#endif #endif
#if os(iOS) #if os(iOS)
Section(header: SettingsHeader(text: "Orientation".localized())) { Section(header: SettingsHeader(text: "Fullscreen".localized())) {
if idiom == .pad { if Constants.isIPad {
enterFullscreenInLandscapeToggle enterFullscreenInLandscapeToggle
} }
honorSystemOrientationLockToggle
exitFullscreenOnEOFToggle
rotateToLandscapeOnEnterFullScreenPicker rotateToLandscapeOnEnterFullScreenPicker
} }
#endif #endif
@@ -318,20 +319,15 @@ struct PlayerSettings: View {
#endif #endif
#if os(iOS) #if os(iOS)
private var honorSystemOrientationLockToggle: some View {
Toggle("Honor orientation lock", isOn: $honorSystemOrientationLock)
.disabled(!enterFullscreenInLandscape)
}
private var enterFullscreenInLandscapeToggle: some View { private var enterFullscreenInLandscapeToggle: some View {
Toggle("Enter fullscreen in landscape", isOn: $enterFullscreenInLandscape) Toggle("Enter fullscreen in landscape orientation", isOn: $enterFullscreenInLandscape)
.disabled(lockPortraitWhenBrowsing)
} }
private var rotateToLandscapeOnEnterFullScreenPicker: some View { private var rotateToLandscapeOnEnterFullScreenPicker: some View {
Picker("Rotate when entering fullscreen on landscape video", selection: $rotateToLandscapeOnEnterFullScreen) { Picker("Default orientation", selection: $rotateToLandscapeOnEnterFullScreen) {
Text("Landscape left").tag(FullScreenRotationSetting.landscapeLeft) Text("Landscape left").tag(FullScreenRotationSetting.landscapeLeft)
Text("Landscape right").tag(FullScreenRotationSetting.landscapeRight) Text("Landscape right").tag(FullScreenRotationSetting.landscapeRight)
Text("No rotation").tag(FullScreenRotationSetting.disabled)
} }
.modifier(SettingsPickerModifier()) .modifier(SettingsPickerModifier())
} }

View File

@@ -204,9 +204,14 @@ struct YatteeApp: App {
} }
#if os(iOS) #if os(iOS)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if Defaults[.lockPortraitWhenBrowsing] { if Defaults[.lockPortraitWhenBrowsing] {
Orientation.lockOrientation(.all, andRotateTo: .portrait) Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
} else {
let rotationOrientation =
OrientationTracker.shared.currentDeviceOrientation.rawValue == 4 ? UIInterfaceOrientation.landscapeRight :
(OrientationTracker.shared.currentDeviceOrientation.rawValue == 3 ? UIInterfaceOrientation.landscapeLeft : UIInterfaceOrientation.portrait)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: rotationOrientation)
} }
} }
#endif #endif
@@ -225,6 +230,17 @@ struct YatteeApp: App {
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
self.migrateQualityProfiles() self.migrateQualityProfiles()
} }
#if os(iOS)
DispatchQueue.global(qos: .userInitiated).async {
self.migrateRotateToLandscapeOnEnterFullScreen()
}
DispatchQueue.global(qos: .userInitiated).async {
self.migrateLockPortraitWhenBrowsing()
}
#endif
} }
} }
@@ -253,6 +269,22 @@ struct YatteeApp: App {
} }
} }
#if os(iOS)
func migrateRotateToLandscapeOnEnterFullScreen() {
if Defaults[.rotateToLandscapeOnEnterFullScreen] != .landscapeRight || Defaults[.rotateToLandscapeOnEnterFullScreen] != .landscapeLeft {
Defaults[.rotateToLandscapeOnEnterFullScreen] = .landscapeRight
}
}
func migrateLockPortraitWhenBrowsing() {
if Constants.isIPhone {
Defaults[.lockPortraitWhenBrowsing] = true
} else if Constants.isIPad, Defaults[.lockPortraitWhenBrowsing] {
Defaults[.enterFullscreenInLandscape] = true
}
}
#endif
var navigationStyle: NavigationStyle { var navigationStyle: NavigationStyle {
#if os(iOS) #if os(iOS)
return horizontalSizeClass == .compact ? .tab : .sidebar return horizontalSizeClass == .compact ? .tab : .sidebar

View File

@@ -4103,7 +4103,7 @@
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements"; CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist"; INFOPLIST_FILE = "Open in Yattee/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee"; INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
@@ -4134,7 +4134,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist"; INFOPLIST_FILE = "Open in Yattee/Info.plist";
@@ -4165,7 +4165,7 @@
buildSettings = { buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
@@ -4185,7 +4185,7 @@
buildSettings = { buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
@@ -4349,7 +4349,7 @@
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1", "DEBUG=1",
@@ -4366,7 +4366,9 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiresFullScreen = YES; INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UIStatusBarHidden = NO; INFOPLIST_KEY_UIStatusBarHidden = NO;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UIStatusBarStyle = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -4401,7 +4403,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1"; GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
@@ -4415,7 +4417,9 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiresFullScreen = YES; INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UIStatusBarHidden = NO; INFOPLIST_KEY_UIStatusBarHidden = NO;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UIStatusBarStyle = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -4453,7 +4457,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -4492,13 +4496,14 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ; "DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly; ENABLE_USER_SELECTED_FILES = readonly;
GCC_OPTIMIZATION_LEVEL = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = macOS/Info.plist; INFOPLIST_FILE = macOS/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video";
@@ -4526,7 +4531,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -4549,7 +4554,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -4574,7 +4579,7 @@
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -4598,7 +4603,7 @@
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -4624,7 +4629,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -4664,7 +4669,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ; "DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -4704,7 +4709,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -4727,7 +4732,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 194; CURRENT_PROJECT_VERSION = 195;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",

View File

@@ -1,16 +1,17 @@
import AVFoundation import AVFoundation
import Defaults
import Foundation import Foundation
import Logging import Logging
import UIKit import UIKit
final class AppDelegate: UIResponder, UIApplicationDelegate { 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! private(set) static var instance: AppDelegate!
func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask { func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask {
orientationLock return orientationLock
} }
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // swiftlint:disable:this discouraged_optional_collection 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) #if !os(macOS)
UIViewController.swizzleHomeIndicatorProperty() UIViewController.swizzleHomeIndicatorProperty()
OrientationTracker.shared.startDeviceOrientationTracking() OrientationTracker.shared.startDeviceOrientationTracking()
OrientationModel.shared.startOrientationUpdates()
// Configure the audio session for playback // Configure the audio session for playback
do { do {

View File

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