Android管理音频播放_qaqyqx的博客-程序员宝宝

技术标签: Android  

控制音量与音频播放

  • 首先我们需要做的是鉴别使用的是哪个音频流
    Android为播放音乐,闹铃,通知铃,来电声音,系统声音,打电话声音与拨号声音分别维护了一个独立的音频流。这样做的主要目的是让用户能够单独地控制不同的种类的音频。上述音频种类中,大多数都是被系统限制。例如,除非你的应用需要做替换闹钟的铃声的操作,不然的话你只能通过STREAM_MUSIC来播放你的音频。
  • 使用硬件音量键来控制应用的音量

默认情况下,按下音量控制键会调节当前被激活的音频流,如果我们的应用当前没有播放任何声音,那么按下音量键会调节响铃的音量。对于游戏或者音乐播放器而言,即使是在歌曲之间无声音的状态,或是当前游戏处于无声的状态,用户按下音量键的操作通常都意味着他们希望调节游戏或者音乐的音量。

你可能希望通过监听音量键被按下的事件,来调节音频流的音量。其实我们不必这样做。Android提供了setVolumeControlStream()方法来直接控制指定的音频流。在鉴别出应用会使用哪个音频流之后,我们需要在应用生命周期的早期阶段调用该方法,因为该方法只需要在Activity整个生命周期中调用一次,通常,我们可以在负责控制多媒体的Activity或者Fragment的onCreate()方法中调用它。这样能确保不管应用当前是否可见,音频控制的功能都能符合用户的 预期。

setVolumeControlStream(AudioManager.STREAM_MUSIC);

自此之后,不管目标Activity或Fragment是否可见,按下设备的音量键都能够影响我们指定的音频流(在这个例子中,音频流是”music”)。

  • 使用硬件的播放控制按键来控制应用的音频播放

许多线控或者无线耳机都会有许多媒体播放控制按钮,例如:播放,停止,暂停,跳过,以及回放等。无论用户按下设备上任意一个控制按钮,系统都会广播一个带有ACTION_MEDIA_BUTTON的Intent。为了正确地响应这些操作,需要在Manifest文件中注册一个针对于该Action的BroadcastReceiver,如下所示:

<receiver android:name=".RemoteControlReceiver">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>

在Receiver的实现中,需要判断这个广播是来自于哪一个按钮,Intent通过EXTRA_KEY_EVENT这一Key包含了该信息,另外,KeyEvent类包含了一系列诸如KEYCODE_MEDIA_*的静态变量来表示不同的媒体按钮,例如KEYCODE_MEDIA_PLAY_PAUSEKEYCODE_MEDIA_NEXT

public class RemoteControlReceiver extends BroadcastReceiver {
    
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
            KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
            if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) {
                // Handle key press.
            }
        }
    }
}

因为可能会有多个程序在监听与媒体按钮相关的事件,所以我们必须在代码中控制应用接收相关事件的时机。下面例子显示了如何使用AudioManager来为我们的应用注册监听与取消监听媒体按钮事件,当Receiver被注册上时,它是唯一一个能够响应媒体按钮广播的Receiver。

AudioManager am= mContext.getSystemService(Context.AUDIO_SERVICE);
...
//start listening for button presses
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
...
//stopping listening for button presses
am.unregisterMediaButtonEventReceicer(RemoteControlReceiver);

通常,应用在他们失去焦点或者 不可见的时候(比如在Stop()方法里面)取消监听。但是对于媒体播放并没有这么简单,实际上,在应用不可见(不能通过可见的UI控件进行控制)的时候,仍然能够响应媒体播放按钮事件是及其重要的。为了实现这一点,有一个更好的方法,我们可以在程序获取与失去音频焦点的时候注册与取消对音频按钮事件的监听。


管理音频焦点

由于可能会有多个应用可以播放音频,所以我们应当考虑一下他们应该如何交互。为了防止多个音乐播放应用同时播放音频,Android使用音频焦点(Audio Focus)来控制音频的播放——即只有获取到音频焦点的应用才能够播放音频。

