音樂播放-後臺-耳機控制-耳機插拔

一、首先須要引用系統Framework – AVFoundation,而後在AppDelegate的應用啓動事件裏面添加如下代碼:

AVAudioSession *session = [AVAudioSession sharedInstance]; [session setCategory:AVAudioSessionCategoryPlayback error:nil] [session setActive:YES error:nil]

AVAudioSessionCategoryPlayback是用來指定支持後臺播放的。java

固然代碼添加完了以後並非就已經能夠後臺播放了,還須要在info-plist文件裏面註明咱們的應用須要支持後臺運行。session

打開info- plist,添加Required background modes項,再把Item 0編輯成audio按回車,xCode會自動補全內容app

 

二、咱們接下來須要作的就是向系統註冊遠程控制(Remote Control),在播放音頻的ViewController裏添加如下代碼:ide

- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; [self becomeFirstResponder]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; [self resignFirstResponder]; } - (BOOL)canBecomeFirstResponder { return YES; }
三、完成了註冊工做,須要控制生效的話還須要對不一樣的remote control事件進行響應
- (void)remoteControlReceivedWithEvent:(UIEvent *)event { if (event.type == UIEventTypeRemoteControl) { switch (event.subtype) { case UIEventSubtypeRemoteControlTogglePlayPause: [self resumeOrPause]; // 切換播放、暫停按鈕 break; case UIEventSubtypeRemoteControlPreviousTrack: [self playPrev]; // 播放上一曲按鈕 break; case UIEventSubtypeRemoteControlNextTrack: [self playNext]; // 播放下一曲按鈕 break; default: break; } } }
四、鎖屏的時候能夠顯示當前播放曲目的封面和一些信息
- (void)configPlayingInfo { if (NSClassFromString(@"MPNowPlayingInfoCenter")) { NSMutableDictionary * dict = [[NSMutableDictionary alloc] init]; [dict setObject:@"曲目標題" forKey:MPMediaItemPropertyTitle]; [dict setObject:@"曲目藝術家" forKey:MPMediaItemPropertyArtist]; [dict setObject:[[[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"曲目封面.png"]] autorelease] forKey:MPMediaItemPropertyArtwork]; [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil]; [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict]; } }
五、耳機插拔監控
[[NSNotificationCenterdefaultCenter]

addObserver:selfselector:@selector(outputDeviceChanged:)name:AVAudioSessionRouteChangeNotificationobject:[AVAudioSessionsharedInstance]];

 - (void)outputDeviceChanged:(NSNotification *)aNotification函數

 

{post

 

 // do your jobs hereui

 

}this

請注意,addobserver的參數填寫:其中的object必須是[AVAudioSession sharedInstance],
而不是咱們一般不少狀況下填寫的nil,此處若爲nil,通知也不會觸發。


1. 檢測聲音輸入設備
  1. - (BOOL)hasMicphone {  
  2.     return [[AVAudioSession sharedInstance] inputIsAvailable];  
  3. }
2.輸出設備的檢測,咱們只考慮了2個狀況,一種是設備自身的外放(iTouch/iPad/iPhone都有),一種是當前是否插入了帶外放的耳機
  1. CFStringRef route;  
  2. UInt32 propertySize = sizeof(CFStringRef);  
  3. AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route);
全部設備: 
"Headset"  "Headphone"  "Speaker" "SpeakerAndMicrophone" "HeadphonesAndMicrophone"  "HeadsetInOut" "ReceiverAndMicrophone"  "Lineout" 


判斷有無設備:
 - (BOOL)hasHeadset {  
    #if TARGET_IPHONE_SIMULATOR  
        #warning *** Simulator mode: audio session code works only on a device  
        return NO;  
    #else   
    CFStringRef route;  
    UInt32 propertySize = sizeof(CFStringRef);  
    AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route);  
    if((route == NULL) || (CFStringGetLength(route) == 0)){  
        // Silent Mode  
        NSLog(@"AudioRoute: SILENT, do nothing!");  
    } else {  
        NSString* routeStr = (NSString*)route;  
        NSLog(@"AudioRoute: %@", routeStr);  
        /* Known values of route:  
         * "Headset"  
         * "Headphone"  
         * "Speaker"  
         * "SpeakerAndMicrophone"  
         * "HeadphonesAndMicrophone"  
         * "HeadsetInOut"  
         * "ReceiverAndMicrophone"  
         * "Lineout"  
         */  
        NSRange headphoneRange = [routeStr rangeOfString : @"Headphone"];  
        NSRange headsetRange = [routeStr rangeOfString : @"Headset"];  
        if (headphoneRange.location != NSNotFound) {  
            return YES;  
        } else if(headsetRange.location != NSNotFound) {  
            return YES;  
        }  
    }  
    return NO;  
    #endif  
}

不能再simulator上運行(會直接crush),因此必須先行處理spa

 

強制更改輸出設備code

 - (void)resetOutputTarget {  
    BOOL hasHeadset = [self hasHeadset];  
    NSLog (@"Will Set output target is_headset = %@ .", hasHeadset ? @"YES" : @"NO");  
    UInt32 audioRouteOverride = hasHeadset ?  
        kAudioSessionOverrideAudioRoute_None:kAudioSessionOverrideAudioRoute_Speaker;  
    AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);  
} 

 

4. 設置Audio工做模式(category,我當作工做模式理解的)
iOS系統中Audio支持多種工做模式(category),要實現某個功能,必須首先將AudioSession設置到支持該功能的工做模式下。全部支持的工做模式以下
 Audio Session Categories  
