一、首先须要引用系统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耳机(有无麦克风)拔出/插入事件拔出事件,从而触发相关操做。