Audio Session Interruption
近期处理了一个挂断电话后,莫名手机开始播放音乐的Bug。 所以顺便在这总结一下,对于IOS的AudioSession中断的几种处理情况。
一、通过C语言的init方法配置interruptionl回调。建议用这种方法,但有些细节需要注意,后续会谈到。
AudioSessionInitialize ( NULL, // 1 NULL, // 2 interruptionListenerCallback, // 3 userData // 4 );
然后在回调,实现如下逻辑代码:
void interruptionListenerCallback ( void *inUserData, UInt32 interruptionState ) { AudioViewController *controller = (AudioViewController *)inUserData; if (interruptionState == kAudioSessionBeginInterruption) { if (controller.audioRecorder) { [controller recordOrStop: (id) controller]; } else if (controller.audioPlayer) { [controller pausePlayback]; controller.interruptedOnPlayback = YES; } } else if ((interruptionState == kAudioSessionEndInterruption) && controller.interruptedOnPlayback) { [controller resumePlayback]; controller.interruptedOnPlayback = NO; } }
二、使用AVAudioSessionDelegate。如果你使用的是AVAudioPlayer或AVAudioRecorder,还可以使用对应的AVAudioPlayerDelegate和 AVAudioRecorderDelegate。但AVAudioSessionDelegate在6.0后被弃用,所以使用有局限性。后者没有被弃用。
- (void) beginInterruption { if (playing) { playing = NO; interruptedWhilePlaying = YES; [self updateUserInterface]; } }
NSError *activationError = nil; - (void) endInterruption { if (interruptedWhilePlaying) { BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError]; if (!success) { /* handle the error in activationError */ } [player play]; playing = YES; interruptedWhilePlaying = NO; [self updateUserInterface]; } }
三、如上所说,6.0弃用了AVAudioSessionDelegate。所以6.0之后使用AVAudioSessionInterruptionNotification来实现类似的功能。AVAudioSessionInterruptionNotification的userInfo中包括AVAudioSessionInterruptionTypeKey和AVAudioSessionInterruptionTypeEnded。
四、使用RouteChange的回调。对于音乐播放,如果当然当前是耳机模式,拔掉耳机一般是希望音乐暂停的。类似这种拔插设备,播放语音等声音设备切换,一般通过RouteChange的回调来控制。
注册RouteChange的回调:
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,audioRouteChangeListenerCallback,nil);
回调处理代码:
void audioRouteChangeListenerCallback(void *inUserData, AudioSessionPropertyID inPropertyID, UInt32 inPropertyValueSize, const void *inPropertyValue) { if (inPropertyID != kAudioSessionProperty_AudioRouteChange) return; CFDictionaryRef routeChangeDictionary = inPropertyValue; CFNumberRef routeChangeReasonRef = CFDictionaryGetValue (routeChangeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_Reason)); CFStringRef oldRouteRef = CFDictionaryGetValue (routeChangeDictionary, CFSTR (kAudioSession_AudioRouteChangeKey_OldRoute)); NSString *oldRouteString = (NSString *)oldRouteRef; SInt32 routeChangeReason; CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason); if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) { if (oldRouteStringplaying ) { //需判断不可用Route为耳机时 playing = NO; interruptedWhilePlaying = NO; //清除中断标识,如电话中拔掉耳机挂断时,不需要继续播放 } } }
对于AudioSession的Route,有如下几种模式:
/* Known values of route: *"Headset" * "Headphone" * "Speaker" * "SpeakerAndMicrophone" * "HeadphonesAndMicrophone" * "HeadsetInOut" * "ReceiverAndMicrophone" * "Lineout" */ //ios5以后可使用的一些类型 const CFStringRef kAudioSessionOutputRoute_LineOut; const CFStringRef kAudioSessionOutputRoute_Headphones; const CFStringRef kAudioSessionOutputRoute_BluetoothHFP; const CFStringRef kAudioSessionOutputRoute_BluetoothA2DP; const CFStringRef kAudioSessionOutputRoute_BuiltInReceiver; const CFStringRef kAudioSessionOutputRoute_BuiltInSpeaker; const CFStringRef kAudioSessionOutputRoute_USBAudio; const CFStringRef kAudioSessionOutputRoute_HDMI; const CFStringRef kAudioSessionOutputRoute_AirPlay;
关于其他的一些总结:
1、对于SDK6.0,AudioSession中断是一个Bug版本。不会响应AVAudioSessionDelegate,且不响应AVAudioSessionInterruptionNotification。C语言中断,当使用AVPlayer后,不响应kAudioSessionBeginInterruption,但响应kAudioSessionEndInterruption。 这是苹果的Bug。对上这种Case,有如下处理办法:
1)当收到kAudioSessionEndInterruption时,调用暂停播放更新UI。
2)使用RouteChange来判断电话的接通和挂断情境。但如果是耳机模式,电话的接通和挂断,经测试,使用的是同一种Route。所以,对于SDK6.0,播放过程中未接耳机时,可通过RouteChange来恢复播放。而耳机模式播放时,来电恢复播放,目前看来无完善的处理方式。
2、当后台播放歌曲时,打开游戏之类APP,声道会被其APP占用。这种Case会有kAudioSessionBeginInterruption。但返回时不会有kAudioSessionEndInterruption。所以,这种Case在回到APP时,需要interruptedWhilePlaying这个变量重置。