音乐播放-后台-耳机控制-耳机插拔

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