我有一个音频应用程序并实现了 CarPlay,我已按照本指南添加 CarPlay 支持:https ://blog.fethica.com/add-carplay-support-to-swiftradio/#
该应用程序使用 com.apple.developer.playable-content 权利和媒体播放器框架,据我所知,这是在 iOS 13 及更低版本上将 CarPlay 添加到音频应用程序的唯一方法,但仍向后兼容 iOS 14(尽管 iOS 14 有一个新的 carplay 音频框架)。我所有的 CarPlay 逻辑都是在我的主 AppDelegate 的扩展中编写的(这可能有问题吗?)
在模拟器中,一切正常,音频在外部模拟汽车显示器中播放。但是我的团队在一辆真实的汽车上测试了这个应用程序,当从汽车显示屏上打开应用程序时,它立即崩溃了。
在 iOS 14.4 上的 iPhone 11 Pro 上测试时,我在这里有崩溃日志:
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001b88d13cc
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [27455]
Triggered by Thread: 5
Thread 0 name:
Thread 0:
0 libsystem_kernel.dylib 0x00000001d6aa6f5c __ulock_wait + 8
1 libdispatch.dylib 0x00000001a8746794 _dlock_wait + 56 (lock.c:326)
2 libdispatch.dylib 0x00000001a87466c0 _dispatch_once_wait + 124 (lock.c:382)
3 UIKitCore 0x00000001ab1080f8 -[_UIApplicationConfigurationLoader _loadInitializationContext] + 152 (once.h:84)
4 UIKitCore 0x00000001ab108444 -[_UIApplicationConfigurationLoader applicationInitializationContext] + 32 (_UIApplicationConfigurationLoader.m:161)
5 UIKitCore 0x00000001ab0ef05c -[_UIScreenInitialDisplayConfigurationLoader initialDisplayContext] + 180 (UIScreen.m:371)
6 UIKitCore 0x00000001ab0ef348 +[UIScreen initialize] + 128 (UIScreen.m:626)
7 libobjc.A.dylib 0x00000001bdb67c58 CALLING_SOME_+initialize_METHOD + 24 (objc-initialize.mm:384)
8 libobjc.A.dylib 0x00000001bdb6e318 initializeNonMetaClass + 716 (objc-initialize.mm:554)
9 libobjc.A.dylib 0x00000001bdb6f910 initializeAndMaybeRelock(objc_class*, objc_object*, mutex_tt<false>&, bool) + 280 (objc-runtime-new.mm:2221)
10 libobjc.A.dylib 0x00000001bdb7e498 lookUpImpOrForward + 956 (objc-runtime-new.mm:2237)
11 libobjc.A.dylib 0x00000001bdb68524 _objc_msgSend_uncached + 68
12 UIKitCore 0x00000001ab33ef48 -[_UIRemoteKeyboards keyboardWindow] + 52 (_UIRemoteKeyboards.m:2036)
13 UIKitCore 0x00000001ab33cf6c -[_UIRemoteKeyboards allowedToShowKeyboard] + 96 (_UIRemoteKeyboards.m:1588)
14 UIKitCore 0x00000001ab33bda8 -[_UIRemoteKeyboards checkConnection] + 64 (_UIRemoteKeyboards.m:1315)
15 UIKitCore 0x00000001ab338b60 -[_UIRemoteKeyboards init] + 156 (_UIRemoteKeyboards.m:797)
16 UIKitCore 0x00000001ab338ab4 __43+[_UIRemoteKeyboards sharedRemoteKeyboards]_block_invoke + 20 (_UIRemoteKeyboards.m:785)
17 libdispatch.dylib 0x00000001a8745db0 _dispatch_client_callout + 20 (object.m:559)
18 libdispatch.dylib 0x00000001a87475c8 _dispatch_once_callout + 32 (once.c:52)
19 UIKitCore 0x00000001ab338a9c +[_UIRemoteKeyboards sharedRemoteKeyboards] + 176 (once.h:84)
20 UIKitCore 0x00000001ab50ccac _UIApplicationMainPreparations + 1348 (UIApplication.m:4644)
21 UIKitCore 0x00000001ab50c740 UIApplicationMain + 140 (UIApplication.m:4704)
22 MyAppName 0x000000010458fb04 main + 68 (UITextField+Extension.swift:22)
23 libdyld.dylib 0x00000001a87866b0 start + 4
Thread 1:
0 libsystem_pthread.dylib 0x00000001f4608764 start_wqthread + 0
Thread 2:
0 libsystem_pthread.dylib 0x00000001f4608764 start_wqthread + 0
Thread 3 name:
Thread 3:
0 libsystem_kernel.dylib 0x00000001d6a822d0 mach_msg_trap + 8
1 libsystem_kernel.dylib 0x00000001d6a81660 mach_msg + 76 (mach_msg.c:103)
2 libdispatch.dylib 0x00000001a875e888 _dispatch_mach_send_and_wait_for_reply + 528 (mach.c:812)
3 libdispatch.dylib 0x00000001a875ec24 dispatch_mach_send_with_result_and_wait_for_reply + 56 (mach.c:1998)
4 libxpc.dylib 0x00000001f4626e68 xpc_connection_send_message_with_reply_sync + 240 (connection.c:848)
5 BoardServices 0x00000001c2599958 -[BSXPCServiceConnectionMessage _sendSynchronously:] + 332 (BSXPCServiceConnectionMessage.m:129)
6 BoardServices 0x00000001c259a2cc -[BSXPCServiceConnectionMessage sendSynchronouslyWithError:] + 256 (BSXPCServiceConnectionMessage.m:182)
7 BoardServices 0x00000001c2584afc __71+[BSXPCServiceConnectionProxy createImplementationOfProtocol:forClass:]_block_invoke + 824 (BSXPCServiceConnectionProxy.m:274)
8 UIKitServices 0x00000001acb7fc6c -[UISApplicationSupportClient applicationInitializationContextWithParameters:] + 272 (UISApplicationSupportClient.m:77)
9 UIKitCore 0x00000001ab108278 __63-[_UIApplicationConfigurationLoader _loadInitializationContext]_block_invoke_2 + 228 (_UIApplicationConfigurationLoader.m:135)
10 UIKitCore 0x00000001ab108188 __UIAPPLICATION_IS_LOADING_INITIALIZATION_INFO_FROM_THE_SYSTEM__ + 28 (_UIApplicationConfigurationLoader.m:30)
11 UIKitCore 0x00000001ab108160 __63-[_UIApplicationConfigurationLoader _loadInitializationContext]_block_invoke + 100 (_UIApplicationConfigurationLoader.m:106)
12 libdispatch.dylib 0x00000001a8745db0 _dispatch_client_callout + 20 (object.m:559)
13 libdispatch.dylib 0x00000001a87475c8 _dispatch_once_callout + 32 (once.c:52)
14 UIKitCore 0x00000001ab1080f8 -[_UIApplicationConfigurationLoader _loadInitializationContext] + 152 (once.h:84)
15 UIKitCore 0x00000001ab108408 __70-[_UIApplicationConfigurationLoader startPreloadInitializationContext]_block_invoke + 28 (_UIApplicationConfigurationLoader.m:154)
16 libdispatch.dylib 0x00000001a874424c _dispatch_call_block_and_release + 32 (init.c:1454)
17 libdispatch.dylib 0x00000001a8745db0 _dispatch_client_callout + 20 (object.m:559)
18 libdispatch.dylib 0x00000001a8756a68 _dispatch_root_queue_drain + 656 (inline_internal.h:2548)
19 libdispatch.dylib 0x00000001a8757120 _dispatch_worker_thread2 + 116 (queue.c:6777)
20 libsystem_pthread.dylib 0x00000001f46017d8 _pthread_wqthread + 216 (pthread.c:2223)
21 libsystem_pthread.dylib 0x00000001f460876c start_wqthread + 8
Thread 4:
0 libsystem_pthread.dylib 0x00000001f4608764 start_wqthread + 0
Thread 5 name:
Thread 5 Crashed:
0 FrontBoardServices 0x00000001b88d13cc -[FBSSceneParameters initWithSpecification:] + 228 (FBSSceneParameters.m:34)
1 FrontBoardServices 0x00000001b88d1320 -[FBSSceneParameters initWithSpecification:] + 56 (FBSSceneParameters.m:34)
2 FrontBoardServices 0x00000001b88d1dd8 -[FBSSceneParameters initWithXPCDictionary:] + 128 (FBSSceneParameters.m:150)
3 BaseBoard 0x00000001ad165af4 _BSXPCDecodeObject + 1892 (BSXPCCoder.m:583)
4 BaseBoard 0x00000001ad1639c0 _BSXPCDecodeObjectForKey + 268 (BSXPCCoder.m:459)
5 BoardServices 0x00000001c2585f30 +[BSXPCServiceConnectionProxy decodeArguments:outArgs:fromMessage:forConnection:] + 1608 (BSXPCServiceConnectionProxy.m:592)
6 BoardServices 0x00000001c2583ab0 -[BSXPCServiceConnectionProxy invokeMessage:onTarget:] + 276 (BSXPCServiceConnectionProxy.m:342)
7 BoardServices 0x00000001c258a994 __63-[BSXPCServiceConnectionEventHandler connection:handleMessage:]_block_invoke + 536 (BSXPCServiceConnectionEventHandler.m:261)
8 BoardServices 0x00000001c25a0284 BSXPCServiceConnectionExecuteCallOut + 316 (BSXPCServiceConnection.m:1049)
9 BoardServices 0x00000001c258a708 -[BSXPCServiceConnectionEventHandler connection:handleMessage:] + 172 (BSXPCServiceConnectionEventHandler.m:250)
10 BoardServices 0x00000001c259f668 -[BSXPCServiceConnection _connection_handleMessage:fromPeer:withHandoff:] + 572 (BSXPCServiceConnection.m:834)
11 libdispatch.dylib 0x00000001a874424c _dispatch_call_block_and_release + 32 (init.c:1454)
12 libdispatch.dylib 0x00000001a8745db0 _dispatch_client_callout + 20 (object.m:559)
13 libdispatch.dylib 0x00000001a874d10c _dispatch_lane_serial_drain + 580 (inline_internal.h:2548)
14 libdispatch.dylib 0x00000001a874dc90 _dispatch_lane_invoke + 460 (queue.c:3862)
15 libdispatch.dylib 0x00000001a874cfd8 _dispatch_lane_serial_drain + 272 (inline_internal.h:2589)
16 libdispatch.dylib 0x00000001a874dc90 _dispatch_lane_invoke + 460 (queue.c:3862)
17 libdispatch.dylib 0x00000001a8757d78 _dispatch_workloop_worker_thread + 708 (queue.c:6601)
18 libsystem_pthread.dylib 0x00000001f4601814 _pthread_wqthread + 276 (pthread.c:2210)
19 libsystem_pthread.dylib 0x00000001f460876c start_wqthread + 8
Thread 6 name:
Thread 6:
0 libsystem_kernel.dylib 0x00000001d6a822d0 mach_msg_trap + 8
1 libsystem_kernel.dylib 0x00000001d6a81660 mach_msg + 76 (mach_msg.c:103)
2 IOKit 0x00000001b3b68340 io_hideventsystem_copy_property + 172 (IOHIDEventSystemMIGUser.c:3056)
3 IOKit 0x00000001b3b22c60 IOHIDEventSystemClientCopyProperty + 128 (IOHIDEventSystemClient.c:1097)
4 IOKit 0x00000001b3b22354 __IOHIDEventSystemClientRefresh + 1148 (IOHIDEventSystemClient.c:539)
5 IOKit 0x00000001b3b230cc IOHIDEventSystemClientCreateWithType + 856 (IOHIDEventSystemClient.c:749)
6 BackBoardServices 0x00000001ac0dda88 ___getHIDEventSystemClient_block_invoke + 288 (BKSHIDEvent.m:93)
7 libdispatch.dylib 0x00000001a8745db0 _dispatch_client_callout + 20 (object.m:559)
8 libdispatch.dylib 0x00000001a87475c8 _dispatch_once_callout + 32 (once.c:52)
9 BackBoardServices 0x00000001ac0dd964 BKSHIDEventRegisterEventCallbackOnRunLoop + 152 (once.h:84)
10 UIKitCore 0x00000001ab5babe4 -[UIEventFetcher threadMain] + 416 (UIEventFetcher.m:720)
11 Foundation 0x00000001a9ee7a34 __NSThread__start__ + 864 (NSThread.m:724)
12 libsystem_pthread.dylib 0x00000001f45ffcb0 _pthread_start + 320 (pthread.c:881)
13 libsystem_pthread.dylib 0x00000001f4608778 thread_start + 8
Thread 7:
0 libsystem_pthread.dylib 0x00000001f4608764 start_wqthread + 0
Thread 5 crashed with ARM Thread State (64-bit):
x0: 0x000000020ba16880 x1: 0x000000020ba16880 x2: 0x0000000000000000 x3: 0x00000000000008fd
x4: 0x0000000000000000 x5: 0x00000001bdb9873e x6: 0x000000000000006e x7: 0x0000000000000fb0
x8: 0x85f836c8121c001f x9: 0x85f836c8121c001f x10: 0x0000000000005403 x11: 0x0000000281a71540
x12: 0x00000000000000aa x13: 0x0000000000000001 x14: 0x00000000000000ac x15: 0x0000000000000182
x16: 0x00000002c6d90f88 x17: 0x0000000208ac3dc8 x18: 0x0000000000000000 x19: 0x0000000000000000
x20: 0x0000000281472eb0 x21: 0x0000000000000000 x22: 0x0000000000000000 x23: 0x00000001f75f31f5
x24: 0x000000020ba160d8 x25: 0x0000000000000000 x26: 0x00000001f7ff3288 x27: 0x000000020ba160b0
x28: 0x0000000000000000 fp: 0x000000016bb2e130 lr: 0x86106081b88d1320
sp: 0x000000016bb2e100 pc: 0x00000001b88d13cc cpsr: 0x60000000
esr: 0xf2000000 Address size fault
什么可能导致应用程序在真车中崩溃但在模拟器中运行良好?这是我的 AppDelegate 的一部分,我在其中调用一个函数来设置 carplay:
import UIKit
import AVFoundation
import AVKit
import GoogleInteractiveMediaAds
import MediaPlayer
import GoogleMobileAds
import UserNotifications
import WebKit
import SafariServices
import Hero
import Firebase
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
static let reachable = "Reachable"
static let unReachable = "UnReachable"
let reachability = try! Reachability()
var iOSStream: String?
var preRolladUrl: String?
var window: UIWindow?
var adType: String?
var adServer: String?
var adFeed: String?
var miniPlayer: PlayerPopupControlViewController?
var selectedStation: Players!
// CarPlay
var playableContentManager: MPPlayableContentManager?
let carplayPlaylist = CarPlayPlaylist()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UINavigationBar.appearance().barTintColor = UIColor.black
UINavigationBar.appearance().tintColor = UIColor.black
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UINavigationBar.appearance().isTranslucent = false
if UserDefaults.standard.object(forKey: UserDefaultsConstants.resumeRadioInBackground) == nil {
UserDefaults.standard.set(true, forKey: UserDefaultsConstants.resumeRadioInBackground)
UserDefaults.standard.synchronize()
}
self.setupBaseURL()
// Override point for customization after application launch.
self.startObservingReachability()
UIApplication.shared.beginReceivingRemoteControlEvents()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
AppLogger.error(error)
}
UNUserNotificationCenter.current().delegate = self
UserDefaults.standard.set(false, forKey: UserDefaultsConstants.isMiniPlayerDisplayed)
UserDefaults.standard.synchronize()
SettingsDataManager.sharedInstance.selectedStation = SettingsDataManager.sharedInstance.getSelectedStationFromUserDefaults()
registerForPushNotifications()
setupCarPlay()
FirebaseApp.configure()
return true
}
AppDelegate+CarPlay.swift
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 ?? []
// guard let miniPlayer = self.miniPlayer else {return}
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 {
return 0
}
}
func contentItem(at indexPath: IndexPath) -> MPContentItem? {
let playerData = carplayPlaylist.playerData
// let podCastsFeeds = carplayPlaylist.podCastSettings?.podcasts_feeds
if indexPath.count == 1 {
// Tab section
let item: MPContentItem
if (indexPath[0] == 0) {
// Radio Stations Tab Section
item = MPContentItem(identifier: "Radio")
item.title = "Radio"
item.artwork = MPMediaItemArtwork(boundsSize: #imageLiteral(resourceName: "NavIcon-Broadcast").size, requestHandler: { _ -> UIImage in
return #imageLiteral(resourceName: "NavIcon-Broadcast")
})
} else {
// Podcast Tab Section
item = MPContentItem(identifier: "Podcasts")
item.title = "Podcasts"
item.artwork = MPMediaItemArtwork(boundsSize: #imageLiteral(resourceName: "NavIcon-Podcasts").size, requestHandler: { _ -> UIImage in
return #imageLiteral(resourceName: "NavIcon-Podcasts")
})
}
item.isContainer = true
item.isPlayable = false
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
item.downloadImageAndSetArtwork(from: station.logo ?? "", 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()
}
}
}
}
CarPlayPlaylist.swift
import Foundation
//import UIKit
import AVKit
import MediaPlayer
class CarPlayPlaylist {
let radioPlayer = FRadioPlayer.shared
var playerData: [Players] = []
var podCastItemsArray: [[Item]] = []
var podCastSettings: Podcast_settings?
var podCastFeedNameArray:[String] = []
var isFromCarPlay: Bool = false
var playButtonStatus: Bool = false
var currentPodcastItem: Item?
func load(_ completion: @escaping (Error?) -> Void) {
// if (self.podCastFeedNameArray.count == 0 && self.podCastItemsArray.count == 0) {
// getPodcastDataFromSettingsDataManager()
//
// let podcastFeedsCount = self.podCastSettings?.podcasts_feeds?.count ?? 0
// podCastItemsArray = [[Item]](repeating: [], count: podcastFeedsCount)
// }
self.playerData = SettingsDataManager.sharedInstance.playerData ?? []
completion(nil)
}
func play (playURL: URL) {
isFromCarPlay = true
self.radioPlayer.radioURL = playURL
self.radioPlayer.play()
}
func setupCommandCenter () {
MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyTitle: "Current Playing song"]
MPNowPlayingInfoCenter.default().nowPlayingInfo![MPNowPlayingInfoPropertyPlaybackRate] = 0.0
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.playCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.radioPlayer.play()
// NotificationCenter.default.post(name: NSNotification.Name(rawValue: NotificationCenterConstants.carPlayDidPlay), object: nil, userInfo: nil)
return .success
}
commandCenter.pauseCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.radioPlayer.pause()
// NotificationCenter.default.post(name: NSNotification.Name(rawValue: NotificationCenterConstants.carPlayDidPause), object: nil, userInfo: nil)
return .success
}
}
}
extension CarPlayPlaylist {
func addCommandCenterNotificationObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(commandCenterDidPlay), name: NSNotification.Name(rawValue: NotificationCenterConstants.commandCenterDidPlay), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(commandCenterDidPause), name: NSNotification.Name(rawValue: NotificationCenterConstants.commandCenterDidPause), object: nil)
}
func removeCommandCenterObservers() {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationCenterConstants.commandCenterDidPlay), object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationCenterConstants.commandCenterDidPause), object: nil)
}
@objc func commandCenterDidPlay() {
// Playback rate should change the playback status
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0.0
}
@objc func commandCenterDidPause() {
// Playback rate should change the playback status
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
}
}