Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fixing car-play bugs that only show up in a real car.
Browse files Browse the repository at this point in the history
IE: Two controllers controlling playback, videos disappearing, interruptions, rate change, etc.
  • Loading branch information
Brandon-T committed Jul 15, 2021
1 parent 792913c commit e00524b
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class PlaylistCarplayController: NSObject {
private var assetStateObservers = Set<AnyCancellable>()
private var assetLoadingStateObservers = Set<AnyCancellable>()
private var playlistItemIds = [String]()
private var currentlyPlayingItemIndex = -1

init(player: MediaPlayer, contentManager: MPPlayableContentManager) {
self.player = player
Expand All @@ -38,7 +37,12 @@ class PlaylistCarplayController: NSObject {

contentManager.dataSource = self
contentManager.delegate = self
contentManager.reloadData()

DispatchQueue.main.async {
contentManager.beginUpdates()
contentManager.endUpdates()
contentManager.reloadData()
}

// Workaround to see carplay NowPlaying on the simulator
#if targetEnvironment(simulator)
Expand All @@ -49,6 +53,12 @@ class PlaylistCarplayController: NSObject {
#endif
}

deinit {
// contentManager.delegate = nil
// contentManager.dataSource = nil
// contentManager.reloadData()
}

func observePlayerStates() {
player.publisher(for: .play).sink { _ in
MPNowPlayingInfoCenter.default().playbackState = .playing
Expand All @@ -63,9 +73,7 @@ class PlaylistCarplayController: NSObject {
}.store(in: &playerStateObservers)

player.publisher(for: .changePlaybackRate).sink { [weak self] _ in
guard let self = self else { return }

MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = self.player.rate
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = self?.player.rate
}.store(in: &playerStateObservers)

player.publisher(for: .previousTrack).sink { [weak self] _ in
Expand All @@ -77,19 +85,17 @@ class PlaylistCarplayController: NSObject {
}.store(in: &playerStateObservers)

player.publisher(for: .finishedPlaying).sink { [weak self] event in
guard let self = self else { return }

event.mediaPlayer.pause()
event.mediaPlayer.seek(to: .zero)
self.onNextTrack(isUserInitiated: false)
self?.onNextTrack(isUserInitiated: false)
}.store(in: &playerStateObservers)
}
}

extension PlaylistCarplayController: MPPlayableContentDelegate {
func playableContentManager(_ contentManager: MPPlayableContentManager, didUpdate context: MPPlayableContentManagerContext) {

PlaylistCarplayManager.shared.attemptInterfaceConnection(isCarPlayAvailable: context.endpointAvailable)
log.debug("CAR PLAY CONNECTED: \(context.endpointAvailable)")
}

func playableContentManager(_ contentManager: MPPlayableContentManager, initiatePlaybackOfContentItemAt indexPath: IndexPath, completionHandler: @escaping (Error?) -> Void) {
Expand All @@ -103,15 +109,25 @@ extension PlaylistCarplayController: MPPlayableContentDelegate {
}

self.contentManager.nowPlayingIdentifiers = [mediaItem.src]
self.playItem(item: mediaItem) { error in
self.playItem(item: mediaItem) { [weak self] error in
PlaylistCarplayManager.shared.currentPlaylistItem = nil

switch error {
case .other(let error):
log.error(error)
completionHandler("Unknown Error")
case .expired:
completionHandler(Strings.PlayList.expiredAlertDescription)
case .none:
self.currentlyPlayingItemIndex = indexPath.item
guard let self = self else {
completionHandler("Unknown Error")
return
}

PlaylistCarplayManager.shared.currentlyPlayingItemIndex = indexPath.item
PlaylistCarplayManager.shared.currentPlaylistItem = mediaItem
PlaylistMediaStreamer.setNowPlayingMediaArtwork(artwork: self.contentItem(at: indexPath)?.artwork)

completionHandler(nil)
case .cancelled:
log.debug("User Cancelled Playlist playback")
Expand All @@ -134,8 +150,9 @@ extension PlaylistCarplayController: MPPlayableContentDelegate {
}

func beginLoadingChildItems(at indexPath: IndexPath, completionHandler: @escaping (Error?) -> Void) {
// For some odd reason, this is never called in the simulator.
// It is only called in the car and that's fine.
completionHandler(nil)
contentManager.reloadData()
}
}

Expand Down Expand Up @@ -195,15 +212,15 @@ extension PlaylistCarplayController: MPPlayableContentDataSource {

extension PlaylistCarplayController {
func onPreviousTrack(isUserInitiated: Bool) {
if currentlyPlayingItemIndex <= 0 {
if PlaylistCarplayManager.shared.currentlyPlayingItemIndex <= 0 {
return
}

let index = currentlyPlayingItemIndex - 1
let index = PlaylistCarplayManager.shared.currentlyPlayingItemIndex - 1
if index < PlaylistManager.shared.numberOfAssets,
let item = PlaylistManager.shared.itemAtIndex(index) {
self.currentlyPlayingItemIndex = index
self.playItem(item: item) { [weak self] error in
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = index
playItem(item: item) { [weak self] error in
guard let self = self else { return }

switch error {
Expand All @@ -213,7 +230,7 @@ extension PlaylistCarplayController {
case .expired:
self.displayExpiredResourceError(item: item)
case .none:
self.currentlyPlayingItemIndex = index
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = index
self.updateLastPlayedItem(item: item)
case .cancelled:
log.debug("User Cancelled Playlist Playback")
Expand All @@ -224,8 +241,8 @@ extension PlaylistCarplayController {

func onNextTrack(isUserInitiated: Bool) {
let assetCount = PlaylistManager.shared.numberOfAssets
let isAtEnd = currentlyPlayingItemIndex >= assetCount - 1
var index = currentlyPlayingItemIndex
let isAtEnd = PlaylistCarplayManager.shared.currentlyPlayingItemIndex >= assetCount - 1
var index = PlaylistCarplayManager.shared.currentlyPlayingItemIndex

switch player.repeatState {
case .none:
Expand Down Expand Up @@ -262,12 +279,12 @@ extension PlaylistCarplayController {
self.displayExpiredResourceError(item: item)
} else {
DispatchQueue.main.async {
self.currentlyPlayingItemIndex = index
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = index
self.onNextTrack(isUserInitiated: isUserInitiated)
}
}
case .none:
self.currentlyPlayingItemIndex = index
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = index
self.updateLastPlayedItem(item: item)
case .cancelled:
log.debug("User Cancelled Playlist Playback")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,19 @@ class PlaylistListViewController: UIViewController {
// After reloading all data, update the background
guard PlaylistManager.shared.numberOfAssets > 0 else {
self.updateTableBackgroundView()
self.autoPlayEnabled = true
return
}

// Otherwise prepare to play the first item
playerView.setControlsEnabled(true)

// If car play is active or media is already playing, do nothing
if PlaylistCarplayManager.shared.isCarPlayAvailable && (delegate?.currentPlaylistAsset != nil || delegate?.isPlaying ?? false) {
self.autoPlayEnabled = true
return
}

// If there is no last played item, then just select the first item in the playlist
// which will play it if auto-play is enabled.
guard let lastPlayedItemUrl = Preferences.Playlist.lastPlayedItemUrl.value,
Expand All @@ -134,6 +141,8 @@ class PlaylistListViewController: UIViewController {
}

delegate.playItem(item: item) { [weak self] error in
PlaylistCarplayManager.shared.currentPlaylistItem = nil

guard let self = self,
let delegate = self.delegate else {
self?.commitPlayerItemTransaction(at: indexPath,
Expand All @@ -155,6 +164,7 @@ class PlaylistListViewController: UIViewController {
isExpired: true)
delegate.displayExpiredResourceError(item: item)
case .none:
PlaylistCarplayManager.shared.currentPlaylistItem = item
self.commitPlayerItemTransaction(at: indexPath,
isExpired: false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ protocol PlaylistViewControllerDelegate: AnyObject {
func displayLoadingResourceError()
func displayExpiredResourceError(item: PlaylistInfo)

var isPlaying: Bool { get }
var currentPlaylistItem: AVPlayerItem? { get }
var currentPlaylistAsset: AVAsset? { get }
}
Expand All @@ -53,7 +54,6 @@ class PlaylistViewController: UIViewController {
private var playerStateObservers = Set<AnyCancellable>()
private var assetStateObservers = Set<AnyCancellable>()
private var assetLoadingStateObservers = Set<AnyCancellable>()
private var currentlyPlayingItemIndex = -1

init(player: MediaPlayer) {
self.player = player
Expand All @@ -78,7 +78,10 @@ class PlaylistViewController: UIViewController {
player.pictureInPictureController?.stopPictureInPicture()

// Stop media playback
stop(playerView)
if !PlaylistCarplayManager.shared.isCarPlayAvailable {
stop(playerView)
PlaylistCarplayManager.shared.currentPlaylistItem = nil
}

// If this controller is retained in app-delegate for Picture-In-Picture support
// then we need to re-attach the player layer
Expand All @@ -98,6 +101,7 @@ class PlaylistViewController: UIViewController {

// Setup delegates and state observers
attachPlayerView()
updatePlayerUI()
observePlayerStates()
listController.delegate = self

Expand Down Expand Up @@ -170,6 +174,43 @@ class PlaylistViewController: UIViewController {
}
}

private func updatePlayerUI() {
// Update play/pause button
if isPlaying {
playerView.controlsView.playPauseButton.setImage(#imageLiteral(resourceName: "playlist_pause"), for: .normal)
} else {
playerView.controlsView.playPauseButton.setImage(#imageLiteral(resourceName: "playlist_play"), for: .normal)
}

// Update play-backrate button
let playbackRate = player.rate
let button = playerView.controlsView.playbackRateButton

if playbackRate <= 1.0 {
button.setTitle("1x", for: .normal)
} else if playbackRate == 1.5 {
button.setTitle("1.5x", for: .normal)
} else {
button.setTitle("2x", for: .normal)
}

// Update repeatMode button
switch repeatMode {
case .none:
playerView.controlsView.repeatButton.setImage(#imageLiteral(resourceName: "playlist_repeat"), for: .normal)
case .repeatOne:
playerView.controlsView.repeatButton.setImage(#imageLiteral(resourceName: "playlist_repeat_one"), for: .normal)
case .repeatAll:
playerView.controlsView.repeatButton.setImage(#imageLiteral(resourceName: "playlist_repeat_all"), for: .normal)
}

if let item = PlaylistCarplayManager.shared.currentPlaylistItem {
playerView.setVideoInfo(videoDomain: item.pageSrc, videoTitle: item.pageTitle)
} else {
playerView.resetVideoInfo()
}
}

private func observePlayerStates() {
player.publisher(for: .play).sink { [weak self] _ in
self?.playerView.controlsView.playPauseButton.setImage(#imageLiteral(resourceName: "playlist_pause"), for: .normal)
Expand All @@ -189,7 +230,7 @@ class PlaylistViewController: UIViewController {
let playbackRate = self.player.rate
let button = self.playerView.controlsView.playbackRateButton

if playbackRate == 1.0 {
if playbackRate <= 1.0 {
button.setTitle("1x", for: .normal)
} else if playbackRate == 1.5 {
button.setTitle("1.5x", for: .normal)
Expand All @@ -200,7 +241,6 @@ class PlaylistViewController: UIViewController {

player.publisher(for: .changeRepeatMode).sink { [weak self] _ in
guard let self = self else { return }

switch self.repeatMode {
case .none:
self.playerView.controlsView.repeatButton.setImage(#imageLiteral(resourceName: "playlist_repeat"), for: .normal)
Expand Down Expand Up @@ -329,10 +369,10 @@ extension PlaylistViewController: PlaylistViewControllerDelegate {
func deleteItem(item: PlaylistInfo, at index: Int) {
PlaylistManager.shared.delete(item: item)

if currentlyPlayingItemIndex == index {
if PlaylistCarplayManager.shared.currentlyPlayingItemIndex == index {
PlaylistMediaStreamer.clearNowPlayingInfo()

currentlyPlayingItemIndex = -1
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = -1
playerView.resetVideoInfo()
stop(playerView)

Expand Down Expand Up @@ -388,11 +428,11 @@ extension PlaylistViewController: VideoViewDelegate {
}

func onPreviousTrack(_ videoView: VideoView, isUserInitiated: Bool) {
if currentlyPlayingItemIndex <= 0 {
if PlaylistCarplayManager.shared.currentlyPlayingItemIndex <= 0 {
return
}

let index = currentlyPlayingItemIndex - 1
let index = PlaylistCarplayManager.shared.currentlyPlayingItemIndex - 1
if index < PlaylistManager.shared.numberOfAssets {
let indexPath = IndexPath(row: index, section: 0)
listController.prepareToPlayItem(at: indexPath) { [weak self] item in
Expand All @@ -403,7 +443,7 @@ extension PlaylistViewController: VideoViewDelegate {
return
}

self.currentlyPlayingItemIndex = indexPath.row
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = indexPath.row
self.playItem(item: item) { [weak self] error in
guard let self = self else { return }

Expand All @@ -417,7 +457,7 @@ extension PlaylistViewController: VideoViewDelegate {
self.displayExpiredResourceError(item: item)
case .none:
self.listController.commitPlayerItemTransaction(at: indexPath, isExpired: false)
self.currentlyPlayingItemIndex = index
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = index
self.updateLastPlayedItem(item: item)
case .cancelled:
self.listController.commitPlayerItemTransaction(at: indexPath, isExpired: false)
Expand All @@ -430,8 +470,8 @@ extension PlaylistViewController: VideoViewDelegate {

func onNextTrack(_ videoView: VideoView, isUserInitiated: Bool) {
let assetCount = PlaylistManager.shared.numberOfAssets
let isAtEnd = currentlyPlayingItemIndex >= assetCount - 1
var index = currentlyPlayingItemIndex
let isAtEnd = PlaylistCarplayManager.shared.currentlyPlayingItemIndex >= assetCount - 1
var index = PlaylistCarplayManager.shared.currentlyPlayingItemIndex

switch repeatMode {
case .none:
Expand Down Expand Up @@ -482,13 +522,13 @@ extension PlaylistViewController: VideoViewDelegate {
} else {
DispatchQueue.main.async {
self.listController.commitPlayerItemTransaction(at: indexPath, isExpired: false)
self.currentlyPlayingItemIndex = index
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = index
self.onNextTrack(videoView, isUserInitiated: isUserInitiated)
}
}
case .none:
self.listController.commitPlayerItemTransaction(at: indexPath, isExpired: false)
self.currentlyPlayingItemIndex = index
PlaylistCarplayManager.shared.currentlyPlayingItemIndex = index
self.updateLastPlayedItem(item: item)
case .cancelled:
self.listController.commitPlayerItemTransaction(at: indexPath, isExpired: false)
Expand Down
Loading

0 comments on commit e00524b

Please sign in to comment.