一、首先須要引用系統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. 檢測聲音輸入設備
2.輸出設備的檢測,咱們只考慮了2個狀況,一種是設備自身的外放(iTouch/iPad/iPhone都有),一種是當前是否插入了帶外放的耳機。
全部設備:
"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耳機(有無麥克風)拔出/插入事件拔出事件,從而觸發相關操做。