解决在Android设备播放音频与其他应用重音的问题,并监听耳机的控制按钮

幽沉谢世事,俯默窥唐虞。这篇文章主要讲述解决在Android设备播放音频与其他应用重音的问题,并监听耳机的控制按钮相关的知识,希望能为你提供帮助。
概述
在安卓开发中免不了需要播放一点音乐了,音频了。但是这时候有别的应用正在播放,这时候就会出现重音的现象,完全影响用户体验,我们的项目就遇上了这样的尴尬,然后查找了一些文档,记录一下;
 
管理音频焦点
从谷歌开发了解到,这跟音频的焦点又关系,我们可以获取当前音频的焦点来解决问题; 
有多个应用程序可能播放音频,重要的是要考虑他们应该如何交互。为了避免每个音乐应用程序同时播放,android使用音频焦点来控制音频播放 - 只有拥有音频焦点的应用程序才能播放音频。 
在您的应用程序开始播放音频之前,应该请求并接收音频焦点。同样,它应该知道如何监听音频焦点的丢失,并在发生这种情况时适当地做出反应。
【解决在Android设备播放音频与其他应用重音的问题,并监听耳机的控制按钮】 
请求音频焦点
在应用开始播放任何音频之前,自己的应用应该保留将使用的流的音频焦点,简单说就是我要现在的焦点,其他人靠边,但是别人同样也可能有这样的操作,我们也需要监听自己播放的焦点的变化,增加自己程序的兼容性。这是通过调用requestAudioFocus()完成的,如果您的请求成功,它返回AUDIOFOCUS_REQUEST_GRANTED。
 
您必须指定您使用的流,以及是否需要暂时或永久的音频焦点。当您希望只在短时间内播放音频时请求暂时聚焦(例如在播放导航指示时)。当您计划在可预见的未来播放音频时请求永久音频聚焦(例如,播放音乐时)
 
以下代码片段请求音乐音频流的永久音频焦点。您应该在开始播放之前立即请求音频焦点,例如当用户按下播放或下一个游戏关卡的背景音乐开始时。

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


上述代码你就可以得到焦点播放你的音频,但是一旦你完成播放,一定要调用abandonAudioFocus()。这会通知系统您不再需要焦点和注销相关联的AudioManager.OnAudioFocusChangeListener。在放弃瞬态焦点的情况下,这允许任何中断的应用程序继续播放。
// Abandon audio focus when playback completeam.abandonAudioFocus(afChangeListener);


但是有些时候我们并不想完全停掉另一个程序的声音,比如这时候正在进行高德导航,或者微信语音,希望另一个程序的声音小点,不要盖过自己即可,开车导航听音乐的时候,我们应该遇到过;
 
当请求瞬态音频焦点时,您有一个附加选项:是否要启用“低音”。通常,当良好的音频应用程序失去音频焦点时,它立即使其播放静音。通过请求一个允许回避的暂时音频焦点,你告诉其他音频应用程序它们可以接受他们继续播放,只要他们降低音量,直到焦点回到他们。
// Request audio focus for playbackint 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.}


每当另一个应用程序请求音频焦点如上所述,其永久和瞬态(有或没有支持低音)之间的选择音频焦点由您在请求焦点时注册的侦听器接收。
 
处理音频焦点的丢失
如果您的应用程序可以请求音频焦点,那么当其他应用程序请求焦点时,它会反过来失去焦点。您的应用程序如何响应音频焦点的丢失取决于丢失的方式。
 
您请求音频焦点时注册的音频焦点更改侦听器的onAudioFocusChange()回调方法接收到描述焦点更改事件的参数。具体来说,可能的聚焦丢失事件镜像来自前一部分的聚焦请求类型 - 永久丢失,瞬时丢失和允许具有回避的瞬态。
 
一般来说,暂时的(临时的)音频焦点的丢失应导致您的应用程序使它的音频流静音,否则保持相同的状态。您应该继续监视音频焦点的变化,并准备在恢复焦点后暂停播放。
 
