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

1、首先需要引用系统Framework – AVFoundation,然后在AppDelegate的应用启动事件里面添加以下代码:

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

AVAudioSessionCategoryPlayback是用来指定支持后台播放的。

当然代码添加完了之后并不是就已经可以后台播放了,还需要在info-plist文件里面注明我们的应用需要支持后台运行。

打开info- plist,添加Required background modes项,再把Item 0编辑成audio按回车,xCode会自动补全内容

2、我们接下来需要做的就是向系统注册远程控制(Remote Control),在播放音频的ViewController里添加以下代码:

- (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;
}
3、完成了注册工作,需要控制生效的话还需要对不同的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;
        }
    }
}4、锁屏的时候可以显示当前播放曲目的封面和一些信息
- (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];
  }
}
5、耳机插拔监控[[NSNotificationCenterdefaultCenter] 

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

- (void)outputDeviceChanged:(NSNotification *)aNotification


{


// do your jobs here


}

请注意,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),所以必须先行处理

强制更改输出设备

 - (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耳机(有无麦克风)拔出/插入事件拔出事件,从而触发相关操作。
				
时间: 2024-08-24 21:26:30

音乐播放-后台-耳机控制-耳机插拔的相关文章

Android底层开发之耳机插拔与音频通道切换实例

Android底层开发之耳机插拔与音频通道切换实例 由于使用的是耳机 麦克分离式的耳机,所以要分别上报事件.在Android系统层耳机插孔的检测是基于/sys/class/switch/h2w/state的值来判断的(以4.4.4_r2为例子位于WiredAccessoryManager.java). 只要在内核中实现一个「或真或假」的基于switch类的h2w开关.Android系统就可以监听到插拔信息. 在播放音乐的时候插入耳机,使用tinymix(参考:Android音频底层调试-基于ti

耳机插拔流程

1.1      耳机 在Android系统中,有线耳机分两种,一种带mic,一种不带mic,带mic的耳机被称为Headset,不带mic的耳机被称为HeadPhone.在audio.h中,有以下几个设备来表示耳机: AUDIO_DEVICE_OUT_WIRED_HEADSET             = 0x4, AUDIO_DEVICE_OUT_WIRED_HEADPHONE           = 0x8, AUDIO_DEVICE_IN_WIRED_HEADSET         =

Android4.×耳机插拔检测

Android4.2耳机插拔检测实现方法 1. 耳机检测的硬件原理 一般的耳机检测包含普通的耳机检测和带mic的耳机检测两种,这两种耳机统称为Headset,而对于不带mic的耳机,一般称之为Headphone. 对于Headset装置的插入检测,一般通过Jack即耳机插座来完成,大致的原理是使用带检测机械结构的耳机插座,将检测脚连到可GPIO中断上,当耳机插入时,耳机插头的金属会碰到检测脚,使得检测脚的电平产生变化,从而引起中断.这样就可以在中断处理函数中读取GPIO的的值,进一步判断出耳机是

iOS_33_音乐播放(后台播放+锁屏歌词)

最终效果图: 应用程序代理(后台播放三步曲) // // BeyondAppDelegate.h // 33_音效 // // Created by beyond on 14-9-10. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import <UIKit/UIKit.h> @interface BeyondAppDelegate : UIResponder <UIApplicationDelegate>

AVPlayer 音乐播放后台播放,以及锁屏主题设置

第一步:在appDelegate中通知app支持后台播放:在方法 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {} 中添加如下代码: AVAudioSession *audioSession = [AVAudioSession sharedInstance]; //默认情况下扬声器播放 [audioSession setCa

使用BroadcastReceiver的音乐播放器

广播是Andorid的全局监听器,用于监听全局的广播消息.因此,它可以非常方便地实现系统中不同组件之间的通信.本例将展示通过使用BroadcastReceiver在Activity和Service之间进行通信.UI界面只管界面,并不对音乐的播放进行操作.当用户操作UI时,Activity只是改变UI,并向Service发出广播.接收到广播的Service根据广播的内容,做出相应的操作.同时,Service的状态发生改变时,也会向Activity发出广播,Activity根据广播的内容改变界面.

音乐播放器(支持本地搜索,字母检索,进度条控制,后台来电控制音乐)

音乐播放器(支持本地搜索,字母检索,进度条控制,后台来电控制音乐) 自己做的一个简单的音乐播放器,ViewPager+ListView布局. 下载地址:http://www.devstore.cn/code/info/541.html 运行截图:   版权声明:本文为博主原创文章,未经博主允许不得转载.

Android-Service (基本知识,生命周期,实例-startService 启动的服务音乐播放器后台服务播放)

1.回顾 上篇 学习了 Android的四大组件之一 BroadCastReceiver 的 相关知识 2.重点 (1)Service 分类 (2)Service 的生命周期 (3)Service 标签 下的属性 (4)什么时候使用BindService 和 startService ? (5)实例 - 通过Service 服务 实现 音乐播放器 后台播放 3.Service 分类 3.1 按照地点分 (1)本地服务 服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local

iOS开发——实用技术精选OC篇&amp;后台音乐与锁屏界面音乐播放

一:后台音乐播放 在plist中开启后台服务 实现简单的代码 二:锁屏界面音乐播放 其他按钮的实现(上一曲,下一曲,暂停,播放) 最后将歌词实时的绘制上去: