理解音频焦点 (第 3/3 部分):三个步骤实现音频聚焦


本系列文章旨在让您深刻理解音频焦点的含义,使用方法和其对用户体验的重要性。本篇文章是该系列的第一部分,该系列三篇文章包含了:

  1. 最多见的音频焦点用例和成为一个优秀的媒体事业人员的重要性
  2. 其它一些能体现音频焦点对应用体验的重要性的用例
  3. 在您的应用中实现音频焦点的三个步骤 (此篇文章)

若是您不妥善处理好音频聚焦,您的用户可能受到下图所示的困扰。
前端


如今您已经知道音频聚焦的重要性,让咱们经过一些步骤来让您的应用程序正确处理音频焦点。

开始代码示例以前,先看看下图,它展现了实现步骤:android

步骤一 :请求音频焦点

获取音频焦点的第一个步骤是先向系统发出申请焦点的消息。注意这只是发出请求,并不是直接获取。为了申请到音频聚焦,您必须向系统描述好您的意图。介绍四个常见音频焦点类型:git

  • AUDIOFOCUS_GAIN的使用场景:应用须要聚焦音频的时长会根据用户的使用时长改变,属于不肯定期限。例如:多媒体播放或者播客等应用。
  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的使用场景:应用只需短暂的音频聚焦,来播放一些提示类语音消息,或录制一段语音。例如:闹铃,导航等应用。
  • AUDIOFOCUS_GAIN_TRANSIENT的使用场景:应用只需短暂的音频聚焦,但包含了不一样响应状况,例如:电话、QQ、微信等通话应用。
  • AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 的使用场景:一样您的应用只是须要短暂的音频聚焦。未知时长,但不容许被其它应用截取音频焦点。例如:录音软件。

在 Android O 或者更新的版本上您必须使用 builder 来实例化一个 AudioFocusRequest 类。(在 builder 中必须指明请求的音频焦点类型)github

AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioAttributes mAudioAttributes =
       new AudioAttributes.Builder()
               .setUsage(AudioAttributes.USAGE_MEDIA)
               .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
               .build();
AudioFocusRequest mAudioFocusRequest =
       new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
               .setAudioAttributes(mAudioAttributes)
               .setAcceptsDelayedFocusGain(true)
               .setOnAudioFocusChangeListener(...) // Need to implement listener
               .build();
int focusRequest = mAudioManager.requestAudioFocus(mAudioFocusRequest);
switch (focusRequest) {
   case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
       // 不容许播放
   case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
       // 开始播放
}复制代码

音频焦点类型要点:后端

  1. AudioManager.AUDIOFOCUS_GAIN:请求长时间音频聚焦。若是只是临时须要音频焦点能够选用这几个:AUDIOFOCUS_GAIN_TRANSIENTAUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
  2. 您必须经过 setOnAudioFocusChangeListener() 方法来实现 AudioManager.OnAudioFocusChangeListener 接口。用来响应音频焦点状态的变化,如被其它应用截取了音频焦点,或者其它应用释放焦点,都会在这里回调。
  3. 调用 AudioManager 的 requestAudioFocus(…) 方法,须要用到实例化好的 AudioFocusRequest。 请求结果以一个 int 变量返回:AUDIOFOCUS_REQUEST_GRANTED 表示得到受权, AUDIOFOCUS_REQUEST_FAILED 表示被系统拒绝。

在 Android N 及其更早的版本中,不须要用到 AudioFocusRequest,只需实现 AudioManager.OnAudioFocusChangeListener 接口。代码以下:bash

AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int focusRequest = mAudioManager.requestAudioFocus(
..., // Need to implement listener
       AudioManager.STREAM_MUSIC,
       AudioManager.AUDIOFOCUS_GAIN);
