Android音频焦点

两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。 一次只能有一个应用获得音频焦点。

当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在您获得音频焦点后,您可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有您持有的音频焦点。如果发生这种情况,您的应用应暂停播放或降低音量,以便于用户听到新的音频源。

恰当的音频应用应根据以下一般准则来管理音频焦点:

  • 在即将开始播放之前调用 requestAudioFocus(),并验证调用是否返回 AUDIOFOCUS_REQUEST_GRANTED。则应在媒体会话的 onPlay() 回调中调用 requestAudioFocus()
  • 在其他应用获得音频焦点时,停止或暂停播放,或降低音量。
  • 播放停止后,放弃音频焦点。

我们的应用在开始播放音频文件前,首先应该请求获得音频焦点,并且应该同时注册监听音频焦点的丢失通知,即如果音频焦点被系统或其他的应用程序抢占时,应该做出什么响应。

运行的 Android 版本不同,音频焦点的处理方式也会不同:

  • 从 Android 2.2(API 级别 8)开始,应用通过调用 requestAudioFocus()abandonAudioFocus() 来管理音频焦点。应用还必须为这两个调用注册 AudioManager.OnAudioFocusChangeListener,以便接收回调并管理自己的音量。
  • 对于以 Android 5.0(API 级别 21)及更高版本为目标平台的应用,音频应用应使用 AudioAttributes 来描述应用正在播放的音频类型。例如,播放语音的应用应指定 CONTENT_TYPE_SPEECH
  • 面向 Android 8.0(API 级别 26)或更高版本的应用应使用 requestAudioFocus() 方法,该方法会接受 AudioFocusRequest 参数。AudioFocusRequest 包含有关应用的音频上下文和功能的信息。系统使用这些信息来自动管理音频焦点的得到和失去。

1. Android 8.0 之前的音频焦点

当您调用 requestAudioFocus() 时,您必须指定持续时间提示,当前持有焦点并正在播放的其他应用可能会遵循该提示:

  • 如果您计划在可预见的将来播放音频(例如在播放音乐时),并且希望前一个持有音频焦点的应用停止播放,则应该请求永久性的音频焦点 (AUDIOFOCUS_GAIN)。
  • 如果您只希望在短时间内播放音频,并且希望前一个持有音频焦点的应用暂停播放,则应该请求暂时性的焦点 (AUDIOFOCUS_GAIN_TRANSIENT)。
  • 请求附带“降低音量”(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 的暂时性焦点,表示您只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低其音频输出的情况下继续播放。这两个音频输出会混合到音频流中。降低音量特别适合于间歇性使用音频流的应用,例如有声的行车路线。

requestAudioFocus() 方法同样需要 AudioManager.OnAudioFocusChangeListener。此监听器应在媒体会话所在的 Activity 或服务中创建。它会实现 onAudioFocusChange() 回调,您的应用会在其他应用获取或放弃音频焦点时收到该回调。

requestAudioFocus

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// Request audio focus for playback
int result = audioManager.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
audioManager.abandonAudioFocus(afChangeListener);

2. 音频焦点的变化回调 AudioFocusChangeListener

当应用获得音频焦点后,它必须能够在其他应用为自己请求音频焦点时释放该焦点。出现这种情况时,您的应用会收到对 AudioFocusChangeListener 中的 onAudioFocusChange() 方法的调用,该方法是您在应用调用 requestAudioFocus() 时指定的。

传递给 onAudioFocusChange()focusChange 参数表示所发生的更改类型。它对应于获取焦点的应用所使用的持续时间提示。您的应用应该做出适当的响应。

  • 暂时性失去焦点

    如果焦点更改是暂时性的(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKAUDIOFOCUS_LOSS_TRANSIENT),您的应用应该降低音量(如果您不依赖于自动降低音量)或暂停播放,否则保持相同的状态。在暂时性失去音频焦点时,您应该继续监控音频焦点的变化,并准备好在重新获得焦点后恢复正常播放。当抢占焦点的应用放弃焦点时,您会收到一个回调 (AUDIOFOCUS_GAIN)。此时,您可以将音量恢复到正常水平或重新开始播放。

  • 永久性失去焦点

    如果是永久性失去音频焦点 (AUDIOFOCUS_LOSS),则其他应用会播放音频。您的应用应立即暂停播放,因为它不会收到 AUDIOFOCUS_GAIN 回调。要重新开始播放,用户必须执行明确的操作,例如在通知或应用界面中按播放传输控件。

以下代码段展示了如何实现 OnAudioFocusChangeListener 及其 onAudioFocusChange() 回调。请注意这里使用 Handler 延迟了对永久性失去音频焦点的停止回调。

    private Handler handler = new Handler();
    AudioManager.OnAudioFocusChangeListener afChangeListener =
      new AudioManager.OnAudioFocusChangeListener() {
      	 /**
         * Called on the listener to notify it the audio focus for this listener has been changed.
         * The focusChange value indicates whether the focus was gained,
         * whether the focus was lost, and whether that loss is transient, or whether the new focus
         * holder will hold it for an unknown amount of time.
         * When losing focus, listeners can use the focus change information to decide what
         * behavior to adopt when losing focus. A music player could for instance elect to lower
         * the volume of its music stream (duck) for transient focus losses, and pause otherwise.
         * @param focusChange the type of focus change, one of {@link AudioManager#AUDIOFOCUS_GAIN},
         *   {@link AudioManager#AUDIOFOCUS_LOSS}, {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}
         *   and {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
         */
        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
            handler.postDelayed(delayedStopRunnable,
              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
          }
        }
      };
    
        private Runnable delayedStopRunnable = new Runnable() {
        @Override
        public void run() {
            getMediaController().getTransportControls().stop();
        }
    };
    

