【Audio&Video】管理音频焦点(14)

两个或更多的Android应用程序可以同时播放音频到同一个输出流。系统将所有内容混合在一起。虽然这在技术上令人印象深刻,但对用户而言可能会非常严重。为了避免每个音乐应用同时播放,Android引入了音频焦点的概念。一次只有一个应用程序可以保持音频焦点。

当您的应用需要输出音频时,它应该请求音频焦点。当它有焦点时,它可以播放声音。但是,在获得音频焦点后,您可能无法保留它,直到完成播放。另一个应用程序可以请求焦点,这可以抢占音频焦点。如果发生这种情况,应用程序应暂停播放或降低音量,以便用户更轻松地听到新的音频源。

音频焦点是合作的。我们鼓励应用程序遵守音频重点指导原则,但系统不会执行这些规则。如果应用程序想要在失去音频焦点后继续大声播放,则没有任何事情可以阻止该问题。这是一次糟糕的体验,用户很可能会以这种方式卸载出现不当行为的应用。

一个行为良好的音频应用程序应根据以下一般准则来管理音频焦点:

requestAudioFocus()在开始播放之前立即致电并确认通话返回 AUDIOFOCUS_REQUEST_GRANTED。如果您按照本指南中的描述设计应用程序,requestAudioFocus()则应在onPlay()您的媒体会话回调中拨打电话 。
当另一个应用程序获得音频焦点时,停止或暂停播放,或降低音量。
播放停止时,放弃音频焦点。

音频焦点的处理方式取决于正在运行的Android版本:

  • 从Android 2.2(API级别8)开始,应用程序通过调用requestAudioFocus() 和管理音频焦点 abandonAudioFocus()。应用程序还必须注册 AudioManager.OnAudioFocusChangeListener两个呼叫才能接收回叫并管理自己的音频级别。
  • 对于AudioAttributes面向Android 5.0(API级别21)及更高版本的应用,音频应用应该用来描述您的应用正在播放的音频类型。例如,播放语音的应用程序应该指定CONTENT_TYPE_SPEECH。
  • 运行Android 8.0(API级别26)或更高版本的应用程序应使用requestAudioFocus() 具有AudioFocusRequest参数的 方法。该 AudioFocusRequest包含音频情境,应用程式的能力的信息。系统使用此信息自动管理音频焦点的增益和损失。

音频焦点在Android 8.0及更高版本中


从Android 8.0(API级别26)开始,当您致电时,requestAudioFocus()您必须提供AudioFocusRequest参数。要释放音频焦点,请调用 abandonAudioFocusRequest()也AudioFocusRequest作为参数的方法。AudioFocusRequest请求和放弃焦点时应使用相同的实例。

要创建一个AudioFocusRequest,请使用 AudioFocusRequest.Builder。由于焦点请求必须始终指定请求的类型,因此类型将包含在构建器的构造函数中。使用构建器的方法来设置请求的其他字段。

该FocusGain字段是必需的; 所有其他字段是可选的。
【Audio&Video】管理音频焦点(14)

以下示例显示如何使用AudioFocusRequest.Builder构建AudioFocusRequest和请求并放弃音频焦点:

mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
mPlaybackAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(mPlaybackAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(mMyFocusListener, mMyHandler)
        .build();
mMediaPlayer = new MediaPlayer();
final Object mFocusLock = new Object();

boolean mPlaybackDelayed = false;
boolean mPlaybackNowAuthorized = false;

// ...
int res = mAudioManager.requestAudioFocus(mFocusRequest);
synchronized(mFocusLock) {
    if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
        mPlaybackNowAuthorized = false;
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        mPlaybackNowAuthorized = true;
        playbackNow();
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
       mPlaybackDelayed = true;
       mPlaybackNowAuthorized = false;
    }
}

// ...
@Override
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            if (mPlaybackDelayed || mResumeOnFocusGain) {
                synchronized(mFocusLock) {
                    mPlaybackDelayed = false;
                    mResumeOnFocusGain = false;
                }
                playbackNow();
            }
            break;
        case AudioManager.AUDIOFOCUS_LOSS:
            synchronized(mFocusLock) {
                mResumeOnFocusGain = false;
                mPlaybackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            synchronized(mFocusLock) {
                mResumeOnFocusGain = true;
                mPlaybackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // ... pausing or ducking depends on your app
            break;
        }
    }
}

自动回避

在Android 8.0(API级别26)中,当另一个应用程序请求关注 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK系统时,可以在不调用应用程序的onAudioFocusChange()回调的情况下进行缓存并恢复该卷。

虽然自动回避对于音乐和视频回放应用程序来说是可以接受的行为,但在播放口头内容时(如音频书籍应用程序)则无用。在这种情况下,应用程序应该暂停。

如果您希望应用程序在被要求暂停而不是减少其音量时暂停,请OnAudioFocusChangeListener使用onAudioFocusChange()实现所需暂停/恢复行为的回调方法创建一个。调用setOnAudioFocusChangeListener()注册监听器,然后调用 setWillPauseWhenDucked(true) 告诉系统使用您的回调,而不是执行自动回避。

延迟的焦点增益