如果音频焦点丢失是永久性的,假设另一个应用程序现在正用于听音频,您的应用程序应该有效地结束自己。实际上,这意味着停止播放,删除媒体按钮侦听器 - 允许新的音频播放器专门处理这些事件 - 并放弃您的音频焦点。此时,您需要在恢复播放音频之前需要执行用户操作(在应用程序中按下播放)。
 
在下面的代码片段中,如果音频丢失是暂时的,我们暂停播放我们的媒体播放器对象,并在我们恢复焦点后重新开始播放。如果丢失是永久性的,它将取消注册我们的媒体按钮事件接收器,并停止监视音频焦点更改。
AudioManager.OnAudioFocusChangeListener afChangeListener =new AudioManager.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}}};


处理音频焦点瞬间丢失
在瞬时丢失音频焦点的情况下,允许duck(回避),而不是暂停回放。
OnAudioFocusChangeListener afChangeListener = 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}}};


至此,我们就可以随心所欲的处理我们音频播放的焦点问题了
 
处理音频输出硬件
当用户从他们的Android设备享受音频时,用户有很多选择。大多数设备都有内置扬声器,有线耳机的耳机插孔,许多设备还具有蓝牙连接和支持A2DP音频。
 
您的应用程序的行为可能会受到输出路由到的硬件的影响。
 