在我们的应用开始播放音频之前,它需要先请求音频焦点,然后再获取到音频焦点。另外,它还需要知道如何监听失去音频焦点的事件并对此做出合适的响应。

  • 请求获取音频焦点

在我们开始播放音频前,他需要获取将要使用音频流的音频焦点。通过使用requestAudioFocus()方法可以获取我们希望得到的音频流焦点。如果请求成功,该方法会返回AUDIOFOCUS_REQUEST_GRANTED。
另外我们必须指定正在使用的音频流,而且需要确定所请求的音频焦点是短暂的
(Trainsient)还是永久的(Permanent)

    短暂的焦点锁定:当计划播放一个短暂的音频时使用(比如播放导航提示)。
    永久的焦点锁定:当计划播放一个较长但时长可以预期的音频时使用(如播放音乐)。

下面的例子是一个在播放音乐时请求永久音频焦点的例子,我们必须在开始播放之前立即请求音频焦点,比如在用户点击播放或者游戏中下一关的背景音乐开始前。

AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
//Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
                                    //Use the music stream
                                    AudioManager.STREAM_MUSIC,
                                    //Request Permanent focus
                                    AudioManager.AUDIOFOCUS_GAIN);
if(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED){
    am.registerMedioButtonEventReceiver(RemoteControlReceiver);
    //Start playback
}

一旦结束了播放,需要确保调用了abandonAudioFocus()方法。这样相当于告知系统我们不再需要获取焦点并且注销所关联的AudioManager.OnAudioFocusChangeListener监听器。对于另一种释放短暂音频焦点的情况,这会允许任何被我们打断的应用可以播放。

//Abandon audio focus when playback complete
am.abandonAudioFocus(afChangeListener);

当请求短暂音频焦点的时候,我们可以选择是否开启“Ducking”。通常情况下,一个应用在失去音频焦点时会立即关闭它的播放声音。如果我们选择在请求短暂音频焦点的时候开启了Ducking,那意味着其它应用可以继续播放,仅仅是在这一刻降低自己的音量,直到重新获取到音频焦点后恢复正常音量(译注:也就是说,不用理会这个短暂焦点的请求,这并不会打断目前正在播放的音频。比如在播放音乐的时候突然出现一个短暂的短信提示声音,此时仅仅是把歌曲的音量暂时调低,使得用户能够听到短信提示声,在此之后便立马恢复正常播放)。

//request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
                                    //Use the music stream
                                    AudioManager.STREAM_MUSIC,
                                    //request permanent focus
                                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED){
    //start playback
}

Ducking对于那些间歇性使用音频焦点的应用来说特别合适,比如语音导航。如果有另一个应用向上述那样请求音频焦点,它所请求的永久音频焦点(支持Ducking或之者不支持Ducking),都会被你在请求获取音频焦点时所注册的监听器接收到。

  • 处理失去音频焦点

如果应用A请求获取了音频焦点,那么在应用B请求获取音频焦点的时候,A获取到的焦点就会失去,如何响应失去焦点事件,取决于失去焦点的方式。

在音频焦点的监听器里面,当接受到描述音频焦点改变的事件时会触发onAudioFocusChange()回调方法。如之前提到的,获取焦点有三种类型,我们同样会有三种失去焦点的类型:永久失去,短暂失去,允许Ducking的短暂失去。

失去短暂焦点:通常在失去短暂焦点的情况下,我们会暂停当前音频的播放或者降低音量,同时需要准备在重新获取到焦点之后恢复播放。
失去永久焦点:假设另外一个应用开始播放音乐,那么我们的应用就应该有效地将自己停止。在实际场景当中,这意味着停止播放,移除媒体按钮监听,允许新的音频播放器可以唯一地监听那些按钮事件,并且放弃自己的音频焦点。此时,如果想要恢复自己的音频播放,我们需要等待某种特定用户行为发生(例如按下了我们应用当中的播放按钮)。

