当前位置: 首页 > 工具软件 > focus-android > 使用案例 >

Android10.0AudioFocus之AudioFocusRequest

方茂
2023-12-01
前言

以前做Android4.0的时候申请AudioFocus基本就是传个streamtype,durationHint和listener,不知不觉到了android O、P、Q。也就是到了Android10.0发现突然多了好多与音频焦点相关的类,看的眼花缭乱。今天抽时间梳理了一下这些类都是做什么的。

正文

具体罗列如下:

  • AudioFocusRequest
  • FocusRequestInfo
  • AudioFocusInfo
  • FocusRequester
    是不是第一次看也是一脸懵逼,那么我们就从源码角度来逐一分析。
    首先几天先看下AudioFocusRequest,这个是我们在申请音频焦点和释放音频焦点需要传入的参数,主要是设置申请焦点时的一些参数设定。
/**
 1. A class to encapsulate information about an audio focus request.
 2. An {@code AudioFocusRequest} instance is built by {@link Builder}, and is used to
 3. request and abandon audio focus, respectively
 4. with {@link AudioManager#requestAudioFocus(AudioFocusRequest)} and
 5. {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}.
 6.  7. <h3>What is audio focus?</h3>
 8. <p>Audio focus is a concept introduced in API 8. It is used to convey the fact that a user can
 9. only focus on a single audio stream at a time, e.g. listening to music or a podcast, but not
 10. both at the same time. In some cases, multiple audio streams can be playing at the same time,
 11. but there is only one the user would really listen to (focus on), while the other plays in
 12. the background. An example of this is driving directions being spoken while music plays at
 13. a reduced volume (a.k.a. ducking).
 14. <p>When an application requests audio focus, it expresses its intention to “own” audio focus to
 15. play audio. Let’s review the different types of focus requests, the return value after a request,
 16. and the responses to a loss.
 17. <p class="note">Note: applications should not play anything until granted focus.</p>
 18.  19. <h3>The different types of focus requests</h3>
 20. <p>There are four focus request types. A successful focus request with each will yield different
 21. behaviors by the system and the other application that previously held audio focus.
 22. <ul>
 23. <li>{@link AudioManager#AUDIOFOCUS_GAIN} expresses the fact that your application is now the
 24. sole source of audio that the user is listening to. The duration of the audio playback is
 25. unknown, and is possibly very long: after the user finishes interacting with your application,
 26. (s)he doesn’t expect another audio stream to resume. Examples of uses of this focus gain are
 27. for music playback, for a game or a video player.</li>
 28.  29. <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT} is for a situation when you know your
 30. application is temporarily grabbing focus from the current owner, but the user expects playback
 31. to go back to where it was once your application no longer requires audio focus. An example is
 32. for playing an alarm, or during a VoIP call. The playback is known to be finite: the alarm will
 33. time-out or be dismissed, the VoIP call has a beginning and an end. When any of those events
 34. ends, and if the user was listening to music when it started, the user expects music to resume,
 35. but didn’t wish to listen to both at the same time.</li>
 36.  37. <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}: this focus request type is similar
 38. to {@code AUDIOFOCUS_GAIN_TRANSIENT} for the temporary aspect of the focus request, but it also
 39. expresses the fact during the time you own focus, you allow another application to keep playing
 40. at a reduced volume, “ducked”. Examples are when playing driving directions or notifications,
 41. it’s ok for music to keep playing, but not loud enough that it would prevent the directions to
 42. be hard to understand. A typical attenuation by the “ducked” application is a factor of 0.2f
 43. (or -14dB), that can for instance be applied with {@code MediaPlayer.setVolume(0.2f)} when
 44. using this class for playback.</li>
 45.  46. <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} is also for a temporary request,
 47. but also expresses that your application expects the device to not play anything else. This is
 48. typically used if you are doing audio recording or speech recognition, and don’t want for
 49. examples notifications to be played by the system during that time.</li>
 50. </ul>
 51.  52. <p>An {@code AudioFocusRequest} instance always contains one of the four types of requests
 53. explained above. It is passed when building an {@code AudioFocusRequest} instance with its
 54. builder in the {@link Builder} constructor
 55. {@link AudioFocusRequest.Builder#AudioFocusRequest.Builder(int)}, or
 56. with {@link AudioFocusRequest.Builder#setFocusGain(int)} after copying an existing instance with
 57. {@link AudioFocusRequest.Builder#AudioFocusRequest.Builder(AudioFocusRequest)}.
 58.  59. <h3>Qualifying your focus request</h3>
 60. <h4>Use case requiring a focus request</h4>
 61. <p>Any focus request is qualified by the {@link AudioAttributes}
 62. (see {@link Builder#setAudioAttributes(AudioAttributes)}) that describe the audio use case that
 63. will follow the request (once it's successful or granted). It is recommended to use the
 64. same {@code AudioAttributes} for the request as the attributes you are using for audio/media
 65. playback.
 66. <br>If no attributes are set, default attributes of {@link AudioAttributes#USAGE_MEDIA} are used.
 67.  68. <h4>Delayed focus</h4>
 69. <p>Audio focus can be "locked" by the system for a number of reasons: during a phone call, when
 70. the car to which the device is connected plays an emergency message... To support these
 71. situations, the application can request to be notified when its request is fulfilled, by flagging
 72. its request as accepting delayed focus, with {@link Builder#setAcceptsDelayedFocusGain(boolean)}.
 73. <br>If focus is requested while being locked by the system,
 74. {@link AudioManager#requestAudioFocus(AudioFocusRequest)} will return
 75. {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}. When focus isn't locked anymore, the focus
 76. listener set with {@link Builder#setOnAudioFocusChangeListener(OnAudioFocusChangeListener)}
 77. or with {@link Builder#setOnAudioFocusChangeListener(OnAudioFocusChangeListener, Handler)} will
 78. be called to notify the application it now owns audio focus.
 79.  80. <h4>Pausing vs ducking</h4>
 81. <p>When an application requested audio focus with
 82. {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, the system will duck the current focus
 83. owner.
 84. <p class="note">Note: this behavior is <b>new for Android O</b>, whereas applications targeting
 85. SDK level up to API 25 had to implement the ducking themselves when they received a focus
 86. loss of {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
 87. <p>But ducking is not always the behavior expected by the user. A typical example is when the
 88. device plays driving directions while the user is listening to an audio book or podcast, and
 89. expects the audio playback to pause, instead of duck, as it is hard to understand a navigation
 90. prompt and spoken content at the same time. Therefore the system will not automatically duck
 91. when it detects it would be ducking spoken content: such content is detected when the
 92. {@code AudioAttributes} of the player are qualified by
 93. {@link AudioAttributes#CONTENT_TYPE_SPEECH}. Refer for instance to
 94. {@link AudioAttributes.Builder#setContentType(int)} and
 95. {@link MediaPlayer#setAudioAttributes(AudioAttributes)} if you are writing a media playback
 96. application for audio book, podcasts... Since the system will not automatically duck applications
 97. that play speech, it calls their focus listener instead to notify them of
 98. {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}, so they can pause instead. Note that
 99. this behavior is independent of the use of {@code AudioFocusRequest}, but tied to the use
 100. of {@code AudioAttributes}.
 101. <p>If your application requires pausing instead of ducking for any other reason than playing
 102. speech, you can also declare so with {@link Builder#setWillPauseWhenDucked(boolean)}, which will
 103. cause the system to call your focus listener instead of automatically ducking.
 104.  105. <h4>Example</h4>
 106. <p>The example below covers the following steps to be found in any application that would play
 107. audio, and use audio focus. Here we play an audio book, and our application is intended to pause
 108. rather than duck when it loses focus. These steps consist in:
 109. <ul>
 110. <li>Creating {@code AudioAttributes} to be used for the playback and the focus request.</li>
 111. <li>Configuring and creating the {@code AudioFocusRequest} instance that defines the intended
 112.     focus behaviors.</li>
 113. <li>Requesting audio focus and checking the return code to see if playback can happen right
 114.     away, or is delayed.</li>
 115. <li>Implementing a focus change listener to respond to focus gains and losses.</li>
 116. </ul>
 117. <p>
 118. <pre class="prettyprint">
 119. // initialization of the audio attributes and focus request
 120. mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
 121. mPlaybackAttributes = new AudioAttributes.Builder()
 122.         .setUsage(AudioAttributes.USAGE_MEDIA)
 123.         .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
 124.         .build();
 125. mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
 126.         .setAudioAttributes(mPlaybackAttributes)
 127.         .setAcceptsDelayedFocusGain(true)
 128.         .setWillPauseWhenDucked(true)
 129.         .setOnAudioFocusChangeListener(this, mMyHandler)
 130.         .build();
 131. mMediaPlayer = new MediaPlayer();
 132. mMediaPlayer.setAudioAttributes(mPlaybackAttributes);
 133. final Object mFocusLock = new Object();
 134.  135. boolean mPlaybackDelayed = false;
 136.  137. // requesting audio focus
 138. int res = mAudioManager.requestAudioFocus(mFocusRequest);
 139. synchronized (mFocusLock) {
 140.     if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
 141.         mPlaybackDelayed = false;
 142.     } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
 143.         mPlaybackDelayed = false;
 144.         playbackNow();
 145.     } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
 146.        mPlaybackDelayed = true;
 147.     }
 148. }
 149.  150. // implementation of the OnAudioFocusChangeListener
 151. &#64;Override
 152. public void onAudioFocusChange(int focusChange) {
 153.     switch (focusChange) {
 154.         case AudioManager.AUDIOFOCUS_GAIN:
 155.             if (mPlaybackDelayed || mResumeOnFocusGain) {
 156.                 synchronized (mFocusLock) {
 157.                     mPlaybackDelayed = false;
 158.                     mResumeOnFocusGain = false;
 159.                 }
 160.                 playbackNow();
 161.             }
 162.             break;
 163.         case AudioManager.AUDIOFOCUS_LOSS:
 164.             synchronized (mFocusLock) {
 165.                 // this is not a transient loss, we shouldn't automatically resume for now
 166.                 mResumeOnFocusGain = false;
 167.                 mPlaybackDelayed = false;
 168.             }
 169.             pausePlayback();
 170.             break;
 171.         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
 172.         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
 173.             // we handle all transient losses the same way because we never duck audio books
 174.             synchronized (mFocusLock) {
 175.                 // we should only resume if playback was interrupted
 176.                 mResumeOnFocusGain = mMediaPlayer.isPlaying();
 177.                 mPlaybackDelayed = false;
 178.             }
 179.             pausePlayback();
 180.             break;
 181.     }
 182. }
 183.  184. // Important:
 185. // Also set "mResumeOnFocusGain" to false when the user pauses or stops playback: this way your
 186. // application doesn't automatically restart when it gains focus, even though the user had
 187. // stopped it.
 188. </pre>
 */

