Add basic PiP support

This commit is contained in:
Arkadiusz Fal 2021-08-18 00:00:53 +02:00
parent 2dff68200d
commit 8d5a191779
6 changed files with 70 additions and 18 deletions

View File

@ -23,6 +23,8 @@ final class PlayerState: ObservableObject {
let maxResolution: Stream.Resolution?
var timeObserver: Any?
var playingOutsideViewController = false
init(_ video: Video? = nil, maxResolution: Stream.Resolution? = nil) {
self.video = video
self.maxResolution = maxResolution
@ -222,6 +224,11 @@ final class PlayerState: ObservableObject {
fileprivate func destroyPlayer() {
logger.critical("destroying player")
guard !playingOutsideViewController else {
logger.critical("cannot destroy, playing outside view controller")
return
}
player?.currentItem?.tracks.forEach { $0.assetTrack?.asset?.cancelLoading() }
player?.replaceCurrentItem(with: nil)

View File

@ -232,6 +232,7 @@
37977582268922F600DD52A8 /* InvidiousAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvidiousAPI.swift; sourceTree = "<group>"; };
3797758A2689345500DD52A8 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
379775922689365600DD52A8 /* Array+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Next.swift"; sourceTree = "<group>"; };
37992DC726CC50BC003D4C27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
37AAF27D26737323007FC770 /* PopularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularView.swift; sourceTree = "<group>"; };
37AAF27F26737550007FC770 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
37AAF2892673AB89007FC770 /* ChannelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelView.swift; sourceTree = "<group>"; };
@ -356,6 +357,14 @@
name = Frameworks;
sourceTree = "<group>";
};
37992DC826CC50CD003D4C27 /* iOS */ = {
isa = PBXGroup;
children = (
37992DC726CC50BC003D4C27 /* Info.plist */,
);
path = iOS;
sourceTree = "<group>";
};
37BE0BD826A214500092E2DB /* macOS */ = {
isa = PBXGroup;
children = (
@ -378,6 +387,7 @@
37D4B0BC2671614700C925CA = {
isa = PBXGroup;
children = (
37992DC826CC50CD003D4C27 /* iOS */,
37BE0BD826A214500092E2DB /* macOS */,
37D4B159267164AE00C925CA /* tvOS */,
37D4B0C12671614700C925CA /* Shared */,
@ -1082,6 +1092,7 @@
DEVELOPMENT_TEAM = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iOS/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@ -1112,6 +1123,7 @@
DEVELOPMENT_TEAM = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iOS/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;

View File

@ -6,8 +6,6 @@ final class PlayerViewController: UIViewController {
var video: Video!
var playerLoaded = false
var playingFullScreen = false
var player = AVPlayer()
var playerState: PlayerState! = PlayerState()
var playerViewController = AVPlayerViewController()
@ -15,19 +13,19 @@ final class PlayerViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !playerLoaded {
loadPlayer()
}
loadPlayer()
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try? AVAudioSession.sharedInstance().setActive(true)
}
override func viewDidDisappear(_ animated: Bool) {
#if os(iOS)
if !playingFullScreen {
if !playerState.playingOutsideViewController {
playerViewController.player?.replaceCurrentItem(with: nil)
playerViewController.player = nil
try? AVAudioSession.sharedInstance().setActive(false)
}
#endif
@ -35,6 +33,10 @@ final class PlayerViewController: UIViewController {
}
func loadPlayer() {
guard !playerLoaded else {
return
}
playerState.player = player
playerViewController.player = playerState.player
playerState.loadVideo(video)
@ -42,6 +44,16 @@ final class PlayerViewController: UIViewController {
#if os(tvOS)
present(playerViewController, animated: false)
#else
embedViewController()
#endif
playerViewController.allowsPictureInPicturePlayback = true
playerViewController.delegate = self
playerLoaded = true
}
#if !os(tvOS)
func embedViewController() {
playerViewController.exitsFullScreenWhenPlaybackEnds = true
playerViewController.view.frame = view.bounds
@ -49,11 +61,8 @@ final class PlayerViewController: UIViewController {
view.addSubview(playerViewController.view)
playerViewController.didMove(toParent: self)
#endif
playerViewController.delegate = self
playerLoaded = true
}
}
#endif
}
extension PlayerViewController: AVPlayerViewControllerDelegate {
@ -61,8 +70,12 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
true
}
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_: AVPlayerViewController) -> Bool {
false
}
func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) {
playingFullScreen = false
playerState.playingOutsideViewController = false
dismiss(animated: false)
}
@ -70,7 +83,7 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
_: AVPlayerViewController,
willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
) {
playingFullScreen = true
playerState.playingOutsideViewController = true
}
func playerViewController(
@ -79,8 +92,16 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
) {
coordinator.animate(alongsideTransition: nil) { context in
if !context.isCancelled {
self.playingFullScreen = false
self.playerState.playingOutsideViewController = false
}
}
}
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {
playerState.playingOutsideViewController = true
}
func playerViewControllerWillStopPictureInPicture(_: AVPlayerViewController) {
playerState.playingOutsideViewController = false
}
}

10
iOS/Info.plist Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</dict>
</plist>

View File

@ -26,7 +26,7 @@ final class PlayerViewController: NSViewController {
playerState.player = player
playerView.player = playerState.player
playerView.controlsStyle = .floating
playerView.allowsPictureInPicturePlayback = true
playerView.showsFullScreenToggleButton = true
view = playerView

View File

@ -2,7 +2,9 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</dict>
</plist>