在下面的代码片段中,如果焦点的失去时短暂的,我们将音频播放对象暂停,并在重新获取到焦点后进行恢复。如果是永久型的焦点失去事件,那我们的媒体按钮监听器会被注销,并且不再监听音频焦点的改变。

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener(){
    public void onAudioFocusChange(int focusChange){
        if(focusChange == AUDIOFOCUS_LOSS_TRANSIENT){
            //Pause playback
        }else if(focusChange == AudioManager.AUDIOFOCUS_GAIN){
            //Resume playback
        }else if(focusChange == AudioManager.AUDIOFOCUS_LOSS){
            am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
            am.abandonAudioFocus(afChangeListener);
            //Stop playback
        }
    }
};

在上面失去短暂焦点的例子中,如果允许Ducking,那么除了暂停当前的播放之外,我们还可以选择使用“Ducking”。

  • Duck

在使用Ducking时,正常播放的歌曲会降低音量来凸显这个短暂的音频声音,这样既让这个短暂的声音比较突出,又不至于打断正常的声音。
下面的代码片段让我们的播放器在短暂失去音频焦点的时降低音量,并在重新获得音频焦点之后恢复原来的音量。

OnAudioFocusChangeListener afChangeListenre = new OnAudioFocusChangeListener(){
    public void onAudioFocusChange(int focusChange){
        if(focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK){
            //Lower the volume
        }else if(focusChange == AudioManager.AUDIOFOCUS_GAIN){
            //Raise it back to normal
        }
};

音频焦点的失去是我们需要响应的最重要的事件广播之一,但除此之外还有很多其他重要的广播需要我们正确地做出响应。系统会广播一系列的Intent来向你告知用户正在使用音频过程当中的各种变化。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qaqyqx/article/details/82387992

智能推荐

VI操作--跳到最后一行的最后一个字符_ZYG-csdn的博客-程序员宝宝_vi 最后一个字符

vi操作在非编辑状态下,就是 “vi” 进入编辑模式 但是没有点击“i”1.跳到文本的最后一行:按“G”,即“shift+g”2.跳到最后一行的最后一个字符 : 先重复1的操作即按“G”,之后按“$”键,即“shift+4”。3.跳到第一行的第一个字符:先按两次“g”,4.跳转到当前行的第一个字符:在当前行按“0”。5.vi加密。进入vi,输入"

python中表示空类型的是_python中表示空_weixin_39761647的博客-程序员宝宝

广告关闭腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!nonetype表示该值是一个空对象,空值是python里一个特殊的值,用none表示。 none不能理解为0,因为0是有意义的,而none是一个特殊的空值。 可以将none赋值给任何变量,也可以给none值变量赋值in: type()out: str知识点扩展:在python中,n...

C语言--函数_驼同学.的博客-程序员宝宝

这个作业属于哪个班级C语言–网络2011/2012这个作业的地址C语言博客作业03–函数这个作业的目标学习如何设计函数、C语言基本数据类型姓名骆锟宏文章目录0. 展示PTA的题目1. 本章学习总结:1.1 函数的定义、调用、声明;1.1.1函数的定义:1.1.2函数的调用:1.1.3函数的声明:1.2 全局、局部变量,静态局部变量;1.2.1全局变量:1.2.2局部变量:1.2.3静态局部变量:1.3 C语言数据类型及注意点;1.4 C运算符;1.5 全局变量和局...

mysql 修改校对规则_MYSQL校对规则_杜连涛的博客-程序员宝宝

PythonPython开发Python语言MYSQL校对规则 一、前言有时候遇到这种情况,你用一个like语句查询,查到的结果中有一些并没有包含你查询的关键词的纪录;有时候遇到这种情况,你的数据库自作聪明的大小写不敏感,让你在更新时把大小写不同的两条记录都更新了;有时候遇到这种情况,你的查询语句一切正常,查询却失败了,报告Illegal mix of collations错...

c语言常见运行错误提示,c语言运行时的错误提示_李菲浩的博客-程序员宝宝

c语言运行时的错误提示 可能很多人在安装 VC 6.0 后有过点击“Compile”或者“Build”后被出现的 “Compiling. ,Error spawning cl.”错误提示给郁闷过。很多人的 选择是重装,实际上这个问题很多情况下是由于路径设置的问题引起的, “CL.”是 VC 使用真正的编译器(编译程序),其路径在“VC 根目录/VC98/Bin”下面, 你可以到相应的路径下找到这个...

互斥量(转)_weixiuc的博客-程序员宝宝

一、什么是互斥锁       另一种在多线程程序中同步访问手段是使用互斥量。程序员给某个对象加上一把“锁”,每次只允许一个线程去访问它。如果想对代码关键部分的访问进行控制,你必须在进入这段代码之前锁定一把互斥量,在完成操作之后再打开它。        互斥量函数有       pthread_mutex_init 初始化一个互斥量       pthread_mutex_loc

随便推点

宏碁笔记本一键重装win7系统教程_qq_33320528的博客-程序员宝宝

U盘装机大师一键重装宏碁笔记本win7系统教程win7系统是最多电脑用户使用的系统了,但是使用了那么久的win7系统你懂得如何安装吗?下面给大家介绍宏碁笔记本如何进行一键重装win7系统教程,让您轻松学会用U盘安装win7系统!第一步:下载U启动盘制作工具工具:U盘装机大师U盘启动盘制作工具版本:v3.6.5 官方版大小:717MB第二步:U盘启动盘制作把之前准备好的U盘插入电脑的USB插口,这个...

使用putty远程连接centos阿里云服务器_包子源的博客-程序员宝宝

首先需要下载putty,很小,而且是开元免费的https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html安装很简单,一直下一步,直到安装完成就可以了。然后打开putty。输入centos服务器IP和端口22。点击open输入用户名(一般都是root)然后Enter。再输入密码(这里输入密码不会显示出来哦)Enter。出现...

Java——创建一个简单的窗口_RTFIL的博客-程序员宝宝_java创建一个窗口

今天刚刚上完Java饶有兴趣的我 做了这么一个简单的窗口。挺有成就感的。package 国玉; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class MainUI exten...

matlab论文图示例代码,MATLAB抠图(示例代码)_assassin chen的博客-程序员宝宝

http://blog.sina.com.cn/s/blog_5dd2e9270100xdcp.html代码不能运行,不知道咋回事poison matting matlab code注:显示图像后,单击鼠标左键连出完全背景区域,双击左键结束;图像改变后,再单击鼠标左键连出完全前景区域,双击左键结束。即可得到结果。A=imread(‘ppmm2.bmp‘);imshow(A);hold on;Thr...

JDE和JRE、JVM之间的关系_young_qin的博客-程序员宝宝_jde和jre

JDE(JAVA Development Kit):是JAVA程序开发包,为开发人员使用的。包含JRE和一些工具。JRE( JAVA runtime Environment):是JAVA运行时需要的运行环境,包含JVM和运行时必备的核心类库(lib)。JVM(JAVA Virtual Machine):JAVA虚拟机,它是整个java实现跨平台的最核心的部分。用于编译程序时,(.class)由JAVA虚拟机先执行完成后,将结果交给主机。也就是说,(.class)文件并不是直接与机器的操作系统直接互动,而

Java基础1.3_weixin_33835690的博客-程序员宝宝

什么是批处理:批处理就是多个dos命令组成的,双击可执行里面的命令。(微软系统)批处理:桌面文件以双击就能打开,而java一双击是打不开的因为java是一个class文件他需要虚拟机得运行才能打开。Java不擅长做桌面程序你要是非要做你就得写一个简单的批处理操作。 run.bat:在批处理文件中运行你要运行的文件(java,javaw)加文件名。b...

推荐文章

热门文章

相关标签