mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
parent
99881ccc23
commit
60f38a80aa
@ -59,6 +59,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
var controller: AppleAVPlayerViewController?
|
var controller: AppleAVPlayerViewController?
|
||||||
#endif
|
#endif
|
||||||
var startPictureInPictureOnPlay = false
|
var startPictureInPictureOnPlay = false
|
||||||
|
var startPictureInPictureOnSwitch = false
|
||||||
|
|
||||||
private var asset: AVURLAsset?
|
private var asset: AVURLAsset?
|
||||||
private var composition = AVMutableComposition()
|
private var composition = AVMutableComposition()
|
||||||
@ -124,6 +125,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
avPlayer.play()
|
avPlayer.play()
|
||||||
|
model.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func pause() {
|
func pause() {
|
||||||
@ -132,6 +134,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
avPlayer.pause()
|
avPlayer.pause()
|
||||||
|
model.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func togglePlay() {
|
func togglePlay() {
|
||||||
@ -147,7 +150,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
|
|
||||||
avPlayer.seek(
|
avPlayer.seek(
|
||||||
to: time,
|
to: time,
|
||||||
toleranceBefore: .secondsInDefaultTimescale(1),
|
toleranceBefore: .zero,
|
||||||
toleranceAfter: .zero,
|
toleranceAfter: .zero,
|
||||||
completionHandler: completionHandler ?? { _ in }
|
completionHandler: completionHandler ?? { _ in }
|
||||||
)
|
)
|
||||||
@ -165,6 +168,8 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
|
|
||||||
func closeItem() {
|
func closeItem() {
|
||||||
avPlayer.replaceCurrentItem(with: nil)
|
avPlayer.replaceCurrentItem(with: nil)
|
||||||
|
video = nil
|
||||||
|
stream = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closePiP() {
|
func closePiP() {
|
||||||
@ -294,6 +299,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !preservingTime,
|
if !preservingTime,
|
||||||
|
!self.model.transitioningToPiP,
|
||||||
let segment = self.model.sponsorBlock.segments.first,
|
let segment = self.model.sponsorBlock.segments.first,
|
||||||
segment.start < 3,
|
segment.start < 3,
|
||||||
self.model.lastSkipped.isNil
|
self.model.lastSkipped.isNil
|
||||||
@ -434,11 +440,37 @@ 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)
|
||||||
{
|
{
|
||||||
self.model.updateAspectRatio()
|
self.model.updateAspectRatio()
|
||||||
self.model.play()
|
|
||||||
|
if self.startPictureInPictureOnPlay,
|
||||||
|
let controller = self.model.pipController,
|
||||||
|
controller.isPictureInPicturePossible
|
||||||
|
{
|
||||||
|
self.tryStartingPictureInPicture()
|
||||||
|
} else {
|
||||||
|
self.model.play()
|
||||||
|
}
|
||||||
|
} else if self.startPictureInPictureOnPlay {
|
||||||
|
self.startPictureInPictureOnPlay = false
|
||||||
|
self.model.stream = self.stream
|
||||||
|
self.model.streamSelection = self.stream
|
||||||
|
|
||||||
|
if self.model.activeBackend != .appleAVPlayer {
|
||||||
|
self.startPictureInPictureOnSwitch = true
|
||||||
|
let seconds = self.model.mpvBackend.currentTime?.seconds ?? 0
|
||||||
|
self.seek(to: seconds) { finished in
|
||||||
|
guard finished else { return }
|
||||||
|
self.model.pause()
|
||||||
|
self.model.changeActiveBackend(from: .mpv, to: .appleAVPlayer, changingStream: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .failed:
|
case .failed:
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -483,7 +515,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
forInterval: interval,
|
forInterval: interval,
|
||||||
queue: .main
|
queue: .main
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
guard let self = self else {
|
guard let self = self, self.model.activeBackend == .appleAVPlayer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,6 +543,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
if self.controlsUpdates {
|
if self.controlsUpdates {
|
||||||
self.playerTime.duration = self.playerItemDuration ?? .zero
|
self.playerTime.duration = self.playerItemDuration ?? .zero
|
||||||
self.playerTime.currentTime = self.currentTime ?? .zero
|
self.playerTime.currentTime = self.currentTime ?? .zero
|
||||||
|
self.model.objectWillChange.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -553,17 +586,6 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if player.timeControlStatus != .waitingToPlayAtSpecifiedRate {
|
if player.timeControlStatus != .waitingToPlayAtSpecifiedRate {
|
||||||
if let controller = self.model.pipController {
|
|
||||||
if controller.isPictureInPicturePossible {
|
|
||||||
if self.startPictureInPictureOnPlay {
|
|
||||||
self.startPictureInPictureOnPlay = false
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.model.pipController?.startPictureInPicture()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.model.objectWillChange.send()
|
self?.model.objectWillChange.send()
|
||||||
}
|
}
|
||||||
@ -598,10 +620,12 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
logger.info("starting controls updates")
|
logger.info("starting controls updates")
|
||||||
controlsUpdates = true
|
controlsUpdates = true
|
||||||
|
model.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopControlsUpdates() {
|
func stopControlsUpdates() {
|
||||||
controlsUpdates = false
|
controlsUpdates = false
|
||||||
|
model.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func startMusicMode() {
|
func startMusicMode() {
|
||||||
@ -633,13 +657,33 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func didChangeTo() {
|
func didChangeTo() {
|
||||||
if model.musicMode {
|
if startPictureInPictureOnSwitch {
|
||||||
|
startPictureInPictureOnSwitch = false
|
||||||
|
tryStartingPictureInPicture()
|
||||||
|
} else if model.musicMode {
|
||||||
startMusicMode()
|
startMusicMode()
|
||||||
} else {
|
} else {
|
||||||
stopMusicMode()
|
stopMusicMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tryStartingPictureInPicture() {
|
||||||
|
guard let controller = model.pipController else { return }
|
||||||
|
|
||||||
|
var opened = false
|
||||||
|
for delay in [0.1, 0.3, 0.5, 1, 2, 3, 5] {
|
||||||
|
Delay.by(delay) {
|
||||||
|
guard !opened else { return }
|
||||||
|
if controller.isPictureInPicturePossible {
|
||||||
|
opened = true
|
||||||
|
controller.startPictureInPicture()
|
||||||
|
} else {
|
||||||
|
print("PiP not possible, waited \(delay) seconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setNeedsDrawing(_: Bool) {}
|
func setNeedsDrawing(_: Bool) {}
|
||||||
func setSize(_: Double, _: Double) {}
|
func setSize(_: Double, _: Double) {}
|
||||||
func setNeedsNetworkStateUpdates(_: Bool) {}
|
func setNeedsNetworkStateUpdates(_: Bool) {}
|
||||||
|
@ -169,7 +169,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
stream.resolution != .unknown && stream.format != .av1
|
stream.resolution != .unknown && stream.format != .av1
|
||||||
}
|
}
|
||||||
|
|
||||||
func playStream(_ stream: Stream, of video: Video, preservingTime: Bool, upgrading _: Bool) {
|
func playStream(_ stream: Stream, of video: Video, preservingTime: Bool, upgrading: Bool) {
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
if model.presentingPlayer {
|
if model.presentingPlayer {
|
||||||
UIApplication.shared.isIdleTimerDisabled = true
|
UIApplication.shared.isIdleTimerDisabled = true
|
||||||
@ -204,6 +204,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
self.startClientUpdates()
|
self.startClientUpdates()
|
||||||
|
|
||||||
if !preservingTime,
|
if !preservingTime,
|
||||||
|
!upgrading,
|
||||||
let segment = self.model.sponsorBlock.segments.first,
|
let segment = self.model.sponsorBlock.segments.first,
|
||||||
self.model.lastSkipped.isNil
|
self.model.lastSkipped.isNil
|
||||||
{
|
{
|
||||||
@ -325,6 +326,8 @@ final class MPVBackend: PlayerBackend {
|
|||||||
func closeItem() {
|
func closeItem() {
|
||||||
client?.pause()
|
client?.pause()
|
||||||
client?.stop()
|
client?.stop()
|
||||||
|
self.video = nil
|
||||||
|
self.stream = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closePiP() {}
|
func closePiP() {}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import AVKit
|
import AVKit
|
||||||
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@ -15,16 +16,21 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
|
|||||||
func pictureInPictureControllerWillStartPictureInPicture(_: AVPictureInPictureController) {}
|
func pictureInPictureControllerWillStartPictureInPicture(_: AVPictureInPictureController) {}
|
||||||
|
|
||||||
func pictureInPictureControllerDidStartPictureInPicture(_: AVPictureInPictureController) {
|
func pictureInPictureControllerDidStartPictureInPicture(_: AVPictureInPictureController) {
|
||||||
player?.playingInPictureInPicture = true
|
guard let player = player else { return }
|
||||||
player?.avPlayerBackend.startPictureInPictureOnPlay = false
|
|
||||||
|
player.playingInPictureInPicture = true
|
||||||
|
player.avPlayerBackend.startPictureInPictureOnPlay = false
|
||||||
|
player.avPlayerBackend.startPictureInPictureOnSwitch = false
|
||||||
|
player.controls.objectWillChange.send()
|
||||||
|
|
||||||
|
if Defaults[.closePlayerOnOpeningPiP] { Delay.by(0.1) { player.hide() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
func pictureInPictureControllerDidStopPictureInPicture(_: AVPictureInPictureController) {
|
func pictureInPictureControllerDidStopPictureInPicture(_: AVPictureInPictureController) {
|
||||||
guard let player = player else {
|
guard let player = player else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
player.playingInPictureInPicture = false
|
player.playingInPictureInPicture = false
|
||||||
|
player.controls.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func pictureInPictureControllerWillStopPictureInPicture(_: AVPictureInPictureController) {}
|
func pictureInPictureControllerWillStopPictureInPicture(_: AVPictureInPictureController) {}
|
||||||
|
@ -126,26 +126,6 @@ final class PlayerControlsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startPiP(startImmediately: Bool = true) {
|
|
||||||
player?.avPlayerBackend.startPictureInPictureOnPlay = true
|
|
||||||
|
|
||||||
#if !os(macOS)
|
|
||||||
player.exitFullScreen()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if player.activeBackend != PlayerBackendType.appleAVPlayer {
|
|
||||||
player.saveTime { [weak player] in
|
|
||||||
player?.changeActiveBackend(from: .mpv, to: .appleAVPlayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak player] in
|
|
||||||
if startImmediately {
|
|
||||||
player?.pipController?.startPictureInPicture()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeTimer() {
|
func removeTimer() {
|
||||||
timer?.invalidate()
|
timer?.invalidate()
|
||||||
timer = nil
|
timer = nil
|
||||||
|
@ -272,6 +272,9 @@ final class PlayerModel: ObservableObject {
|
|||||||
Orientation.lockOrientation(.allButUpsideDown)
|
Orientation.lockOrientation(.allButUpsideDown)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#if os(macOS)
|
||||||
|
Windows.player.hide()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func togglePlayer() {
|
func togglePlayer() {
|
||||||
@ -280,6 +283,13 @@ final class PlayerModel: ObservableObject {
|
|||||||
Windows.player.open()
|
Windows.player.open()
|
||||||
}
|
}
|
||||||
Windows.player.focus()
|
Windows.player.focus()
|
||||||
|
|
||||||
|
if Windows.player.visible,
|
||||||
|
closePiPOnOpeningPlayer
|
||||||
|
{
|
||||||
|
closePiP()
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
if presentingPlayer {
|
if presentingPlayer {
|
||||||
hide()
|
hide()
|
||||||
@ -398,7 +408,8 @@ final class PlayerModel: ObservableObject {
|
|||||||
_ stream: Stream,
|
_ stream: Stream,
|
||||||
of video: Video,
|
of video: Video,
|
||||||
preservingTime: Bool = false,
|
preservingTime: Bool = false,
|
||||||
upgrading: Bool = false
|
upgrading: Bool = false,
|
||||||
|
withBackend: PlayerBackend? = nil
|
||||||
) {
|
) {
|
||||||
playerError = nil
|
playerError = nil
|
||||||
if !upgrading {
|
if !upgrading {
|
||||||
@ -420,9 +431,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playerTime.reset()
|
(withBackend ?? backend).playStream(
|
||||||
|
|
||||||
backend.playStream(
|
|
||||||
stream,
|
stream,
|
||||||
of: video,
|
of: video,
|
||||||
preservingTime: preservingTime,
|
preservingTime: preservingTime,
|
||||||
@ -515,15 +524,13 @@ final class PlayerModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func changeActiveBackend(from: PlayerBackendType, to: PlayerBackendType) {
|
func changeActiveBackend(from: PlayerBackendType, to: PlayerBackendType, changingStream: Bool = true) {
|
||||||
guard activeBackend != to else {
|
guard activeBackend != to else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("changing backend from \(from.rawValue) to \(to.rawValue)")
|
logger.info("changing backend from \(from.rawValue) to \(to.rawValue)")
|
||||||
|
|
||||||
pause()
|
|
||||||
|
|
||||||
if to == .mpv {
|
if to == .mpv {
|
||||||
closePiP()
|
closePiP()
|
||||||
}
|
}
|
||||||
@ -531,15 +538,17 @@ final class PlayerModel: ObservableObject {
|
|||||||
Defaults[.activeBackend] = to
|
Defaults[.activeBackend] = to
|
||||||
self.activeBackend = to
|
self.activeBackend = to
|
||||||
|
|
||||||
self.backend.didChangeTo()
|
|
||||||
|
|
||||||
guard var stream = stream else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let fromBackend: PlayerBackend = from == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
let fromBackend: PlayerBackend = from == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
||||||
let toBackend: PlayerBackend = to == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
let toBackend: PlayerBackend = to == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
||||||
|
|
||||||
|
self.backend.didChangeTo()
|
||||||
|
|
||||||
|
fromBackend.pause()
|
||||||
|
|
||||||
|
guard var stream = stream, changingStream else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if let stream = toBackend.stream, toBackend.video == fromBackend.video {
|
if let stream = toBackend.stream, toBackend.video == fromBackend.video {
|
||||||
toBackend.seek(to: fromBackend.currentTime?.seconds ?? .zero) { finished in
|
toBackend.seek(to: fromBackend.currentTime?.seconds ?? .zero) { finished in
|
||||||
guard finished else {
|
guard finished else {
|
||||||
@ -610,11 +619,54 @@ final class PlayerModel: ObservableObject {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startPiP() {
|
||||||
|
avPlayerBackend.startPictureInPictureOnPlay = false
|
||||||
|
avPlayerBackend.startPictureInPictureOnSwitch = false
|
||||||
|
|
||||||
|
if activeBackend == .appleAVPlayer {
|
||||||
|
avPlayerBackend.tryStartingPictureInPicture()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let video = currentVideo else { return }
|
||||||
|
guard let stream = avPlayerBackend.bestPlayable(availableStreams, maxResolution: .hd720p30) else { return }
|
||||||
|
|
||||||
|
exitFullScreen()
|
||||||
|
|
||||||
|
if avPlayerBackend.video == video {
|
||||||
|
if activeBackend != .appleAVPlayer {
|
||||||
|
avPlayerBackend.startPictureInPictureOnSwitch = true
|
||||||
|
changeActiveBackend(from: activeBackend, to: .appleAVPlayer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
avPlayerBackend.startPictureInPictureOnPlay = true
|
||||||
|
playStream(stream, of: video, preservingTime: true, upgrading: true, withBackend: avPlayerBackend)
|
||||||
|
}
|
||||||
|
|
||||||
|
controls.objectWillChange.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
var transitioningToPiP: Bool {
|
||||||
|
avPlayerBackend.startPictureInPictureOnPlay || avPlayerBackend.startPictureInPictureOnSwitch
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipPossible: Bool {
|
||||||
|
guard activeBackend == .appleAVPlayer else { return !transitioningToPiP }
|
||||||
|
|
||||||
|
guard let pipController = pipController else { return false }
|
||||||
|
guard !pipController.isPictureInPictureActive else { return true }
|
||||||
|
|
||||||
|
return pipController.isPictureInPicturePossible && !transitioningToPiP
|
||||||
|
}
|
||||||
|
|
||||||
func closePiP() {
|
func closePiP() {
|
||||||
guard playingInPictureInPicture else {
|
guard playingInPictureInPicture else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
avPlayerBackend.startPictureInPictureOnPlay = false
|
||||||
|
avPlayerBackend.startPictureInPictureOnSwitch = false
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
show()
|
show()
|
||||||
#endif
|
#endif
|
||||||
|
@ -127,6 +127,7 @@ extension Defaults.Keys {
|
|||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
static let closePiPAndOpenPlayerOnEnteringForeground = Key<Bool>("closePiPAndOpenPlayerOnEnteringForeground", default: false)
|
static let closePiPAndOpenPlayerOnEnteringForeground = Key<Bool>("closePiPAndOpenPlayerOnEnteringForeground", default: false)
|
||||||
#endif
|
#endif
|
||||||
|
static let closePlayerOnOpeningPiP = Key<Bool>("closePlayerOnOpeningPiP", default: false)
|
||||||
|
|
||||||
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) {
|
func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) {
|
||||||
if Defaults[.pauseOnHidingPlayer] {
|
if Defaults[.pauseOnHidingPlayer], !playerModel.playingInPictureInPicture {
|
||||||
playerModel.pause()
|
playerModel.pause()
|
||||||
}
|
}
|
||||||
dismiss(animated: false)
|
dismiss(animated: false)
|
||||||
@ -121,15 +121,12 @@ extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
|
|||||||
self.playerModel.show()
|
self.playerModel.show()
|
||||||
self.playerModel.setNeedsDrawing(true)
|
self.playerModel.setNeedsDrawing(true)
|
||||||
|
|
||||||
#if os(tvOS)
|
if self.playerModel.playingInPictureInPicture {
|
||||||
if self.playerModel.playingInPictureInPicture {
|
self.present(self.playerView, animated: false) {
|
||||||
self.present(self.playerView, animated: false) {
|
completionHandler(true)
|
||||||
completionHandler(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#else
|
}
|
||||||
completionHandler(true)
|
completionHandler(true)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,9 +277,11 @@ struct PlayerControls: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var pipButton: some View {
|
private var pipButton: some View {
|
||||||
button("PiP", systemImage: "pip") {
|
let image = player.transitioningToPiP ? "pip.fill" : player.pipController?.isPictureInPictureActive ?? false ? "pip.exit" : "pip.enter"
|
||||||
model.startPiP()
|
return button("PiP", systemImage: image) {
|
||||||
|
(player.pipController?.isPictureInPictureActive ?? false) ? player.closePiP() : player.startPiP()
|
||||||
}
|
}
|
||||||
|
.disabled(!player.pipPossible)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
@ -17,6 +17,7 @@ struct PlayerSettings: View {
|
|||||||
#endif
|
#endif
|
||||||
@Default(.closePiPOnNavigation) private var closePiPOnNavigation
|
@Default(.closePiPOnNavigation) private var closePiPOnNavigation
|
||||||
@Default(.closePiPOnOpeningPlayer) private var closePiPOnOpeningPlayer
|
@Default(.closePiPOnOpeningPlayer) private var closePiPOnOpeningPlayer
|
||||||
|
@Default(.closePlayerOnOpeningPiP) private var closePlayerOnOpeningPiP
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
@Default(.closePlayerOnItemClose) private var closePlayerOnItemClose
|
@Default(.closePlayerOnItemClose) private var closePlayerOnItemClose
|
||||||
@Default(.pauseOnEnteringBackground) private var pauseOnEnteringBackground
|
@Default(.pauseOnEnteringBackground) private var pauseOnEnteringBackground
|
||||||
@ -96,6 +97,7 @@ struct PlayerSettings: View {
|
|||||||
Section(header: SettingsHeader(text: "Picture in Picture")) {
|
Section(header: SettingsHeader(text: "Picture in Picture")) {
|
||||||
closePiPOnNavigationToggle
|
closePiPOnNavigationToggle
|
||||||
closePiPOnOpeningPlayerToggle
|
closePiPOnOpeningPlayerToggle
|
||||||
|
closePlayerOnOpeningPiPToggle
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
closePiPAndOpenPlayerOnEnteringForegroundToggle
|
closePiPAndOpenPlayerOnEnteringForegroundToggle
|
||||||
#endif
|
#endif
|
||||||
@ -201,6 +203,10 @@ struct PlayerSettings: View {
|
|||||||
Toggle("Close PiP when player is opened", isOn: $closePiPOnOpeningPlayer)
|
Toggle("Close PiP when player is opened", isOn: $closePiPOnOpeningPlayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var closePlayerOnOpeningPiPToggle: some View {
|
||||||
|
Toggle("Close player when starting PiP", isOn: $closePlayerOnOpeningPiP)
|
||||||
|
}
|
||||||
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
private var closePiPAndOpenPlayerOnEnteringForegroundToggle: some View {
|
private var closePiPAndOpenPlayerOnEnteringForegroundToggle: some View {
|
||||||
Toggle("Close PiP and open player when application enters foreground", isOn: $closePiPAndOpenPlayerOnEnteringForeground)
|
Toggle("Close PiP and open player when application enters foreground", isOn: $closePiPAndOpenPlayerOnEnteringForeground)
|
||||||
|
@ -167,7 +167,15 @@ struct VideoContextMenuView: View {
|
|||||||
|
|
||||||
private var playNowInPictureInPictureButton: some View {
|
private var playNowInPictureInPictureButton: some View {
|
||||||
Button {
|
Button {
|
||||||
player.controls.startPiP(startImmediately: player.presentingPlayer && player.activeBackend == .appleAVPlayer)
|
player.avPlayerBackend.startPictureInPictureOnPlay = true
|
||||||
|
|
||||||
|
#if !os(macOS)
|
||||||
|
player.exitFullScreen()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if player.activeBackend != PlayerBackendType.appleAVPlayer {
|
||||||
|
player.changeActiveBackend(from: .mpv, to: .appleAVPlayer)
|
||||||
|
}
|
||||||
player.hide()
|
player.hide()
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
@ -257,7 +257,6 @@
|
|||||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||||
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||||
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||||
374108D1272B11B2006C5CC8 /* PictureInPictureDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374108D0272B11B2006C5CC8 /* PictureInPictureDelegate.swift */; };
|
|
||||||
3741A32C27E7EFFD00D266D1 /* PlayerControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */; };
|
3741A32C27E7EFFD00D266D1 /* PlayerControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */; };
|
||||||
3743B86927216D3600261544 /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
|
3743B86927216D3600261544 /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
|
||||||
3743B86A27216D3600261544 /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
|
3743B86A27216D3600261544 /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
|
||||||
@ -1043,7 +1042,6 @@
|
|||||||
373CFADA269663F1003CB2C6 /* Thumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnail.swift; sourceTree = "<group>"; };
|
373CFADA269663F1003CB2C6 /* Thumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnail.swift; sourceTree = "<group>"; };
|
||||||
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistFormView.swift; sourceTree = "<group>"; };
|
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistFormView.swift; sourceTree = "<group>"; };
|
||||||
373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToPlaylistView.swift; sourceTree = "<group>"; };
|
373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToPlaylistView.swift; sourceTree = "<group>"; };
|
||||||
374108D0272B11B2006C5CC8 /* PictureInPictureDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PictureInPictureDelegate.swift; sourceTree = "<group>"; };
|
|
||||||
3743B86727216D3600261544 /* ChannelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelCell.swift; sourceTree = "<group>"; };
|
3743B86727216D3600261544 /* ChannelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelCell.swift; sourceTree = "<group>"; };
|
||||||
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueueRow.swift; sourceTree = "<group>"; };
|
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueueRow.swift; sourceTree = "<group>"; };
|
||||||
3743CA51270F284F00E4D32B /* View+Borders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Borders.swift"; sourceTree = "<group>"; };
|
3743CA51270F284F00E4D32B /* View+Borders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Borders.swift"; sourceTree = "<group>"; };
|
||||||
@ -1917,7 +1915,6 @@
|
|||||||
37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */,
|
37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */,
|
||||||
37FD43DB270470B70073EE42 /* InstancesSettings.swift */,
|
37FD43DB270470B70073EE42 /* InstancesSettings.swift */,
|
||||||
3751BA7D27E63F1D007B1A60 /* MPVOGLView.swift */,
|
3751BA7D27E63F1D007B1A60 /* MPVOGLView.swift */,
|
||||||
374108D0272B11B2006C5CC8 /* PictureInPictureDelegate.swift */,
|
|
||||||
37F7AB5428A951B200FB46B5 /* Power.swift */,
|
37F7AB5428A951B200FB46B5 /* Power.swift */,
|
||||||
37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */,
|
37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */,
|
||||||
3751BA7F27E64244007B1A60 /* VideoLayer.swift */,
|
3751BA7F27E64244007B1A60 /* VideoLayer.swift */,
|
||||||
@ -3118,7 +3115,6 @@
|
|||||||
375F7411289DC35A00747050 /* PlayerBackendView.swift in Sources */,
|
375F7411289DC35A00747050 /* PlayerBackendView.swift in Sources */,
|
||||||
37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */,
|
37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */,
|
||||||
37E64DD226D597EB00C71877 /* SubscriptionsModel.swift in Sources */,
|
37E64DD226D597EB00C71877 /* SubscriptionsModel.swift in Sources */,
|
||||||
374108D1272B11B2006C5CC8 /* PictureInPictureDelegate.swift in Sources */,
|
|
||||||
37F0F4EF286F734400C06C2E /* AdvancedSettings.swift in Sources */,
|
37F0F4EF286F734400C06C2E /* AdvancedSettings.swift in Sources */,
|
||||||
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||||
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import AVKit
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
final class PictureInPictureDelegate: NSObject, AVPlayerViewPictureInPictureDelegate {
|
|
||||||
var playerModel: PlayerModel!
|
|
||||||
|
|
||||||
func playerViewShouldAutomaticallyDismissAtPicture(inPictureStart _: AVPlayerView) -> Bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
func playerViewWillStartPicture(inPicture _: AVPlayerView) {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
|
||||||
self?.playerModel.playingInPictureInPicture = true
|
|
||||||
self?.playerModel.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func playerViewWillStopPicture(inPicture _: AVPlayerView) {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
|
||||||
self?.playerModel.playingInPictureInPicture = false
|
|
||||||
self?.playerModel.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func playerView(
|
|
||||||
_: AVPlayerView,
|
|
||||||
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: (Bool) -> Void
|
|
||||||
) {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
|
||||||
self?.playerModel.show()
|
|
||||||
}
|
|
||||||
completionHandler(true)
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,9 +47,17 @@ enum Windows: String, CaseIterable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hide() {
|
||||||
|
window?.close()
|
||||||
|
}
|
||||||
|
|
||||||
func toggleFullScreen() {
|
func toggleFullScreen() {
|
||||||
window?.toggleFullScreen(nil)
|
window?.toggleFullScreen(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var visible: Bool {
|
||||||
|
window?.isVisible ?? false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HostingWindowFinder: NSViewRepresentable {
|
struct HostingWindowFinder: NSViewRepresentable {
|
||||||
|
Loading…
Reference in New Issue
Block a user