mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
AVPlayer system controls on iOS
This commit is contained in:
parent
a4fdd50388
commit
5383cf0e90
21
Backports/ToolbarBackground+Backport.swift
Normal file
21
Backports/ToolbarBackground+Backport.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension Backport where Content: View {
|
||||||
|
@ViewBuilder func toolbarBackground(_ color: Color) -> some View {
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
content
|
||||||
|
.toolbarBackground(color, for: .navigationBar)
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder func toolbarBackgroundVisibility(_ visible: Bool) -> some View {
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
content
|
||||||
|
.toolbarBackground(visible ? .visible : .hidden, for: .navigationBar)
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
Backports/ToolbarColorScheme+Backport.swift
Normal file
12
Backports/ToolbarColorScheme+Backport.swift
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension Backport where Content: View {
|
||||||
|
@ViewBuilder func toolbarColorScheme(_ colorScheme: ColorScheme) -> some View {
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
content
|
||||||
|
.toolbarColorScheme(colorScheme, for: .navigationBar)
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Extensions/AVPlayerViewController+FullScreen.swift
Normal file
11
Extensions/AVPlayerViewController+FullScreen.swift
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import AVKit
|
||||||
|
|
||||||
|
extension AVPlayerViewController {
|
||||||
|
func enterFullScreen(animated: Bool) {
|
||||||
|
perform(NSSelectorFromString("enterFullScreenAnimated:completionHandler:"), with: animated, with: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exitFullScreen(animated: Bool) {
|
||||||
|
perform(NSSelectorFromString("exitFullScreenAnimated:completionHandler:"), with: animated, with: nil)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import AVFoundation
|
import AVKit
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
import Logging
|
import Logging
|
||||||
@ -6,6 +6,7 @@ import MediaPlayer
|
|||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
#endif
|
#endif
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
final class AVPlayerBackend: PlayerBackend {
|
final class AVPlayerBackend: PlayerBackend {
|
||||||
static let assetKeysToLoad = ["tracks", "playable", "duration"]
|
static let assetKeysToLoad = ["tracks", "playable", "duration"]
|
||||||
@ -84,6 +85,10 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
private(set) var playerLayer = AVPlayerLayer()
|
private(set) var playerLayer = AVPlayerLayer()
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
var controller: AppleAVPlayerViewController?
|
var controller: AppleAVPlayerViewController?
|
||||||
|
#elseif os(iOS)
|
||||||
|
var controller = AVPlayerViewController() { didSet {
|
||||||
|
controller.player = avPlayer
|
||||||
|
}}
|
||||||
#endif
|
#endif
|
||||||
var startPictureInPictureOnPlay = false
|
var startPictureInPictureOnPlay = false
|
||||||
var startPictureInPictureOnSwitch = false
|
var startPictureInPictureOnSwitch = false
|
||||||
@ -108,6 +113,9 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
addPlayerTimeControlStatusObserver()
|
addPlayerTimeControlStatusObserver()
|
||||||
|
|
||||||
playerLayer.player = avPlayer
|
playerLayer.player = avPlayer
|
||||||
|
#if os(iOS)
|
||||||
|
controller.player = avPlayer
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting) -> Stream? {
|
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting) -> Stream? {
|
||||||
@ -469,10 +477,6 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
|
|
||||||
switch playerItem.status {
|
switch playerItem.status {
|
||||||
case .readyToPlay:
|
case .readyToPlay:
|
||||||
if self.model.playingInPictureInPicture {
|
|
||||||
self.startPictureInPictureOnSwitch = false
|
|
||||||
self.startPictureInPictureOnPlay = false
|
|
||||||
}
|
|
||||||
if self.model.activeBackend == .appleAVPlayer,
|
if self.model.activeBackend == .appleAVPlayer,
|
||||||
self.isAutoplaying(playerItem)
|
self.isAutoplaying(playerItem)
|
||||||
{
|
{
|
||||||
@ -487,17 +491,21 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
self.model.play()
|
self.model.play()
|
||||||
}
|
}
|
||||||
} else if self.startPictureInPictureOnPlay {
|
} else if self.startPictureInPictureOnPlay {
|
||||||
self.startPictureInPictureOnPlay = false
|
|
||||||
self.model.stream = self.stream
|
self.model.stream = self.stream
|
||||||
self.model.streamSelection = self.stream
|
self.model.streamSelection = self.stream
|
||||||
|
|
||||||
if self.model.activeBackend != .appleAVPlayer {
|
if self.model.activeBackend != .appleAVPlayer {
|
||||||
self.startPictureInPictureOnSwitch = true
|
self.startPictureInPictureOnSwitch = true
|
||||||
let seconds = self.model.mpvBackend.currentTime?.seconds ?? 0
|
let seconds = self.model.mpvBackend.currentTime?.seconds ?? 0
|
||||||
self.seek(to: seconds, seekType: .backendSync) { _ in
|
self.seek(to: seconds, seekType: .backendSync) { finished in
|
||||||
|
guard finished else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.model.pause()
|
self.model.pause()
|
||||||
self.model.changeActiveBackend(from: .mpv, to: .appleAVPlayer, changingStream: false)
|
self.model.changeActiveBackend(from: .mpv, to: .appleAVPlayer, changingStream: false)
|
||||||
|
|
||||||
|
Delay.by(3) {
|
||||||
|
self.startPictureInPictureOnPlay = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -688,7 +696,6 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
|
|
||||||
func didChangeTo() {
|
func didChangeTo() {
|
||||||
if startPictureInPictureOnSwitch {
|
if startPictureInPictureOnSwitch {
|
||||||
startPictureInPictureOnSwitch = false
|
|
||||||
tryStartingPictureInPicture()
|
tryStartingPictureInPicture()
|
||||||
} else if model.musicMode {
|
} else if model.musicMode {
|
||||||
startMusicMode()
|
startMusicMode()
|
||||||
@ -697,6 +704,10 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isStartingPiP: Bool {
|
||||||
|
startPictureInPictureOnPlay || startPictureInPictureOnSwitch
|
||||||
|
}
|
||||||
|
|
||||||
func tryStartingPictureInPicture() {
|
func tryStartingPictureInPicture() {
|
||||||
guard let controller = model.pipController else { return }
|
guard let controller = model.pipController else { return }
|
||||||
|
|
||||||
@ -712,6 +723,32 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Delay.by(5) {
|
||||||
|
self.startPictureInPictureOnSwitch = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPlayerInLayer(_ playerIsPresented: Bool) {
|
||||||
|
if playerIsPresented {
|
||||||
|
bindPlayerToLayer()
|
||||||
|
} else {
|
||||||
|
removePlayerFromLayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePlayerFromLayer() {
|
||||||
|
playerLayer.player = nil
|
||||||
|
#if os(iOS)
|
||||||
|
controller.player = nil
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindPlayerToLayer() {
|
||||||
|
playerLayer.player = avPlayer
|
||||||
|
#if os(iOS)
|
||||||
|
controller.player = avPlayer
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTimeUpdates() {}
|
func getTimeUpdates() {}
|
||||||
|
@ -4,7 +4,7 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
|
final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
|
||||||
var player: PlayerModel!
|
var player: PlayerModel { .shared }
|
||||||
|
|
||||||
func pictureInPictureController(
|
func pictureInPictureController(
|
||||||
_: AVPictureInPictureController,
|
_: AVPictureInPictureController,
|
||||||
@ -16,19 +16,17 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
|
|||||||
func pictureInPictureControllerWillStartPictureInPicture(_: AVPictureInPictureController) {}
|
func pictureInPictureControllerWillStartPictureInPicture(_: AVPictureInPictureController) {}
|
||||||
|
|
||||||
func pictureInPictureControllerDidStartPictureInPicture(_: AVPictureInPictureController) {
|
func pictureInPictureControllerDidStartPictureInPicture(_: AVPictureInPictureController) {
|
||||||
guard let player else { return }
|
player.play()
|
||||||
|
|
||||||
player.playingInPictureInPicture = true
|
player.playingInPictureInPicture = true
|
||||||
player.avPlayerBackend.startPictureInPictureOnPlay = false
|
player.avPlayerBackend.startPictureInPictureOnPlay = false
|
||||||
player.avPlayerBackend.startPictureInPictureOnSwitch = false
|
player.avPlayerBackend.startPictureInPictureOnSwitch = false
|
||||||
player.controls.objectWillChange.send()
|
player.controls.objectWillChange.send()
|
||||||
|
|
||||||
if Defaults[.closePlayerOnOpeningPiP] { Delay.by(0.1) { player.hide() } }
|
if Defaults[.closePlayerOnOpeningPiP] { Delay.by(0.1) { self.player.hide() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
func pictureInPictureControllerDidStopPictureInPicture(_: AVPictureInPictureController) {
|
func pictureInPictureControllerDidStopPictureInPicture(_: AVPictureInPictureController) {
|
||||||
guard let player else { return }
|
|
||||||
|
|
||||||
player.playingInPictureInPicture = false
|
player.playingInPictureInPicture = false
|
||||||
player.controls.objectWillChange.send()
|
player.controls.objectWillChange.send()
|
||||||
}
|
}
|
||||||
@ -39,6 +37,8 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
|
|||||||
_: AVPictureInPictureController,
|
_: AVPictureInPictureController,
|
||||||
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
|
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
|
||||||
) {
|
) {
|
||||||
|
let wasPlaying = player.isPlaying
|
||||||
|
|
||||||
var delay = 0.0
|
var delay = 0.0
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if !player.presentingPlayer {
|
if !player.presentingPlayer {
|
||||||
@ -50,7 +50,7 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if !player.currentItem.isNil, !player.musicMode {
|
if !player.currentItem.isNil, !player.musicMode {
|
||||||
player?.show()
|
player.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||||
@ -58,6 +58,11 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
|
|||||||
self?.player.playingInPictureInPicture = false
|
self?.player.playingInPictureInPicture = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if wasPlaying {
|
||||||
|
Delay.by(1) {
|
||||||
|
self?.player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
completionHandler(true)
|
completionHandler(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,6 @@ final class PlayerModel: ObservableObject {
|
|||||||
|
|
||||||
let logger = Logger(label: "stream.yattee.app")
|
let logger = Logger(label: "stream.yattee.app")
|
||||||
|
|
||||||
var avPlayerView = AppleAVPlayerView()
|
|
||||||
var playerItem: AVPlayerItem?
|
var playerItem: AVPlayerItem?
|
||||||
|
|
||||||
var mpvPlayerView = MPVPlayerView()
|
var mpvPlayerView = MPVPlayerView()
|
||||||
@ -153,6 +152,9 @@ final class PlayerModel: ObservableObject {
|
|||||||
@Published var playingInPictureInPicture = false
|
@Published var playingInPictureInPicture = false
|
||||||
var pipController: AVPictureInPictureController?
|
var pipController: AVPictureInPictureController?
|
||||||
var pipDelegate = PiPDelegate()
|
var pipDelegate = PiPDelegate()
|
||||||
|
#if !os(macOS)
|
||||||
|
var appleAVPlayerViewControllerDelegate = AppleAVPlayerViewControllerDelegate()
|
||||||
|
#endif
|
||||||
|
|
||||||
var playerError: Error? { didSet {
|
var playerError: Error? { didSet {
|
||||||
if let error = playerError {
|
if let error = playerError {
|
||||||
@ -164,6 +166,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
@Default(.saveLastPlayed) var saveLastPlayed
|
@Default(.saveLastPlayed) var saveLastPlayed
|
||||||
@Default(.lastPlayed) var lastPlayed
|
@Default(.lastPlayed) var lastPlayed
|
||||||
@Default(.qualityProfiles) var qualityProfiles
|
@Default(.qualityProfiles) var qualityProfiles
|
||||||
|
@Default(.avPlayerUsesSystemControls) var avPlayerUsesSystemControls
|
||||||
@Default(.forceAVPlayerForLiveStreams) var forceAVPlayerForLiveStreams
|
@Default(.forceAVPlayerForLiveStreams) var forceAVPlayerForLiveStreams
|
||||||
@Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer
|
@Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer
|
||||||
@Default(.closePiPOnNavigation) var closePiPOnNavigation
|
@Default(.closePiPOnNavigation) var closePiPOnNavigation
|
||||||
@ -187,16 +190,17 @@ final class PlayerModel: ObservableObject {
|
|||||||
mpvBackend.client = mpvController.client
|
mpvBackend.client = mpvController.client
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Defaults[.activeBackend] = .mpv
|
|
||||||
playbackMode = Defaults[.playbackMode]
|
playbackMode = Defaults[.playbackMode]
|
||||||
|
|
||||||
guard pipController.isNil else { return }
|
guard pipController.isNil else { return }
|
||||||
pipController = .init(playerLayer: avPlayerBackend.playerLayer)
|
|
||||||
let pipDelegate = PiPDelegate()
|
|
||||||
pipDelegate.player = self
|
|
||||||
|
|
||||||
self.pipDelegate = pipDelegate
|
pipController = .init(playerLayer: avPlayerBackend.playerLayer)
|
||||||
pipController?.delegate = pipDelegate
|
pipController?.delegate = pipDelegate
|
||||||
|
#if os(iOS)
|
||||||
|
if #available(iOS 14.2, *) {
|
||||||
|
pipController?.canStartPictureInPictureAutomaticallyFromInline = true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
currentRate = playerRate
|
currentRate = playerRate
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,6 +479,12 @@ final class PlayerModel: ObservableObject {
|
|||||||
private func handlePresentationChange() {
|
private func handlePresentationChange() {
|
||||||
backend.setNeedsDrawing(presentingPlayer)
|
backend.setNeedsDrawing(presentingPlayer)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
if presentingPlayer, activeBackend == .appleAVPlayer, avPlayerUsesSystemControls, Constants.isIPhone {
|
||||||
|
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
controls.hide()
|
controls.hide()
|
||||||
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
@ -542,10 +552,15 @@ final class PlayerModel: ObservableObject {
|
|||||||
self.stream = stream
|
self.stream = stream
|
||||||
streamSelection = stream
|
streamSelection = stream
|
||||||
|
|
||||||
|
self.upgradeToStream(stream, force: true)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !backend.canPlay(stream) || (to == .mpv && !stream.hlsURL.isNil) {
|
if !backend.canPlay(stream) ||
|
||||||
|
(to == .mpv && stream.isHLS) ||
|
||||||
|
(to == .appleAVPlayer && !stream.isHLS)
|
||||||
|
{
|
||||||
guard let preferredStream = streamByQualityProfile else {
|
guard let preferredStream = streamByQualityProfile else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -631,8 +646,8 @@ final class PlayerModel: ObservableObject {
|
|||||||
if avPlayerBackend.video == video {
|
if avPlayerBackend.video == video {
|
||||||
if activeBackend != .appleAVPlayer {
|
if activeBackend != .appleAVPlayer {
|
||||||
avPlayerBackend.startPictureInPictureOnSwitch = true
|
avPlayerBackend.startPictureInPictureOnSwitch = true
|
||||||
changeActiveBackend(from: activeBackend, to: .appleAVPlayer)
|
|
||||||
}
|
}
|
||||||
|
changeActiveBackend(from: activeBackend, to: .appleAVPlayer)
|
||||||
} else {
|
} else {
|
||||||
avPlayerBackend.startPictureInPictureOnPlay = true
|
avPlayerBackend.startPictureInPictureOnPlay = true
|
||||||
playStream(stream, of: video, preservingTime: true, upgrading: true, withBackend: avPlayerBackend)
|
playStream(stream, of: video, preservingTime: true, upgrading: true, withBackend: avPlayerBackend)
|
||||||
@ -882,7 +897,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
#else
|
#else
|
||||||
func handleEnterForeground() {
|
func handleEnterForeground() {
|
||||||
setNeedsDrawing(presentingPlayer)
|
setNeedsDrawing(presentingPlayer)
|
||||||
avPlayerBackend.playerLayer.player = avPlayerBackend.avPlayer
|
avPlayerBackend.bindPlayerToLayer()
|
||||||
|
|
||||||
guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else {
|
guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else {
|
||||||
return
|
return
|
||||||
@ -896,7 +911,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode {
|
if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode {
|
||||||
pause()
|
pause()
|
||||||
} else if !playingInPictureInPicture {
|
} else if !playingInPictureInPicture {
|
||||||
avPlayerBackend.playerLayer.player = nil
|
avPlayerBackend.removePlayerFromLayer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -919,6 +934,13 @@ final class PlayerModel: ObservableObject {
|
|||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
guard activeBackend == .mpv else { return }
|
guard activeBackend == .mpv else { return }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
guard let video = currentItem?.video else {
|
guard let video = currentItem?.video else {
|
||||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = .none
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = .none
|
||||||
return
|
return
|
||||||
@ -986,13 +1008,23 @@ final class PlayerModel: ObservableObject {
|
|||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if playingFullScreen {
|
if playingFullScreen {
|
||||||
|
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
|
||||||
|
avPlayerBackend.controller.enterFullScreen(animated: true)
|
||||||
|
}
|
||||||
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
|
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
|
||||||
if currentVideoIsLandscape {
|
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
|
// not sure why but first rotation call is ignore so doing rotate to same orientation first
|
||||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
|
Delay.by(delay) {
|
||||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: rotateToLandscapeOnEnterFullScreen.interaceOrientation)
|
let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interaceOrientation
|
||||||
|
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
|
||||||
|
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
|
||||||
|
avPlayerBackend.controller.exitFullScreen(animated: true)
|
||||||
|
}
|
||||||
let rotationOrientation = rotateToPortraitOnExitFullScreen ? UIInterfaceOrientation.portrait : nil
|
let rotationOrientation = rotateToPortraitOnExitFullScreen ? UIInterfaceOrientation.portrait : nil
|
||||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: rotationOrientation)
|
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: rotationOrientation)
|
||||||
}
|
}
|
||||||
|
@ -176,6 +176,10 @@ class Stream: Equatable, Hashable, Identifiable {
|
|||||||
localURL != nil
|
localURL != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isHLS: Bool {
|
||||||
|
hlsURL != nil
|
||||||
|
}
|
||||||
|
|
||||||
var quality: String {
|
var quality: String {
|
||||||
guard localURL.isNil else { return "Opened File" }
|
guard localURL.isNil else { return "Opened File" }
|
||||||
return kind == .hls ? "adaptive (HLS)" : "\(resolution.name)\(kind == .stream ? " (\(kind.rawValue))" : "")"
|
return kind == .hls ? "adaptive (HLS)" : "\(resolution.name)\(kind == .stream ? " (\(kind.rawValue))" : "")"
|
||||||
@ -229,8 +233,14 @@ class Stream: Equatable, Hashable, Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(videoAsset?.url)
|
if let url = videoAsset?.url {
|
||||||
hasher.combine(audioAsset?.url)
|
hasher.combine(url)
|
||||||
hasher.combine(hlsURL)
|
}
|
||||||
|
if let url = audioAsset?.url {
|
||||||
|
hasher.combine(url)
|
||||||
|
}
|
||||||
|
if let url = hlsURL {
|
||||||
|
hasher.combine(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,6 +132,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 avPlayerUsesSystemControls = Key<Bool>("avPlayerUsesSystemControls", default: true)
|
||||||
static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true)
|
static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true)
|
||||||
static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5)
|
static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5)
|
||||||
static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 30.0)
|
static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 30.0)
|
||||||
|
@ -15,6 +15,8 @@ struct ContentView: View {
|
|||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -133,6 +135,7 @@ struct ContentView: View {
|
|||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
.alert(isPresented: $navigation.presentingAlert) { navigation.alert }
|
.alert(isPresented: $navigation.presentingAlert) { navigation.alert }
|
||||||
|
.statusBarHidden(player.playingFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationStyle: NavigationStyle {
|
var navigationStyle: NavigationStyle {
|
||||||
@ -150,9 +153,11 @@ struct ContentView: View {
|
|||||||
playerView
|
playerView
|
||||||
.transition(.asymmetric(insertion: .identity, removal: .opacity))
|
.transition(.asymmetric(insertion: .identity, removal: .opacity))
|
||||||
.zIndex(3)
|
.zIndex(3)
|
||||||
} else if player.activeBackend == .appleAVPlayer {
|
} else if player.activeBackend == .appleAVPlayer,
|
||||||
|
avPlayerUsesSystemControls || player.avPlayerBackend.isStartingPiP
|
||||||
|
{
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
playerView.offset(y: UIScreen.main.bounds.height)
|
AppleAVPlayerLayerView().offset(y: UIScreen.main.bounds.height)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,119 @@ import AVKit
|
|||||||
import Defaults
|
import Defaults
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
#if !os(macOS)
|
||||||
|
final class AppleAVPlayerViewControllerDelegate: NSObject, AVPlayerViewControllerDelegate {
|
||||||
|
var player: PlayerModel { .shared }
|
||||||
|
|
||||||
|
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_: AVPlayerViewController) -> Bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewController(_: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator) {
|
||||||
|
Delay.by(0.5) { [weak self] in
|
||||||
|
self?.player.playingFullScreen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewController(_: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
let wasPlaying = player.isPlaying
|
||||||
|
coordinator.animate(alongsideTransition: nil) { context in
|
||||||
|
#if os(iOS)
|
||||||
|
if wasPlaying {
|
||||||
|
self.player.play()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if !context.isCancelled {
|
||||||
|
#if os(iOS)
|
||||||
|
self.player.lockedOrientation = nil
|
||||||
|
|
||||||
|
if Defaults[.rotateToPortraitOnExitFullScreen] {
|
||||||
|
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wasPlaying {
|
||||||
|
self.player.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.player.playingFullScreen = false
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {}
|
||||||
|
|
||||||
|
func playerViewControllerWillStopPictureInPicture(_: AVPlayerViewController) {}
|
||||||
|
|
||||||
|
func playerViewControllerDidStartPictureInPicture(_: AVPlayerViewController) {
|
||||||
|
player.playingInPictureInPicture = true
|
||||||
|
player.avPlayerBackend.startPictureInPictureOnPlay = false
|
||||||
|
player.avPlayerBackend.startPictureInPictureOnSwitch = false
|
||||||
|
player.controls.objectWillChange.send()
|
||||||
|
|
||||||
|
if Defaults[.closePlayerOnOpeningPiP] { Delay.by(0.1) { self.player.hide() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewControllerDidStopPictureInPicture(_: AVPlayerViewController) {
|
||||||
|
player.playingInPictureInPicture = false
|
||||||
|
player.controls.objectWillChange.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewController(_: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
||||||
|
player.presentingPlayer = true
|
||||||
|
withAnimation(.linear(duration: 0.3)) {
|
||||||
|
self.player.playingInPictureInPicture = false
|
||||||
|
Delay.by(0.5) {
|
||||||
|
completionHandler(true)
|
||||||
|
Delay.by(0.2) {
|
||||||
|
self.player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewController(_: AVPlayerViewController, restoreUserInterfaceForFullScreenExitWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
||||||
|
withAnimation(nil) {
|
||||||
|
player.presentingPlayer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
struct AppleAVPlayerView: UIViewRepresentable {
|
struct AppleAVPlayerView: UIViewControllerRepresentable {
|
||||||
|
@State private var controller = AVPlayerViewController()
|
||||||
|
|
||||||
|
func makeUIViewController(context _: Context) -> AVPlayerViewController {
|
||||||
|
setupController()
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_: AVPlayerViewController, context _: Context) {
|
||||||
|
setupController()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupController() {
|
||||||
|
controller.delegate = PlayerModel.shared.appleAVPlayerViewControllerDelegate
|
||||||
|
controller.allowsPictureInPicturePlayback = true
|
||||||
|
if #available(iOS 14.2, *) {
|
||||||
|
controller.canStartPictureInPictureAutomaticallyFromInline = true
|
||||||
|
}
|
||||||
|
PlayerModel.shared.avPlayerBackend.controller = controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppleAVPlayerLayerView: UIViewRepresentable {
|
||||||
func makeUIView(context _: Context) -> some UIView {
|
func makeUIView(context _: Context) -> some UIView {
|
||||||
PlayerLayerView()
|
PlayerLayerView(frame: .zero)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIView(_: UIViewType, context _: Context) {}
|
func updateUIView(_: UIViewType, context _: Context) {}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
|
#elseif os(tvOS)
|
||||||
struct AppleAVPlayerView: UIViewControllerRepresentable {
|
struct AppleAVPlayerView: UIViewControllerRepresentable {
|
||||||
func makeUIViewController(context _: Context) -> AppleAVPlayerViewController {
|
func makeUIViewController(context _: Context) -> AppleAVPlayerViewController {
|
||||||
let controller = AppleAVPlayerViewController()
|
let controller = AppleAVPlayerViewController()
|
||||||
@ -23,4 +127,27 @@ import SwiftUI
|
|||||||
PlayerModel.shared.rebuildTVMenu()
|
PlayerModel.shared.rebuildTVMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
struct AppleAVPlayerView: NSViewRepresentable {
|
||||||
|
@State private var pictureInPictureDelegate = MacOSPiPDelegate()
|
||||||
|
|
||||||
|
func makeNSView(context _: Context) -> some NSView {
|
||||||
|
let view = AVPlayerView()
|
||||||
|
view.player = PlayerModel.shared.avPlayerBackend.avPlayer
|
||||||
|
view.showsFullScreenToggleButton = true
|
||||||
|
view.allowsPictureInPicturePlayback = true
|
||||||
|
view.pictureInPictureDelegate = pictureInPictureDelegate
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_: NSViewType, context _: Context) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppleAVPlayerLayerView: NSViewRepresentable {
|
||||||
|
func makeNSView(context _: Context) -> some NSView {
|
||||||
|
PlayerLayerView(frame: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_: NSViewType, context _: Context) {}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import Defaults
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PlayerBackendView: View {
|
struct PlayerBackendView: View {
|
||||||
@ -7,6 +8,8 @@ struct PlayerBackendView: View {
|
|||||||
@ObservedObject private var player = PlayerModel.shared
|
@ObservedObject private var player = PlayerModel.shared
|
||||||
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
|
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
|
||||||
|
|
||||||
|
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
Group {
|
Group {
|
||||||
@ -16,7 +19,20 @@ struct PlayerBackendView: View {
|
|||||||
case .mpv:
|
case .mpv:
|
||||||
player.mpvPlayerView
|
player.mpvPlayerView
|
||||||
case .appleAVPlayer:
|
case .appleAVPlayer:
|
||||||
player.avPlayerView
|
#if os(tvOS)
|
||||||
|
AppleAVPlayerView()
|
||||||
|
#else
|
||||||
|
if avPlayerUsesSystemControls,
|
||||||
|
!player.playingInPictureInPicture,
|
||||||
|
!player.avPlayerBackend.isStartingPiP
|
||||||
|
{
|
||||||
|
AppleAVPlayerView()
|
||||||
|
} else if !avPlayerUsesSystemControls ||
|
||||||
|
player.avPlayerBackend.isStartingPiP
|
||||||
|
{
|
||||||
|
AppleAVPlayerLayerView()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.zIndex(0)
|
.zIndex(0)
|
||||||
@ -31,17 +47,16 @@ struct PlayerBackendView: View {
|
|||||||
.onChange(of: proxy.size) { _ in player.playerSize = proxy.size }
|
.onChange(of: proxy.size) { _ in player.playerSize = proxy.size }
|
||||||
.onChange(of: player.controls.presentingOverlays) { _ in player.playerSize = proxy.size }
|
.onChange(of: player.controls.presentingOverlays) { _ in player.playerSize = proxy.size }
|
||||||
})
|
})
|
||||||
#if os(iOS)
|
|
||||||
.padding(.top, player.playingFullScreen && verticalSizeClass == .regular ? 20 : 0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
PlayerGestures()
|
if player.activeBackend == .mpv || !avPlayerUsesSystemControls {
|
||||||
PlayerControls()
|
PlayerGestures()
|
||||||
#if os(iOS)
|
PlayerControls()
|
||||||
.padding(.top, controlsTopPadding)
|
#if os(iOS)
|
||||||
.padding(.bottom, controlsBottomPadding)
|
.padding(.top, controlsTopPadding)
|
||||||
#endif
|
.padding(.bottom, controlsBottomPadding)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
hiddenControlsButton
|
hiddenControlsButton
|
||||||
#endif
|
#endif
|
||||||
|
@ -3,7 +3,7 @@ import SwiftUI
|
|||||||
|
|
||||||
extension VideoPlayerView {
|
extension VideoPlayerView {
|
||||||
var playerDragGesture: some Gesture {
|
var playerDragGesture: some Gesture {
|
||||||
DragGesture(minimumDistance: 0, coordinateSpace: .global)
|
DragGesture(minimumDistance: 30, coordinateSpace: .global)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.updating($dragGestureOffset) { value, state, _ in
|
.updating($dragGestureOffset) { value, state, _ in
|
||||||
guard isVerticalDrag else { return }
|
guard isVerticalDrag else { return }
|
||||||
@ -36,7 +36,12 @@ extension VideoPlayerView {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if !isVerticalDrag, horizontalPlayerGestureEnabled, abs(horizontalDrag) > seekGestureSensitivity, !isHorizontalDrag {
|
if !isVerticalDrag,
|
||||||
|
horizontalPlayerGestureEnabled,
|
||||||
|
abs(horizontalDrag) > seekGestureSensitivity,
|
||||||
|
!isHorizontalDrag,
|
||||||
|
player.activeBackend == .mpv || !avPlayerUsesSystemControls
|
||||||
|
{
|
||||||
isHorizontalDrag = true
|
isHorizontalDrag = true
|
||||||
player.seek.onSeekGestureStart()
|
player.seek.onSeekGestureStart()
|
||||||
viewDragOffset = 0
|
viewDragOffset = 0
|
||||||
@ -80,6 +85,16 @@ extension VideoPlayerView {
|
|||||||
player.seek.onSeekGestureEnd()
|
player.seek.onSeekGestureEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewDragOffset > 60,
|
||||||
|
player.playingFullScreen
|
||||||
|
{
|
||||||
|
#if os(iOS)
|
||||||
|
player.lockedOrientation = nil
|
||||||
|
#endif
|
||||||
|
player.exitFullScreen(showControls: false)
|
||||||
|
viewDragOffset = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
isVerticalDrag = false
|
isVerticalDrag = false
|
||||||
|
|
||||||
guard player.presentingPlayer,
|
guard player.presentingPlayer,
|
||||||
|
@ -4,8 +4,8 @@ import SwiftUI
|
|||||||
struct VideoPlayerSizeModifier: ViewModifier {
|
struct VideoPlayerSizeModifier: ViewModifier {
|
||||||
let geometry: GeometryProxy
|
let geometry: GeometryProxy
|
||||||
let aspectRatio: Double?
|
let aspectRatio: Double?
|
||||||
let minimumHeightLeft: Double
|
|
||||||
let fullScreen: Bool
|
let fullScreen: Bool
|
||||||
|
var detailsHiddenInFullScreen = true
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
@ -14,26 +14,31 @@ struct VideoPlayerSizeModifier: ViewModifier {
|
|||||||
init(
|
init(
|
||||||
geometry: GeometryProxy,
|
geometry: GeometryProxy,
|
||||||
aspectRatio: Double? = nil,
|
aspectRatio: Double? = nil,
|
||||||
minimumHeightLeft: Double? = nil,
|
fullScreen: Bool = false,
|
||||||
fullScreen: Bool = false
|
detailsHiddenInFullScreen: Bool = false
|
||||||
) {
|
) {
|
||||||
self.geometry = geometry
|
self.geometry = geometry
|
||||||
self.aspectRatio = aspectRatio ?? VideoPlayerView.defaultAspectRatio
|
self.aspectRatio = aspectRatio ?? VideoPlayerView.defaultAspectRatio
|
||||||
self.minimumHeightLeft = minimumHeightLeft ?? VideoPlayerView.defaultMinimumHeightLeft
|
|
||||||
self.fullScreen = fullScreen
|
self.fullScreen = fullScreen
|
||||||
|
self.detailsHiddenInFullScreen = detailsHiddenInFullScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.frame(width: geometry.size.width)
|
.frame(maxWidth: geometry.size.width)
|
||||||
.frame(maxHeight: maxHeight)
|
.frame(maxHeight: maxHeight)
|
||||||
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
.aspectRatio(fullScreen ? nil : usedAspectRatio, contentMode: usedAspectRatioContentMode)
|
.aspectRatio(ratio, contentMode: usedAspectRatioContentMode)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ratio: CGFloat? {
|
||||||
|
fullScreen ? detailsHiddenInFullScreen ? nil : usedAspectRatio : usedAspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
var usedAspectRatio: Double {
|
var usedAspectRatio: Double {
|
||||||
guard let aspectRatio, aspectRatio > 0, aspectRatio < VideoPlayerView.defaultAspectRatio else {
|
guard let aspectRatio, aspectRatio > 0 else {
|
||||||
return VideoPlayerView.defaultAspectRatio
|
return VideoPlayerView.defaultAspectRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,15 +55,13 @@ struct VideoPlayerSizeModifier: ViewModifier {
|
|||||||
|
|
||||||
var maxHeight: Double {
|
var maxHeight: Double {
|
||||||
guard !fullScreen else {
|
guard !fullScreen else {
|
||||||
return .infinity
|
if detailsHiddenInFullScreen {
|
||||||
|
return geometry.size.height
|
||||||
|
} else {
|
||||||
|
return geometry.size.width / usedAspectRatio
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
return max(geometry.size.height - VideoPlayerView.defaultMinimumHeightLeft, 0)
|
||||||
let height = verticalSizeClass == .regular ? geometry.size.height - minimumHeightLeft : .infinity
|
|
||||||
#else
|
|
||||||
let height = geometry.size.height - minimumHeightLeft
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return [height, 0].max()!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ struct VideoPlayerView: View {
|
|||||||
@Default(.playerSidebar) var playerSidebar
|
@Default(.playerSidebar) var playerSidebar
|
||||||
@Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration
|
@Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration
|
||||||
@Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration
|
@Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration
|
||||||
|
@Default(.avPlayerUsesSystemControls) internal var avPlayerUsesSystemControls
|
||||||
|
|
||||||
@ObservedObject internal var controlsOverlayModel = ControlOverlaysModel.shared
|
@ObservedObject internal var controlsOverlayModel = ControlOverlaysModel.shared
|
||||||
|
|
||||||
@ -98,12 +99,12 @@ struct VideoPlayerView: View {
|
|||||||
return GeometryReader { geometry in
|
return GeometryReader { geometry in
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
content
|
content
|
||||||
.ignoresSafeArea(.all, edges: .bottom)
|
|
||||||
.frame(height: playerHeight.isNil ? nil : Double(playerHeight!))
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
playerSize = geometry.size
|
playerSize = geometry.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ignoresSafeArea(.all, edges: .bottom)
|
||||||
|
.frame(height: playerHeight.isNil ? nil : Double(playerHeight!))
|
||||||
.onChange(of: geometry.size) { _ in
|
.onChange(of: geometry.size) { _ in
|
||||||
self.playerSize = geometry.size
|
self.playerSize = geometry.size
|
||||||
}
|
}
|
||||||
@ -279,7 +280,8 @@ struct VideoPlayerView: View {
|
|||||||
VideoPlayerSizeModifier(
|
VideoPlayerSizeModifier(
|
||||||
geometry: geometry,
|
geometry: geometry,
|
||||||
aspectRatio: player.aspectRatio,
|
aspectRatio: player.aspectRatio,
|
||||||
fullScreen: fullScreenPlayer
|
fullScreen: fullScreenPlayer,
|
||||||
|
detailsHiddenInFullScreen: detailsHiddenInFullScreen
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
@ -303,15 +305,12 @@ struct VideoPlayerView: View {
|
|||||||
|
|
||||||
.background(Color.black)
|
.background(Color.black)
|
||||||
|
|
||||||
if !fullScreenPlayer {
|
if !detailsHiddenInFullScreen {
|
||||||
VideoDetails(
|
VideoDetails(
|
||||||
video: player.videoForDisplay,
|
video: player.videoForDisplay,
|
||||||
fullScreen: $fullScreenDetails,
|
fullScreen: $fullScreenDetails,
|
||||||
sidebarQueue: $sidebarQueue
|
sidebarQueue: $sidebarQueue
|
||||||
)
|
)
|
||||||
#if os(iOS)
|
|
||||||
.ignoresSafeArea(.all, edges: .bottom)
|
|
||||||
#endif
|
|
||||||
.modifier(VideoDetailsPaddingModifier(
|
.modifier(VideoDetailsPaddingModifier(
|
||||||
playerSize: player.playerSize,
|
playerSize: player.playerSize,
|
||||||
fullScreen: fullScreenDetails
|
fullScreen: fullScreenDetails
|
||||||
@ -369,7 +368,7 @@ struct VideoPlayerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if !fullScreenPlayer {
|
if !detailsHiddenInFullScreen {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if sidebarQueue {
|
if sidebarQueue {
|
||||||
List {
|
List {
|
||||||
@ -404,6 +403,12 @@ struct VideoPlayerView: View {
|
|||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.statusBar(hidden: fullScreenPlayer)
|
.statusBar(hidden: fullScreenPlayer)
|
||||||
|
.backport
|
||||||
|
.toolbarBackground(colorScheme == .light ? .white : .black)
|
||||||
|
.backport
|
||||||
|
.toolbarBackgroundVisibility(true)
|
||||||
|
.backport
|
||||||
|
.toolbarColorScheme(colorScheme)
|
||||||
#endif
|
#endif
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.background(
|
.background(
|
||||||
@ -414,6 +419,16 @@ struct VideoPlayerView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var detailsHiddenInFullScreen: Bool {
|
||||||
|
guard fullScreenPlayer else { return false }
|
||||||
|
|
||||||
|
if player.activeBackend == .mpv {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !avPlayerUsesSystemControls || verticalSizeClass == .compact
|
||||||
|
}
|
||||||
|
|
||||||
var fullScreenPlayer: Bool {
|
var fullScreenPlayer: Bool {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
player.playingFullScreen || verticalSizeClass == .compact
|
player.playingFullScreen || verticalSizeClass == .compact
|
||||||
|
@ -2,6 +2,8 @@ import Defaults
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PlayerControlsSettings: View {
|
struct PlayerControlsSettings: View {
|
||||||
|
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
|
||||||
|
|
||||||
@Default(.systemControlsCommands) private var systemControlsCommands
|
@Default(.systemControlsCommands) private var systemControlsCommands
|
||||||
@Default(.playerControlsLayout) private var playerControlsLayout
|
@Default(.playerControlsLayout) private var playerControlsLayout
|
||||||
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
|
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
|
||||||
@ -61,6 +63,9 @@ struct PlayerControlsSettings: View {
|
|||||||
@ViewBuilder var sections: some View {
|
@ViewBuilder var sections: some View {
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
Section(header: SettingsHeader(text: "Controls".localized()), footer: controlsLayoutFooter) {
|
Section(header: SettingsHeader(text: "Controls".localized()), footer: controlsLayoutFooter) {
|
||||||
|
#if !os(tvOS)
|
||||||
|
avPlayerUsesSystemControlsToggle
|
||||||
|
#endif
|
||||||
horizontalPlayerGestureEnabledToggle
|
horizontalPlayerGestureEnabledToggle
|
||||||
SettingsHeader(text: "Seek gesture sensitivity".localized(), secondary: true)
|
SettingsHeader(text: "Seek gesture sensitivity".localized(), secondary: true)
|
||||||
seekGestureSensitivityPicker
|
seekGestureSensitivityPicker
|
||||||
@ -143,6 +148,10 @@ struct PlayerControlsSettings: View {
|
|||||||
Toggle("Seek with horizontal swipe on video", isOn: $horizontalPlayerGestureEnabled)
|
Toggle("Seek with horizontal swipe on video", isOn: $horizontalPlayerGestureEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var avPlayerUsesSystemControlsToggle: some View {
|
||||||
|
Toggle("Use system controls with AVPlayer", isOn: $avPlayerUsesSystemControls)
|
||||||
|
}
|
||||||
|
|
||||||
private var seekGestureSensitivityPicker: some View {
|
private var seekGestureSensitivityPicker: some View {
|
||||||
Picker("Seek gesture sensitivity", selection: $seekGestureSensitivity) {
|
Picker("Seek gesture sensitivity", selection: $seekGestureSensitivity) {
|
||||||
Text("Highest").tag(1.0)
|
Text("Highest").tag(1.0)
|
||||||
|
@ -723,6 +723,8 @@
|
|||||||
37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerModel.swift */; };
|
37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerModel.swift */; };
|
||||||
37B795902771DAE0001CF27B /* OpenURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */; };
|
37B795902771DAE0001CF27B /* OpenURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */; };
|
||||||
37B795912771DAE0001CF27B /* OpenURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */; };
|
37B795912771DAE0001CF27B /* OpenURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */; };
|
||||||
|
37B7CFE92A19603B001B0564 /* ToolbarBackground+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7CFE82A19603B001B0564 /* ToolbarBackground+Backport.swift */; };
|
||||||
|
37B7CFEB2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7CFEA2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift */; };
|
||||||
37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */; };
|
37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */; };
|
||||||
37B81AFA26D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */; };
|
37B81AFA26D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */; };
|
||||||
37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */; };
|
37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */; };
|
||||||
@ -885,6 +887,10 @@
|
|||||||
37D9BA0829507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; };
|
37D9BA0829507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; };
|
||||||
37DCD3112A18E8150059A470 /* OrientationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3102A18E8150059A470 /* OrientationModel.swift */; };
|
37DCD3112A18E8150059A470 /* OrientationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3102A18E8150059A470 /* OrientationModel.swift */; };
|
||||||
37DCD3152A18F7630059A470 /* SafeAreaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3142A18F7630059A470 /* SafeAreaModel.swift */; };
|
37DCD3152A18F7630059A470 /* SafeAreaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3142A18F7630059A470 /* SafeAreaModel.swift */; };
|
||||||
|
37DCD3172A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */; };
|
||||||
|
37DCD3182A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */; };
|
||||||
|
37DCD3192A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */; };
|
||||||
|
37DCD31A2A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */; };
|
||||||
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||||
37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||||
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||||
@ -1379,6 +1385,8 @@
|
|||||||
37B4E804277D0AB4004BF56A /* Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Orientation.swift; sourceTree = "<group>"; };
|
37B4E804277D0AB4004BF56A /* Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Orientation.swift; sourceTree = "<group>"; };
|
||||||
37B767DA2677C3CA0098BAA8 /* PlayerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerModel.swift; sourceTree = "<group>"; };
|
37B767DA2677C3CA0098BAA8 /* PlayerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerModel.swift; sourceTree = "<group>"; };
|
||||||
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenURLHandler.swift; sourceTree = "<group>"; };
|
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenURLHandler.swift; sourceTree = "<group>"; };
|
||||||
|
37B7CFE82A19603B001B0564 /* ToolbarBackground+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolbarBackground+Backport.swift"; sourceTree = "<group>"; };
|
||||||
|
37B7CFEA2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolbarColorScheme+Backport.swift"; sourceTree = "<group>"; };
|
||||||
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSizeModifier.swift; sourceTree = "<group>"; };
|
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSizeModifier.swift; sourceTree = "<group>"; };
|
||||||
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailsPaddingModifier.swift; sourceTree = "<group>"; };
|
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailsPaddingModifier.swift; sourceTree = "<group>"; };
|
||||||
37B81AFE26D2CA3700675966 /* VideoDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetails.swift; sourceTree = "<group>"; };
|
37B81AFE26D2CA3700675966 /* VideoDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetails.swift; sourceTree = "<group>"; };
|
||||||
@ -1458,6 +1466,7 @@
|
|||||||
37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsSettings.swift; sourceTree = "<group>"; };
|
37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsSettings.swift; sourceTree = "<group>"; };
|
||||||
37DCD3102A18E8150059A470 /* OrientationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationModel.swift; sourceTree = "<group>"; };
|
37DCD3102A18E8150059A470 /* OrientationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationModel.swift; sourceTree = "<group>"; };
|
||||||
37DCD3142A18F7630059A470 /* SafeAreaModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaModel.swift; sourceTree = "<group>"; };
|
37DCD3142A18F7630059A470 /* SafeAreaModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaModel.swift; sourceTree = "<group>"; };
|
||||||
|
37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVPlayerViewController+FullScreen.swift"; sourceTree = "<group>"; };
|
||||||
37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; };
|
37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; };
|
||||||
37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; };
|
37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; };
|
||||||
37DD9DAF2785D58D00539416 /* RefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = "<group>"; };
|
37DD9DAF2785D58D00539416 /* RefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = "<group>"; };
|
||||||
@ -1897,6 +1906,8 @@
|
|||||||
37E80F3F287B472300561799 /* ScrollContentBackground+Backport.swift */,
|
37E80F3F287B472300561799 /* ScrollContentBackground+Backport.swift */,
|
||||||
376E331128AD3B320070E30C /* ScrollDismissesKeyboard+Backport.swift */,
|
376E331128AD3B320070E30C /* ScrollDismissesKeyboard+Backport.swift */,
|
||||||
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */,
|
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */,
|
||||||
|
37B7CFE82A19603B001B0564 /* ToolbarBackground+Backport.swift */,
|
||||||
|
37B7CFEA2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift */,
|
||||||
3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */,
|
3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */,
|
||||||
3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */,
|
3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */,
|
||||||
);
|
);
|
||||||
@ -2228,6 +2239,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
379775922689365600DD52A8 /* Array+Next.swift */,
|
379775922689365600DD52A8 /* Array+Next.swift */,
|
||||||
|
37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */,
|
||||||
376578842685429C00D4EA09 /* CaseIterable+Next.swift */,
|
376578842685429C00D4EA09 /* CaseIterable+Next.swift */,
|
||||||
37C0697D2725C8D400F7F6CB /* CMTime+DefaultTimescale.swift */,
|
37C0697D2725C8D400F7F6CB /* CMTime+DefaultTimescale.swift */,
|
||||||
378AE942274EF00A006A4EE1 /* Color+Background.swift */,
|
378AE942274EF00A006A4EE1 /* Color+Background.swift */,
|
||||||
@ -3296,6 +3308,8 @@
|
|||||||
37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||||
377692562946476F0055EC18 /* ChannelPlaylistsCacheModel.swift in Sources */,
|
377692562946476F0055EC18 /* ChannelPlaylistsCacheModel.swift in Sources */,
|
||||||
3769C02E2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */,
|
3769C02E2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */,
|
||||||
|
37B7CFE92A19603B001B0564 /* ToolbarBackground+Backport.swift in Sources */,
|
||||||
|
37DCD3172A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
|
||||||
379EF9E029AA585F009FE6C6 /* HideShortsButtons.swift in Sources */,
|
379EF9E029AA585F009FE6C6 /* HideShortsButtons.swift in Sources */,
|
||||||
37F5E8B6291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */,
|
37F5E8B6291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */,
|
||||||
37579D5D27864F5F00FD0B98 /* Help.swift in Sources */,
|
37579D5D27864F5F00FD0B98 /* Help.swift in Sources */,
|
||||||
@ -3305,6 +3319,7 @@
|
|||||||
375EC972289F2ABF00751258 /* MultiselectRow.swift in Sources */,
|
375EC972289F2ABF00751258 /* MultiselectRow.swift in Sources */,
|
||||||
37001563271B1F250049C794 /* AccountsModel.swift in Sources */,
|
37001563271B1F250049C794 /* AccountsModel.swift in Sources */,
|
||||||
3795593627B08538007FF8F4 /* StreamControl.swift in Sources */,
|
3795593627B08538007FF8F4 /* StreamControl.swift in Sources */,
|
||||||
|
37B7CFEB2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift in Sources */,
|
||||||
37A2B346294723850050933E /* CacheModel.swift in Sources */,
|
37A2B346294723850050933E /* CacheModel.swift in Sources */,
|
||||||
37CC3F50270D010D00608308 /* VideoBanner.swift in Sources */,
|
37CC3F50270D010D00608308 /* VideoBanner.swift in Sources */,
|
||||||
378E50FB26FE8B9F00F49626 /* Instance.swift in Sources */,
|
378E50FB26FE8B9F00F49626 /* Instance.swift in Sources */,
|
||||||
@ -3558,6 +3573,7 @@
|
|||||||
3711404026B206A6005B3555 /* SearchModel.swift in Sources */,
|
3711404026B206A6005B3555 /* SearchModel.swift in Sources */,
|
||||||
37484C2A26FC83FF00287258 /* AccountForm.swift in Sources */,
|
37484C2A26FC83FF00287258 /* AccountForm.swift in Sources */,
|
||||||
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||||
|
37DCD3182A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
|
||||||
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
||||||
37D2E0D528B67EFC00F64D52 /* Delay.swift in Sources */,
|
37D2E0D528B67EFC00F64D52 /* Delay.swift in Sources */,
|
||||||
37977584268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
37977584268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
||||||
@ -3615,6 +3631,7 @@
|
|||||||
3774124D27387D2300423605 /* PlaylistsModel.swift in Sources */,
|
3774124D27387D2300423605 /* PlaylistsModel.swift in Sources */,
|
||||||
3774124B27387D2300423605 /* ThumbnailsModel.swift in Sources */,
|
3774124B27387D2300423605 /* ThumbnailsModel.swift in Sources */,
|
||||||
3774125427387D2300423605 /* Store.swift in Sources */,
|
3774125427387D2300423605 /* Store.swift in Sources */,
|
||||||
|
37DCD31A2A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
|
||||||
3774125027387D2300423605 /* Video.swift in Sources */,
|
3774125027387D2300423605 /* Video.swift in Sources */,
|
||||||
37EF9A79275BEB8E0043B585 /* CommentView.swift in Sources */,
|
37EF9A79275BEB8E0043B585 /* CommentView.swift in Sources */,
|
||||||
3774125327387D2300423605 /* Country.swift in Sources */,
|
3774125327387D2300423605 /* Country.swift in Sources */,
|
||||||
@ -3813,6 +3830,7 @@
|
|||||||
372D85E0283842EE00FF3C7D /* PlayerLayerView.swift in Sources */,
|
372D85E0283842EE00FF3C7D /* PlayerLayerView.swift in Sources */,
|
||||||
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
|
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
|
||||||
37F64FE626FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
37F64FE626FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
||||||
|
37DCD3192A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
|
||||||
37B2631C2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
|
37B2631C2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
|
||||||
37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */,
|
37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */,
|
||||||
37FB2860272225E800A57617 /* ContentItemView.swift in Sources */,
|
37FB2860272225E800A57617 /* ContentItemView.swift in Sources */,
|
||||||
|
Loading…
Reference in New Issue
Block a user