Handling playback events in CarPlay with MPNowPlayingInfoCenter

点点圈 提交于 2019-12-21 06:25:24

问题


I am trying to build a sample audio app with CarPlay integration. The app is a test project - no API, no streaming, no complicated UI. Just a short list of song titles with the functionality to select one and play it. My goal is to handle the callback when the play button is pressed on the Now Playing screen and play a file.

I don't have any problems setting up the MPPlayableContentManager, MPPlayableContentDataSource and MPPlayableContentDelegate. My content is parsed from a JSON file and it shows up correctly on the simulator's screen.

// MARK: - MPPlayableContentDataSource methods
extension PlayableContentManager: MPPlayableContentDataSource {
  func numberOfChildItems(at indexPath: IndexPath) -> Int {
    if indexPath.count == 0 {
      return playlists.count
    } else if indexPath.count == 1 {
      return playlists[indexPath.first ?? 0].playlist.count
    } else if indexPath.count == 2 {
      return playlists.last?.playlist.count ?? 0
    }

    return 0
 }

  func contentItem(at indexPath: IndexPath) -> MPContentItem? {
    if indexPath.count == 1 {
      return createTabbar(item: playlists[indexPath.first ?? 
0].name.capitalized, itemType: playlists[indexPath.first ?? 0].type)
    } else if indexPath.count == 2 {
      if indexPath.first == 0 {
        return createContent(item: playlists[0].playlist[indexPath.last 
?? 0], isContainer: false, isPlayable: true)
      } else {
        return createContainerContent(item: playlists[indexPath.first 
?? 0].playlist[indexPath.last ?? 0])
      }
    }

    return createTabbar(item: "", itemType: nil)
  }
}

This code gives the following graphic output:

CarPlay simulator

The two tabs contain the two playlists. Each playlist has a number of songs.

When I tap on a song the app becomes the Now Playing app but only if I have both began and ended receiving remote control events at the time of the user's interaction with the row.

The initiatePlaybackOfContentItemAt method is called when a click on the table row is detected.

// MARK: - MPPlayableContentDelegate methods
extension PlayableContentManager: MPPlayableContentDelegate {
  func playableContentManager(_ contentManager: 
MPPlayableContentManager, initiatePlaybackOfContentItemAt indexPath: 
 IndexPath, completionHandler: @escaping (Error?) -> Void) {
    DispatchQueue.main.async {
      UIApplication.shared.beginReceivingRemoteControlEvents()
      self.infoCenter.nowPlayingInfo = self.setupNowPlayingInfo(for: 
indexPath)
      completionHandler(nil)
      UIApplication.shared.endReceivingRemoteControlEvents()
    }
  }
}

This is the only code that works for me if I want the app to transition to the Now Playing screen. If I place any of the UIApplication methods anywhere else the app quits responding to row touches and doesn't enter the Now Playing screen.

However, I'm guessing because I'm invoking the endReceivingRemoteControlEvents(), I can't get the callback for the different events. The now playing info is set, I can see the play button in the UI but, when I press it, the callback doesn't execute.

private func setupPlaybackCommands() {
    commandCenter = MPRemoteCommandCenter.shared()

    commandCenter.playCommand.addTarget { [unowned self] event in
      if self.audioPlayer.rate == 0.0 {
        self.play()
        return .success
      }
      return .commandFailed
    }
....
}

Now playing screen

What am I doing wrong?

Could this have something to do with the fact that I'm testing on a simulator? Will this work on a real device?

If anyone can shed some light on how to correctly setup the CarPlay integration to enter the Now Playing screen and respond to events, please, share. I'm having a lot of trouble finding any usable code samples or examples.

I know I can but I haven't applied for a CarPlay entitlement because this project is for research purposes only and I highly doubt I'll get approved.


回答1:


I was able to "fix" the playCommand and stopCommand to receive callbacks only if call the beginReceivingRemoteControlEvents method after a quick pause (like a second):

extension PlayableContentManager: MPPlayableContentDelegate {
  func playableContentManager(_ contentManager: 
MPPlayableContentManager, initiatePlaybackOfContentItemAt indexPath: 
 IndexPath, completionHandler: @escaping (Error?) -> Void) {
    DispatchQueue.main.async {
      UIApplication.shared.beginReceivingRemoteControlEvents()
      self.infoCenter.nowPlayingInfo = self.setupNowPlayingInfo(for: 
indexPath)
      completionHandler(nil)
      UIApplication.shared.endReceivingRemoteControlEvents()

      // TODO: add 1 second timeout, and call this after
      UIApplication.shared.beginReceivingRemoteControlEvents()
    }
  }
}

one more thing, when a user is navigated to the "Now Playing" screen, the iOS assumes that the user has to hit the "Play" button and only then you should start a playback (by handling the remote command callback). Otherwise, you will be redirected to the "Now Playing" screen with active audio and without any ability to make the button with the "Stop" icon.

UPDATE

While the logic above works for Simulator, I've tested on a real CarPlay device and it's not required. You start the playback and call the completion handler. All the rest is handled automatically by iOS including the transition to the "Now Playing" screen.



来源:https://stackoverflow.com/questions/52818170/handling-playback-events-in-carplay-with-mpnowplayinginfocenter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!