mirror of
https://github.com/yattee/yattee.git
synced 2025-12-13 03:28:14 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0264aaabe | ||
|
|
035f3503c4 | ||
|
|
e3ac11c172 | ||
|
|
7aed6ac0d9 | ||
|
|
457c0ce7b3 | ||
|
|
747baf3edd | ||
|
|
cd24a0322f | ||
|
|
d525a22215 | ||
|
|
322a550666 | ||
|
|
98fa0b98e5 | ||
|
|
5313e4ead0 | ||
|
|
fa7b897e76 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,11 +1,9 @@
|
||||
## Build 194
|
||||
* Gestures: swipe up toggles fullscreen by @stonerl in https://github.com/yattee/yattee/pull/778
|
||||
* don’t 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
|
||||
## Build 195
|
||||
* iOS: Simplified fullscreen and orientation by @stonerl in https://github.com/yattee/yattee/pull/786
|
||||
* macOS: only apply player shortcuts when window is active by @stonerl in https://github.com/yattee/yattee/pull/802
|
||||
* player controls: add background opacity selection by @stonerl in https://github.com/yattee/yattee/pull/799
|
||||
* add missing Shorts resolutions by @stonerl in https://github.com/yattee/yattee/pull/797
|
||||
* use -O1 on macOS by @stonerl in https://github.com/yattee/yattee/pull/801
|
||||
|
||||
## Previous builds
|
||||
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
|
||||
@@ -24,6 +22,13 @@
|
||||
* Add import export of missing settings
|
||||
* macOS: Fix settings windows layout
|
||||
* Fix seek OSD layout on tvOS, revert OSD position
|
||||
* Gestures: swipe up toggles fullscreen by @stonerl in https://github.com/yattee/yattee/pull/778
|
||||
* don’t 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
|
||||
* 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
|
||||
|
||||
@@ -10,6 +10,7 @@ final class ConstrolsSettingsGroupExporter: SettingsGroupExporter {
|
||||
"seekGestureSpeed": Defaults[.seekGestureSpeed],
|
||||
"playerControlsLayout": Defaults[.playerControlsLayout].rawValue,
|
||||
"fullScreenPlayerControlsLayout": Defaults[.fullScreenPlayerControlsLayout].rawValue,
|
||||
"playerControlsBackgroundOpacity": Defaults[.playerControlsBackgroundOpacity],
|
||||
"systemControlsCommands": Defaults[.systemControlsCommands].rawValue,
|
||||
"buttonBackwardSeekDuration": Defaults[.buttonBackwardSeekDuration],
|
||||
"buttonForwardSeekDuration": Defaults[.buttonForwardSeekDuration],
|
||||
|
||||
@@ -44,7 +44,7 @@ final class PlayerSettingsGroupExporter: SettingsGroupExporter {
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
export["honorSystemOrientationLock"].bool = Defaults[.honorSystemOrientationLock]
|
||||
export["isOrientationLocked"].bool = Defaults[.isOrientationLocked]
|
||||
export["enterFullscreenInLandscape"].bool = Defaults[.enterFullscreenInLandscape]
|
||||
export["rotateToLandscapeOnEnterFullScreen"].string = Defaults[.rotateToLandscapeOnEnterFullScreen].rawValue
|
||||
#endif
|
||||
|
||||
@@ -33,6 +33,10 @@ struct ConstrolsSettingsGroupImporter {
|
||||
Defaults[.fullScreenPlayerControlsLayout] = fullScreenPlayerControlsLayout
|
||||
}
|
||||
|
||||
if let playerControlsBackgroundOpacity = json["playerControlsBackgroundOpacity"].double {
|
||||
Defaults[.playerControlsBackgroundOpacity] = playerControlsBackgroundOpacity
|
||||
}
|
||||
|
||||
if let systemControlsCommandsString = json["systemControlsCommands"].string,
|
||||
let systemControlsCommands = SystemControlsCommands(rawValue: systemControlsCommandsString)
|
||||
{
|
||||
|
||||
@@ -97,8 +97,8 @@ struct PlayerSettingsGroupImporter {
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
if let honorSystemOrientationLock = json["honorSystemOrientationLock"].bool {
|
||||
Defaults[.honorSystemOrientationLock] = honorSystemOrientationLock
|
||||
if let isOrientationLocked = json["isOrientationLocked"].bool {
|
||||
Defaults[.isOrientationLocked] = isOrientationLocked
|
||||
}
|
||||
|
||||
if let enterFullscreenInLandscape = json["enterFullscreenInLandscape"].bool {
|
||||
|
||||
@@ -14,6 +14,8 @@ final class MPVClient: ObservableObject {
|
||||
}
|
||||
|
||||
private var logger = Logger(label: "mpv-client")
|
||||
private var needsDrawingCooldown = false
|
||||
private var needsDrawingWorkItem: DispatchWorkItem?
|
||||
|
||||
var mpv: OpaquePointer!
|
||||
var mpvGL: OpaquePointer!
|
||||
@@ -389,10 +391,30 @@ final class MPVClient: ObservableObject {
|
||||
}
|
||||
|
||||
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)")
|
||||
|
||||
// Set the cooldown flag to true and cancel any existing work item
|
||||
needsDrawingCooldown = true
|
||||
needsDrawingWorkItem?.cancel()
|
||||
|
||||
#if !os(macOS)
|
||||
glView?.needsDrawing = needsDrawing
|
||||
#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(
|
||||
|
||||
@@ -56,7 +56,6 @@ final class PlayerModel: ObservableObject {
|
||||
@Published var presentingPlayer = false { didSet { handlePresentationChange() } }
|
||||
@Published var activeBackend = PlayerBackendType.mpv
|
||||
@Published var forceBackendOnPlay: PlayerBackendType?
|
||||
@Published var wasFullscreen = false
|
||||
|
||||
var avPlayerBackend = AVPlayerBackend()
|
||||
var mpvBackend = MPVBackend()
|
||||
@@ -131,6 +130,12 @@ final class PlayerModel: ObservableObject {
|
||||
|
||||
#if os(iOS)
|
||||
@Published var lockedOrientation: UIInterfaceOrientationMask?
|
||||
@Published var isOrientationLocked: Bool {
|
||||
didSet {
|
||||
Defaults[.isOrientationLocked] = isOrientationLocked
|
||||
}
|
||||
}
|
||||
|
||||
@Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
|
||||
#endif
|
||||
|
||||
@@ -201,6 +206,16 @@ final class PlayerModel: ObservableObject {
|
||||
#endif
|
||||
|
||||
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)
|
||||
mpvBackend.controller = mpvController
|
||||
mpvBackend.client = mpvController.client
|
||||
@@ -517,7 +532,10 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
|
||||
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 presentingPlayer, activeBackend == .appleAVPlayer, avPlayerUsesSystemControls, Constants.isIPhone {
|
||||
@@ -551,8 +569,6 @@ final class PlayerModel: ObservableObject {
|
||||
} else {
|
||||
Orientation.lockOrientation(.allButUpsideDown)
|
||||
}
|
||||
|
||||
OrientationModel.shared.stopOrientationUpdates()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -659,32 +675,37 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
|
||||
func closeCurrentItem(finished: Bool = false) {
|
||||
pause()
|
||||
videoBeingOpened = nil
|
||||
advancing = false
|
||||
forceBackendOnPlay = nil
|
||||
|
||||
guard !closing else { return }
|
||||
closing = true
|
||||
controls.presentingControls = false
|
||||
|
||||
self.prepareCurrentItemForHistory(finished: finished)
|
||||
if playingFullScreen { exitFullScreen() }
|
||||
|
||||
self.hide()
|
||||
|
||||
Delay.by(0.8) { [weak self] in
|
||||
Delay.by(0.3) { [weak self] in
|
||||
guard let self else { return }
|
||||
self.closePiP()
|
||||
pause()
|
||||
videoBeingOpened = nil
|
||||
advancing = false
|
||||
forceBackendOnPlay = nil
|
||||
|
||||
withAnimation {
|
||||
self.currentItem = nil
|
||||
controls.presentingControls = false
|
||||
|
||||
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() {
|
||||
toggleFullscreen(playingFullScreen, showControls: false)
|
||||
toggleFullscreen(playingFullScreen, showControls: false, initiatedByButton: true)
|
||||
}
|
||||
|
||||
func togglePiPAction() {
|
||||
@@ -786,20 +807,21 @@ final class PlayerModel: ObservableObject {
|
||||
|
||||
#if os(iOS)
|
||||
var lockOrientationImage: String {
|
||||
lockedOrientation.isNil ? "lock.rotation.open" : "lock.rotation"
|
||||
isOrientationLocked ? "lock.rotation" : "lock.rotation.open"
|
||||
}
|
||||
|
||||
func lockOrientationAction() {
|
||||
if lockedOrientation.isNil {
|
||||
// This makes toggling orientation lock more robust
|
||||
if lockedOrientation.isNil || !isOrientationLocked {
|
||||
isOrientationLocked = true
|
||||
let orientationMask = OrientationTracker.shared.currentInterfaceOrientationMask
|
||||
lockedOrientation = orientationMask
|
||||
let orientation = OrientationTracker.shared.currentInterfaceOrientation
|
||||
Orientation.lockOrientation(orientationMask, andRotateTo: .landscapeLeft)
|
||||
// iOS 16 workaround
|
||||
Orientation.lockOrientation(orientationMask, andRotateTo: orientation)
|
||||
Orientation.lockOrientation(orientationMask, andRotateTo: playingFullScreen ? nil : orientation)
|
||||
} else {
|
||||
isOrientationLocked = false
|
||||
lockedOrientation = nil
|
||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
|
||||
Orientation.lockOrientation(.allButUpsideDown)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -985,7 +1007,14 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
#else
|
||||
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 {
|
||||
mpvBackend.addVideoTrackFromStream()
|
||||
@@ -995,17 +1024,6 @@ final class PlayerModel: ObservableObject {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@@ -1018,6 +1036,10 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
|
||||
func handleEnterBackground() {
|
||||
#if os(iOS)
|
||||
OrientationTracker.shared.stopDeviceOrientationTracking()
|
||||
#endif
|
||||
|
||||
if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode {
|
||||
pause()
|
||||
} else if !playingInPictureInPicture, activeBackend == .appleAVPlayer {
|
||||
@@ -1025,15 +1047,6 @@ final class PlayerModel: ObservableObject {
|
||||
} else if activeBackend == .mpv, !musicMode {
|
||||
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
|
||||
|
||||
@@ -1124,7 +1137,7 @@ final class PlayerModel: ObservableObject {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func toggleFullscreen(_ isFullScreen: Bool, showControls: Bool = true) {
|
||||
func toggleFullscreen(_ isFullScreen: Bool, showControls: Bool = true, initiatedByButton: Bool = false) {
|
||||
controls.presentingControls = showControls && isFullScreen
|
||||
|
||||
#if os(macOS)
|
||||
@@ -1139,15 +1152,13 @@ final class PlayerModel: ObservableObject {
|
||||
avPlayerBackend.controller.enterFullScreen(animated: true)
|
||||
return
|
||||
}
|
||||
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
|
||||
let lockOrientation = rotateToLandscapeOnEnterFullScreen.interfaceOrientation
|
||||
if currentVideoIsLandscape {
|
||||
let delay = activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0
|
||||
// not sure why but first rotation call is ignore so doing rotate to same orientation first
|
||||
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)
|
||||
if initiatedByButton {
|
||||
Orientation.lockOrientation(self.isOrientationLocked ? (lockOrientation == .landscapeRight ? .landscapeRight : .landscapeLeft) : .landscape)
|
||||
}
|
||||
let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interfaceOrientation
|
||||
Orientation.lockOrientation(self.isOrientationLocked ? (lockOrientation == .landscapeRight ? .landscapeRight : .landscapeLeft) : .landscape, andRotateTo: orientation)
|
||||
}
|
||||
} else {
|
||||
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
|
||||
@@ -1155,10 +1166,12 @@ final class PlayerModel: ObservableObject {
|
||||
avPlayerBackend.controller.dismiss(animated: true)
|
||||
return
|
||||
}
|
||||
let rotationOrientation = Constants.isIPhone ? UIInterfaceOrientation.portrait : nil
|
||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: rotationOrientation)
|
||||
if Defaults[.lockPortraitWhenBrowsing] {
|
||||
lockedOrientation = UIInterfaceOrientationMask.portrait
|
||||
}
|
||||
let rotationOrientation = Defaults[.lockPortraitWhenBrowsing] ? UIInterfaceOrientation.portrait : nil
|
||||
Orientation.lockOrientation(Defaults[.lockPortraitWhenBrowsing] ? .portrait : .allButUpsideDown, andRotateTo: rotationOrientation)
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1286,7 +1299,10 @@ final class PlayerModel: ObservableObject {
|
||||
|
||||
#if os(macOS)
|
||||
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 {
|
||||
case 124:
|
||||
if !self.liveStreamInAVPlayer {
|
||||
|
||||
@@ -139,10 +139,14 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
|
||||
case sd428p30
|
||||
case sd428p25
|
||||
case sd426p30
|
||||
case sd426p25
|
||||
case sd360p30
|
||||
case sd360p25
|
||||
case sd320p30
|
||||
case sd320p25
|
||||
case sd256p30
|
||||
case sd256p25
|
||||
case sd240p30
|
||||
case sd240p25
|
||||
case sd214p30
|
||||
@@ -253,7 +257,7 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
case .sd480p30, .sd480p25:
|
||||
return 2_500_000 // 2.5 Mbit/s
|
||||
|
||||
case .sd428p30, .sd428p25:
|
||||
case .sd428p30, .sd428p25, .sd426p30, .sd426p25:
|
||||
return 2_000_000 // 2 Mbit/s
|
||||
|
||||
case .sd360p30, .sd360p25:
|
||||
@@ -262,7 +266,7 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
case .sd320p30, .sd320p25:
|
||||
return 1_200_000 // 1.2 Mbit/s
|
||||
|
||||
case .sd240p30, .sd240p25:
|
||||
case .sd256p30, .sd256p25, .sd240p30, .sd240p25:
|
||||
return 1_000_000 // 1 Mbit/s
|
||||
|
||||
case .sd214p30, .sd214p25:
|
||||
|
||||
@@ -93,12 +93,9 @@ extension Defaults.Keys {
|
||||
static let enableReturnYouTubeDislike = Key<Bool>("enableReturnYouTubeDislike", default: false)
|
||||
|
||||
#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 rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>(
|
||||
"rotateToLandscapeOnEnterFullScreen",
|
||||
default: Constants.isIPhone ? .landscapeRight : .disabled
|
||||
)
|
||||
static let rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>("rotateToLandscapeOnEnterFullScreen", default: .landscapeRight)
|
||||
#endif
|
||||
|
||||
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 fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault)
|
||||
static let playerControlsBackgroundOpacity = Key<Double>("playerControlsBackgroundOpacity", default: 0.2)
|
||||
|
||||
static let systemControlsCommands = Key<SystemControlsCommands>("systemControlsCommands", default: .restartAndAdvanceToNext)
|
||||
|
||||
@@ -612,26 +610,19 @@ enum PlayerTapGestureAction: String, CaseIterable, Defaults.Serializable {
|
||||
}
|
||||
|
||||
enum FullScreenRotationSetting: String, CaseIterable, Defaults.Serializable {
|
||||
case disabled
|
||||
case landscapeLeft
|
||||
case landscapeRight
|
||||
|
||||
#if os(iOS)
|
||||
var interaceOrientation: UIInterfaceOrientation {
|
||||
var interfaceOrientation: UIInterfaceOrientation {
|
||||
switch self {
|
||||
case .landscapeLeft:
|
||||
return .landscapeLeft
|
||||
case .landscapeRight:
|
||||
return .landscapeRight
|
||||
default:
|
||||
return .portrait
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var isRotating: Bool {
|
||||
self != .disabled
|
||||
}
|
||||
}
|
||||
|
||||
struct WidgetSettings: Defaults.Serializable {
|
||||
|
||||
@@ -17,12 +17,11 @@ import SwiftUI
|
||||
|
||||
#if os(iOS)
|
||||
func playerViewController(_: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator) {
|
||||
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
|
||||
if PlayerModel.shared.currentVideoIsLandscape {
|
||||
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
|
||||
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: orientation)
|
||||
}
|
||||
@@ -37,8 +36,6 @@ import SwiftUI
|
||||
}
|
||||
if !context.isCancelled {
|
||||
#if os(iOS)
|
||||
self.player.lockedOrientation = nil
|
||||
|
||||
if Constants.isIPhone {
|
||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ struct PlayerControls: View {
|
||||
|
||||
@Default(.playerControlsLayout) private var regularPlayerControlsLayout
|
||||
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
|
||||
@Default(.playerControlsBackgroundOpacity) private var playerControlsBackgroundOpacity
|
||||
@Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration
|
||||
@Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration
|
||||
|
||||
@@ -270,6 +271,9 @@ struct PlayerControls: View {
|
||||
}
|
||||
} else if player.videoForDisplay == nil {
|
||||
Color.black
|
||||
} else if model.presentingControls {
|
||||
Color.black.opacity(playerControlsBackgroundOpacity)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,7 +393,7 @@ struct PlayerControls: View {
|
||||
|
||||
#if os(iOS)
|
||||
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
|
||||
|
||||
|
||||
@@ -64,11 +64,7 @@ extension VideoPlayerView {
|
||||
|
||||
// Toggle fullscreen on upward drag only when not disabled
|
||||
if verticalDrag < -50 {
|
||||
if player.playingFullScreen {
|
||||
player.exitFullScreen(showControls: false)
|
||||
} else {
|
||||
player.enterFullScreen()
|
||||
}
|
||||
player.toggleFullScreenAction()
|
||||
disableGestureTemporarily()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ struct VideoActions: View {
|
||||
actionButton("PiP", systemImage: player.pipImage, active: player.playingInPictureInPicture, action: player.togglePiPAction)
|
||||
#if os(iOS)
|
||||
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
|
||||
case .restart:
|
||||
actionButton("Replay", systemImage: "backward.end.fill", action: player.replayAction)
|
||||
|
||||
@@ -111,9 +111,6 @@ struct VideoPlayerView: View {
|
||||
.onChange(of: geometry.size) { _ in
|
||||
self.playerSize = geometry.size
|
||||
}
|
||||
.onChange(of: fullScreenDetails) { value in
|
||||
player.backend.setNeedsDrawing(!value)
|
||||
}
|
||||
#if os(iOS)
|
||||
.onChange(of: player.presentingPlayer) { newValue in
|
||||
if newValue {
|
||||
@@ -127,19 +124,6 @@ struct VideoPlayerView: View {
|
||||
}
|
||||
#endif
|
||||
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) {
|
||||
guard !dragGestureState else { return }
|
||||
@@ -313,11 +297,14 @@ struct VideoPlayerView: View {
|
||||
playerSize: player.playerSize,
|
||||
fullScreen: fullScreenDetails
|
||||
))
|
||||
#if os(macOS)
|
||||
// TODO: Check whether this is needed on macOS.
|
||||
.onDisappear {
|
||||
if player.presentingPlayer {
|
||||
player.setNeedsDrawing(true)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
.id(player.currentVideo?.cacheKey)
|
||||
.transition(.opacity)
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ struct BrowsingSettings: View {
|
||||
@Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges
|
||||
@Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop
|
||||
#if os(iOS)
|
||||
@Default(.enterFullscreenInLandscape) private var enterFullscreenInLandscape
|
||||
@Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing
|
||||
@Default(.showDocuments) private var showDocuments
|
||||
#endif
|
||||
@@ -161,14 +162,18 @@ struct BrowsingSettings: View {
|
||||
#if os(iOS)
|
||||
Toggle("Show Documents", isOn: $showDocuments)
|
||||
|
||||
Toggle("Lock portrait mode", isOn: $lockPortraitWhenBrowsing)
|
||||
.onChange(of: lockPortraitWhenBrowsing) { lock in
|
||||
if lock {
|
||||
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
|
||||
} else {
|
||||
Orientation.lockOrientation(.allButUpsideDown)
|
||||
if Constants.isIPad {
|
||||
Toggle("Lock portrait mode", isOn: $lockPortraitWhenBrowsing)
|
||||
.onChange(of: lockPortraitWhenBrowsing) { lock in
|
||||
if lock {
|
||||
enterFullscreenInLandscape = true
|
||||
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
|
||||
} else {
|
||||
enterFullscreenInLandscape = false
|
||||
Orientation.lockOrientation(.allButUpsideDown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if !accounts.isEmpty {
|
||||
|
||||
@@ -38,6 +38,7 @@ struct PlayerControlsSettings: View {
|
||||
@Default(.playerControlsAdvanceToNextEnabled) private var playerControlsAdvanceToNextEnabled
|
||||
@Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled
|
||||
@Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled
|
||||
@Default(.playerControlsBackgroundOpacity) private var playerControlsBackgroundOpacity
|
||||
|
||||
private var player = PlayerModel.shared
|
||||
|
||||
@@ -76,6 +77,8 @@ struct PlayerControlsSettings: View {
|
||||
playerControlsLayoutPicker
|
||||
SettingsHeader(text: "Fullscreen size".localized(), secondary: true)
|
||||
fullScreenPlayerControlsLayoutPicker
|
||||
SettingsHeader(text: "Background opacity".localized(), secondary: true)
|
||||
playerControlsBackgroundOpacityPicker
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -202,6 +205,15 @@ struct PlayerControlsSettings: View {
|
||||
.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 {
|
||||
seekingDurationSetting("System controls", $systemControlsSeekDuration)
|
||||
.foregroundColor(systemControlsCommands == .restartAndAdvanceToNext ? .secondary : .primary)
|
||||
|
||||
@@ -18,8 +18,8 @@ struct PlayerSettings: View {
|
||||
@Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer
|
||||
@Default(.closeVideoOnEOF) private var closeVideoOnEOF
|
||||
#if os(iOS)
|
||||
@Default(.honorSystemOrientationLock) private var honorSystemOrientationLock
|
||||
@Default(.enterFullscreenInLandscape) private var enterFullscreenInLandscape
|
||||
@Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing
|
||||
@Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
|
||||
#endif
|
||||
@Default(.closePiPOnNavigation) private var closePiPOnNavigation
|
||||
@@ -87,7 +87,7 @@ struct PlayerSettings: View {
|
||||
}
|
||||
pauseOnHidingPlayerToggle
|
||||
closeVideoOnEOFToggle
|
||||
#if !os(tvOS)
|
||||
#if os(macOS)
|
||||
exitFullscreenOnEOFToggle
|
||||
#endif
|
||||
#if !os(macOS)
|
||||
@@ -202,11 +202,12 @@ struct PlayerSettings: View {
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
Section(header: SettingsHeader(text: "Orientation".localized())) {
|
||||
if idiom == .pad {
|
||||
Section(header: SettingsHeader(text: "Fullscreen".localized())) {
|
||||
if Constants.isIPad {
|
||||
enterFullscreenInLandscapeToggle
|
||||
}
|
||||
honorSystemOrientationLockToggle
|
||||
|
||||
exitFullscreenOnEOFToggle
|
||||
rotateToLandscapeOnEnterFullScreenPicker
|
||||
}
|
||||
#endif
|
||||
@@ -318,20 +319,15 @@ struct PlayerSettings: View {
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
private var honorSystemOrientationLockToggle: some View {
|
||||
Toggle("Honor orientation lock", isOn: $honorSystemOrientationLock)
|
||||
.disabled(!enterFullscreenInLandscape)
|
||||
}
|
||||
|
||||
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 {
|
||||
Picker("Rotate when entering fullscreen on landscape video", selection: $rotateToLandscapeOnEnterFullScreen) {
|
||||
Picker("Default orientation", selection: $rotateToLandscapeOnEnterFullScreen) {
|
||||
Text("Landscape left").tag(FullScreenRotationSetting.landscapeLeft)
|
||||
Text("Landscape right").tag(FullScreenRotationSetting.landscapeRight)
|
||||
Text("No rotation").tag(FullScreenRotationSetting.disabled)
|
||||
}
|
||||
.modifier(SettingsPickerModifier())
|
||||
}
|
||||
|
||||
@@ -204,9 +204,14 @@ struct YatteeApp: App {
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
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
|
||||
@@ -225,6 +230,17 @@ struct YatteeApp: App {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
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 {
|
||||
#if os(iOS)
|
||||
return horizontalSizeClass == .compact ? .tab : .sidebar
|
||||
|
||||
@@ -4103,7 +4103,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
|
||||
@@ -4134,7 +4134,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||
@@ -4165,7 +4165,7 @@
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
@@ -4185,7 +4185,7 @@
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
@@ -4349,7 +4349,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -4366,7 +4366,9 @@
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
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;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4401,7 +4403,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
|
||||
@@ -4415,7 +4417,9 @@
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
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;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4453,7 +4457,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@@ -4492,13 +4496,14 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
GCC_OPTIMIZATION_LEVEL = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = macOS/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video";
|
||||
@@ -4526,7 +4531,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4549,7 +4554,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4574,7 +4579,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4598,7 +4603,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4624,7 +4629,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -4664,7 +4669,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4704,7 +4709,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4727,7 +4732,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
CURRENT_PROJECT_VERSION = 195;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import AVFoundation
|
||||
import Defaults
|
||||
import Foundation
|
||||
import Logging
|
||||
import UIKit
|
||||
|
||||
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var orientationLock = UIInterfaceOrientationMask.all
|
||||
var orientationLock = UIInterfaceOrientationMask.allButUpsideDown
|
||||
|
||||
private var logger = Logger(label: "stream.yattee.app.delegalate")
|
||||
private var logger = Logger(label: "stream.yattee.app.delegate")
|
||||
private(set) static var instance: AppDelegate!
|
||||
|
||||
func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask {
|
||||
orientationLock
|
||||
return orientationLock
|
||||
}
|
||||
|
||||
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // swiftlint:disable:this discouraged_optional_collection
|
||||
@@ -19,6 +20,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
#if !os(macOS)
|
||||
UIViewController.swizzleHomeIndicatorProperty()
|
||||
OrientationTracker.shared.startDeviceOrientationTracking()
|
||||
OrientationModel.shared.startOrientationUpdates()
|
||||
|
||||
// Configure the audio session for playback
|
||||
do {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
import Logging
|
||||
import Repeat
|
||||
import SwiftUI
|
||||
|
||||
final class OrientationModel {
|
||||
static var shared = OrientationModel()
|
||||
let logger = Logger(label: "stream.yattee.orientation.model")
|
||||
|
||||
var orientation = UIInterfaceOrientation.portrait
|
||||
var lastOrientation: UIInterfaceOrientation?
|
||||
@@ -13,79 +15,69 @@ final class OrientationModel {
|
||||
|
||||
private var player = PlayerModel.shared
|
||||
|
||||
func configureOrientationUpdatesBasedOnAccelerometer() {
|
||||
let currentOrientation = OrientationTracker.shared.currentInterfaceOrientation
|
||||
if currentOrientation.isLandscape,
|
||||
Defaults[.enterFullscreenInLandscape],
|
||||
!Defaults[.honorSystemOrientationLock],
|
||||
!player.playingFullScreen,
|
||||
!player.currentItem.isNil,
|
||||
player.lockedOrientation.isNil || player.lockedOrientation!.contains(.landscape),
|
||||
!player.playingInPictureInPicture,
|
||||
player.presentingPlayer
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
self.player.controls.presentingControls = false
|
||||
self.player.enterFullScreen(showControls: false)
|
||||
}
|
||||
|
||||
player.onPresentPlayer.append {
|
||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: currentOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
func startOrientationUpdates() {
|
||||
// Ensure the orientation observer is active
|
||||
orientationObserver = NotificationCenter.default.addObserver(
|
||||
forName: OrientationTracker.deviceOrientationChangedNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { _ in
|
||||
guard !Defaults[.honorSystemOrientationLock],
|
||||
self.player.presentingPlayer,
|
||||
!self.player.playingInPictureInPicture,
|
||||
self.player.lockedOrientation.isNil
|
||||
self.logger.info("Notification received: Device orientation changed.")
|
||||
|
||||
// We only allow .portrait and are not showing the player
|
||||
guard (!self.player.presentingPlayer && !Defaults[.lockPortraitWhenBrowsing]) || self.player.presentingPlayer
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let orientation = OrientationTracker.shared.currentInterfaceOrientation
|
||||
self.logger.info("Current interface orientation: \(orientation)")
|
||||
|
||||
guard self.lastOrientation != orientation else {
|
||||
// Always update lastOrientation to keep track of the latest state
|
||||
if self.lastOrientation != orientation {
|
||||
self.lastOrientation = orientation
|
||||
self.logger.info("Orientation changed to: \(orientation)")
|
||||
} else {
|
||||
self.logger.info("Orientation has not changed.")
|
||||
}
|
||||
|
||||
// Only take action if the player is active and presenting
|
||||
guard (!self.player.isOrientationLocked && !self.player.playingInPictureInPicture) || (!Defaults[.lockPortraitWhenBrowsing] && !self.player.presentingPlayer) || (!Defaults[.lockPortraitWhenBrowsing] && self.player.presentingPlayer && !self.player.isOrientationLocked)
|
||||
else {
|
||||
self.logger.info("Only updating orientation without actions.")
|
||||
return
|
||||
}
|
||||
|
||||
self.lastOrientation = orientation
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard Defaults[.enterFullscreenInLandscape],
|
||||
self.player.presentingPlayer
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
self.orientationDebouncer.callback = {
|
||||
DispatchQueue.main.async {
|
||||
if orientation.isLandscape {
|
||||
self.player.controls.presentingControls = false
|
||||
self.player.enterFullScreen(showControls: false)
|
||||
if Defaults[.enterFullscreenInLandscape], self.player.presentingPlayer {
|
||||
self.logger.info("Entering fullscreen because orientation is landscape.")
|
||||
self.player.controls.presentingControls = false
|
||||
self.player.enterFullScreen(showControls: false)
|
||||
}
|
||||
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
|
||||
} else {
|
||||
self.player.exitFullScreen(showControls: false)
|
||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
|
||||
self.logger.info("Exiting fullscreen because orientation is portrait.")
|
||||
if self.player.playingFullScreen {
|
||||
self.player.exitFullScreen(showControls: false)
|
||||
}
|
||||
if Defaults[.lockPortraitWhenBrowsing] {
|
||||
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
|
||||
} else {
|
||||
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.orientationDebouncer.call()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stopOrientationUpdates() {
|
||||
guard let observer = orientationObserver else { return }
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
|
||||
func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation? = nil) {
|
||||
logger.info("Locking orientation to: \(orientation), rotating to: \(String(describing: rotateOrientation)).")
|
||||
if let rotateOrientation {
|
||||
self.orientation = rotateOrientation
|
||||
lastOrientation = rotateOrientation
|
||||
|
||||
Reference in New Issue
Block a user