• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.exoplayer2;
17 
18 import android.content.Context;
19 import android.media.AudioFocusRequest;
20 import android.media.AudioManager;
21 import android.os.Handler;
22 import androidx.annotation.IntDef;
23 import androidx.annotation.Nullable;
24 import androidx.annotation.RequiresApi;
25 import androidx.annotation.VisibleForTesting;
26 import com.google.android.exoplayer2.audio.AudioAttributes;
27 import com.google.android.exoplayer2.util.Assertions;
28 import com.google.android.exoplayer2.util.Log;
29 import com.google.android.exoplayer2.util.Util;
30 import java.lang.annotation.Documented;
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
34 
35 /** Manages requesting and responding to changes in audio focus. */
36 /* package */ final class AudioFocusManager {
37 
38   /** Interface to allow AudioFocusManager to give commands to a player. */
39   public interface PlayerControl {
40     /**
41      * Called when the volume multiplier on the player should be changed.
42      *
43      * @param volumeMultiplier The new volume multiplier.
44      */
setVolumeMultiplier(float volumeMultiplier)45     void setVolumeMultiplier(float volumeMultiplier);
46 
47     /**
48      * Called when a command must be executed on the player.
49      *
50      * @param playerCommand The command that must be executed.
51      */
executePlayerCommand(@layerCommand int playerCommand)52     void executePlayerCommand(@PlayerCommand int playerCommand);
53   }
54 
55   /**
56    * Player commands. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link
57    * #PLAYER_COMMAND_WAIT_FOR_CALLBACK} or {@link #PLAYER_COMMAND_PLAY_WHEN_READY}.
58    */
59   @Documented
60   @Retention(RetentionPolicy.SOURCE)
61   @IntDef({
62     PLAYER_COMMAND_DO_NOT_PLAY,
63     PLAYER_COMMAND_WAIT_FOR_CALLBACK,
64     PLAYER_COMMAND_PLAY_WHEN_READY,
65   })
66   public @interface PlayerCommand {}
67   /** Do not play. */
68   public static final int PLAYER_COMMAND_DO_NOT_PLAY = -1;
69   /** Do not play now. Wait for callback to play. */
70   public static final int PLAYER_COMMAND_WAIT_FOR_CALLBACK = 0;
71   /** Play freely. */
72   public static final int PLAYER_COMMAND_PLAY_WHEN_READY = 1;
73 
74   /** Audio focus state. */
75   @Documented
76   @Retention(RetentionPolicy.SOURCE)
77   @IntDef({
78     AUDIO_FOCUS_STATE_NO_FOCUS,
79     AUDIO_FOCUS_STATE_HAVE_FOCUS,
80     AUDIO_FOCUS_STATE_LOSS_TRANSIENT,
81     AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK
82   })
83   private @interface AudioFocusState {}
84   /** No audio focus is currently being held. */
85   private static final int AUDIO_FOCUS_STATE_NO_FOCUS = 0;
86   /** The requested audio focus is currently held. */
87   private static final int AUDIO_FOCUS_STATE_HAVE_FOCUS = 1;
88   /** Audio focus has been temporarily lost. */
89   private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT = 2;
90   /** Audio focus has been temporarily lost, but playback may continue with reduced volume. */
91   private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK = 3;
92 
93   private static final String TAG = "AudioFocusManager";
94 
95   private static final float VOLUME_MULTIPLIER_DUCK = 0.2f;
96   private static final float VOLUME_MULTIPLIER_DEFAULT = 1.0f;
97 
98   private final AudioManager audioManager;
99   private final AudioFocusListener focusListener;
100   @Nullable private PlayerControl playerControl;
101   @Nullable private AudioAttributes audioAttributes;
102 
103   @AudioFocusState private int audioFocusState;
104   @C.AudioFocusGain private int focusGain;
105   private float volumeMultiplier = VOLUME_MULTIPLIER_DEFAULT;
106 
107   private @MonotonicNonNull AudioFocusRequest audioFocusRequest;
108   private boolean rebuildAudioFocusRequest;
109 
110   /**
111    * Constructs an AudioFocusManager to automatically handle audio focus for a player.
112    *
113    * @param context The current context.
114    * @param eventHandler A {@link Handler} to for the thread on which the player is used.
115    * @param playerControl A {@link PlayerControl} to handle commands from this instance.
116    */
AudioFocusManager(Context context, Handler eventHandler, PlayerControl playerControl)117   public AudioFocusManager(Context context, Handler eventHandler, PlayerControl playerControl) {
118     this.audioManager =
119         (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
120     this.playerControl = playerControl;
121     this.focusListener = new AudioFocusListener(eventHandler);
122     this.audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS;
123   }
124 
125   /** Gets the current player volume multiplier. */
getVolumeMultiplier()126   public float getVolumeMultiplier() {
127     return volumeMultiplier;
128   }
129 
130   /**
131    * Sets audio attributes that should be used to manage audio focus.
132    *
133    * <p>Call {@link #updateAudioFocus(boolean, int)} to update the audio focus based on these
134    * attributes.
135    *
136    * @param audioAttributes The audio attributes or {@code null} if audio focus should not be
137    *     managed automatically.
138    */
setAudioAttributes(@ullable AudioAttributes audioAttributes)139   public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) {
140     if (!Util.areEqual(this.audioAttributes, audioAttributes)) {
141       this.audioAttributes = audioAttributes;
142       focusGain = convertAudioAttributesToFocusGain(audioAttributes);
143       Assertions.checkArgument(
144           focusGain == C.AUDIOFOCUS_GAIN || focusGain == C.AUDIOFOCUS_NONE,
145           "Automatic handling of audio focus is only available for USAGE_MEDIA and USAGE_GAME.");
146     }
147   }
148 
149   /**
150    * Called by the player to abandon or request audio focus based on the desired player state.
151    *
152    * @param playWhenReady The desired value of playWhenReady.
153    * @param playbackState The desired playback state.
154    * @return A {@link PlayerCommand} to execute on the player.
155    */
156   @PlayerCommand
updateAudioFocus(boolean playWhenReady, @Player.State int playbackState)157   public int updateAudioFocus(boolean playWhenReady, @Player.State int playbackState) {
158     if (shouldAbandonAudioFocus(playbackState)) {
159       abandonAudioFocus();
160       return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY;
161     }
162     return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY;
163   }
164 
165   /**
166    * Called when the manager is no longer required. Audio focus will be released without making any
167    * calls to the {@link PlayerControl}.
168    */
release()169   public void release() {
170     playerControl = null;
171     abandonAudioFocus();
172   }
173 
174   // Internal methods.
175 
176   @VisibleForTesting
getFocusListener()177   /* package */ AudioManager.OnAudioFocusChangeListener getFocusListener() {
178     return focusListener;
179   }
180 
shouldAbandonAudioFocus(@layer.State int playbackState)181   private boolean shouldAbandonAudioFocus(@Player.State int playbackState) {
182     return playbackState == Player.STATE_IDLE || focusGain != C.AUDIOFOCUS_GAIN;
183   }
184 
185   @PlayerCommand
requestAudioFocus()186   private int requestAudioFocus() {
187     if (audioFocusState == AUDIO_FOCUS_STATE_HAVE_FOCUS) {
188       return PLAYER_COMMAND_PLAY_WHEN_READY;
189     }
190     int requestResult = Util.SDK_INT >= 26 ? requestAudioFocusV26() : requestAudioFocusDefault();
191     if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
192       setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
193       return PLAYER_COMMAND_PLAY_WHEN_READY;
194     } else {
195       setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
196       return PLAYER_COMMAND_DO_NOT_PLAY;
197     }
198   }
199 
abandonAudioFocus()200   private void abandonAudioFocus() {
201     if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
202       return;
203     }
204     if (Util.SDK_INT >= 26) {
205       abandonAudioFocusV26();
206     } else {
207       abandonAudioFocusDefault();
208     }
209     setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
210   }
211 
requestAudioFocusDefault()212   private int requestAudioFocusDefault() {
213     return audioManager.requestAudioFocus(
214         focusListener,
215         Util.getStreamTypeForAudioUsage(Assertions.checkNotNull(audioAttributes).usage),
216         focusGain);
217   }
218 
219   @RequiresApi(26)
requestAudioFocusV26()220   private int requestAudioFocusV26() {
221     if (audioFocusRequest == null || rebuildAudioFocusRequest) {
222       AudioFocusRequest.Builder builder =
223           audioFocusRequest == null
224               ? new AudioFocusRequest.Builder(focusGain)
225               : new AudioFocusRequest.Builder(audioFocusRequest);
226 
227       boolean willPauseWhenDucked = willPauseWhenDucked();
228       audioFocusRequest =
229           builder
230               .setAudioAttributes(Assertions.checkNotNull(audioAttributes).getAudioAttributesV21())
231               .setWillPauseWhenDucked(willPauseWhenDucked)
232               .setOnAudioFocusChangeListener(focusListener)
233               .build();
234 
235       rebuildAudioFocusRequest = false;
236     }
237     return audioManager.requestAudioFocus(audioFocusRequest);
238   }
239 
abandonAudioFocusDefault()240   private void abandonAudioFocusDefault() {
241     audioManager.abandonAudioFocus(focusListener);
242   }
243 
244   @RequiresApi(26)
abandonAudioFocusV26()245   private void abandonAudioFocusV26() {
246     if (audioFocusRequest != null) {
247       audioManager.abandonAudioFocusRequest(audioFocusRequest);
248     }
249   }
250 
willPauseWhenDucked()251   private boolean willPauseWhenDucked() {
252     return audioAttributes != null && audioAttributes.contentType == C.CONTENT_TYPE_SPEECH;
253   }
254 
255   /**
256    * Converts {@link AudioAttributes} to one of the audio focus request.
257    *
258    * <p>This follows the class Javadoc of {@link AudioFocusRequest}.
259    *
260    * @param audioAttributes The audio attributes associated with this focus request.
261    * @return The type of audio focus gain that should be requested.
262    */
263   @C.AudioFocusGain
convertAudioAttributesToFocusGain(@ullable AudioAttributes audioAttributes)264   private static int convertAudioAttributesToFocusGain(@Nullable AudioAttributes audioAttributes) {
265     if (audioAttributes == null) {
266       // Don't handle audio focus. It may be either video only contents or developers
267       // want to have more finer grained control. (e.g. adding audio focus listener)
268       return C.AUDIOFOCUS_NONE;
269     }
270 
271     switch (audioAttributes.usage) {
272         // USAGE_VOICE_COMMUNICATION_SIGNALLING is for DTMF that may happen multiple times
273         // during the phone call when AUDIOFOCUS_GAIN_TRANSIENT is requested for that.
274         // Don't request audio focus here.
275       case C.USAGE_VOICE_COMMUNICATION_SIGNALLING:
276         return C.AUDIOFOCUS_NONE;
277 
278         // Javadoc says 'AUDIOFOCUS_GAIN: Examples of uses of this focus gain are for music
279         // playback, for a game or a video player'
280       case C.USAGE_GAME:
281       case C.USAGE_MEDIA:
282         return C.AUDIOFOCUS_GAIN;
283 
284         // Special usages: USAGE_UNKNOWN shouldn't be used. Request audio focus to prevent
285         // multiple media playback happen at the same time.
286       case C.USAGE_UNKNOWN:
287         Log.w(
288             TAG,
289             "Specify a proper usage in the audio attributes for audio focus"
290                 + " handling. Using AUDIOFOCUS_GAIN by default.");
291         return C.AUDIOFOCUS_GAIN;
292 
293         // Javadoc says 'AUDIOFOCUS_GAIN_TRANSIENT: An example is for playing an alarm, or
294         // during a VoIP call'
295       case C.USAGE_ALARM:
296       case C.USAGE_VOICE_COMMUNICATION:
297         return C.AUDIOFOCUS_GAIN_TRANSIENT;
298 
299         // Javadoc says 'AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: Examples are when playing
300         // driving directions or notifications'
301       case C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
302       case C.USAGE_ASSISTANCE_SONIFICATION:
303       case C.USAGE_NOTIFICATION:
304       case C.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
305       case C.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
306       case C.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
307       case C.USAGE_NOTIFICATION_EVENT:
308       case C.USAGE_NOTIFICATION_RINGTONE:
309         return C.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
310 
311         // Javadoc says 'AUDIOFOCUS_GAIN_EXCLUSIVE: This is typically used if you are doing
312         // audio recording or speech recognition'.
313         // Assistant is considered as both recording and notifying developer
314       case C.USAGE_ASSISTANT:
315         if (Util.SDK_INT >= 19) {
316           return C.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
317         } else {
318           return C.AUDIOFOCUS_GAIN_TRANSIENT;
319         }
320 
321         // Special usages:
322       case C.USAGE_ASSISTANCE_ACCESSIBILITY:
323         if (audioAttributes.contentType == C.CONTENT_TYPE_SPEECH) {
324           // Voice shouldn't be interrupted by other playback.
325           return C.AUDIOFOCUS_GAIN_TRANSIENT;
326         }
327         return C.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
328       default:
329         Log.w(TAG, "Unidentified audio usage: " + audioAttributes.usage);
330         return C.AUDIOFOCUS_NONE;
331     }
332   }
333 
setAudioFocusState(@udioFocusState int audioFocusState)334   private void setAudioFocusState(@AudioFocusState int audioFocusState) {
335     if (this.audioFocusState == audioFocusState) {
336       return;
337     }
338     this.audioFocusState = audioFocusState;
339 
340     float volumeMultiplier =
341         (audioFocusState == AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK)
342             ? AudioFocusManager.VOLUME_MULTIPLIER_DUCK
343             : AudioFocusManager.VOLUME_MULTIPLIER_DEFAULT;
344     if (this.volumeMultiplier == volumeMultiplier) {
345       return;
346     }
347     this.volumeMultiplier = volumeMultiplier;
348     if (playerControl != null) {
349       playerControl.setVolumeMultiplier(volumeMultiplier);
350     }
351   }
352 
handlePlatformAudioFocusChange(int focusChange)353   private void handlePlatformAudioFocusChange(int focusChange) {
354     switch (focusChange) {
355       case AudioManager.AUDIOFOCUS_GAIN:
356         setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
357         executePlayerCommand(PLAYER_COMMAND_PLAY_WHEN_READY);
358         return;
359       case AudioManager.AUDIOFOCUS_LOSS:
360         executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
361         abandonAudioFocus();
362         return;
363       case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
364       case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
365         if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || willPauseWhenDucked()) {
366           executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
367           setAudioFocusState(AUDIO_FOCUS_STATE_LOSS_TRANSIENT);
368         } else {
369           setAudioFocusState(AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK);
370         }
371         return;
372       default:
373         Log.w(TAG, "Unknown focus change type: " + focusChange);
374     }
375   }
376 
executePlayerCommand(@layerCommand int playerCommand)377   private void executePlayerCommand(@PlayerCommand int playerCommand) {
378     if (playerControl != null) {
379       playerControl.executePlayerCommand(playerCommand);
380     }
381   }
382 
383   // Internal audio focus listener.
384 
385   private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
386     private final Handler eventHandler;
387 
AudioFocusListener(Handler eventHandler)388     public AudioFocusListener(Handler eventHandler) {
389       this.eventHandler = eventHandler;
390     }
391 
392     @Override
onAudioFocusChange(int focusChange)393     public void onAudioFocusChange(int focusChange) {
394       eventHandler.post(() -> handlePlatformAudioFocusChange(focusChange));
395     }
396   }
397 }
398