PiP improvements

This commit is contained in:
Arkadiusz Fal 2022-08-19 00:40:46 +02:00
parent ace8c6e3ff
commit 6c6ba19df4
16 changed files with 120 additions and 162 deletions

View File

@ -34,9 +34,9 @@ final class NetworkStateModel: ObservableObject {
var needsUpdates: Bool {
if let player = player {
return pausedForCache || player.isSeeking || player.isLoadingVideo || player.controls.presentingControlsOverlay
return !player.currentItem.isNil && (pausedForCache || player.isSeeking || player.isLoadingVideo || player.controls.presentingControlsOverlay)
}
return pausedForCache
return false
}
}

View File

@ -35,10 +35,7 @@ final class AVPlayerBackend: PlayerBackend {
var aspectRatio: Double {
#if os(iOS)
guard let view = model?.playerLayerView else { return VideoPlayerView.defaultAspectRatio }
let videoRect = view.playerLayer.videoRect
return videoRect.width / videoRect.height
playerLayer.videoRect.width / playerLayer.videoRect.height
#else
VideoPlayerView.defaultAspectRatio
#endif
@ -54,7 +51,10 @@ final class AVPlayerBackend: PlayerBackend {
}
private(set) var avPlayer = AVPlayer()
var controller: AppleAVPlayerViewController?
private(set) var playerLayer = AVPlayerLayer()
#if os(tvOS)
var controller: AppleAVPlayerViewController?
#endif
var startPictureInPictureOnPlay = false
private var asset: AVURLAsset?
@ -79,6 +79,8 @@ final class AVPlayerBackend: PlayerBackend {
addFrequentTimeObserver()
addInfrequentTimeObserver()
addPlayerTimeControlStatusObserver()
playerLayer.player = avPlayer
}
func bestPlayable(_ streams: [Stream], maxResolution _: ResolutionSetting) -> Stream? {
@ -157,39 +159,9 @@ final class AVPlayerBackend: PlayerBackend {
avPlayer.replaceCurrentItem(with: nil)
}
#if os(tvOS)
func closePiP(wasPlaying: Bool) {
let item = avPlayer.currentItem
let time = avPlayer.currentTime()
avPlayer.replaceCurrentItem(with: nil)
guard !item.isNil else {
return
}
avPlayer.seek(to: time)
avPlayer.replaceCurrentItem(with: item)
guard wasPlaying else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.play()
}
}
#else
func closePiP(wasPlaying: Bool) {
model.pipController?.stopPictureInPicture()
guard wasPlaying else {
return
}
play()
}
#endif
func closePiP() {
model.pipController?.stopPictureInPicture()
}
private func loadSingleAsset(
_ url: URL,

View File

@ -324,7 +324,7 @@ final class MPVBackend: PlayerBackend {
client?.stop()
}
func closePiP(wasPlaying _: Bool) {}
func closePiP() {}
func updateControls() {
self.logger.info("updating controls")

View File

@ -45,7 +45,7 @@ protocol PlayerBackend {
func closeItem()
func closePiP(wasPlaying: Bool)
func closePiP()
func updateControls()
func startControlsUpdates()

View File

@ -67,6 +67,8 @@ final class PlayerModel: ObservableObject {
}
}
var playerBackendView = PlayerBackendView()
@Published var playerSize: CGSize = .zero { didSet {
#if !os(tvOS)
backend.setSize(playerSize.width, playerSize.height)
@ -167,9 +169,6 @@ final class PlayerModel: ObservableObject {
#endif
private var currentArtwork: MPMediaItemArtwork?
#if !os(macOS)
var playerLayerView: PlayerLayerView!
#endif
var onPresentPlayer: (() -> Void)?
private var remoteCommandCenterConfigured = false
@ -207,6 +206,14 @@ final class PlayerModel: ObservableObject {
Defaults[.activeBackend] = .mpv
playbackMode = Defaults[.playbackMode]
guard pipController.isNil else { return }
pipController = .init(playerLayer: avPlayerBackend.playerLayer)
let pipDelegate = PiPDelegate()
pipDelegate.player = self
self.pipDelegate = pipDelegate
pipController?.delegate = pipDelegate
}
func show() {
@ -421,7 +428,9 @@ final class PlayerModel: ObservableObject {
return
}
if let qualityProfileBackend = qualityProfile?.backend, qualityProfileBackend != activeBackend {
if let qualityProfileBackend = qualityProfile?.backend, qualityProfileBackend != activeBackend,
qualityProfileBackend == .appleAVPlayer || !(avPlayerBackend.startPictureInPictureOnPlay || playingInPictureInPicture)
{
changeActiveBackend(from: activeBackend, to: qualityProfileBackend)
}
@ -464,16 +473,10 @@ final class PlayerModel: ObservableObject {
}
if !presentingPlayer, pauseOnHidingPlayer, !playingInPictureInPicture {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
DispatchQueue.main.async { [weak self] in
self?.pause()
}
}
if !presentingPlayer, !pauseOnHidingPlayer, backend.isPlaying {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.play()
}
}
}
func changeActiveBackend(from: PlayerBackendType, to: PlayerBackendType) {
@ -483,6 +486,10 @@ final class PlayerModel: ObservableObject {
pause()
if to == .mpv {
closePiP()
}
Defaults[.activeBackend] = to
self.activeBackend = to
@ -553,6 +560,7 @@ final class PlayerModel: ObservableObject {
func closeCurrentItem(finished: Bool = false) {
pause()
closePiP()
prepareCurrentItemForHistory(finished: finished)
currentItem = nil
@ -562,7 +570,6 @@ final class PlayerModel: ObservableObject {
aspectRatio = VideoPlayerView.defaultAspectRatio
resetAutoplay()
closePiP()
exitFullScreen()
#if !os(macOS)
@ -577,14 +584,11 @@ final class PlayerModel: ObservableObject {
return
}
let wasPlaying = isPlaying
pause()
#if os(tvOS)
show()
#endif
backend.closePiP(wasPlaying: wasPlaying)
backend.closePiP()
}
func handleQueueChange() {

View File

@ -132,21 +132,29 @@ struct ContentView: View {
@ViewBuilder var videoPlayer: some View {
if player.presentingPlayer {
VideoPlayerView()
.environmentObject(accounts)
.environmentObject(comments)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(subscriptions)
.environmentObject(thumbnailsModel)
.environment(\.navigationStyle, navigationStyle)
playerView
.transition(.move(edge: .bottom))
} else if player.activeBackend == .appleAVPlayer {
#if os(iOS)
playerView.offset(y: UIScreen.main.bounds.height)
#endif
}
}
var playerView: some View {
VideoPlayerView()
.environmentObject(accounts)
.environmentObject(comments)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(subscriptions)
.environmentObject(thumbnailsModel)
.environment(\.navigationStyle, navigationStyle)
}
}
struct ContentView_Previews: PreviewProvider {

View File

@ -6,8 +6,9 @@ struct AppleAVPlayerView: UIViewRepresentable {
@EnvironmentObject<PlayerModel> private var player
func makeUIView(context _: Context) -> some UIView {
player.playerLayerView = PlayerLayerView(frame: .zero)
return player.playerLayerView
let playerLayerView = PlayerLayerView(frame: .zero)
playerLayerView.player = player
return playerLayerView
}
func updateUIView(_: UIViewType, context _: Context) {}

View File

@ -227,8 +227,8 @@ struct PlayerControls: View {
HStack(spacing: 20) {
fullscreenButton
pipButton
#if os(iOS)
pipButton
lockOrientationButton
#endif

View File

@ -15,17 +15,6 @@ struct PlayerBackendView: View {
player.mpvPlayerView
case .appleAVPlayer:
player.avPlayerView
#if os(iOS)
.onAppear {
player.pipController = .init(playerLayer: player.playerLayerView.playerLayer)
let pipDelegate = PiPDelegate()
pipDelegate.player = player
player.pipDelegate = pipDelegate
player.pipController?.delegate = pipDelegate
player.playerLayerView.playerLayer.player = player.avPlayerBackend.avPlayer
}
#endif
}
}
.overlay(GeometryReader { proxy in

View File

@ -1,23 +1,51 @@
import AVFoundation
import Foundation
import UIKit
#if os(macOS)
import AppKit
#else
import UIKit
#endif
final class PlayerLayerView: UIView {
var playerLayer = AVPlayerLayer()
#if os(macOS)
final class PlayerLayerView: NSView {
var player: PlayerModel! { didSet {
wantsLayer = true
}}
override init(frame: CGRect) {
super.init(frame: frame)
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
layer.addSublayer(playerLayer)
override func makeBackingLayer() -> CALayer {
player.avPlayerBackend.playerLayer
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
#else
final class PlayerLayerView: UIView {
var player: PlayerModel!
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
private var layerAdded = false
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
if !layerAdded {
layerAdded = true
layer.addSublayer(player.avPlayerBackend.playerLayer)
}
player.avPlayerBackend.playerLayer.frame = bounds
super.layoutSubviews()
}
}
}
#endif

View File

@ -33,7 +33,9 @@ struct VideoDetails: View {
@StateObject private var page: Page = .first()
@Environment(\.navigationStyle) private var navigationStyle
@Environment(\.verticalSizeClass) private var verticalSizeClass
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
#endif
@EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<CommentsModel> private var comments
@ -227,7 +229,9 @@ struct VideoDetails: View {
.redacted(reason: .placeholder)
} else if video.description != nil, !video.description!.isEmpty {
VideoDescription(video: video, detailsSize: detailsSize)
#if os(iOS)
.padding(.bottom, fullScreenLayout ? 10 : SafeArea.insets.bottom)
#endif
} else {
Text("No description")
.foregroundColor(.secondary)

View File

@ -245,14 +245,14 @@ struct VideoPlayerView: View {
ZStack(alignment: .bottomLeading) {
#if os(tvOS)
ZStack {
PlayerBackendView()
player.playerBackendView
tvControls
}
.ignoresSafeArea()
#else
GeometryReader { geometry in
PlayerBackendView()
player.playerBackendView
#if !os(tvOS)
.modifier(
VideoPlayerSizeModifier(

View File

@ -59,10 +59,8 @@ struct VideoContextMenuView: View {
Section {
playNowButton
#if os(iOS)
playNowInPictureInPictureButton
#endif
#if !os(tvOS)
playNowInPictureInPictureButton
playNowInMusicMode
#endif
}
@ -169,7 +167,8 @@ struct VideoContextMenuView: View {
private var playNowInPictureInPictureButton: some View {
Button {
player.controls.startPiP(startImmediately: false)
player.controls.startPiP(startImmediately: player.presentingPlayer && player.activeBackend == .appleAVPlayer)
player.hide()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
player.play(video, at: watch?.timeToRestart, showingPlayer: false)

View File

@ -161,6 +161,7 @@
37169AA62729E2CC0011DE61 /* AccountsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */; };
37169AA72729E2CC0011DE61 /* AccountsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */; };
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */; };
37192D5528B0D5D60012EEDD /* PlayerLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373031F22838388A000CFD59 /* PlayerLayerView.swift */; };
371B7E5C27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
371B7E5D27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
371B7E5E27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
@ -623,7 +624,6 @@
37BE0BD426A1D47D0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
37BE0BD626A1D4A90092E2DB /* AppleAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */; };
37BE0BD726A1D4A90092E2DB /* AppleAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */; };
37BE0BDA26A214630092E2DB /* AppleAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD926A214630092E2DB /* AppleAVPlayerViewController.swift */; };
37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */; };
37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661B27308859008CCFB0 /* DropFavorite.swift */; };
37BF661D27308859008CCFB0 /* DropFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661B27308859008CCFB0 /* DropFavorite.swift */; };
@ -1000,7 +1000,7 @@
3729037D2739E47400EA99F6 /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = "<group>"; };
372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
372CFD14285F2E2A00B0B54B /* ControlsBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsBar.swift; sourceTree = "<group>"; };
373031F22838388A000CFD59 /* PlayerLayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerLayerView.swift; sourceTree = "<group>"; };
373031F22838388A000CFD59 /* PlayerLayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerLayerView.swift; sourceTree = "<group>"; tabWidth = 5; };
373031F428383A89000CFD59 /* PiPDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPDelegate.swift; sourceTree = "<group>"; };
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingView.swift; sourceTree = "<group>"; };
373197D82732015300EF734F /* RelatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelatedView.swift; sourceTree = "<group>"; };
@ -1161,7 +1161,6 @@
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerView.swift; sourceTree = "<group>"; };
37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerViewController.swift; sourceTree = "<group>"; };
37BE0BD926A214630092E2DB /* AppleAVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerViewController.swift; sourceTree = "<group>"; };
37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerView.swift; sourceTree = "<group>"; };
37BF661B27308859008CCFB0 /* DropFavorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropFavorite.swift; sourceTree = "<group>"; };
37BF661E27308884008CCFB0 /* DropFavoriteOutside.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropFavoriteOutside.swift; sourceTree = "<group>"; };
@ -1889,7 +1888,6 @@
children = (
374C0542272496E4009BDDBE /* AppDelegate.swift */,
37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */,
37BE0BD926A214630092E2DB /* AppleAVPlayerViewController.swift */,
37FD43DB270470B70073EE42 /* InstancesSettings.swift */,
3751BA7D27E63F1D007B1A60 /* MPVOGLView.swift */,
374108D0272B11B2006C5CC8 /* PictureInPictureDelegate.swift */,
@ -2972,6 +2970,7 @@
377ABC4D286E6A78009C986F /* LocationsSettings.swift in Sources */,
3782B95027553A6700990149 /* SearchSuggestions.swift in Sources */,
371B7E6B2759791900D21217 /* CommentsModel.swift in Sources */,
37192D5528B0D5D60012EEDD /* PlayerLayerView.swift in Sources */,
371F2F1B269B43D300E4A7AB /* NavigationModel.swift in Sources */,
3756C2AB2861151C00E4B059 /* NetworkStateModel.swift in Sources */,
375EC95A289EEB8200751258 /* QualityProfileForm.swift in Sources */,
@ -3075,7 +3074,6 @@
377ABC49286E5887009C986F /* Sequence+Unique.swift in Sources */,
3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */,
3784B23E2728B85300B09468 /* ShareButton.swift in Sources */,
37BE0BDA26A214630092E2DB /* AppleAVPlayerViewController.swift in Sources */,
375F7411289DC35A00747050 /* PlayerBackendView.swift in Sources */,
37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */,
37E64DD226D597EB00C71877 /* SubscriptionsModel.swift in Sources */,

View File

@ -1,27 +1,16 @@
import Defaults
import SwiftUI
struct AppleAVPlayerView: NSViewControllerRepresentable {
struct AppleAVPlayerView: NSViewRepresentable {
@EnvironmentObject<PlayerModel> private var player
@State private var controller: AppleAVPlayerViewController?
func makeNSView(context _: Context) -> some NSView {
let playerLayerView = PlayerLayerView(frame: .zero)
init(controller: AppleAVPlayerViewController? = nil) {
self.controller = controller
playerLayerView.player = player
return playerLayerView
}
func makeNSViewController(context _: Context) -> AppleAVPlayerViewController {
if self.controller != nil {
return self.controller!
}
let controller = AppleAVPlayerViewController()
controller.playerModel = player
player.avPlayerBackend.controller = controller
return controller
}
func updateNSViewController(_: AppleAVPlayerViewController, context _: Context) {}
func updateNSView(_: NSViewType, context _: Context) {}
}

View File

@ -1,34 +0,0 @@
import AVKit
import SwiftUI
final class AppleAVPlayerViewController: NSViewController {
var playerModel: PlayerModel!
var playerView = AVPlayerView()
var pictureInPictureDelegate = PictureInPictureDelegate()
var aspectRatio: Double? {
let ratio = Double(playerView.videoBounds.width) / Double(playerView.videoBounds.height)
if !ratio.isFinite {
return VideoPlayerView.defaultAspectRatio
}
return [ratio, 1.0].max()!
}
override func viewDidDisappear() {
super.viewDidDisappear()
}
override func loadView() {
playerView.player = playerModel.avPlayerBackend.avPlayer
pictureInPictureDelegate.playerModel = playerModel
playerView.controlsStyle = .none
playerView.allowsPictureInPicturePlayback = true
playerView.pictureInPictureDelegate = pictureInPictureDelegate
view = playerView
}
}