From 5a303aa7878ef853571b15d59d8e500d8639a02d Mon Sep 17 00:00:00 2001 From: Guilherme Rambo Date: Thu, 30 May 2024 14:17:38 -0300 Subject: [PATCH 1/2] Updated project settings --- WWDC.xcodeproj/project.pbxproj | 2 +- WWDC.xcodeproj/xcshareddata/xcschemes/WWDC.xcscheme | 2 +- .../xcshareddata/xcschemes/WWDC_iCloud.xcscheme | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/WWDC.xcodeproj/project.pbxproj b/WWDC.xcodeproj/project.pbxproj index 0424f11c..916345b7 100644 --- a/WWDC.xcodeproj/project.pbxproj +++ b/WWDC.xcodeproj/project.pbxproj @@ -1289,7 +1289,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1530; ORGANIZATIONNAME = "Guilherme Rambo"; TargetAttributes = { DD36A4AB1E478C6900B2EA88 = { diff --git a/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC.xcscheme b/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC.xcscheme index 03d76857..cf29f437 100644 --- a/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC.xcscheme +++ b/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC.xcscheme @@ -1,6 +1,6 @@ + isEnabled = "YES"> + argument = "2024-06-10T17:10:00+00:00" + isEnabled = "YES"> From 3daf60699996ccb2dbb3fc61eac50ed9e035275e Mon Sep 17 00:00:00 2001 From: Guilherme Rambo Date: Thu, 30 May 2024 14:27:25 -0300 Subject: [PATCH 2/2] Removed custom external playback support --- ...etachedPlaybackStatusViewController.swift} | 4 +- PlayerUI/Definitions/Colors.swift | 3 - ...ExternalPlaybackProviderRegistration.swift | 15 - .../PUIExternalPlaybackConsumer.swift | 56 --- .../PUIExternalPlaybackProvider.swift | 72 ---- .../Protocols/PUIPlayerViewDelegates.swift | 1 - PlayerUI/Views/PUIPlayerView.swift | 312 +++------------ WWDC.xcodeproj/project.pbxproj | 40 +- .../xcschemes/WWDC_iCloud.xcscheme | 4 +- WWDC/AppCoordinator+Shelf.swift | 4 +- WWDC/ChromeCastPlaybackProvider.swift | 357 ------------------ WWDC/VideoPlayerViewController.swift | 8 - 12 files changed, 55 insertions(+), 821 deletions(-) rename PlayerUI/Controllers/{PUIExternalPlaybackStatusViewController.swift => PUIDetachedPlaybackStatusViewController.swift} (97%) delete mode 100644 PlayerUI/Models/PUIExternalPlaybackProviderRegistration.swift delete mode 100644 PlayerUI/Protocols/PUIExternalPlaybackConsumer.swift delete mode 100644 PlayerUI/Protocols/PUIExternalPlaybackProvider.swift delete mode 100644 WWDC/ChromeCastPlaybackProvider.swift diff --git a/PlayerUI/Controllers/PUIExternalPlaybackStatusViewController.swift b/PlayerUI/Controllers/PUIDetachedPlaybackStatusViewController.swift similarity index 97% rename from PlayerUI/Controllers/PUIExternalPlaybackStatusViewController.swift rename to PlayerUI/Controllers/PUIDetachedPlaybackStatusViewController.swift index d71a2f71..6de679f0 100644 --- a/PlayerUI/Controllers/PUIExternalPlaybackStatusViewController.swift +++ b/PlayerUI/Controllers/PUIDetachedPlaybackStatusViewController.swift @@ -1,5 +1,5 @@ // -// PUIExternalPlaybackStatusViewController.swift +// PUIDetachedPlaybackStatusViewController.swift // PlayerUI // // Created by Guilherme Rambo on 13/05/17. @@ -8,7 +8,7 @@ import Cocoa -class PUIExternalPlaybackStatusViewController: NSViewController { +final class PUIDetachedPlaybackStatusViewController: NSViewController { private lazy var context = CIContext(options: [.useSoftwareRenderer: true]) diff --git a/PlayerUI/Definitions/Colors.swift b/PlayerUI/Definitions/Colors.swift index 8334c56c..528f9de7 100644 --- a/PlayerUI/Definitions/Colors.swift +++ b/PlayerUI/Definitions/Colors.swift @@ -29,7 +29,4 @@ extension NSColor { return NSColor(calibratedRed: 1.00, green: 1.00, blue: 1.00, alpha: 1.00) } - static var externalPlaybackText: NSColor { - return #colorLiteral(red: 0.8980392156862745, green: 0.8980392156862745, blue: 0.8980392156862745, alpha: 1) - } } diff --git a/PlayerUI/Models/PUIExternalPlaybackProviderRegistration.swift b/PlayerUI/Models/PUIExternalPlaybackProviderRegistration.swift deleted file mode 100644 index 2b8c5c94..00000000 --- a/PlayerUI/Models/PUIExternalPlaybackProviderRegistration.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// PUIExternalPlaybackProviderRegistration.swift -// PlayerUI -// -// Created by Guilherme Rambo on 01/05/17. -// Copyright © 2017 Guilherme Rambo. All rights reserved. -// - -import Cocoa - -struct PUIExternalPlaybackProviderRegistration { - let provider: PUIExternalPlaybackProvider - var button: PUIButton - var menu: NSMenu -} diff --git a/PlayerUI/Protocols/PUIExternalPlaybackConsumer.swift b/PlayerUI/Protocols/PUIExternalPlaybackConsumer.swift deleted file mode 100644 index 92793600..00000000 --- a/PlayerUI/Protocols/PUIExternalPlaybackConsumer.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// PUIExternalPlaybackConsumer.swift -// PlayerUI -// -// Created by Guilherme Rambo on 01/05/17. -// Copyright © 2017 Guilherme Rambo. All rights reserved. -// - -import Foundation -import AVFoundation - -public protocol PUIExternalPlaybackConsumer: AnyObject { - - /// Tells the consumer that this provider's availability status has changed - /// - /// - Parameter provider: The provider that called the method - func externalPlaybackProviderDidChangeAvailabilityStatus(_ provider: PUIExternalPlaybackProvider) - - /// Tells the consumer that the provider's media status has changed - /// - /// - Parameter provider: The provider that called the method - func externalPlaybackProviderDidChangeMediaStatus(_ provider: PUIExternalPlaybackProvider) - - /// Tells the consumer that this provider's device selection menu has changed - /// - /// - Parameters: - /// - provider: The provider that called the method - /// - menu: The updated menu to be showed when the provider's icon is clicked - func externalPlaybackProvider(_ provider: PUIExternalPlaybackProvider, deviceSelectionMenuDidChangeWith menu: NSMenu) - - /// Tells the consumer that the media is now playing on one of the devices offered by the provider - /// - /// - Parameter provider: The provider that called the method - func externalPlaybackProviderDidBecomeCurrent(_ provider: PUIExternalPlaybackProvider) - - /// Tells the consumer that the current playback session for the provider is no longer valid - /// - /// - Parameter provider: The provider that called the method - func externalPlaybackProviderDidInvalidatePlaybackSession(_ provider: PUIExternalPlaybackProvider) - - /// The media for the remote URL to be played by the provider - var remoteMediaUrl: URL? { get } - - /// The URL for a poster image representing the current media - var mediaPosterUrl: URL? { get } - - /// The title for the program being played - var mediaTitle: String? { get } - - /// Whether the current media is a live stream - var mediaIsLiveStream: Bool { get } - - /// The `AVPlayer` instance the consumer is using to play its media - var player: AVPlayer? { get } - -} diff --git a/PlayerUI/Protocols/PUIExternalPlaybackProvider.swift b/PlayerUI/Protocols/PUIExternalPlaybackProvider.swift deleted file mode 100644 index d3c1c061..00000000 --- a/PlayerUI/Protocols/PUIExternalPlaybackProvider.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// PUIExternalPlaybackProvider.swift -// PlayerUI -// -// Created by Guilherme Rambo on 01/05/17. -// Copyright © 2017 Guilherme Rambo. All rights reserved. -// - -import Cocoa - -public struct PUIExternalPlaybackMediaStatus { - - /// The rate at which the media is playing (1 = playing. 0 = paused) - public var rate: Float - - /// The current timestamp the media is playing at (in seconds) - public var currentTime: Double - - /// The volume on the external device (between 0 and 1) - public var volume: Float - - public init(rate: Float = 0, volume: Float = 1, currentTime: Double = 0) { - self.rate = rate - self.volume = volume - self.currentTime = currentTime - } - -} - -public protocol PUIExternalPlaybackProvider: AnyObject { - - /// Initializes the external playback provider to start playing the media at the specified URL - /// - /// - Parameter consumer: The consumer that's going to be using this provider - init(consumer: PUIExternalPlaybackConsumer) - - /// Whether this provider only works with a remote URL or can be used with only the `AVPlayer` instance - var requiresRemoteMediaUrl: Bool { get } - - /// The name of the external playback system (ex: "AirPlay") - static var name: String { get } - - /// An image to be used as the icon in the UI - var icon: NSImage { get } - - /// A larger image to be used when the provider is current - var image: NSImage { get } - - /// The current media status - var status: PUIExternalPlaybackMediaStatus { get } - - /// Extra information to be displayed on-screen when this playback provider is current - var info: String { get } - - /// Return whether this playback system is available - var isAvailable: Bool { get } - - /// Tells the external playback provider to play - func play() - - /// Tells the external playback provider to pause - func pause() - - /// Tells the external playback provider to seek to the specified time (in seconds) - func seek(to timestamp: Double) - - /// Tells the external playback provider to change the volume on the device - /// - /// - Parameter volume: The volume (value between 0 and 1) - func setVolume(_ volume: Float) - -} diff --git a/PlayerUI/Protocols/PUIPlayerViewDelegates.swift b/PlayerUI/Protocols/PUIPlayerViewDelegates.swift index 6440b595..c7b8a008 100644 --- a/PlayerUI/Protocols/PUIPlayerViewDelegates.swift +++ b/PlayerUI/Protocols/PUIPlayerViewDelegates.swift @@ -27,7 +27,6 @@ public protocol PUIPlayerViewAppearanceDelegate: AnyObject { func playerViewShouldShowAnnotationControls(_ playerView: PUIPlayerView) -> Bool func playerViewShouldShowBackAndForwardControls(_ playerView: PUIPlayerView) -> Bool func playerViewShouldShowTimestampLabels(_ playerView: PUIPlayerView) -> Bool - func playerViewShouldShowExternalPlaybackControls(_ playerView: PUIPlayerView) -> Bool func playerViewShouldShowFullScreenButton(_ playerView: PUIPlayerView) -> Bool func playerViewShouldShowBackAndForward30SecondsButtons(_ playerView: PUIPlayerView) -> Bool diff --git a/PlayerUI/Views/PUIPlayerView.swift b/PlayerUI/Views/PUIPlayerView.swift index 47266a38..58ffdad6 100644 --- a/PlayerUI/Views/PUIPlayerView.swift +++ b/PlayerUI/Views/PUIPlayerView.swift @@ -107,14 +107,6 @@ public final class PUIPlayerView: NSView { } public var isPlaying: Bool { - if let externalProvider = currentExternalPlaybackProvider { - return !externalProvider.status.rate.isZero - } else { - return isInternalPlayerPlaying - } - } - - public var isInternalPlayerPlaying: Bool { guard let player = player else { return false } return !player.rate.isZero @@ -123,11 +115,7 @@ public final class PUIPlayerView: NSView { public var currentTimestamp: Double { guard let player = player else { return 0 } - if let externalProvider = currentExternalPlaybackProvider { - return Double(externalProvider.status.currentTime) - } else { - return Double(CMTimeGetSeconds(player.currentTime())) - } + return Double(CMTimeGetSeconds(player.currentTime())) } public var firstAnnotationBeforeCurrentTime: PUITimelineAnnotation? { @@ -146,7 +134,7 @@ public final class PUIPlayerView: NSView { didSet { guard let player = player else { return } - if playbackSpeed != oldValue, isPlaying && !isPlayingExternally { + if playbackSpeed != oldValue, isPlaying { player.rate = playbackSpeed.rawValue player.seek(to: player.currentTime()) // Helps the AV sync when speeds change with the TimeDomain algorithm enabled } @@ -159,37 +147,11 @@ public final class PUIPlayerView: NSView { } } - public var isPlayingExternally: Bool { - return currentExternalPlaybackProvider != nil - } - public var hideAllControls: Bool = false { didSet { controlsContainerView.isHidden = hideAllControls - extrasMenuContainerView.isHidden = hideAllControls - } - } - - // MARK: External playback - - fileprivate(set) var externalPlaybackProviders: [PUIExternalPlaybackProviderRegistration] = [] { - didSet { - updateExternalPlaybackMenus() - } - } - - public func registerExternalPlaybackProvider(_ provider: PUIExternalPlaybackProvider.Type) { - // prevent registering the same provider multiple times - guard !externalPlaybackProviders.contains(where: { type(of: $0.provider).name == provider.name }) else { - log.error("Tried to register provider \(provider.name, privacy: .public) which was already registered") - return + topTrailingMenuContainerView.isHidden = hideAllControls } - - let instance = provider.init(consumer: self) - let button = self.button(for: instance) - let registration = PUIExternalPlaybackProviderRegistration(provider: instance, button: button, menu: NSMenu()) - - externalPlaybackProviders.append(registration) } public func invalidateAppearance() { @@ -527,7 +489,7 @@ public final class PUIPlayerView: NSView { fileprivate var wasPlayingBeforeStartingInteractiveSeek = false - private var extrasMenuContainerView: NSStackView! + private var topTrailingMenuContainerView: NSStackView! fileprivate var scrimContainerView: PUIScrimView! private var controlsContainerView: NSStackView! private var volumeControlsContainerView: NSStackView! @@ -707,14 +669,14 @@ public final class PUIPlayerView: NSView { private lazy var videoLayoutGuide = NSLayoutGuide() - private var extrasMenuTopConstraint: NSLayoutConstraint! + private var topTrailingMenuTopConstraint: NSLayoutConstraint! - private lazy var externalStatusController = PUIExternalPlaybackStatusViewController() + private lazy var detachedStatusController = PUIDetachedPlaybackStatusViewController() private func setupControls() { addLayoutGuide(videoLayoutGuide) - externalStatusController.view.translatesAutoresizingMaskIntoConstraints = false + detachedStatusController.view.translatesAutoresizingMaskIntoConstraints = false let playerView = NSView() playerView.translatesAutoresizingMaskIntoConstraints = false playerView.wantsLayer = true @@ -726,13 +688,13 @@ public final class PUIPlayerView: NSView { playerView.topAnchor.constraint(equalTo: topAnchor).isActive = true playerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - externalStatusController.hide() + detachedStatusController.hide() - addSubview(externalStatusController.view) - externalStatusController.view.leadingAnchor.constraint(equalTo: videoLayoutGuide.leadingAnchor).isActive = true - externalStatusController.view.trailingAnchor.constraint(equalTo: videoLayoutGuide.trailingAnchor).isActive = true - externalStatusController.view.topAnchor.constraint(equalTo: videoLayoutGuide.topAnchor).isActive = true - externalStatusController.view.bottomAnchor.constraint(equalTo: videoLayoutGuide.bottomAnchor).isActive = true + addSubview(detachedStatusController.view) + detachedStatusController.view.leadingAnchor.constraint(equalTo: videoLayoutGuide.leadingAnchor).isActive = true + detachedStatusController.view.trailingAnchor.constraint(equalTo: videoLayoutGuide.trailingAnchor).isActive = true + detachedStatusController.view.topAnchor.constraint(equalTo: videoLayoutGuide.topAnchor).isActive = true + detachedStatusController.view.bottomAnchor.constraint(equalTo: videoLayoutGuide.bottomAnchor).isActive = true // Volume controls volumeControlsContainerView = NSStackView(views: [volumeButton, volumeSlider]) @@ -835,17 +797,16 @@ public final class PUIPlayerView: NSView { centerButtonsContainerView.leadingAnchor.constraint(equalTo: controlsContainerView.leadingAnchor).isActive = true centerButtonsContainerView.trailingAnchor.constraint(equalTo: controlsContainerView.trailingAnchor).isActive = true - // Extras menu (external playback, fullscreen button) - extrasMenuContainerView = NSStackView(views: [fullScreenButton]) - extrasMenuContainerView.orientation = .horizontal - extrasMenuContainerView.alignment = .centerY - extrasMenuContainerView.distribution = .equalSpacing - extrasMenuContainerView.spacing = 30 + topTrailingMenuContainerView = NSStackView(views: [fullScreenButton]) + topTrailingMenuContainerView.orientation = .horizontal + topTrailingMenuContainerView.alignment = .centerY + topTrailingMenuContainerView.distribution = .equalSpacing + topTrailingMenuContainerView.spacing = 30 - addSubview(extrasMenuContainerView) + addSubview(topTrailingMenuContainerView) - extrasMenuContainerView.trailingAnchor.constraint(equalTo: videoLayoutGuide.trailingAnchor, constant: -12).isActive = true - updateExtrasMenuPosition() + topTrailingMenuContainerView.trailingAnchor.constraint(equalTo: videoLayoutGuide.trailingAnchor, constant: -12).isActive = true + updateTopTrailingMenuPosition() speedButton.$speed.removeDuplicates().sink { [weak self] speed in guard let self else { return } @@ -908,8 +869,6 @@ public final class PUIPlayerView: NSView { forwardButton.action = #selector(goForwardInTime) forwardButton.toolTip = goForwardInTimeDescription - updateExternalPlaybackControlsAvailability() - fullScreenButton.isHidden = !d.playerViewShouldShowFullScreenButton(self) timelineView.isHidden = !d.playerViewShouldShowTimelineView(self) } @@ -928,13 +887,6 @@ public final class PUIPlayerView: NSView { self.addAnnotationButton.isEnabled = !tooCloseForComfort } - fileprivate func updateExternalPlaybackControlsAvailability() { - guard let d = appearanceDelegate else { return } - - let disableExternalPlayback = !d.playerViewShouldShowExternalPlaybackControls(self) - externalPlaybackProviders.forEach({ $0.button.isHidden = disableExternalPlayback }) - } - private var isDominantViewInWindow: Bool { guard let contentView = window?.contentView else { return false } guard contentView != self else { return true } @@ -942,42 +894,17 @@ public final class PUIPlayerView: NSView { return bounds.height >= contentView.bounds.height } - private func updateExtrasMenuPosition() { + private func updateTopTrailingMenuPosition() { let topConstant: CGFloat = isDominantViewInWindow ? 34 : 12 - if extrasMenuTopConstraint == nil { - extrasMenuTopConstraint = extrasMenuContainerView.topAnchor.constraint(equalTo: videoLayoutGuide.topAnchor, constant: topConstant) - extrasMenuTopConstraint.isActive = true + if topTrailingMenuTopConstraint == nil { + topTrailingMenuTopConstraint = topTrailingMenuContainerView.topAnchor.constraint(equalTo: videoLayoutGuide.topAnchor, constant: topConstant) + topTrailingMenuTopConstraint.isActive = true } else { - extrasMenuTopConstraint.constant = topConstant + topTrailingMenuTopConstraint.constant = topConstant } } - fileprivate func updateExternalPlaybackMenus() { - // clean menu - extrasMenuContainerView.arrangedSubviews.enumerated().forEach { idx, view in - guard idx < extrasMenuContainerView.arrangedSubviews.count - 1 else { return } - - extrasMenuContainerView.removeArrangedSubview(view) - } - - // repopulate - externalPlaybackProviders.filter({ $0.provider.isAvailable }).forEach { registration in - registration.button.menu = registration.menu - extrasMenuContainerView.insertArrangedSubview(registration.button, at: 0) - } - } - - private func button(for provider: PUIExternalPlaybackProvider) -> PUIButton { - let b = PUIButton(frame: .zero) - - b.image = provider.icon - b.toolTip = type(of: provider).name - b.showsMenuOnLeftClick = true - - return b - } - // MARK: - Control actions private var playerVolumeBeforeMuting: Float = 1.0 @@ -996,13 +923,7 @@ public final class PUIPlayerView: NSView { @IBAction func volumeSliderAction(_ sender: Any?) { guard let player = player else { return } - let v = Float(volumeSlider.doubleValue) - - if isPlayingExternally { - currentExternalPlaybackProvider?.setVolume(v) - } else { - player.volume = v - } + player.volume = Float(volumeSlider.doubleValue) } @IBAction public func togglePlaying(_ sender: Any?) { @@ -1016,11 +937,7 @@ public final class PUIPlayerView: NSView { } @IBAction public func pause(_ sender: Any?) { - if isPlayingExternally { - currentExternalPlaybackProvider?.pause() - } else { - player?.rate = 0 - } + player?.rate = 0 } @IBAction public func play(_ sender: Any?) { @@ -1034,16 +951,12 @@ public final class PUIPlayerView: NSView { player?.replaceCurrentItem(with: AVPlayerItem(asset: AVURLAsset(url: asset.url))) } - if isPlayingExternally { - currentExternalPlaybackProvider?.play() - } else { - guard let player = player else { return } - if player.hasFinishedPlaying { - seek(to: 0) - } - - player.rate = playbackSpeed.rawValue + guard let player = player else { return } + if player.hasFinishedPlaying { + seek(to: 0) } + + player.rate = playbackSpeed.rawValue } @IBAction public func previousAnnotation(_ sender: Any?) { @@ -1161,11 +1074,7 @@ public final class PUIPlayerView: NSView { private func seek(to time: CMTime) { guard time.isValid && time.isNumeric else { return } - if isPlayingExternally { - currentExternalPlaybackProvider?.seek(to: CMTimeGetSeconds(time)) - } else { - player?.seek(to: time) - } + player?.seek(to: time) } private func invalidateTouchBar(destructive: Bool = false) { @@ -1444,7 +1353,7 @@ public final class PUIPlayerView: NSView { ctx.duration = animated ? 0.4 : 0.0 scrimContainerView.animator().alphaValue = opacity controlsContainerView.animator().alphaValue = opacity - extrasMenuContainerView.animator().alphaValue = opacity + topTrailingMenuContainerView.animator().alphaValue = opacity }, completionHandler: nil) } @@ -1465,7 +1374,7 @@ public final class PUIPlayerView: NSView { super.viewDidMoveToWindow() resetMouseIdleTimer() - updateExtrasMenuPosition() + updateTopTrailingMenuPosition() if window != nil { lastKnownWindow = window @@ -1482,7 +1391,7 @@ public final class PUIPlayerView: NSView { @objc private func windowWillEnterFullScreen() { fullScreenButton.isHidden = true - updateExtrasMenuPosition() + updateTopTrailingMenuPosition() } @objc private func windowWillExitFullScreen() { @@ -1490,7 +1399,7 @@ public final class PUIPlayerView: NSView { fullScreenButton.isHidden = !d.playerViewShouldShowFullScreenButton(self) } - updateExtrasMenuPosition() + updateTopTrailingMenuPosition() } @objc private func windowDidBecomeMain() { @@ -1618,70 +1527,6 @@ public final class PUIPlayerView: NSView { } } - // MARK: - External playback state management - - private func unhighlightExternalPlaybackButtons() { - externalPlaybackProviders.forEach { registration in - registration.button.tintColor = .buttonColor - } - } - - fileprivate var currentExternalPlaybackProvider: PUIExternalPlaybackProvider? { - didSet { - if currentExternalPlaybackProvider != nil { - perform(#selector(transitionToExternalPlayback), with: nil, afterDelay: 0) - } else { - transitionToInternalPlayback() - } - } - } - - @objc private func transitionToExternalPlayback() { - guard let current = currentExternalPlaybackProvider else { - transitionToInternalPlayback() - return - } - - let currentProviderName = type(of: current).name - - unhighlightExternalPlaybackButtons() - - guard let registration = externalPlaybackProviders.first(where: { type(of: $0.provider).name == currentProviderName }) else { return } - - registration.button.tintColor = .playerHighlight - - snapshotPlayer { [weak self] image in - self?.externalStatusController.snapshot = image - } - - externalStatusController.providerIcon = current.image - externalStatusController.providerName = currentProviderName - externalStatusController.providerDescription = "Playing in \(currentProviderName)" + "\n" + current.info - externalStatusController.show() - - pipButton.isEnabled = false - subtitlesButton.isEnabled = false - speedButton.isEnabled = false - forwardButton.isEnabled = false - backButton.isEnabled = false - - controlsContainerView.alphaValue = 0.5 - } - - @objc private func transitionToInternalPlayback() { - unhighlightExternalPlaybackButtons() - - pipButton.isEnabled = true - subtitlesButton.isEnabled = true - speedButton.isEnabled = true - forwardButton.isEnabled = true - backButton.isEnabled = true - - controlsContainerView.alphaValue = 1 - - externalStatusController.hide() - } - } // MARK: - PUITimelineViewDelegate @@ -1708,11 +1553,7 @@ extension PUIPlayerView: PUITimelineViewDelegate { let targetTime = progress * Double(CMTimeGetSeconds(duration)) let time = CMTimeMakeWithSeconds(targetTime, preferredTimescale: duration.timescale) - if isPlayingExternally { - currentExternalPlaybackProvider?.seek(to: targetTime) - } else { - player?.seek(to: time) - } + player?.seek(to: time) } func timelineViewDidFinishInteractiveSeek() { @@ -1723,69 +1564,6 @@ extension PUIPlayerView: PUITimelineViewDelegate { } -// MARK: - External playback support - -extension PUIPlayerView: PUIExternalPlaybackConsumer { - - private func isCurrentProvider(_ provider: PUIExternalPlaybackProvider) -> Bool { - guard let currentProvider = currentExternalPlaybackProvider else { return false } - - return type(of: provider).name == type(of: currentProvider).name - } - - public func externalPlaybackProviderDidChangeMediaStatus(_ provider: PUIExternalPlaybackProvider) { - volumeSlider.doubleValue = Double(provider.status.volume) - - if let speed = PUIPlaybackSpeed(rawValue: provider.status.rate) { - playbackSpeed = speed - } - - let time = CMTimeMakeWithSeconds(Float64(provider.status.currentTime), preferredTimescale: 9000) - playerTimeDidChange(time: time) - - updatePlayingState() - } - - public func externalPlaybackProviderDidChangeAvailabilityStatus(_ provider: PUIExternalPlaybackProvider) { - updateExternalPlaybackMenus() - updateExternalPlaybackControlsAvailability() - - if !provider.isAvailable && isCurrentProvider(provider) { - // current provider got invalidated, go back to internal playback - currentExternalPlaybackProvider = nil - } - } - - public func externalPlaybackProviderDidInvalidatePlaybackSession(_ provider: PUIExternalPlaybackProvider) { - if isCurrentProvider(provider) { - let wasPlaying = !provider.status.rate.isZero - - // provider session invalidated, go back to internal playback - currentExternalPlaybackProvider = nil - - if wasPlaying { - player?.play() - updatePlayingState() - } - } - } - - public func externalPlaybackProvider(_ provider: PUIExternalPlaybackProvider, deviceSelectionMenuDidChangeWith menu: NSMenu) { - guard let registrationIndex = externalPlaybackProviders.firstIndex(where: { type(of: $0.provider).name == type(of: provider).name }) else { return } - - externalPlaybackProviders[registrationIndex].menu = menu - } - - public func externalPlaybackProviderDidBecomeCurrent(_ provider: PUIExternalPlaybackProvider) { - if isInternalPlayerPlaying { - player?.rate = 0 - } - - currentExternalPlaybackProvider = provider - } - -} - // MARK: - PiP delegate extension PUIPlayerView: AVPictureInPictureControllerDelegate { @@ -1796,13 +1574,13 @@ extension PUIPlayerView: AVPictureInPictureControllerDelegate { delegate?.playerViewWillEnterPictureInPictureMode(self) snapshotPlayer { [weak self] image in - self?.externalStatusController.snapshot = image + self?.detachedStatusController.snapshot = image } - externalStatusController.providerIcon = .PUIPictureInPictureLarge.withPlayerMetrics(.large) - externalStatusController.providerName = "Picture in Picture" - externalStatusController.providerDescription = "Playing in Picture in Picture" - externalStatusController.show() + detachedStatusController.providerIcon = .PUIPictureInPictureLarge.withPlayerMetrics(.large) + detachedStatusController.providerName = "Picture in Picture" + detachedStatusController.providerDescription = "Playing in Picture in Picture" + detachedStatusController.show() } public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { @@ -1851,7 +1629,7 @@ extension PUIPlayerView: AVPictureInPictureControllerDelegate { // Called Last public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { pipButton.state = .off - externalStatusController.hide() + detachedStatusController.hide() invalidateTouchBar() } } diff --git a/WWDC.xcodeproj/project.pbxproj b/WWDC.xcodeproj/project.pbxproj index 916345b7..39721228 100644 --- a/WWDC.xcodeproj/project.pbxproj +++ b/WWDC.xcodeproj/project.pbxproj @@ -80,7 +80,6 @@ DD90CDC81ED77A3900CADE86 /* SearchFiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90CDC71ED77A3900CADE86 /* SearchFiltersViewController.swift */; }; DD90CDCB1ED77A4800CADE86 /* FilterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90CDCA1ED77A4800CADE86 /* FilterType.swift */; }; DD90CDCD1ED7A5ED00CADE86 /* SearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90CDCC1ED7A5ED00CADE86 /* SearchCoordinator.swift */; }; - DD9301C01EE3212D00BE724B /* ChromeCastPlaybackProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9301BF1EE3212D00BE724B /* ChromeCastPlaybackProvider.swift */; }; DD9564231ED27FBE00051D07 /* NSImage+Thumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9564221ED27FBE00051D07 /* NSImage+Thumbnail.swift */; }; DDA50E3524AA5090007C77C6 /* Boot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA50E3424AA5090007C77C6 /* Boot.swift */; }; DDA5E4941EDCD6E7003B1780 /* WhiteSpinner.car in Resources */ = {isa = PBXBuildFile; fileRef = DDA5E4931EDCD6E7003B1780 /* WhiteSpinner.car */; }; @@ -148,13 +147,10 @@ DDF7219A1ECA12780054C503 /* PlayerUI.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF721981ECA12780054C503 /* PlayerUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; + DDF721C71ECA12A40054C503 /* PUIDetachedPlaybackStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721A51ECA12A40054C503 /* PUIDetachedPlaybackStatusViewController.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 */; }; - 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 */; }; DDF721D11ECA12A40054C503 /* PUITimelineAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721B31ECA12A40054C503 /* PUITimelineAnnotation.swift */; }; DDF721D21ECA12A40054C503 /* PUITimelineDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF721B41ECA12A40054C503 /* PUITimelineDelegate.swift */; }; @@ -351,7 +347,6 @@ DD90CDC71ED77A3900CADE86 /* SearchFiltersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchFiltersViewController.swift; sourceTree = ""; }; DD90CDCA1ED77A4800CADE86 /* FilterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterType.swift; sourceTree = ""; }; DD90CDCC1ED7A5ED00CADE86 /* SearchCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchCoordinator.swift; sourceTree = ""; }; - DD9301BF1EE3212D00BE724B /* ChromeCastPlaybackProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChromeCastPlaybackProvider.swift; sourceTree = ""; }; DD9564221ED27FBE00051D07 /* NSImage+Thumbnail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSImage+Thumbnail.swift"; sourceTree = ""; }; DDA50E3424AA5090007C77C6 /* Boot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Boot.swift; sourceTree = ""; }; DDA5E4931EDCD6E7003B1780 /* WhiteSpinner.car */ = {isa = PBXFileReference; lastKnownFileType = file; name = WhiteSpinner.car; path = Design/WhiteSpinner.car; sourceTree = SOURCE_ROOT; }; @@ -422,13 +417,10 @@ DDF721961ECA12780054C503 /* PlayerUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayerUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + DDF721A51ECA12A40054C503 /* PUIDetachedPlaybackStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUIDetachedPlaybackStatusViewController.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 = ""; }; - 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 = ""; }; DDF721B31ECA12A40054C503 /* PUITimelineAnnotation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUITimelineAnnotation.swift; sourceTree = ""; }; DDF721B41ECA12A40054C503 /* PUITimelineDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PUITimelineDelegate.swift; sourceTree = ""; }; @@ -589,7 +581,6 @@ DD59106E1ECA0C2F003C32A4 /* WWDC.entitlements */, F4D0F0372A20161400C74B50 /* Config */, F474DECA2673800000B28B31 /* SharePlay */, - DD9301BE1EE3210F00BE724B /* Playback Support */, DD34A7991EC3CD4F00E0B575 /* Definitions */, DDB352851EC7C75100254815 /* Helpers */, DD7F387E1EAC15A1002D8C00 /* Util */, @@ -833,14 +824,6 @@ name = Search; sourceTree = ""; }; - DD9301BE1EE3210F00BE724B /* Playback Support */ = { - isa = PBXGroup; - children = ( - DD9301BF1EE3212D00BE724B /* ChromeCastPlaybackProvider.swift */, - ); - name = "Playback Support"; - sourceTree = ""; - }; DDAE7C531E5BC6CD00CEA205 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -990,7 +973,6 @@ DDBA0D77208D21920075670F /* MediaPlayer Support */, DDF721A41ECA12A40054C503 /* Controllers */, DDF721A71ECA12A40054C503 /* Definitions */, - DDF721AB1ECA12A40054C503 /* Models */, DDF721AF1ECA12A40054C503 /* Protocols */, DDF721B51ECA12A40054C503 /* Resources */, DDF721B81ECA12A40054C503 /* Util */, @@ -1004,7 +986,7 @@ DDF721A41ECA12A40054C503 /* Controllers */ = { isa = PBXGroup; children = ( - DDF721A51ECA12A40054C503 /* PUIExternalPlaybackStatusViewController.swift */, + DDF721A51ECA12A40054C503 /* PUIDetachedPlaybackStatusViewController.swift */, DDC6781E1EDB8EDA00A4E19C /* PUIAnnotationWindowController.swift */, ); path = Controllers; @@ -1020,19 +1002,9 @@ path = Definitions; sourceTree = ""; }; - DDF721AB1ECA12A40054C503 /* Models */ = { - isa = PBXGroup; - children = ( - DDF721AC1ECA12A40054C503 /* PUIExternalPlaybackProviderRegistration.swift */, - ); - path = Models; - sourceTree = ""; - }; DDF721AF1ECA12A40054C503 /* Protocols */ = { isa = PBXGroup; children = ( - DDF721B01ECA12A40054C503 /* PUIExternalPlaybackConsumer.swift */, - DDF721B11ECA12A40054C503 /* PUIExternalPlaybackProvider.swift */, DDF721B21ECA12A40054C503 /* PUIPlayerViewDelegates.swift */, DDF721B31ECA12A40054C503 /* PUITimelineAnnotation.swift */, DDF721B41ECA12A40054C503 /* PUITimelineDelegate.swift */, @@ -1509,7 +1481,6 @@ DD34A79B1EC3CD5900E0B575 /* Constants.swift in Sources */, 4DA9C4D120EC098800710354 /* DownloadsManagementViewController.swift in Sources */, DD90CDCD1ED7A5ED00CADE86 /* SearchCoordinator.swift in Sources */, - DD9301C01EE3212D00BE724B /* ChromeCastPlaybackProvider.swift in Sources */, DDB28F6F1EACFCDB0077703F /* VibrantButton.swift in Sources */, 914367202A4C6B0E004E4392 /* Sequence+GroupedBy.swift in Sources */, 4D7482CA20FF735D008D156C /* WWDCWindowController.swift in Sources */, @@ -1578,17 +1549,14 @@ DDF721D51ECA12A40054C503 /* AVPlayer+Validation.swift in Sources */, DDF721CA1ECA12A40054C503 /* Images.swift in Sources */, 4DA83FE222AC3F2F0062DB8B /* PUIVibrantBackgroundButton.swift in Sources */, - DDF721CE1ECA12A40054C503 /* PUIExternalPlaybackConsumer.swift in Sources */, DDC970BF209291630072B822 /* PUITouchBarController.swift in Sources */, DDF721DA1ECA12A40054C503 /* PUIAnnotationLayer.swift in Sources */, 4DBFA4DA20E160CB00BDF34B /* AVAsset+AsyncHelpers.swift in Sources */, - DDF721CC1ECA12A40054C503 /* PUIExternalPlaybackProviderRegistration.swift in Sources */, DDF721C91ECA12A40054C503 /* Colors.swift in Sources */, DDED75571ED3C1BF000BA817 /* PUIScrimView.swift in Sources */, F422B8B02C079DEA00C4B337 /* PUISettings.swift in Sources */, DDF721D01ECA12A40054C503 /* PUIPlayerViewDelegates.swift in Sources */, DDBA0D79208D21BD0075670F /* PUINowPlayingInfoCoordinator.swift in Sources */, - DDF721CF1ECA12A40054C503 /* PUIExternalPlaybackProvider.swift in Sources */, DDF721DE1ECA12A40054C503 /* PUIPlayerView.swift in Sources */, F4F189782C0774BE006EA9A2 /* PUIPlaybackSpeedToggle.swift in Sources */, F4F1897A2C0775C5006EA9A2 /* NumericContentTransition.swift in Sources */, @@ -1597,7 +1565,7 @@ DDF721D81ECA12A40054C503 /* NSWindow+Snapshot.swift in Sources */, DDBA0D7B208D3DE70075670F /* PUINowPlayingInfo.swift in Sources */, DDC678241EDBA25A00A4E19C /* PUIAnnotationWindow.swift in Sources */, - DDF721C71ECA12A40054C503 /* PUIExternalPlaybackStatusViewController.swift in Sources */, + DDF721C71ECA12A40054C503 /* PUIDetachedPlaybackStatusViewController.swift in Sources */, DDF721E11ECA12A40054C503 /* PUITimelineView.swift in Sources */, DDF721DF1ECA12A40054C503 /* PUIPlayerWindow.swift in Sources */, DDF721CB1ECA12A40054C503 /* Speeds.swift in Sources */, diff --git a/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC_iCloud.xcscheme b/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC_iCloud.xcscheme index 37804a29..fb15045b 100644 --- a/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC_iCloud.xcscheme +++ b/WWDC.xcodeproj/xcshareddata/xcschemes/WWDC_iCloud.xcscheme @@ -151,11 +151,11 @@ + isEnabled = "NO"> + isEnabled = "NO"> diff --git a/WWDC/AppCoordinator+Shelf.swift b/WWDC/AppCoordinator+Shelf.swift index e1c98068..585fecc4 100644 --- a/WWDC/AppCoordinator+Shelf.swift +++ b/WWDC/AppCoordinator+Shelf.swift @@ -38,8 +38,8 @@ extension AppCoordinator: ShelfViewControllerDelegate { // Everything after this point is for automatically entering PiP - // ignore when not playing or when playing externally - guard playerController.playerView.isInternalPlayerPlaying else { return } + // ignore when not playing + guard playerController.playerView.isPlaying else { return } // ignore when playing in fullscreen guard !playerController.playerView.isInFullScreenPlayerWindow else { return } diff --git a/WWDC/ChromeCastPlaybackProvider.swift b/WWDC/ChromeCastPlaybackProvider.swift deleted file mode 100644 index e9011eb3..00000000 --- a/WWDC/ChromeCastPlaybackProvider.swift +++ /dev/null @@ -1,357 +0,0 @@ -// -// ChromeCastPlaybackProvider.swift -// WWDC -// -// Created by Guilherme Rambo on 03/06/17. -// Copyright © 2017 Guilherme Rambo. All rights reserved. -// - -/* - ChromeCast support was disabled as part of the Apple Silicon transition, - since we're moving to SPM instead of Carthage. ChromeCastCore includes - Objective-C code which would need to be rewritten in Swift in order - to work properly under SPM. Additionally, I'm not sure how much people - actually use this feature and have no way to test it in practice, - so I've decided to just disable it for the time being. - */ -#if ENABLE_CHROMECAST - -import Cocoa -import ChromeCastCore -import PlayerUI -import CoreMedia -import OSLog - -private struct ChromeCastConstants { - static let defaultHost = "devstreaming-cdn.apple.com" - static let chromeCastSupportedHost = "devstreaming.apple.com" - static let placeholderImageURL = URL(string: "https://wwdc.io/images/placeholder.jpg")! -} - -private extension URL { - - /// The default host returned by Apple's WWDC app has invalid headers for ChromeCast streaming, - /// this rewrites the URL to use another host which returns a valid response for the ChromeCast device - /// Calling this on a non-streaming URL doesn't change the URL - var chromeCastSupportedURL: URL? { - guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return nil } - - if components.host == ChromeCastConstants.defaultHost { - components.scheme = "http" - components.host = ChromeCastConstants.chromeCastSupportedHost - } - - return components.url - } - -} - -final class ChromeCastPlaybackProvider: PUIExternalPlaybackProvider, Logging { - - fileprivate weak var consumer: PUIExternalPlaybackConsumer? - - private lazy var scanner: CastDeviceScanner = CastDeviceScanner() - - static let log = makeLogger() - - /// Initializes the external playback provider to start playing the media at the specified URL - /// - /// - Parameter consumer: The consumer that's going to be using this provider - init(consumer: PUIExternalPlaybackConsumer) { - self.consumer = consumer - status = PUIExternalPlaybackMediaStatus() - - NotificationCenter.default.addObserver(self, - selector: #selector(deviceListDidChange), - name: CastDeviceScanner.DeviceListDidChange, - object: scanner) - - scanner.startScanning() - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - /// Whether this provider only works with a remote URL or can be used with only the `AVPlayer` instance - var requiresRemoteMediaUrl: Bool { - return true - } - - /// The name of the external playback system (ex: "AirPlay") - static var name: String { - return "ChromeCast" - } - - /// An image to be used as the icon in the UI - var icon: NSImage { - return #imageLiteral(resourceName: "chromecast") - } - - var image: NSImage { - return #imageLiteral(resourceName: "chromecast-large") - } - - var info: String { - return "To control playback, use the Google Home app on your phone" - } - - /// The current media status - var status: PUIExternalPlaybackMediaStatus - - /// Return whether this playback system is available - var isAvailable: Bool = false - - /// Tells the external playback provider to play - func play() { - - } - - /// Tells the external playback provider to pause - func pause() { - - } - - /// Tells the external playback provider to seek to the specified time (in seconds) - func seek(to timestamp: Double) { - - } - - /// Tells the external playback provider to change the volume on the device - /// - /// - Parameter volume: The volume (value between 0 and 1) - func setVolume(_ volume: Float) { - - } - - // MARK: - ChromeCast management - - fileprivate var client: CastClient? - fileprivate var mediaPlayerApp: CastApp? - fileprivate var currentSessionId: Int? - fileprivate var mediaStatusRefreshTimer: Timer? - - @objc private func deviceListDidChange() { - isAvailable = scanner.devices.count > 0 - - buildMenu() - } - - private func buildMenu() { - let menu = NSMenu() - - scanner.devices.forEach { device in - let item = NSMenuItem(title: device.name, action: #selector(didSelectDeviceOnMenu), keyEquivalent: "") - item.representedObject = device - item.target = self - - if device.hostName == selectedDevice?.hostName { - item.state = .on - } - - menu.addItem(item) - } - - // send menu to consumer - consumer?.externalPlaybackProvider(self, deviceSelectionMenuDidChangeWith: menu) - } - - private var selectedDevice: CastDevice? - - @objc private func didSelectDeviceOnMenu(_ sender: NSMenuItem) { - guard let device = sender.representedObject as? CastDevice else { return } - - scanner.stopScanning() - - if let previousClient = client { - if let app = mediaPlayerApp { - client?.stop(app: app) - } - - mediaStatusRefreshTimer?.invalidate() - mediaStatusRefreshTimer = nil - - previousClient.disconnect() - client = nil - } - - if device.hostName == selectedDevice?.hostName { - sender.state = .off - - consumer?.externalPlaybackProviderDidInvalidatePlaybackSession(self) - } else { - selectedDevice = device - sender.state = .on - - client = CastClient(device: device) - client?.delegate = self - - client?.connect() - - consumer?.externalPlaybackProviderDidBecomeCurrent(self) - } - } - - fileprivate var mediaForChromeCast: CastMedia? { - guard let originalMediaURL = consumer?.remoteMediaUrl else { - log.error("Unable to play because the player view doesn't have a remote media URL associated with it") - return nil - } - - guard let mediaURL = originalMediaURL.chromeCastSupportedURL else { - log.error("Error generating ChromeCast-compatible media URL") - return nil - } - - log.info("ChromeCast media URL is \(mediaURL.absoluteString, privacy: .public)") - - let posterURL: URL - - if let poster = consumer?.mediaPosterUrl { - posterURL = poster - } else { - posterURL = ChromeCastConstants.placeholderImageURL - } - - let title: String - - if let playerTitle = consumer?.mediaTitle { - title = playerTitle - } else { - title = "WWDC Video" - } - - let streamType: CastMediaStreamType - - if let isLive = consumer?.mediaIsLiveStream { - streamType = isLive ? .live : .buffered - } else { - streamType = .buffered - } - - var currentTime: Double = 0 - - if let playerTime = consumer?.player?.currentTime() { - currentTime = Double(CMTimeGetSeconds(playerTime)) - } - - let media = CastMedia(title: title, - url: mediaURL, - poster: posterURL, - contentType: "application/vnd.apple.mpegurl", - streamType: streamType, - autoplay: true, - currentTime: currentTime) - - return media - } - - fileprivate func loadMediaOnDevice() { - guard let media = mediaForChromeCast else { return } - guard let app = mediaPlayerApp else { return } - guard let url = consumer?.remoteMediaUrl else { return } - - log.debug("Load media at \(url.absoluteString, privacy: .public) on session ID \(app.sessionId, privacy: .public)") - - var currentTime: Double = 0 - - if let playerTime = consumer?.player?.currentTime() { - currentTime = Double(CMTimeGetSeconds(playerTime)) - } - - log.info("Will start media on ChromeCast at \(currentTime, privacy: .public)s") - - client?.load(media: media, with: app) { [weak self] error, mediaStatus in - guard let self = self else { return } - - guard let mediaStatus = mediaStatus, error == nil else { - if let error = error { - log.error("Failed to load media on ChromeCast: \(String(describing: error), privacy: .public)") - WWDCAlert.show(with: error) - } - return - } - - self.currentSessionId = mediaStatus.mediaSessionId - - log.info("The media is now loaded with session ID \(mediaStatus.mediaSessionId, privacy: .public)") - log.info("Current media status is \(String(describing: mediaStatus), privacy: .public)") - - self.startFetchingMediaStatusPeriodically() - } - } - - fileprivate func startFetchingMediaStatusPeriodically() { - mediaStatusRefreshTimer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(requestMediaStatus), userInfo: nil, repeats: true) - } - - @objc private func requestMediaStatus(_ sender: Any?) { - do { - try client?.requestStatus() - } catch { - log.error("Failed to obtain status from connected ChromeCast device: \(String(describing: error), privacy: .public)") - } - } - -} - -extension ChromeCastPlaybackProvider: CastClientDelegate { - - public func castClient(_ client: CastClient, willConnectTo device: CastDevice) { - log.debug("Will connect to device \(device.name, privacy: .public)") - } - - public func castClient(_ client: CastClient, didConnectTo device: CastDevice) { - log.debug("Connected to device \(device.name, privacy: .public). Launching media player app.") - - client.launch(appId: .defaultMediaPlayer) { [weak self] error, app in - guard let self = self else { return } - - guard let app = app, error == nil else { - if let error = error { - log.error("Failed to launch media player app: \(String(describing: error), privacy: .public)") - WWDCAlert.show(with: error) - } - return - } - - log.info("Media player launched. Session id is \(app.sessionId, privacy: .public)") - - self.mediaPlayerApp = app - self.loadMediaOnDevice() - } - } - - public func castClient(_ client: CastClient, didDisconnectFrom device: CastDevice) { - consumer?.externalPlaybackProviderDidInvalidatePlaybackSession(self) - } - - public func castClient(_ client: CastClient, connectionTo device: CastDevice, didFailWith error: NSError) { - WWDCAlert.show(with: error) - - consumer?.externalPlaybackProviderDidInvalidatePlaybackSession(self) - } - - public func castClient(_ client: CastClient, deviceStatusDidChange status: CastStatus) { - self.status.volume = Float(status.volume.level) - - consumer?.externalPlaybackProviderDidChangeMediaStatus(self) - } - - public func castClient(_ client: CastClient, mediaStatusDidChange status: CastMediaStatus) { - let rate: Float = status.playerState == .playing ? 1.0 : 0.0 - - let newStatus = PUIExternalPlaybackMediaStatus(rate: rate, - volume: self.status.volume, - currentTime: status.currentTime) - - self.status = newStatus - - log.debug("Media status: \(String(describing: newStatus), privacy: .public)") - - consumer?.externalPlaybackProviderDidChangeMediaStatus(self) - } - -} - -#endif diff --git a/WWDC/VideoPlayerViewController.swift b/WWDC/VideoPlayerViewController.swift index fafdcad0..1e63b631 100644 --- a/WWDC/VideoPlayerViewController.swift +++ b/WWDC/VideoPlayerViewController.swift @@ -90,10 +90,6 @@ final class VideoPlayerViewController: NSViewController { playerView.frame = view.bounds view.addSubview(playerView) - #if ENABLE_CHROMECAST - playerView.registerExternalPlaybackProvider(ChromeCastPlaybackProvider.self) - #endif - playerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true playerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true playerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true @@ -372,10 +368,6 @@ extension VideoPlayerViewController: PUIPlayerViewAppearanceDelegate { return !sessionViewModel.sessionInstance.isCurrentlyLive } - func playerViewShouldShowExternalPlaybackControls(_ playerView: PUIPlayerView) -> Bool { - return true - } - func playerViewShouldShowFullScreenButton(_ playerView: PUIPlayerView) -> Bool { return true }