diff --git a/PlayerUI/Controllers/PUIPictureContainerViewController.swift b/PlayerUI/Controllers/PUIPictureContainerViewController.swift deleted file mode 100644 index 18296284..00000000 --- a/PlayerUI/Controllers/PUIPictureContainerViewController.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// PUIPictureContainerViewController.swift -// PlayerUI -// -// Created by Guilherme Rambo on 13/05/17. -// Copyright © 2017 Guilherme Rambo. All rights reserved. -// - -import Cocoa -import AVFoundation - -// swiftlint:disable:next type_name -protocol PUIPictureContainerViewControllerDelegate: AnyObject { - - func pictureContainerViewSuperviewDidChange(to superview: NSView?) - -} - -final class PUIPictureContainerViewController: NSViewController { - - weak var delegate: PUIPictureContainerViewControllerDelegate? - - let playerLayer: AVPlayerLayer - - init(playerLayer: AVPlayerLayer) { - self.playerLayer = playerLayer - - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - view = NSView() - view.wantsLayer = true - view.layer = PUIBoringLayer() - view.layer?.backgroundColor = NSColor.black.cgColor - - view.layer?.addSublayer(playerLayer) - - view.addObserver(self, forKeyPath: #keyPath(NSView.superview), options: [.new], context: nil) - } - - override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - guard let path = keyPath else { return } - switch path { - case #keyPath(NSView.superview): - viewDidMoveToSuperview() - default: - super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) - } - } - - func viewDidMoveToSuperview() { - delegate?.pictureContainerViewSuperviewDidChange(to: view.superview) - } - - override func viewDidLayout() { - super.viewDidLayout() - - playerLayer.frame = view.bounds - } - - deinit { - view.removeObserver(self, forKeyPath: #keyPath(NSView.superview)) - } -} diff --git a/PlayerUI/PiP Support/PIP.h b/PlayerUI/PiP Support/PIP.h deleted file mode 100644 index f1e16c67..00000000 --- a/PlayerUI/PiP Support/PIP.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// PIP.h -// PiP Client -// -// Created by Guilherme Rambo on 30/10/16. -// Copyright © 2016 Guilherme Rambo. All rights reserved. -// - -#import - -@class PIPViewController; - -@protocol PIPViewControllerDelegate - -@optional -- (void)pipActionStop:(PIPViewController *__nonnull)pip; -- (void)pipActionPause:(PIPViewController *__nonnull)pip; -- (void)pipActionPlay:(PIPViewController *__nonnull)pip; -- (void)pipActionReturn:(PIPViewController *__nonnull)pip; -- (void)pipDidClose:(PIPViewController *__nonnull)pip; -- (void)pipWillClose:(PIPViewController *__nonnull)pip; -@end - -@interface PIPViewController : NSViewController - -@property (nonatomic, weak) id __nullable delegate; -@property (nonatomic, assign) NSRect replacementRect; -@property (nonatomic, weak) NSWindow *__nullable replacementWindow; -@property (nonatomic, weak) NSView *__nullable replacementView; -@property (nonatomic, copy) NSString *__nullable name; -@property (nonatomic, assign) NSSize aspectRatio; - -- (void)presentViewControllerAsPictureInPicture:(__kindof NSViewController *__nonnull)controller; -- (void)setPlaying:(BOOL)playing; -- (BOOL)playing; - -- (instancetype __nonnull)init; - -@end diff --git a/PlayerUI/PlayerUI.h b/PlayerUI/PlayerUI.h index 4e1d9d7e..d3aee31f 100644 --- a/PlayerUI/PlayerUI.h +++ b/PlayerUI/PlayerUI.h @@ -15,5 +15,3 @@ FOUNDATION_EXPORT double PlayerUIVersionNumber; FOUNDATION_EXPORT const unsigned char PlayerUIVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import - -#import diff --git a/PlayerUI/Protocols/PUIPlayerViewDelegates.swift b/PlayerUI/Protocols/PUIPlayerViewDelegates.swift index b0231cc6..6440b595 100644 --- a/PlayerUI/Protocols/PUIPlayerViewDelegates.swift +++ b/PlayerUI/Protocols/PUIPlayerViewDelegates.swift @@ -8,14 +8,10 @@ import Cocoa -public enum PUIPiPExitReason { - case returnButton, exitButton -} - public protocol PUIPlayerViewDelegate: AnyObject { func playerViewWillEnterPictureInPictureMode(_ playerView: PUIPlayerView) - func playerViewWillExitPictureInPictureMode(_ playerView: PUIPlayerView, reason: PUIPiPExitReason) + func playerWillRestoreUserInterfaceForPictureInPictureStop(_ playerView: PUIPlayerView) func playerViewDidSelectAddAnnotation(_ playerView: PUIPlayerView, at timestamp: Double) func playerViewDidSelectToggleFullScreen(_ playerView: PUIPlayerView) func playerViewDidSelectLike(_ playerView: PUIPlayerView) diff --git a/PlayerUI/Views/PUIPlayerView.swift b/PlayerUI/Views/PUIPlayerView.swift index 6f08ee6c..748af663 100644 --- a/PlayerUI/Views/PUIPlayerView.swift +++ b/PlayerUI/Views/PUIPlayerView.swift @@ -9,33 +9,19 @@ import Cocoa import AVFoundation import os.log +import AVKit +import Combine public final class PUIPlayerView: NSView { private let log = OSLog(subsystem: "PlayerUI", category: "PUIPlayerView") + private var cancellables: Set = [] // MARK: - Public API public weak var delegate: PUIPlayerViewDelegate? - public internal(set) var isInPictureInPictureMode: Bool = false { - didSet { - guard isInPictureInPictureMode != oldValue else { return } - - pipButton.state = isInPictureInPictureMode ? .on : .off - - if isInPictureInPictureMode { - externalStatusController.providerIcon = .PUIPictureInPictureLarge - externalStatusController.providerName = "Picture in Picture" - externalStatusController.providerDescription = "Playing in Picture in Picture" - externalStatusController.view.isHidden = false - } else { - externalStatusController.view.isHidden = true - } - - invalidateTouchBar() - } - } + public var isInPictureInPictureMode: Bool { pipController?.isPictureInPictureActive == true } public weak var appearanceDelegate: PUIPlayerViewAppearanceDelegate? { didSet { @@ -71,9 +57,9 @@ public final class PUIPlayerView: NSView { teardown(player: oldPlayer) } - guard player != nil else { return } + guard let player else { return } - setupPlayer() + setupPlayer(player) } } @@ -86,10 +72,13 @@ public final class PUIPlayerView: NSView { public var mediaTitle: String? public var mediaIsLiveStream: Bool = false - var pictureContainer: PUIPictureContainerViewController! - public init(player: AVPlayer) { self.player = player + if AVPictureInPictureController.isPictureInPictureSupported() { + self.pipController = AVPictureInPictureController(contentSource: .init(playerLayer: playerLayer)) + } else { + self.pipController = nil + } super.init(frame: .zero) @@ -97,7 +86,7 @@ public final class PUIPlayerView: NSView { layer = PUIBoringLayer() layer?.backgroundColor = NSColor.black.cgColor - setupPlayer() + setupPlayer(player) setupControls() } @@ -216,42 +205,55 @@ public final class PUIPlayerView: NSView { return player?.currentItem?.asset } - private var playerLayer = PUIBoringPlayerLayer() + private let playerLayer = PUIBoringPlayerLayer() + + private func setupPlayer(_ player: AVPlayer) { + if let pipController { + pipPossibleObservation = pipController.observe( + \AVPictureInPictureController.isPictureInPicturePossible, options: [.initial, .new] + ) { [weak self] _, change in + self?.pipButton.isEnabled = change.newValue ?? false + } + pipController.delegate = self + } else { + pipButton.isEnabled = false + } - private func setupPlayer() { elapsedTimeLabel.stringValue = elapsedTimeInitialValue remainingTimeLabel.stringValue = remainingTimeInitialValue timelineView.resetUI() - guard let player = player else { return } - playerLayer.player = player playerLayer.videoGravity = .resizeAspect - if pictureContainer == nil { - pictureContainer = PUIPictureContainerViewController(playerLayer: playerLayer) - pictureContainer.delegate = self - pictureContainer.view.frame = bounds - pictureContainer.view.autoresizingMask = [.width, .height] + let options: NSKeyValueObservingOptions = [.initial, .new] + player.publisher(for: \.status, options: options).sink { [weak self] change in + self?.playerStatusChanged() + }.store(in: &cancellables) + player.publisher(for: \.volume, options: options).sink { [weak self] change in + self?.playerVolumeChanged() + }.store(in: &cancellables) + player.publisher(for: \.rate, options: options).sink { [weak self] change in + self?.updatePlayingState() + self?.updatePowerAssertion() + }.store(in: &cancellables) + player.publisher(for: \.currentItem, options: options).sink { [weak self] change in + if let playerItem = self?.player?.currentItem { + playerItem.audioTimePitchAlgorithm = .timeDomain + } + }.store(in: &cancellables) + player.publisher(for: \.currentItem?.loadedTimeRanges, options: [.initial, .new]).sink { [weak self] change in + self?.updateBufferedSegments() + }.store(in: &cancellables) - addSubview(pictureContainer.view) + Task { [weak self] in + guard let asset = self?.asset else { return } + async let duration = asset.load(.duration) + async let legible = asset.loadMediaSelectionGroup(for: .legible) + self?.timelineView.mediaDuration = Double(CMTimeGetSeconds(try await duration)) + self?.updateSubtitleSelectionMenu(subtitlesGroup: try await legible) } - player.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.initial, .new], context: nil) - player.addObserver(self, forKeyPath: #keyPath(AVPlayer.volume), options: [.initial, .new], context: nil) - player.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: [.initial, .new], context: nil) - player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: [.initial, .new], context: nil) - player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.loadedTimeRanges), options: [.initial, .new], context: nil) - - asset?.loadValuesAsynchronously(forKeys: ["duration"], completionHandler: durationBecameAvailable) - - asset?.loadValuesAsynchronously(forKeys: ["availableMediaCharacteristicsWithMediaSelectionOptions"], completionHandler: { [weak self] in - - if self?.asset?.statusOfValue(forKey: "availableMediaCharacteristicsWithMediaSelectionOptions", error: nil) == .loaded { - DispatchQueue.main.async { self?.updateSubtitleSelectionMenu() } - } - }) - playerTimeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(0.5, preferredTimescale: 9000), queue: .main) { [weak self] currentTime in self?.playerTimeDidChange(time: currentTime) } @@ -264,40 +266,11 @@ public final class PUIPlayerView: NSView { oldValue.pause() oldValue.cancelPendingPrerolls() + cancellables.removeAll() if let observer = playerTimeObserver { oldValue.removeTimeObserver(observer) playerTimeObserver = nil } - - oldValue.removeObserver(self, forKeyPath: #keyPath(AVPlayer.status)) - oldValue.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate)) - oldValue.removeObserver(self, forKeyPath: #keyPath(AVPlayer.volume)) - oldValue.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.loadedTimeRanges)) - oldValue.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem)) - } - - public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - DispatchQueue.main.async { - guard let keyPath = keyPath else { return } - - switch keyPath { - case #keyPath(AVPlayer.status): - self.playerStatusChanged() - case #keyPath(AVPlayer.currentItem.loadedTimeRanges): - self.updateBufferedSegments() - case #keyPath(AVPlayer.volume): - self.playerVolumeChanged() - case #keyPath(AVPlayer.rate): - self.updatePlayingState() - self.updatePowerAssertion() - case #keyPath(AVPlayer.currentItem): - if let playerItem = self.player?.currentItem { - playerItem.audioTimePitchAlgorithm = .timeDomain - } - default: - super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) - } - } } private func playerVolumeChanged() { @@ -340,8 +313,6 @@ public final class PUIPlayerView: NSView { } fileprivate func updatePlayingState() { - pipController?.setPlaying(isPlaying) - if isPlaying { playButton.image = .PUIPause } else { @@ -384,14 +355,6 @@ public final class PUIPlayerView: NSView { } } - private func durationBecameAvailable() { - guard let duration = asset?.duration else { return } - - DispatchQueue.main.async { - self.timelineView.mediaDuration = Double(CMTimeGetSeconds(duration)) - } - } - fileprivate func playerTimeDidChange(time: CMTime) { guard let player = player else { return } guard player.hasValidMediaDuration else { return } @@ -645,10 +608,12 @@ public final class PUIPlayerView: NSView { let b = PUIButton(frame: .zero) b.isToggle = true - b.image = .PUIPictureInPicture + b.image = AVPictureInPictureController.pictureInPictureButtonStartImage + b.alternateImage = AVPictureInPictureController.pictureInPictureButtonStopImage b.target = self b.action = #selector(togglePip) b.toolTip = "Toggle picture in picture" + b.isEnabled = false return b }() @@ -660,6 +625,17 @@ public final class PUIPlayerView: NSView { private func setupControls() { externalStatusController.view.isHidden = true externalStatusController.view.translatesAutoresizingMaskIntoConstraints = false + let playerView = NSView() + playerView.translatesAutoresizingMaskIntoConstraints = false + playerView.wantsLayer = true + playerView.layer = playerLayer + playerLayer.backgroundColor = .clear + addSubview(playerView) + playerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + playerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + playerView.topAnchor.constraint(equalTo: topAnchor).isActive = true + playerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + addSubview(externalStatusController.view) externalStatusController.view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true externalStatusController.view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true @@ -1018,9 +994,9 @@ public final class PUIPlayerView: NSView { @IBAction public func togglePip(_ sender: NSView?) { if isInPictureInPictureMode { - exitPictureInPictureMode() + pipController?.stopPictureInPicture() } else { - enterPictureInPictureMode() + pipController?.startPictureInPicture() } } @@ -1062,10 +1038,9 @@ public final class PUIPlayerView: NSView { private var subtitlesMenu: NSMenu? private var subtitlesGroup: AVMediaSelectionGroup? - private func updateSubtitleSelectionMenu() { - guard let playerItem = player?.currentItem else { return } - - guard let subtitlesGroup = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else { + @MainActor + private func updateSubtitleSelectionMenu(subtitlesGroup: AVMediaSelectionGroup?) { + guard let subtitlesGroup else { subtitlesButton.isHidden = true return } @@ -1092,16 +1067,15 @@ public final class PUIPlayerView: NSView { guard let option = sender.representedObject as? AVMediaSelectionOption else { return } // reset all item's states - sender.menu?.items.forEach({ $0.state = .on }) + sender.menu?.items.forEach({ $0.state = .off }) + // The current language was clicked again, turn subtitles off if option.extendedLanguageTag == player?.currentItem?.currentMediaSelection.selectedMediaOption(in: subtitlesGroup)?.extendedLanguageTag { player?.currentItem?.select(nil, in: subtitlesGroup) - sender.state = .off return } player?.currentItem?.select(option, in: subtitlesGroup) - sender.state = .on } @@ -1151,16 +1125,22 @@ public final class PUIPlayerView: NSView { case l static func fromEvent(_ event: NSEvent) -> KeyCommands? { + // `keyCode` and `charactersIgnoringModifiers` both will raise exceptions if called on + // events that are not key events + guard event.type == .keyDown else { return nil } + switch event.keyCode { case 123: return .leftArrow case 124: return .rightArrow default: break } - + + // Correctly support keyboard localization, different keyboard layouts produce different + // characters for the same `keyCode` guard let character = event.charactersIgnoringModifiers else { return nil } - + switch character { case " ": return .spaceBar case "-": return .minus @@ -1266,31 +1246,8 @@ public final class PUIPlayerView: NSView { } } - fileprivate var pipController: PIPViewController? - - fileprivate func enterPictureInPictureMode() { - delegate?.playerViewWillEnterPictureInPictureMode(self) - - snapshotPlayer { [weak self] image in - self?.externalStatusController.snapshot = image - } - - pipController = PIPViewController() - pipController?.delegate = self - pipController?.setPlaying(isPlaying) - pipController?.aspectRatio = currentPresentationSize ?? NSSize(width: 640, height: 360) - pipController?.view.layer?.backgroundColor = NSColor.black.cgColor - - pipController?.presentAsPicture(inPicture: pictureContainer) - - isInPictureInPictureMode = true - } - - fileprivate func exitPictureInPictureMode() { - if pictureContainer.presentingViewController == pipController { - pipController?.dismiss(pictureContainer) - } - } + private let pipController: AVPictureInPictureController? + private var pipPossibleObservation: Any? // MARK: - Visibility management @@ -1643,64 +1600,68 @@ extension PUIPlayerView: PUIExternalPlaybackConsumer { // MARK: - PiP delegate -extension PUIPlayerView: PIPViewControllerDelegate, PUIPictureContainerViewControllerDelegate { - - public func pipActionStop(_ pip: PIPViewController) { - pause(pip) - delegate?.playerViewWillExitPictureInPictureMode(self, reason: .exitButton) - } +extension PUIPlayerView: AVPictureInPictureControllerDelegate { - public func pipActionReturn(_ pip: PIPViewController) { - delegate?.playerViewWillExitPictureInPictureMode(self, reason: .returnButton) + // Start - if !NSApp.isActive { - NSApp.activate(ignoringOtherApps: true) - } - - if let window = lastKnownWindow { - window.makeKeyAndOrderFront(pip) + public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + delegate?.playerViewWillEnterPictureInPictureMode(self) - if window.isMiniaturized { - window.deminiaturize(nil) - } + snapshotPlayer { [weak self] image in + self?.externalStatusController.snapshot = image } } - public func pipActionPause(_ pip: PIPViewController) { - pause(pip) + public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + pipButton.state = .on + externalStatusController.providerIcon = .PUIPictureInPictureLarge + externalStatusController.providerName = "Picture in Picture" + externalStatusController.providerDescription = "Playing in Picture in Picture" + externalStatusController.view.isHidden = false + + invalidateTouchBar() } - public func pipActionPlay(_ pip: PIPViewController) { - play(pip) + public func pictureInPictureController( + _ pictureInPictureController: AVPictureInPictureController, + failedToStartPictureInPictureWithError error: Error + ) { + os_log(.error, log: log, "Failed to start PiP \(error, privacy: .public)") } - public func pipDidClose(_ pip: PIPViewController) { - pictureContainer.view.frame = bounds + // Stop - addSubview(pictureContainer.view, positioned: .below, relativeTo: scrimContainerView) + // Called 1st + public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { - isInPictureInPictureMode = false - pipController = nil } - public func pipWillClose(_ pip: PIPViewController) { - pip.replacementRect = frame - pip.replacementView = self - pip.replacementWindow = lastKnownWindow - } + // Called 2nd, not called when the exit button is pressed + public func pictureInPictureController( + _ pictureInPictureController: AVPictureInPictureController, + restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void + ) { + delegate?.playerWillRestoreUserInterfaceForPictureInPictureStop(self) - func pictureContainerViewSuperviewDidChange(to superview: NSView?) { - guard let superview = superview else { return } + if !NSApp.isActive { + NSApp.activate(ignoringOtherApps: true) + } - pictureContainer.view.frame = superview.bounds + if let window = lastKnownWindow { + window.makeKeyAndOrderFront(pictureInPictureController) - if superview == self, pipController != nil { - if pictureContainer.presentingViewController == pipController { - pipController?.dismiss(pictureContainer) + if window.isMiniaturized { + window.deminiaturize(nil) } - - pipController = nil } + + completionHandler(true) } + // Called Last + public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + pipButton.state = .off + externalStatusController.view.isHidden = true + invalidateTouchBar() + } } diff --git a/PlayerUI/Views/PUITimelineView.swift b/PlayerUI/Views/PUITimelineView.swift index c234cf0e..c0e0be84 100644 --- a/PlayerUI/Views/PUITimelineView.swift +++ b/PlayerUI/Views/PUITimelineView.swift @@ -61,11 +61,9 @@ public final class PUITimelineView: NSView { } } - public var mediaDuration: Double = 0 { - didSet { - needsLayout = true - } - } + @MainActor + @Invalidating(.layout) + public var mediaDuration: Double = 0 public var hasValidMediaDuration: Bool { return AVPlayer.validateMediaDurationWithSeconds(mediaDuration) diff --git a/WWDC.xcodeproj/project.pbxproj b/WWDC.xcodeproj/project.pbxproj index 24e55a0f..79450fdb 100644 --- a/WWDC.xcodeproj/project.pbxproj +++ b/WWDC.xcodeproj/project.pbxproj @@ -147,12 +147,10 @@ DDF7219D1ECA12780054C503 /* PlayerUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDF721961ECA12780054C503 /* PlayerUI.framework */; }; DDF7219E1ECA12780054C503 /* PlayerUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DDF721961ECA12780054C503 /* PlayerUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DDF721C71ECA12A40054C503 /* PUIExternalPlaybackStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721A51ECA12A40054C503 /* PUIExternalPlaybackStatusViewController.swift */; }; - DDF721C81ECA12A40054C503 /* PUIPictureContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721A61ECA12A40054C503 /* PUIPictureContainerViewController.swift */; }; DDF721C91ECA12A40054C503 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721A81ECA12A40054C503 /* Colors.swift */; }; DDF721CA1ECA12A40054C503 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721A91ECA12A40054C503 /* Images.swift */; }; DDF721CB1ECA12A40054C503 /* Speeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721AA1ECA12A40054C503 /* Speeds.swift */; }; DDF721CC1ECA12A40054C503 /* PUIExternalPlaybackProviderRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721AC1ECA12A40054C503 /* PUIExternalPlaybackProviderRegistration.swift */; }; - DDF721CD1ECA12A40054C503 /* PIP.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF721AE1ECA12A40054C503 /* PIP.h */; settings = {ATTRIBUTES = (Public, ); }; }; DDF721CE1ECA12A40054C503 /* PUIExternalPlaybackConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721B01ECA12A40054C503 /* PUIExternalPlaybackConsumer.swift */; }; DDF721CF1ECA12A40054C503 /* PUIExternalPlaybackProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721B11ECA12A40054C503 /* PUIExternalPlaybackProvider.swift */; }; DDF721D01ECA12A40054C503 /* PUIPlayerViewDelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721B21ECA12A40054C503 /* PUIPlayerViewDelegates.swift */; }; @@ -415,12 +413,10 @@ DDF721981ECA12780054C503 /* PlayerUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlayerUI.h; sourceTree = ""; }; DDF721991ECA12780054C503 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DDF721A51ECA12A40054C503 /* PUIExternalPlaybackStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUIExternalPlaybackStatusViewController.swift; sourceTree = ""; }; - DDF721A61ECA12A40054C503 /* PUIPictureContainerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUIPictureContainerViewController.swift; sourceTree = ""; }; DDF721A81ECA12A40054C503 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; DDF721A91ECA12A40054C503 /* Images.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = ""; }; DDF721AA1ECA12A40054C503 /* Speeds.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Speeds.swift; sourceTree = ""; }; DDF721AC1ECA12A40054C503 /* PUIExternalPlaybackProviderRegistration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUIExternalPlaybackProviderRegistration.swift; sourceTree = ""; }; - DDF721AE1ECA12A40054C503 /* PIP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PIP.h; sourceTree = ""; }; DDF721B01ECA12A40054C503 /* PUIExternalPlaybackConsumer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUIExternalPlaybackConsumer.swift; sourceTree = ""; }; DDF721B11ECA12A40054C503 /* PUIExternalPlaybackProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUIExternalPlaybackProvider.swift; sourceTree = ""; }; DDF721B21ECA12A40054C503 /* PUIPlayerViewDelegates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUIPlayerViewDelegates.swift; sourceTree = ""; }; @@ -974,7 +970,6 @@ DDF721A41ECA12A40054C503 /* Controllers */, DDF721A71ECA12A40054C503 /* Definitions */, DDF721AB1ECA12A40054C503 /* Models */, - DDF721AD1ECA12A40054C503 /* PiP Support */, DDF721AF1ECA12A40054C503 /* Protocols */, DDF721B51ECA12A40054C503 /* Resources */, DDF721B81ECA12A40054C503 /* Util */, @@ -989,7 +984,6 @@ isa = PBXGroup; children = ( DDF721A51ECA12A40054C503 /* PUIExternalPlaybackStatusViewController.swift */, - DDF721A61ECA12A40054C503 /* PUIPictureContainerViewController.swift */, DDC6781E1EDB8EDA00A4E19C /* PUIAnnotationWindowController.swift */, ); path = Controllers; @@ -1013,14 +1007,6 @@ path = Models; sourceTree = ""; }; - DDF721AD1ECA12A40054C503 /* PiP Support */ = { - isa = PBXGroup; - children = ( - DDF721AE1ECA12A40054C503 /* PIP.h */, - ); - path = "PiP Support"; - sourceTree = ""; - }; DDF721AF1ECA12A40054C503 /* Protocols */ = { isa = PBXGroup; children = ( @@ -1174,7 +1160,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DDF721CD1ECA12A40054C503 /* PIP.h in Headers */, DDF7219A1ECA12780054C503 /* PlayerUI.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1557,7 +1542,6 @@ buildActionMask = 2147483647; files = ( DDF721D11ECA12A40054C503 /* PUITimelineAnnotation.swift in Sources */, - DDF721C81ECA12A40054C503 /* PUIPictureContainerViewController.swift in Sources */, DDF721D51ECA12A40054C503 /* AVPlayer+Validation.swift in Sources */, DDF721CA1ECA12A40054C503 /* Images.swift in Sources */, 4DA83FE222AC3F2F0062DB8B /* PUIVibrantBackgroundButton.swift in Sources */, diff --git a/WWDC/AppCoordinator+Shelf.swift b/WWDC/AppCoordinator+Shelf.swift index f7a20067..9071fffa 100644 --- a/WWDC/AppCoordinator+Shelf.swift +++ b/WWDC/AppCoordinator+Shelf.swift @@ -92,8 +92,7 @@ extension AppCoordinator: ShelfViewControllerDelegate { if currentPlayerController == nil { currentPlayerController = VideoPlayerViewController(player: playbackViewModel.player, session: viewModel) - currentPlayerController?.playerWillExitPictureInPicture = { [weak self] reason in - guard reason == .returnButton else { return } + currentPlayerController?.playerWillRestoreUserInterfaceForPictureInPictureStop = { [weak self] in self?.returnToPlayingSessionContext() } diff --git a/WWDC/LiveObserver.swift b/WWDC/LiveObserver.swift index 8e069199..e1fefec2 100644 --- a/WWDC/LiveObserver.swift +++ b/WWDC/LiveObserver.swift @@ -115,37 +115,27 @@ final class LiveObserver: NSObject { } } - setLiveFlag(false, for: notLiveAnymore) - setLiveFlag(true, for: liveInstances.toArray()) - os_log("There are %{public}d live instances. %{public}d instances are not live anymore", log: log, type: .debug, liveInstances.count, notLiveAnymore.count) + setLiveFlag(false, for: notLiveAnymore) + setLiveFlag(true, for: liveInstances.toArray()) let liveIdentifiers: [String] = liveInstances.map({ $0.identifier }) let notLiveAnymoreIdentifiers: [String] = notLiveAnymore.map({ $0.identifier }) if liveIdentifiers.count > 0 { os_log("The following sessions are currently live: %{public}@", log: log, type: .debug, liveIdentifiers.joined(separator: ",")) - } else { - os_log("There are no live sessions at the moment", log: log, type: .debug) } if notLiveAnymoreIdentifiers.count > 0 { os_log("The following sessions are NOT live anymore: %{public}@", log: log, type: .debug, notLiveAnymoreIdentifiers.joined(separator: ",")) - } else { - os_log("There are no sessions that were live and are not live anymore", log: log, type: .debug) } } private func setLiveFlag(_ value: Bool, for instances: [SessionInstance]) { - os_log("Setting live flag to %{public}@ for %{public}d instances", - log: log, - type: .info, - String(describing: value), instances.count) - storage.modify(instances) { bgInstances in bgInstances.forEach { instance in guard !instance.isForcedLive else { return } diff --git a/WWDC/VideoPlayerViewController.swift b/WWDC/VideoPlayerViewController.swift index 30889ba5..84e82bb3 100644 --- a/WWDC/VideoPlayerViewController.swift +++ b/WWDC/VideoPlayerViewController.swift @@ -50,7 +50,7 @@ final class VideoPlayerViewController: NSViewController { } } - var playerWillExitPictureInPicture: ((PUIPiPExitReason) -> Void)? + var playerWillRestoreUserInterfaceForPictureInPictureStop: (() -> Void)? var playerWillExitFullScreen: (() -> Void)? init(player: AVPlayer, session: SessionViewModel) { @@ -336,8 +336,8 @@ extension VideoPlayerViewController: PUIPlayerViewDelegate { playerView.snapshotPlayer(completion: completion) } - func playerViewWillExitPictureInPictureMode(_ playerView: PUIPlayerView, reason: PUIPiPExitReason) { - playerWillExitPictureInPicture?(reason) + func playerWillRestoreUserInterfaceForPictureInPictureStop(_ playerView: PUIPlayerView) { + playerWillRestoreUserInterfaceForPictureInPictureStop?() } func playerViewWillEnterPictureInPictureMode(_ playerView: PUIPlayerView) {