Search code examples
swiftxcodeswift3ios13carplay

Why MPPlayableContentManager protocols (MPPlayableContentDataSource and MPPlayableContentDelegate) in iOS 13.2 is not called?


The Carplay audio app was working until iOS v12.4. When I created the below class, I followed the answer on this link and it works perfectly fine. Below is the sample code.

The now playing is working on Carplay but when the app is open on the dashboard, it displays unable to connect. I'm using Xcode v11.2.1 (11B500), simulator iOS v13.2.2, iPhone 11 (v13.2.3).

Did try print logs on initiatePlaybackOfContentItemAt and it's not being called. What do you think I'm missing so that it could be working and compatible with iOS 13 or a higher version?

class PlayManager: NSObject {

    static let shared = PlayManager()
    var currentStation: Int64 = 0
    var isRadio: Bool = true
    var contentList: [RadioObj] = []
    let commandCenter = MPRemoteCommandCenter.shared()
    var nowPlayingSongManager: MPNowPlayingInfoCenter?
    var playableContentManager: MPPlayableContentManager?

    override init() {
        super.init()

        let artist = "Play"
        nowPlayingSongManager = MPNowPlayingInfoCenter.default()
        nowPlayingSongManager?.nowPlayingInfo = [
            MPNowPlayingInfoPropertyMediaType : "Audio",
            MPMediaItemPropertyTitle : api.defaults.defaultArtist,
            MPMediaItemPropertyArtist: artist
        ]
        player.initializeMedia(song: api.defaults.defaultArtist, artist: artist, album: api.defaults.appLogo)

        playableContentManager = MPPlayableContentManager.shared()
        playableContentManager?.dataSource = self
        playableContentManager?.delegate = self
    }

    func loadData(){
        api.data.getRadioData(event_source: "carplay") { (done, obj) in

            if done {
                if obj.count > 0 {
                    let indeces: Int = 0
                    api.currentRadio = obj[indeces]
                    api.isRadio = true
                    api.isPodcast = false
                }

                self.contentList = obj
                self.playableContentManager.reloadData()
            }
        }
    }
}


extension PlayManager: MPPlayableContentDelegate, MPPlayableContentDataSource {

    func playableContentManager(_ contentManager: MPPlayableContentManager, initiatePlaybackOfContentItemAt indexPath: IndexPath, completionHandler: @escaping (Error?) -> Void) {

        print("check if its called")

        let indeces: Int = indexPath[0]
        let radio_name = contentList[indeces].name
        let id = contentList[indeces].id
        let radio_logo = contentList[indeces].logo_rounded_url
        let stream_url = contentList[indeces].stream_url
        let hd_stream_url = contentList[indeces].hd_stream_url
        var currentIdentifier = ""

        if contentManager.nowPlayingIdentifiers.count > 0 {
            currentIdentifier = contentManager.nowPlayingIdentifiers[0]
        }

        var newIdenditier = "radio_"

        if let id = id {
            newIdenditier += "\(id)"
        }

        let radioObj = RadioObj()
        radioObj.stream_url = stream_url
        radioObj.hd_stream_url = hd_stream_url
        radioObj.name = radio_name
        radioObj.logo_url = radio_logo

        api.currentRadio = contentList[indeces]
        api.isRadio = true

        if let id = id {
            currentStation = id
        }

        guard let logo = radio_logo, let name = radio_name else { return }

        player.initializeMedia(song: name, artist: api.defaults.defaultArtist, album: api.defaults.appLogo)

        api.download(image: logo) { (image) in
            player.initializeMedia(song: name, artist: api.defaults.defaultArtist, album: image)
        }

        if api.isPlaying && currentIdentifier != newIdenditier {
            contentManager.nowPlayingIdentifiers = [newIdenditier]
            DispatchQueue.main.async {
                player.start()
            }
        }

        onTapSound(contentManager: contentManager, completionHandler: completionHandler, indexPath: indexPath)
    }

    func onTapSound(contentManager:MPPlayableContentManager,completionHandler: @escaping (Error?) -> Void, indexPath : IndexPath){
        completionHandler(nil)
    }

    func numberOfChildItems(at indexPath: IndexPath) -> Int {
        if indexPath.count == 0 {
            return contentList.count
        } else if indexPath.count == 1 {
            let _: Int = indexPath.first ?? 0

        } else if indexPath.count == 2 {

        }
        return 0
    }


    func contentItem(at indexPath: IndexPath) -> MPContentItem? {
        let indeces: Int = indexPath[0]
        let _: Int = indexPath.indices.count

        let radio_name = contentList[indeces].name
        let id = contentList[indeces].id
        let radio_logo = contentList[indeces].car_logo
        if let id = id, let radio_logo = radio_logo {
            let contentItem = MPContentItem(identifier: "radio_\(id)")
            contentItem.title = radio_name
            contentItem.isContainer = false
            contentItem.isPlayable = true
            contentItem.isStreamingContent = true

            DispatchQueue.global().async {
                if let url = URL(string:radio_logo) {
                    do {
                        let radio =  try UIImage(data: Data(contentsOf: url))
                        if let image = radio {

                            let artWork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { (size) -> UIImage in
                                return image
                            })

                            contentItem.artwork = artWork
                        }
                    } catch{}
                }
            }

            return contentItem
        }

        return MPContentItem()
    }

    open func setupEvents(isRadio isTrue: Bool?) -> PlayManager {
        guard let isTrue = isTrue else { return self }
        commandCenter.playCommand.isEnabled = isTrue


        if isTrue {
            commandCenter.skipForwardCommand.isEnabled = false
            commandCenter.skipBackwardCommand.isEnabled = false
            commandCenter.stopCommand.isEnabled = true
            commandCenter.pauseCommand.isEnabled = false

            if #available(iOS 10.0, *) {
                nowPlayingSongManager?.nowPlayingInfo?[MPNowPlayingInfoPropertyIsLiveStream] = true
            }
        } else {
            commandCenter.stopCommand.isEnabled = false
            commandCenter.pauseCommand.isEnabled = true
        }

        commandCenter.stopCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
            api.isPlaying = false
            player.stop()
            return .success
        }

        commandCenter.pauseCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
            player.pause()
            return .success
        }

        commandCenter.playCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
            api.isPlaying = true

            if api.isRadio {
                self.playableContentManager?.nowPlayingIdentifiers = ["radio_\(self.currentStation)"]

                guard let logo = api.currentRadio?.logo_rounded_url, let name = api.currentRadio?.name else { return .noSuchContent }

                api.download(image: logo) { (image) in
                    player.initializeMedia(song: name, artist: api.defaults.defaultArtist, album: image)
                }

                player.start()
            }

            if api.isPodcast {
                player.resume()
            }

            return .success
        }

        return self
    }
}

Solution

  • Seems to be an Xcode 11 bug. I've tried Xcode 13 beta, but the result was the same.

    In my case, there was a blank white screen with empty tabs with a blue tint. Then the error was shown, saying "Unable to load content (null)" iOS 13 Simulator screenshot.

    Ended up installing iPhone 5s/iOS 12.4.1 simulator.

    On the client's side with a real device iOS 13 worked as expected.