我摘录了源码注释部分,这块主要分了四部分:

  1. 解释AudioFocusRequest
  2. 什么是audio focus
  3. 介绍申请音频焦点的各种type
  4. 提供一个使用音频焦点播放的demo

个人英语水平有限,就不卖弄了,原文大家自行翻译吧。

这个类采用的也是这种builder的设计模式,这种模式的优点就是我们可以只初始化我们关心的参数那么接下来我们继续分析都做了什么

       public @NonNull Builder setFocusGain(int focusGain) {
            if (!isValidFocusGain(focusGain)) {
                throw new IllegalArgumentException("Illegal audio focus gain type " + focusGain);
            }
            mFocusGain = focusGain;
            return this;
        }

先说一个这个,这个是我一直觉得很尴尬的一个方法,因为我们在使用build的方式创建AudioFocusRequest的时候我们要传入一个focusGain

        public Builder(int focusGain) {
            setFocusGain(focusGain);
        }

那么这个方法就显得有些多余了,直到某一天我才蓦然发现其实我们也可以不通过build的方式创建AudioFocusRequest,那么这个方法就显得尤为重要了。这个方法我们采用build的方式构建AudioFocusRequest的时候就不用再次设置了,否则一定需要设置的。因为默认的focusGain是0,而我们在requestAudioFocus的时候会对focusGain进行check检查

  if (!AudioFocusRequest.isValidFocusGain(durationHint)) {
            throw new IllegalArgumentException("Invalid duration hint");
        }

