import Foundation import MediaPlayer extension AppDelegate{ func setupCarPlay() { playableContentManager = MPPlayableContentManager.shared() playableContentManager?.delegate = self playableContentManager?.dataSource = self self.carplayPlaylist.setupCommandCenter() } } extension AppDelegate: MPPlayableContentDelegate { // This is called when user selects a 'playable' item (MPContentItem) func playableContentManager(_ contentManager: MPPlayableContentManager, initiatePlaybackOfContentItemAt indexPath: IndexPath, completionHandler: @escaping (Error?) -> Void) { self.carplayPlaylist.playerData = SettingsDataManager.sharedInstance.playerData ?? [] DispatchQueue.main.async { // App should play music either from the radio stream or podcasts if indexPath.count == 2 { if(indexPath[0] == 0) { // When user selects playable item within Radio tab if (self.carplayPlaylist.playerData.count > 0) { if let stationURL = URL(string: self.carplayPlaylist.playerData[indexPath[1]].streams?.iOS ?? "") { self.carplayPlaylist.play(playURL: stationURL) SettingsDataManager.sharedInstance.selectedStation = self.carplayPlaylist.playerData[indexPath[1]] } } } } completionHandler(nil) // Workaround to make the Now Playing working on the simulator: // #if targetEnvironment(simulator) // UIApplication.shared.endReceivingRemoteControlEvents() // UIApplication.shared.beginReceivingRemoteControlEvents() // #endif } } func beginLoadingChildItems(at indexPath: IndexPath, completionHandler: @escaping (Error?) -> Void) { carplayPlaylist.load { error in completionHandler(error) } } } extension AppDelegate: MPPlayableContentDataSource { func numberOfChildItems(at indexPath: IndexPath) -> Int { if indexPath.indices.count == 0 { // This returns the number of tabs at the top of the CarPlay display return 1 // changed to 1 to allow only radio streams } else if indexPath.indices.count == 1 { // This returns the number of rows within each tab if indexPath[0] == 0 { // Radio Stations rows return carplayPlaylist.playerData.count } else { // Podcasts feed rows return 0 // Return 0 while podcasts are disabled } // } else if indexPath.indices.count == 2 { // // Podcast item rows // return carplayPlaylist.podCastItemsArray[indexPath[1]].count // } else { return 0 } } func contentItem(at indexPath: IndexPath) -> MPContentItem? { let playerData = carplayPlaylist.playerData // let podCastsFeeds = carplayPlaylist.podCastSettings?.podcasts_feeds if indexPath.count == 1 { // Radio Stations Tab Section let item = MPContentItem(identifier: "Radio") item.title = "Radio" item.isContainer = true item.isPlayable = false item.artwork = MPMediaItemArtwork(boundsSize: imageLiteral(resourceName: "NavIcon-Broadcast").size, requestHandler: { _ -> UIImage in return imageLiteral(resourceName: "NavIcon-Broadcast") }) return item } else if indexPath.count == 2, indexPath.item < playerData.count, indexPath[0] == 0 { // Radio station items section let station = playerData[indexPath.item] let item = MPContentItem(identifier: "\(station.name)") item.title = station.name item.subtitle = station.call_letters item.isPlayable = true item.isStreamingContent = true if let logoUrl = station.logo { item.downloadImageAndSetArtwork(from: logoUrl, completion: {}) } return item } else { return nil } } } ///extension to downlaod the image from the api extension MPContentItem { func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) { URLSession.shared.dataTask(with: url, completionHandler: completion).resume() } func downloadImageAndSetArtwork(from address: String, completion: @escaping () -> Void) { let defaultImage = imageLiteral(resourceName: "NavIcon-Music") let defaultArtwork = MPMediaItemArtwork(boundsSize: defaultImage.size, requestHandler: { _ -> UIImage in return defaultImage }) guard let url = URL(string: address) else { self.artwork = defaultArtwork return } getData(from: url) { data, response, error in guard let data = data, error == nil else { AppLogger.error("Image Error") self.artwork = defaultArtwork return } DispatchQueue.main.async() { guard let image = UIImage(data: data) else { self.artwork = defaultArtwork return } self.artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { _ -> UIImage in return image }) completion() } } } }