Category identifiers for audio sessions, used as values for the setCategory:error: method.  
NSString *const AVAudioSessionCategoryAmbient;  
NSString *const AVAudioSessionCategorySoloAmbient;  
NSString *const AVAudioSessionCategoryPlayback;  
NSString *const AVAudioSessionCategoryRecord;  
NSString *const AVAudioSessionCategoryPlayAndRecord;  
NSString *const AVAudioSessionCategoryAudioProcessing; 


具體每個category的功能請參考iOS文檔,其中AVAudioSessionCategoryRecord爲獨立錄音模式,而
AVAudioSessionCategoryPlayAndRecord爲支持錄音盒播放的模式,而
AVAudioSessionCategoryPlayback爲普通播放模式。
設置category:
 
     - (BOOL)checkAndPrepareCategoryForRecording {  
        recording = YES;  
        BOOL hasMicphone = [self hasMicphone];  
        NSLog(@"Will Set category for recording! hasMicophone = %@", hasMicphone?@"YES":@"NO");  
        if (hasMicphone) {  
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord  
                                                   error:nil];  
        }  
        [self resetOutputTarget];  
        return hasMicphone;  
    }  
    - (void)resetCategory {  
        if (!recording) {  
            NSLog(@"Will Set category to static value = AVAudioSessionCategoryPlayback!");  
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback  
                                                   error:nil];  
        }  
    }  

5. 檢測耳機插入/拔出事件


耳機插入拔出事件是經過監聽AudioSession的RouteChange事件而後判斷耳機狀態實現的。實現步驟分爲兩步,首先註冊監聽函數,而後再監聽函數中判斷耳機狀態。
註冊監聽函數:

 

 AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange,  
                                     audioRouteChangeListenerCallback,  
                                     self);

 

 


咱們的需求是當耳機插入或拔出時作出響應,而產生AouteChange事件的緣由有多種,因此須要對各類類型進行處理並結合當前耳機狀態進行判斷。在iOS文檔中,產生AouteChange事件的緣由有以下幾種:

 

 Audio Session Route Change Reasons  
Identifiers for the various reasons that an audio route can change while your iOS application is running.  
enum {  
   kAudioSessionRouteChangeReason_Unknown                    = 0,  
   kAudioSessionRouteChangeReason_NewDeviceAvailable         = 1,  
   kAudioSessionRouteChangeReason_OldDeviceUnavailable       = 2,  
   kAudioSessionRouteChangeReason_CategoryChange             = 3,  
   kAudioSessionRouteChangeReason_Override                   = 4,  
   // this enum has no constant with a value of 5  
   kAudioSessionRouteChangeReason_WakeFromSleep              = 6,  
   kAudioSessionRouteChangeReason_NoSuitableRouteForCategory = 7  
};


具體每一個類型的含義請查閱iOS文檔,其中咱們關注的是kAudioSessionRouteChangeReason_NewDeviceAvailable有新設備插入、
kAudioSessionRouteChangeReason_OldDeviceUnavailable原有設備被拔出以及
kAudioSessionRouteChangeReason_NoSuitableRouteForCategory當前工做模式缺乏合適設備。
當有新設備接入時,若是檢測耳機,則斷定爲耳機插入事件;當原有設備移除時,若是沒法檢測耳機,則斷定爲耳機拔出事件;當出現「當前工做模式缺乏合適設備時」,直接斷定爲錄音時拔出了麥克風。
很明顯,這個斷定邏輯實際上不許確,好比原來就有耳機可是插入了一個新的audio設備或者是原來就沒有耳機可是拔出了一個原有的audio設備,咱們的斷定都會出錯。可是對於咱們的項目來講,其實關注的不是耳機是拔出仍是插入,真正關注的是有audio設備插入/拔出時可以根據當前耳機/麥克風狀態去調整設置,因此這個斷定實現對咱們來講是正確的。
監聽函數的實現:
 void audioRouteChangeListenerCallback (  
                                       void                      *inUserData,  
                                       AudioSessionPropertyID    inPropertyID,  
                                       UInt32                    inPropertyValueSize,  
                                       const void                *inPropertyValue  
                                       ) {  
    if (inPropertyID != kAudioSessionProperty_AudioRouteChange) return;  
    // Determines the reason for the route change, to ensure that it is not  
    //        because of a category change.  
  
    CFDictionaryRef    routeChangeDictionary = inPropertyValue;  
    CFNumberRef routeChangeReasonRef =  
    CFDictionaryGetValue (routeChangeDictionary,  
                          CFSTR (kAudioSession_AudioRouteChangeKey_Reason));  
    SInt32 routeChangeReason;  
    CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);  
    NSLog(@" ======================= RouteChangeReason : %d", routeChangeReason);  
    AudioHelper *_self = (AudioHelper *) inUserData;  
    if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) {  
        [_self resetSettings];  
        if (![_self hasHeadset]) {  
            [[NSNotificationCenter defaultCenter] postNotificationName:@"ununpluggingHeadse  
                                                                object:nil];  
        }  
    } else if (routeChangeReason == kAudioSessionRouteChangeReason_NewDeviceAvailable) {  
        [_self resetSettings];  
        if (![_self hasMicphone]) {  
            [[NSNotificationCenter defaultCenter] postNotificationName:@"pluggInMicrophone"  
                                                                object:nil];  
        }  
    } else if (routeChangeReason == kAudioSessionRouteChangeReason_NoSuitableRouteForCategory) {  
        [_self resetSettings];  
        [[NSNotificationCenter defaultCenter] postNotificationName:@"lostMicroPhone"  
                                                            object:nil];  
    }  
    //else if (routeChangeReason == kAudioSessionRouteChangeReason_CategoryChange  ) {  
    //    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];         
    //}  
    [_self printCurrentCategory];  
} 
檢測到相關事件後,經過NSNotificationCenter通知observers耳機(有無麥克風)拔出/插入事件拔出事件,從而觸發相關操做。
相關文章
相關標籤/搜索