近期處理了一個掛斷電話后,莫名手機開始播放音樂的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這個變量重置。