满足需求的focusGain如下

    final static boolean isValidFocusGain(int focusGain) {
        switch (focusGain) {
            case AudioManager.AUDIOFOCUS_GAIN:
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
                return true;
            default:
                return false;
        }
    }

没有 AudioManager.AUDIOFOCUS_NONE,因此我们一定要setFocusGain。
继续

        public @NonNull Builder setOnAudioFocusChangeListener(
                @NonNull OnAudioFocusChangeListener listener) {
            if (listener == null) {
                throw new NullPointerException("Illegal null focus listener");
            }
            mFocusListener = listener;
            mListenerHandler = null;
            return this;
        }

        /**
         * @hide
         * Internal listener setter, no null checks on listener nor handler
         * @param listener
         * @param handler
         * @return this {@code Builder} instance.
         */
        @NonNull Builder setOnAudioFocusChangeListenerInt(
                OnAudioFocusChangeListener listener, Handler handler) {
            mFocusListener = listener;
            mListenerHandler = handler;
            return this;
        }


        public @NonNull Builder setOnAudioFocusChangeListener(
                @NonNull OnAudioFocusChangeListener listener, @NonNull Handler handler) {
            if (listener == null || handler == null) {
                throw new NullPointerException("Illegal null focus listener or handler");
            }
            mFocusListener = listener;
            mListenerHandler = handler;
            return this;
        }

