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