为了确保在用户重新开始播放时不会触发延迟停止,请调用 mHandler.removeCallbacks(mDelayedStopRunnable) 来响应任何状态变化。例如,在回调的 onPlay()onSkipToNext() 等中调用 removeCallbacks()。此外,在清理服务使用的资源时,您也应该在服务的 onDestroy() 回调中调用此方法。

3.Android 8.0 及更高版本中的音频焦点

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

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

FocusGain 字段为必需字段;所有其他字段均为可选字段。

[setFocusGain()]每个请求中都必须包含此字段。此字段的值与 Android 8.0 之前的 requestAudioFocus() 调用中所使用的 durationHint 值相同:AUDIOFOCUS_GAINAUDIOFOCUS_GAIN_TRANSIENTAUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKAUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
setAudioAttributes()AudioAttributes 描述了应用的用例。系统会在应用获得和失去音频焦点时查看这些属性。这些属性取代了音频流类型的概念。在 Android 8.0(API 级别 26)及更高版本中,弃用了除音量控制以外的所有操作的音频流类型。在焦点请求中使用与音频播放器中相同的属性(如此表下面的示例中所示)。
setWillPauseWhenDucked()当其他应用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 请求焦点时,持有焦点的应用通常不会收到 onAudioFocusChange() 回调,因为系统可以自行降低音量。如果您需要暂停播放而不是降低音量,请调用 setWillPauseWhenDucked(true),然后创建并设置 OnAudioFocusChangeListener,具体如自动降低音量中所述。
setAcceptsDelayedFocusGain()当焦点被其他应用锁定时,对音频焦点的请求可能会失败。此方法可实现延迟获取焦点,即在焦点可用时异步获取焦点。
请注意,要使“延迟获取焦点”起作用,您还必须在音频请求中指定 AudioManager.OnAudioFocusChangeListener,因为您的应用必须收到回调才能知道自己获取了焦点。
[setOnAudioFocusChangeListener()](https://developer.android.com/reference/android/media/AudioFocusRequest.Builder?hl=zh-cn#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler))只有当您在请求中还指定了 willPauseWhenDucked(true)setAcceptsDelayedFocusGain(true) 时,才需要 OnAudioFocusChangeListener。<br 有两个方法可以设置监听器:一个带处理程序参数,一个不带。处理程序是运行监听器的线程。如果您未指定处理程序,则会使用与主 Looper 关联的处理程序

展示了如何使用 AudioFocusRequest.Builder 构建 AudioFocusRequest 来请求和放弃音频焦点:

    audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
    playbackAttributes = new AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_GAME)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build();
    focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
            .setAudioAttributes(playbackAttributes)
            .setAcceptsDelayedFocusGain(true)
            .setOnAudioFocusChangeListener(afChangeListener, handler)
            .build();
    mediaPlayer = new MediaPlayer();
    final Object focusLock = new Object();

    boolean playbackDelayed = false;
    boolean playbackNowAuthorized = false;

    // ...
    int res = audioManager.requestAudioFocus(focusRequest);
    synchronized(focusLock) {
        if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
            playbackNowAuthorized = false;
        } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            playbackNowAuthorized = true;
            playbackNow();
        } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
           playbackDelayed = true;
           playbackNowAuthorized = false;
        }
    }

    // ...
    @Override
    public void onAudioFocusChange(int focusChange) {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                if (playbackDelayed || resumeOnFocusGain) {
                    synchronized(focusLock) {
                        playbackDelayed = false;
                        resumeOnFocusGain = false;
                    }
                    playbackNow();
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                synchronized(focusLock) {
                    resumeOnFocusGain = false;
                    playbackDelayed = false;
                }
                pausePlayback();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                synchronized(focusLock) {
                    resumeOnFocusGain = true;
                    playbackDelayed = 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() 回调的情况下降低和恢复音量。

虽然自动降低音量的行为对于音乐和视频播放应用来说是可接受的,但在播放语音内容时(例如在听书应用中)就没什么用处了。在这种情况下,应用应该暂停播放。

如果您希望应用在被要求降低音量时暂停播放,请创建包含 onAudioFocusChange() 回调方法的 OnAudioFocusChangeListener,该回调方法可以实现所需的暂停/恢复行为。 调用 setOnAudioFocusChangeListener() 来注册监听器,然后调用 setWillPauseWhenDucked(true) 告诉系统使用您的回调,而不是执行自动降低音量。

延迟获取焦点

在有些情况下,系统不能批准对音频焦点的请求,因为焦点被其他应用“锁定”了,例如在通话过程中。在这种情况下,requestAudioFocus() 会返回 AUDIOFOCUS_REQUEST_FAILED。在这种情况下,您的应用将不会播放音频,因为它未获得焦点。

方法 setAcceptsDelayedFocusGain(true) 可让您的应用异步处理焦点请求。设置此标记后,在焦点锁定时发出的请求会返回 AUDIOFOCUS_REQUEST_DELAYED。当锁定音频焦点的情况不再存在时(例如当通话结束时),系统会批准待处理的焦点请求,并调用 onAudioFocusChange() 来通知您的应用。

为了处理“延迟获取焦点”,您必须创建包含 onAudioFocusChange() 回调方法的 OnAudioFocusChangeListener,该回调方法会通过调用 setOnAudioFocusChangeListener() 来实现所需行为并注册监听器。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值