三个setListener的方法,关于第二个我一直没有看明白,因为没有参数校验,我只能怀疑是为了兼容以前版本,而以前版本这个listener可以为null,或者现在的版本某些情况不需要这个listener所以允许传null。但我又未发现使用的地方。这个不说了,来说下一和三的区别,也就是有没有handler。区别在于有handler我们可以指定lisnter回调到handler线程,而没有handler只能默认回调到与我们初始化audiomanager的那个线程,因此建议还是单独回调到指定的handler线程,因为如果使用audiomanager也就是申请焦点的线程容易造成listener回调的卡顿,这样造成的后果就是我们要停止的声音没有及时停止而造成了短暂混音。

这个我当时做android4.0的时候确实遇到过,当时是播放音乐和申请焦点是一个线程,因为那个时候还有没AudioFocusRequest,所以当时解决的方式就是把申请焦点的逻辑单独拿到一个线程去处理的,不过现在简单多了,我们传个handler就可以了,显然谷歌也是发现了这个可能出现的问题。
有点跑题了继续分析

        public @NonNull Builder setAudioAttributes(@NonNull AudioAttributes attributes) {
            if (attributes == null) {
                throw new NullPointerException("Illegal null AudioAttributes");
            }
            mAttr = attributes;
            return this;
        }

这个不多说了,设置audioAttributes,这里虽然对AudioAttributes的usage和contentType没有啥要求,但建议设置为与我们申请焦点目的相关的属性,比如我们申请焦点是为了播放音乐,那么我们的AudioAttributes的usage和contentType就可以设置成music,这样我们在使用Mediaplayer或者AudioTrack播放的时候可以直接设置这个AudioAttributes,就不用在重新弄一个了。

        public @NonNull Builder setWillPauseWhenDucked(boolean pauseOnDuck) {
            mPausesOnDuck = pauseOnDuck;
            return this;
        }

这个方法是说当前音源在收到AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK这个callback的时候,是否自动duck(降低声音)处理。如果我们设置了true,那么就不会执行duck,举个例子比如我们在放音乐,当导航播报的时候会发现音乐的声音变低了(尤其车载系统中),导航播放结束音乐的声音又回到了之前的音量这种效果就是设置了setWillPauseWhenDucked(false)。

        public @NonNull Builder setAcceptsDelayedFocusGain(boolean acceptsDelayedFocusGain) {
            mDelayedFocus = acceptsDelayedFocusGain;
            return this;
        }

这个方法举个例子说明更容易理解,我们现在播放QQ音乐,这个时候来了一个电话,此时QQ音乐暂停播放,如果打完电话挂断,我们知道正常的逻辑是会继续播放QQ音乐,但如果打电话过程中要播放一个网易云音乐如果setAcceptsDelayedFocusGain(true)那么当挂断电话后,网易云音乐回收一个granted的callback,就可以继续播放网易云音乐了,而不是继续播放QQ音乐。

        public @NonNull Builder setForceDucking(boolean forceDucking) {
            mA11yForceDucking = forceDucking;
            return this;
        }

这个不是很常用,只有当AudioAttributes的usage是USAGE_ASSISTANCE_ACCESSIBILITY以及申请的是AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的时候,会强制duck其他音源,这个主要用于无障碍服务中。

总结

AudioFocusRequest的创建方式两种,一种通过构造函数传入所有参数,一种通过builder的方式,设置我们只关心的参数。主要的函数是setFocusGain和setOnAudioFocusChangeListener和setAudioAttributes,其他可根据具体需求设置。以上就这么多,如果有问题欢迎大家一起讨论交流。

 类似资料: