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? let maxResolution: Stream.Resolution?
var timeObserver: Any? var timeObserver: Any?
var playingOutsideViewController = false
init(_ video: Video? = nil, maxResolution: Stream.Resolution? = nil) { init(_ video: Video? = nil, maxResolution: Stream.Resolution? = nil) {
self.video = video self.video = video
self.maxResolution = maxResolution self.maxResolution = maxResolution
@ -222,6 +224,11 @@ final class PlayerState: ObservableObject {
fileprivate func destroyPlayer() { fileprivate func destroyPlayer() {
logger.critical("destroying player") 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?.currentItem?.tracks.forEach { $0.assetTrack?.asset?.cancelLoading() }
player?.replaceCurrentItem(with: nil) player?.replaceCurrentItem(with: nil)

View File

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

View File

@ -6,8 +6,6 @@ final class PlayerViewController: UIViewController {
var video: Video! var video: Video!
var playerLoaded = false var playerLoaded = false
var playingFullScreen = false
var player = AVPlayer() var player = AVPlayer()
var playerState: PlayerState! = PlayerState() var playerState: PlayerState! = PlayerState()
var playerViewController = AVPlayerViewController() var playerViewController = AVPlayerViewController()
@ -15,19 +13,19 @@ final class PlayerViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) 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) try? AVAudioSession.sharedInstance().setActive(true)
} }
override func viewDidDisappear(_ animated: Bool) { override func viewDidDisappear(_ animated: Bool) {
#if os(iOS) #if os(iOS)
if !playingFullScreen { if !playerState.playingOutsideViewController {
playerViewController.player?.replaceCurrentItem(with: nil) playerViewController.player?.replaceCurrentItem(with: nil)
playerViewController.player = nil playerViewController.player = nil
try? AVAudioSession.sharedInstance().setActive(false)
} }
#endif #endif
@ -35,6 +33,10 @@ final class PlayerViewController: UIViewController {
} }
func loadPlayer() { func loadPlayer() {
guard !playerLoaded else {
return
}
playerState.player = player playerState.player = player
playerViewController.player = playerState.player playerViewController.player = playerState.player
playerState.loadVideo(video) playerState.loadVideo(video)
@ -42,6 +44,16 @@ final class PlayerViewController: UIViewController {
#if os(tvOS) #if os(tvOS)
present(playerViewController, animated: false) present(playerViewController, animated: false)
#else #else
embedViewController()
#endif
playerViewController.allowsPictureInPicturePlayback = true
playerViewController.delegate = self
playerLoaded = true
}
#if !os(tvOS)
func embedViewController() {
playerViewController.exitsFullScreenWhenPlaybackEnds = true playerViewController.exitsFullScreenWhenPlaybackEnds = true
playerViewController.view.frame = view.bounds playerViewController.view.frame = view.bounds
@ -49,11 +61,8 @@ final class PlayerViewController: UIViewController {
view.addSubview(playerViewController.view) view.addSubview(playerViewController.view)
playerViewController.didMove(toParent: self) playerViewController.didMove(toParent: self)
#endif
playerViewController.delegate = self
playerLoaded = true
} }
#endif
} }
extension PlayerViewController: AVPlayerViewControllerDelegate { extension PlayerViewController: AVPlayerViewControllerDelegate {
@ -61,8 +70,12 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
true true
} }
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_: AVPlayerViewController) -> Bool {
false
}
func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) { func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) {
playingFullScreen = false playerState.playingOutsideViewController = false
dismiss(animated: false) dismiss(animated: false)
} }
@ -70,7 +83,7 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
_: AVPlayerViewController, _: AVPlayerViewController,
willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
) { ) {
playingFullScreen = true playerState.playingOutsideViewController = true
} }
func playerViewController( func playerViewController(
@ -79,8 +92,16 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
) { ) {
coordinator.animate(alongsideTransition: nil) { context in coordinator.animate(alongsideTransition: nil) { context in
if !context.isCancelled { 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 playerState.player = player
playerView.player = playerState.player playerView.player = playerState.player
playerView.controlsStyle = .floating playerView.allowsPictureInPicturePlayback = true
playerView.showsFullScreenToggleButton = true playerView.showsFullScreenToggleButton = true
view = playerView 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"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>LSRequiresIPhoneOS</key> <key>UIBackgroundModes</key>
<true/> <array>
<string>audio</string>
</array>
</dict> </dict>
</plist> </plist>