From 8d5a191779155df190835d16ee9510be2df0e924 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 18 Aug 2021 00:00:53 +0200 Subject: [PATCH] Add basic PiP support --- Model/PlayerState.swift | 7 ++++ Pearvidious.xcodeproj/project.pbxproj | 12 +++++++ Shared/PlayerViewController.swift | 51 +++++++++++++++++++-------- iOS/Info.plist | 10 ++++++ macOS/PlayerViewController.swift | 2 +- tvOS/Info.plist | 6 ++-- 6 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 iOS/Info.plist diff --git a/Model/PlayerState.swift b/Model/PlayerState.swift index b2d2ab02..fbcc8ec8 100644 --- a/Model/PlayerState.swift +++ b/Model/PlayerState.swift @@ -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) diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index 97a98503..777e4ed8 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -232,6 +232,7 @@ 37977582268922F600DD52A8 /* InvidiousAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvidiousAPI.swift; sourceTree = ""; }; 3797758A2689345500DD52A8 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; 379775922689365600DD52A8 /* Array+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Next.swift"; sourceTree = ""; }; + 37992DC726CC50BC003D4C27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37AAF27D26737323007FC770 /* PopularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularView.swift; sourceTree = ""; }; 37AAF27F26737550007FC770 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; 37AAF2892673AB89007FC770 /* ChannelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelView.swift; sourceTree = ""; }; @@ -356,6 +357,14 @@ name = Frameworks; sourceTree = ""; }; + 37992DC826CC50CD003D4C27 /* iOS */ = { + isa = PBXGroup; + children = ( + 37992DC726CC50BC003D4C27 /* Info.plist */, + ); + path = iOS; + sourceTree = ""; + }; 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; diff --git a/Shared/PlayerViewController.swift b/Shared/PlayerViewController.swift index 1f53ddcc..cb06aba9 100644 --- a/Shared/PlayerViewController.swift +++ b/Shared/PlayerViewController.swift @@ -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 + } } diff --git a/iOS/Info.plist b/iOS/Info.plist new file mode 100644 index 00000000..f753731e --- /dev/null +++ b/iOS/Info.plist @@ -0,0 +1,10 @@ + + + + + UIBackgroundModes + + audio + + + diff --git a/macOS/PlayerViewController.swift b/macOS/PlayerViewController.swift index c4bd994c..4135d3b3 100644 --- a/macOS/PlayerViewController.swift +++ b/macOS/PlayerViewController.swift @@ -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 diff --git a/tvOS/Info.plist b/tvOS/Info.plist index cc25efe1..f753731e 100644 --- a/tvOS/Info.plist +++ b/tvOS/Info.plist @@ -2,7 +2,9 @@ - LSRequiresIPhoneOS - + UIBackgroundModes + + audio +