您可以查询AudioManager以确定音频当前是否路由到设备扬声器,有线耳机或连接的蓝牙设备,如以下代码段所示:
if (isBluetoothA2dpOn()) {// Adjust output for Bluetooth.} else if (isSpeakerphoneOn()) {// Adjust output for Speakerphone.} else if (isWiredHeadsetOn()) {// Adjust output for headsets} else {// If audio plays and noone can hear it, is it still playing?}


处理音频输出硬件的更改
 
现在各种的音乐播放器都支持在拔掉耳机或者蓝牙的情况正在播放的音视频暂停,这是个很实用的功能,当然你也有必要支持一下;
 
当耳机拔掉或蓝牙设备断开连接时,音频流自动重新路由到内置扬声器。如果你听到你的音乐高达我的音量,这可能是一个嘈杂的惊喜。
 
幸运的是,当发生这种情况时,系统广播ACTION_AUDIO_BECOMING_NOISY Intent。这是一个好的习惯,注册一个BroadcastReceiver监听这个Intent,无论你在播放音频。在音乐播放器的情况下,用户通常期望暂停播放,而对于游戏,可以选择显着降低音量。
private class NoisyAudiostreamReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {// Pause the playback}}}private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); private void startPlayback() {registerReceiver(myNoisyAudioStreamReceiver(), intentFilter); }private void stopPlayback() {unregisterReceiver(myNoisyAudioStreamReceiver); }


附:
通过物理键或者线控来控制音频播放
标识要使用的音频流
 
创建可预测的音频体验的第一步是了解您的应用将使用哪个音频流。
 
Android维护一个单独的音频流,用于播放音乐,闹钟,通知,来电铃声,系统声音,通话音量和DTMF铃声。 这主要是为了允许用户独立地控制每个流的音量。
 
大多数这些流仅限于系统事件,因此除非您的应用程序是替换闹钟,否则您几乎肯定会使用STREAM_MUSIC流播放您的音频。
 
默认情况下,按音量控制可修改Activity音频流的音量。如果您的应用程式目前没有播放任何内容,按下音量键即可调整铃声音量。
 
如果你有一个游戏或音乐应用程序,那么当用户点击音量键,他们想要控制游戏或音乐的音量,即使他们当前在歌曲之间或没有音乐在当前游戏位置。
 
你可能会试着尝试监听音量键按下并修改音频流的音量。不要慌, Android提供了方便的setVolumeControlStream()方法来将音量键降到您指定的音频流。
 
确定应用程序将使用的音频流后,应将其设置为音频流目标。您应该在应用程序的生命周期中提前进行此调用 - 因为您只需在Activity生命周期中调用一次,通常应在onCreate()方法(控制您的媒体的Activity或Fragment)中调用它。这确保了只要应用程序可见,音量控制功能就像用户期望的那样。
 
setVolumeControlStream(AudioManager.STREAM_MUSIC);
1
从这一点开始,只要目标Activity或Fragment可见,按设备上的音量键就会影响您指定的音频流;
 
媒体播放按钮,如播放,暂停,停止,跳过和上一个在某些手机和许多连接或无线耳机可用。每当用户按下这些硬件键之一时,系统使用ACTION_MEDIA_BUTTON操作广播一个Intent。
 
要响应媒体按钮点击,您需要在您的清单中注册一个BroadcastReceiver,监听此操作广播,如下所示。
< receiver android:name=".RemoteControlReceiver"> < intent-filter> < action android:name="android.intent.action.MEDIA_BUTTON" /> < /intent-filter> < /receiver>


接收器实现本身需要提取哪个键被按下来引起广播。 Intent在EXTRA_KEY_EVENT键下包括此键,而KeyEvent类包括表示每个可能的媒体按钮的列表KEYCODE_MEDIA_ *静态常量,例如KEYCODE_MEDIA_PLAY_PAUSE和KEYCODE_MEDIA_NEXT。
 
以下代码片段显示如何提取按下的媒体按钮,并相应地影响媒体播放:
public class RemoteControlReceiver extends BroadcastReceiver {@Overridepublic 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注册和注销媒体按钮事件接收器。注册后,您的广播接收器是所有媒体按钮广播的专属接收器。
 
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); //...// Start listening for button pressesam.registerMediaButtonEventReceiver(RemoteControlReceiver); //...// Stop listening for button pressesam.unregisterMediaButtonEventReceiver(RemoteControlReceiver);


 
通常,当应用程序变为不活动或不可见时(例如在onStop()回调期间),应取消注册大多数接收者。然而,对于媒体播放应用程序来说并不简单,事实上,当应用程序不可见且无法通过屏幕上的用户界面控制时,响应媒体播放按钮是最重要的。
--------------------- 
from:https://blog.csdn.net/zhaobo012387/article/details/75206268
概述在安卓开发中免不了需要播放一点音乐了,音频了。但是这时候有别的应用正在播放,这时候就会出现重音的现象,完全影响用户体验,我们的项目就遇上了这样的尴尬,然后查找了一些文档,记录一下;
管理音频焦点从谷歌开发了解到,这跟音频的焦点又关系,我们可以获取当前音频的焦点来解决问题;  有多个应用程序可能播放音频,重要的是要考虑他们应该如何交互。为了避免每个音乐应用程序同时播放,Android使用音频焦点来控制音频播放 - 只有拥有音频焦点的应用程序才能播放音频。  在您的应用程序开始播放音频之前,应该请求并接收音频焦点。同样,它应该知道如何监听音频焦点的丢失,并在发生这种情况时适当地做出反应。
请求音频焦点在应用开始播放任何音频之前,自己的应用应该保留将使用的流的音频焦点,简单说就是我要现在的焦点,其他人靠边,但是别人同样也可能有这样的操作,我们也需要监听自己播放的焦点的变化,增加自己程序的兼容性。这是通过调用requestAudioFocus()完成的,如果您的请求成功,它返回AUDIOFOCUS_REQUEST_GRANTED。
您必须指定您使用的流,以及是否需要暂时或永久的音频焦点。当您希望只在短时间内播放音频时请求暂时聚焦(例如在播放导航指示时)。当您计划在可预见的未来播放音频时请求永久音频聚焦(例如,播放音乐时)
以下代码片段请求音乐音频流的永久音频焦点。您应该在开始播放之前立即请求音频焦点,例如当用户按下播放或下一个游戏关卡的背景音乐开始时。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); ...
// Request audio focus for playbackint result = am.requestAudioFocus(afChangeListener,// Use the music stream.AudioManager.STREAM_MUSIC,// Request permanent focus.AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {am.registerMediaButtonEventReceiver(RemoteControlReceiver); // Start playback.}1234567891011121314上述代码你就可以得到焦点播放你的音频,但是一旦你完成播放,一定要调用abandonAudioFocus()。这会通知系统您不再需要焦点和注销相关联的AudioManager.OnAudioFocusChangeListener。在放弃瞬态焦点的情况下,这允许任何中断的应用程序继续播放。
// Abandon audio focus when playback completeam.abandonAudioFocus(afChangeListener); 12但是有些时候我们并不想完全停掉另一个程序的声音,比如这时候正在进行高德导航,或者微信语音,希望另一个程序的声音小点,不要盖过自己即可,开车导航听音乐的时候,我们应该遇到过;
当请求瞬态音频焦点时,您有一个附加选项:是否要启用“低音”。通常,当良好的音频应用程序失去音频焦点时,它立即使其播放静音。通过请求一个允许回避的暂时音频焦点,你告诉其他音频应用程序它们可以接受他们继续播放,只要他们降低音量,直到焦点回到他们。
// Request audio focus for playbackint 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.}12345678910每当另一个应用程序请求音频焦点如上所述,其永久和瞬态(有或没有支持低音)之间的选择音频焦点由您在请求焦点时注册的侦听器接收。
处理音频焦点的丢失如果您的应用程序可以请求音频焦点,那么当其他应用程序请求焦点时,它会反过来失去焦点。您的应用程序如何响应音频焦点的丢失取决于丢失的方式。
您请求音频焦点时注册的音频焦点更改侦听器的onAudioFocusChange()回调方法接收到描述焦点更改事件的参数。具体来说,可能的聚焦丢失事件镜像来自前一部分的聚焦请求类型 - 永久丢失,瞬时丢失和允许具有回避的瞬态。
一般来说,暂时的(临时的)音频焦点的丢失应导致您的应用程序使它的音频流静音,否则保持相同的状态。您应该继续监视音频焦点的变化,并准备在恢复焦点后暂停播放。
如果音频焦点丢失是永久性的,假设另一个应用程序现在正用于听音频,您的应用程序应该有效地结束自己。实际上,这意味着停止播放,删除媒体按钮侦听器 - 允许新的音频播放器专门处理这些事件 - 并放弃您的音频焦点。此时,您需要在恢复播放音频之前需要执行用户操作(在应用程序中按下播放)。
在下面的代码片段中,如果音频丢失是暂时的,我们暂停播放我们的媒体播放器对象,并在我们恢复焦点后重新开始播放。如果丢失是永久性的,它将取消注册我们的媒体按钮事件接收器,并停止监视音频焦点更改。
AudioManager.OnAudioFocusChangeListener afChangeListener =new AudioManager.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}}}; 1234567891011121314处理音频焦点瞬间丢失在瞬时丢失音频焦点的情况下,允许duck(回避),而不是暂停回放。
OnAudioFocusChangeListener afChangeListener = 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}}}; 123456789至此,我们就可以随心所欲的处理我们音频播放的焦点问题了
处理音频输出硬件当用户从他们的Android设备享受音频时,用户有很多选择。大多数设备都有内置扬声器,有线耳机的耳机插孔,许多设备还具有蓝牙连接和支持A2DP音频。
您的应用程序的行为可能会受到输出路由到的硬件的影响。
您可以查询AudioManager以确定音频当前是否路由到设备扬声器,有线耳机或连接的蓝牙设备,如以下代码段所示:
if (isBluetoothA2dpOn()) {// Adjust output for Bluetooth.} else if (isSpeakerphoneOn()) {// Adjust output for Speakerphone.} else if (isWiredHeadsetOn()) {// Adjust output for headsets} else {// If audio plays and noone can hear it, is it still playing?}123456789处理音频输出硬件的更改
现在各种的音乐播放器都支持在拔掉耳机或者蓝牙的情况正在播放的音视频暂停,这是个很实用的功能,当然你也有必要支持一下;
当耳机拔掉或蓝牙设备断开连接时,音频流自动重新路由到内置扬声器。如果你听到你的音乐高达我的音量,这可能是一个嘈杂的惊喜。
幸运的是,当发生这种情况时,系统广播ACTION_AUDIO_BECOMING_NOISY Intent。这是一个好的习惯,注册一个BroadcastReceiver监听这个Intent,无论你在播放音频。在音乐播放器的情况下,用户通常期望暂停播放,而对于游戏,可以选择显着降低音量。
private class NoisyAudioStreamReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {// Pause the playback}}}
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private void startPlayback() {registerReceiver(myNoisyAudioStreamReceiver(), intentFilter); }
private void stopPlayback() {unregisterReceiver(myNoisyAudioStreamReceiver); }123456789101112131415161718附:通过物理键或者线控来控制音频播放标识要使用的音频流
创建可预测的音频体验的第一步是了解您的应用将使用哪个音频流。
Android维护一个单独的音频流,用于播放音乐,闹钟,通知,来电铃声,系统声音,通话音量和DTMF铃声。 这主要是为了允许用户独立地控制每个流的音量。
大多数这些流仅限于系统事件,因此除非您的应用程序是替换闹钟,否则您几乎肯定会使用STREAM_MUSIC流播放您的音频。
默认情况下,按音量控制可修改Activity音频流的音量。如果您的应用程式目前没有播放任何内容,按下音量键即可调整铃声音量。
如果你有一个游戏或音乐应用程序,那么当用户点击音量键,他们想要控制游戏或音乐的音量,即使他们当前在歌曲之间或没有音乐在当前游戏位置。
你可能会试着尝试监听音量键按下并修改音频流的音量。不要慌, Android提供了方便的setVolumeControlStream()方法来将音量键降到您指定的音频流。
确定应用程序将使用的音频流后,应将其设置为音频流目标。您应该在应用程序的生命周期中提前进行此调用 - 因为您只需在Activity生命周期中调用一次,通常应在onCreate()方法(控制您的媒体的Activity或Fragment)中调用它。这确保了只要应用程序可见,音量控制功能就像用户期望的那样。
setVolumeControlStream(AudioManager.STREAM_MUSIC); 1从这一点开始,只要目标Activity或Fragment可见,按设备上的音量键就会影响您指定的音频流;
媒体播放按钮,如播放,暂停,停止,跳过和上一个在某些手机和许多连接或无线耳机可用。每当用户按下这些硬件键之一时,系统使用ACTION_MEDIA_BUTTON操作广播一个Intent。
要响应媒体按钮点击,您需要在您的清单中注册一个BroadcastReceiver,监听此操作广播,如下所示。
< receiver android:name=".RemoteControlReceiver"> < intent-filter> < action android:name="android.intent.action.MEDIA_BUTTON" /> < /intent-filter> < /receiver> 12345接收器实现本身需要提取哪个键被按下来引起广播。 Intent在EXTRA_KEY_EVENT键下包括此键,而KeyEvent类包括表示每个可能的媒体按钮的列表KEYCODE_MEDIA_ *静态常量,例如KEYCODE_MEDIA_PLAY_PAUSE和KEYCODE_MEDIA_NEXT。
以下代码片段显示如何提取按下的媒体按钮,并相应地影响媒体播放:
public class RemoteControlReceiver extends BroadcastReceiver {@Overridepublic 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.}}}}1234567891011因为多个应用程序可能想要监听媒体按钮按下,所以您还必须以编程方式控制应用程序何时应该接收媒体按钮按下事件。
以下代码可在您的应用程序中使用AudioManager注册和注销媒体按钮事件接收器。注册后,您的广播接收器是所有媒体按钮广播的专属接收器。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); //...
// Start listening for button pressesam.registerMediaButtonEventReceiver(RemoteControlReceiver); //...
// Stop listening for button pressesam.unregisterMediaButtonEventReceiver(RemoteControlReceiver); 123456789通常,当应用程序变为不活动或不可见时(例如在onStop()回调期间),应取消注册大多数接收者。然而,对于媒体播放应用程序来说并不简单,事实上,当应用程序不可见且无法通过屏幕上的用户界面控制时,响应媒体播放按钮是最重要的。---------------------  作者:zhaobo012387  来源:CSDN  原文:https://blog.csdn.net/zhaobo012387/article/details/75206268  版权声明:本文为博主原创文章,转载请附上博文链接!















































    推荐阅读