有时系统无法授予音频焦点请求,因为焦点被另一个应用程序“锁定”,例如在电话呼叫期间。在这种情况下, requestAudioFocus()退货AUDIOFOCUS_REQUEST_FAILED。发生这种情况时,应用程序不应该继续播放音频,因为它没有获得重点。

该方法setAcceptsDelayedFocusGain(true)允许您的应用程序异步处理焦点请求。通过设置此标志,返回焦点锁定时发出的请求AUDIOFOCUS_REQUEST_DELAYED。当锁定音频焦点的条件不再存在时(例如电话结束时),系统将授予待处理的焦点请求并呼叫onAudioFocusChange()通知您的应用程序。

为了处理焦点的延迟增益,您必须创建 OnAudioFocusChangeListener一个onAudioFocusChange()回调方法,实现所需的行为并通过调用注册侦听器 setOnAudioFocusChangeListener()。

音频焦点在Android 8.0之前


当你打电话时, requestAudioFocus() 你必须指定一个持续时间提示,这可能会被当前持有焦点和正在播放的另一个应用程序所尊敬:

AUDIOFOCUS_GAIN当您计划在可预见的未来播放音频时(例如,在播放音乐时)请求永久音频聚焦(),并且您希望先前的音频聚焦持有者停止播放。
AUDIOFOCUS_GAIN_TRANSIENT当您期望短时间播放音频并且您期望前一个持有者暂停播放时,请求瞬态对焦()。
使用ducking (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)请求瞬态聚焦以表明您期望仅在短时间内播放音频,并且如果前一个聚焦拥有者在“降低”(降低)其音频输出时继续播放就可以了。两个音频输出都混合到音频流中。Ducking特别适用于间歇使用音频流的应用程序,例如可听见的驾驶路线。
该requestAudioFocus()方法还需要一个AudioManager.OnAudioFocusChangeListener。应该在拥有媒体会话的相同活动或服务中创建此侦听器。它实现了onAudioFocusChange()您的应用在其他应用获取或放弃音频焦点时收到的回调。

以下代码片段要求流中的永久音频焦点 STREAM_MUSIC并注册一个OnAudioFocusChangeListener以处理音频焦点的后续变化。(更改监听器在响应音频焦点更改中进行了讨论 。)

AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// 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) {
    // Start playback
}

完成播放后,请致电 abandonAudioFocus()。

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

这会通知系统您不再需要关注并取消注册关联OnAudioFocusChangeListener。如果您要求瞬态焦点,则会通知暂停或被遮住的应用程序,以便它可以继续播放或恢复其音量。

响应音频焦点更改


当应用程序获取音频焦点时,它必须能够在另一个应用程序为其自身请求音频焦点时释放它。发生这种情况时,您的应用会收到 对应用调用时指定onAudioFocusChange() 方法AudioFocusChangeListener的调用requestAudioFocus()。

focusChange传递给的参数onAudioFocusChange()指示正在发生的更改的类型。它对应于获取焦点的应用使用的持续时间提示。你的应用应该适当地回应。

瞬态失去重点
如果焦点变化是暂时的(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK或 AUDIOFOCUS_LOSS_TRANSIENT),则应用程序应该暂停(如果您不依靠自动回避)或暂停播放,但保持相同的状态。
在暂时失去音频焦点的过程中,您应该继续监视音频焦点的变化,并准备在重新获得焦点时恢复正常播放。当阻止应用程序放弃焦点时,您会收到一个回调(AUDIOFOCUS_GAIN)。此时,您可以将音量恢复到正常水平或重新开始播放。

永久失去重点
如果音频焦点丢失是永久性的(AUDIOFOCUS_LOSS),则另一个应用正在播放音频。您的应用应该立即暂停播放,因为它不会收到AUDIOFOCUS_GAIN回叫。要重新开始播放,用户必须采取明确的操作,例如在通知或应用UI中按播放传输控件。
以下代码片段演示了如何实现 OnAudioFocusChangeListener和它的onAudioFocusChange()回调。请注意,使用a Handler延迟停止回调会导致永久丢失音频焦点。

private Handler mHandler = new Handler();
AudioManager.OnAudioFocusChangeListener afChangeListener =
  new AudioManager.OnAudioFocusChangeListener() {
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
        // Permanent loss of audio focus
        // Pause playback immediately
        mediaController.getTransportControls().pause();
        // Wait 30 seconds before stopping playback
        mHandler.postDelayed(mDelayedStopRunnable,
          TimeUnit.SECONDS.toMillis(30));
      }
      else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
        // Pause playback
      } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
        // Lower the volume, keep playing
      } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
        // Your app has been granted audio focus again
        // Raise volume to normal, restart playback if necessary
      }
    }
  };

处理程序使用一个Runnable看起来像这样的:

private Runnable mDelayedStopRunnable = new Runnable() {
    @Override
    public void run() {
        mediaController.getTransportControls().stop();
    }
};

要确保如果用户重新开始播放时延迟停止不起作用,请 mHandler.removeCallbacks(mDelayedStopRunnable)响应任何状态更改。例如,调用removeCallbacks()在回调的onPlay(), onSkipToNext()等你也应该在你的服务的调用此方法 onDestroy()回调清理您的服务所使用的资源的时候。

联系我

QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【Audio&Video】管理音频焦点(14)

猜你喜欢

转载自blog.51cto.com/4789781/2130464