switch (focusRequest) {
   case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
       // don't start playback case AudioManager.AUDIOFOCUS_REQUEST_GRANTED: // actually start playback }复制代码

上述皆为音频焦点的申请,接下来咱们将介绍 AudioManager.OnAudioFocusChangeListener 如何实现,以此来响应音频焦点的状态。微信

步骤二 :响应音频焦点的状态改变

一旦得到音频聚焦,您的应用要立刻作出响应,由于它的状态可能在任什么时候间发生改变(丢失或从新获取),您能够实现 OnAudioFocusChangeListener 的来响应状态改变。app

如下代码展现了 OnAudioFocusChangeListener 接口的实现,它处理了与 Google Assistant 应用协同工做的时候,音频焦点的各类状态的变化。ide

private final class AudioFocusHelper
        implements AudioManager.OnAudioFocusChangeListener {
private void abandonAudioFocus() {
        mAudioManager.abandonAudioFocus(this);
    }
@Override
    public void onAudioFocusChange(int focusChange) {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                if (mPlayOnAudioFocus && !isPlaying()) {
                    play();
                } else if (isPlaying()) {
                    setVolume(MEDIA_VOLUME_DEFAULT);
                }
                mPlayOnAudioFocus = false;
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                setVolume(MEDIA_VOLUME_DUCK);
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                if (isPlaying()) {
                    mPlayOnAudioFocus = true;
                    pause();
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                mAudioManager.abandonAudioFocus(this);
                mPlayOnAudioFocus = false;
                stop();
                break;
        }
    }
}复制代码

关于暂停播放,应用程序的行为应该是不一样的。若是用户主动暂停播放时,您的应用应释放音频焦点。若是是为了响应音频焦点的暂时丢失而暂停播放,则不该释放音频焦点。 这里有一些用例来讲明这一点。工具

分析上面接口mPlayOnAudioFocus 的场景,您的音频应用正在后台播放音乐:

  1. 用户点击播放,您的应用向系统申请音频聚焦,假如系统受权了。
  2. 如今用户长按 HOME 键启动 Google Assistant。Google Assistant 会向系统申请一个短暂的音频聚焦。
  3. 一旦系统受权给 Google Assistant,您的 OnAudioFocusChangeListener 接口会收到 AUDIOFOCUS_LOSS_TRANSIENT 事件回调。您在这个回调里处理暂停音乐播放。
  4. 当 Google Assistant 使用结束,您的 OnAudioFocusChangeListener 会收到 AUDIOFOCUS_GAIN 事件回调。 在这里您能够处理是否让音乐恢复播放。

如下代码展现如何释放音频焦点:

public final void pause() {
   if (!mPlayOnAudioFocus) {
       mAudioFocusHelper.abandonAudioFocus();
   }
  onPause();
}复制代码

您能够看到释放焦点是在用户暂停播放的时候,而非其它应用请求焦点 AUDIOFOCUS_GAIN_TRANSIENT 致使他们释放焦点。

应对焦点丢失

选择在 OnAudioFocusChangeListener 中暂停仍是下降音量,取决于您应用的交互方式。在 Android O上,会自动的帮您下降音量,因此您能够忽略 OnAudioFocusChangeListener 接口的 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 事件。

在 Android O 如下的版本,您须要本身用代码实现,具体实现方式如上面代码所示。

延迟聚焦

Android O 介绍了延迟聚焦这个概念,您能够在申请音频聚焦的时候来响应 AUDIOFOCUS_REQUEST_DELAYED 这个结果,以下所示:

public void requestPlayback() {
    int audioFocus = mAudioManager.requestAudioFocus(mAudioFocusRequest);
    switch (audioFocus) {
        case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
            ...
        case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
            ...
        case AudioManager.AUDIOFOCUS_REQUEST_DELAYED:
            mAudioFocusPlaybackDelayed = true;
    }
}复制代码

在您 OnAudioFocusChangeListener 的实现,您须要检查 mAudioFocusPlaybackDelayed 这个变量,当您响应 AUDIOFOCUS_GAIN 音频聚焦的时候, 以下所示:

private void onAudioFocusChange(int focusChange) {
   switch (focusChange) {
       case AudioManager.AUDIOFOCUS_GAIN:
           logToUI("Audio Focus: Gained");
           if (mAudioFocusPlaybackDelayed || mAudioFocusResumeOnFocusGained) {
               mAudioFocusPlaybackDelayed = false;
               mAudioFocusResumeOnFocusGained = false;
               start();
           }
           break;
       case AudioManager.AUDIOFOCUS_LOSS:
           mAudioFocusResumeOnFocusGained = false;
           mAudioFocusPlaybackDelayed = false;
           stop();
           break;
       case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
           mAudioFocusResumeOnFocusGained = true;
           mAudioFocusPlaybackDelayed = false;
           pause();
           break;
       case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
           pause();
           break;
   }
}复制代码

步骤三 :释放音频焦点

播放完音频,记得使用 AudioManager.abandonAudioFocus(…) 来释放掉音频焦点。在前面的步骤中,咱们遇到了一个应用暂停播放应该释放音频焦点的状况,可是这个应用依旧保留了音频焦点。

代码示例

几个您能够在您应用使用的案例

GitHub gist 上有三个类关于音频焦点的使用,这可能对您的代码有帮助。

  • AudioFocusRequestCompat:使用这个类来描述您的音频焦点类型
  • AudioFocusHelper:这个类帮助您处理音频焦点,您能够把它加入您的代码,可是必须确保在您的播放 service 中使用 AudioFocusAwarePlayer 这个接口。
  • AudioFocusAwarePlayer:这个接口应该在 service 中实现,来管理您的播放组件(MediaPlayer或者ExoPlayer),它能够确保 AudioFocusHelper 正常工做。

完整的代码示例

android-MediaBrowserService 完整展现了音频焦点的处理,使用 MediaPlayer 来播放音乐,同时使用了 MediaSession

PlayerAdapter展现了音频聚焦的最佳实践,请注意 pause()onAudioFocusChange(int) 方法的实现。

测试您的代码

一旦您在应用中实现了音频焦点的处理,您可使用安卓媒体控制工具来测试您的应用对音频聚焦的真实反映,具体使用方法请查阅 GitHub/Android Media Controller.

Android多媒体开发资源


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索