Audio focus player command clean up
The 'player commands' returned to ExoPlayerImpl instruct the player on how to treat the current audio focus state. The current return value when playWhenReady==false is misleading because it implies we are definitely not allowed to play as if we've lost focus. Instead, we should return the actual player command corresponding to the focus state we are in. This has no practical effect in ExoPlayerImpl as we already ignore the 'player command' completely when playWhenReady=false. To facilitate this change, we also introduce a new internal state for FOCUS_NOT_REQUESTED to distinguish it from the state in which we lost focus. #cherrypick PiperOrigin-RevId: 644416586
This commit is contained in:
parent
048d71e392
commit
66c19390e2
@ -72,13 +72,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
})
|
})
|
||||||
public @interface PlayerCommand {}
|
public @interface PlayerCommand {}
|
||||||
|
|
||||||
/** Do not play. */
|
/** Do not play, because audio focus is lost or denied. */
|
||||||
public static final int PLAYER_COMMAND_DO_NOT_PLAY = -1;
|
public static final int PLAYER_COMMAND_DO_NOT_PLAY = -1;
|
||||||
|
|
||||||
/** Do not play now. Wait for callback to play. */
|
/** Do not play now, because of a transient focus loss. */
|
||||||
public static final int PLAYER_COMMAND_WAIT_FOR_CALLBACK = 0;
|
public static final int PLAYER_COMMAND_WAIT_FOR_CALLBACK = 0;
|
||||||
|
|
||||||
/** Play freely. */
|
/** Play freely, because audio focus is granted or not applicable. */
|
||||||
public static final int PLAYER_COMMAND_PLAY_WHEN_READY = 1;
|
public static final int PLAYER_COMMAND_PLAY_WHEN_READY = 1;
|
||||||
|
|
||||||
/** Audio focus state. */
|
/** Audio focus state. */
|
||||||
@ -86,6 +86,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@IntDef({
|
@IntDef({
|
||||||
|
AUDIO_FOCUS_STATE_NOT_REQUESTED,
|
||||||
AUDIO_FOCUS_STATE_NO_FOCUS,
|
AUDIO_FOCUS_STATE_NO_FOCUS,
|
||||||
AUDIO_FOCUS_STATE_HAVE_FOCUS,
|
AUDIO_FOCUS_STATE_HAVE_FOCUS,
|
||||||
AUDIO_FOCUS_STATE_LOSS_TRANSIENT,
|
AUDIO_FOCUS_STATE_LOSS_TRANSIENT,
|
||||||
@ -93,17 +94,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
})
|
})
|
||||||
private @interface AudioFocusState {}
|
private @interface AudioFocusState {}
|
||||||
|
|
||||||
|
/** Audio focus has not been requested yet. */
|
||||||
|
private static final int AUDIO_FOCUS_STATE_NOT_REQUESTED = 0;
|
||||||
|
|
||||||
/** No audio focus is currently being held. */
|
/** No audio focus is currently being held. */
|
||||||
private static final int AUDIO_FOCUS_STATE_NO_FOCUS = 0;
|
private static final int AUDIO_FOCUS_STATE_NO_FOCUS = 1;
|
||||||
|
|
||||||
/** The requested audio focus is currently held. */
|
/** The requested audio focus is currently held. */
|
||||||
private static final int AUDIO_FOCUS_STATE_HAVE_FOCUS = 1;
|
private static final int AUDIO_FOCUS_STATE_HAVE_FOCUS = 2;
|
||||||
|
|
||||||
/** Audio focus has been temporarily lost. */
|
/** Audio focus has been temporarily lost. */
|
||||||
private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT = 2;
|
private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT = 3;
|
||||||
|
|
||||||
/** Audio focus has been temporarily lost, but playback may continue with reduced volume. */
|
/** Audio focus has been temporarily lost, but playback may continue with reduced volume. */
|
||||||
private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK = 3;
|
private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio focus types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
|
* Audio focus types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
|
||||||
@ -181,7 +185,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
(AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE));
|
(AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE));
|
||||||
this.playerControl = playerControl;
|
this.playerControl = playerControl;
|
||||||
this.focusListener = new AudioFocusListener(eventHandler);
|
this.focusListener = new AudioFocusListener(eventHandler);
|
||||||
this.audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS;
|
this.audioFocusState = AUDIO_FOCUS_STATE_NOT_REQUESTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the current player volume multiplier. */
|
/** Gets the current player volume multiplier. */
|
||||||
@ -217,11 +221,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
*/
|
*/
|
||||||
public @PlayerCommand int updateAudioFocus(
|
public @PlayerCommand int updateAudioFocus(
|
||||||
boolean playWhenReady, @Player.State int playbackState) {
|
boolean playWhenReady, @Player.State int playbackState) {
|
||||||
if (shouldAbandonAudioFocusIfHeld(playbackState)) {
|
if (!shouldHandleAudioFocus(playbackState)) {
|
||||||
abandonAudioFocusIfHeld();
|
abandonAudioFocusIfHeld();
|
||||||
return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY;
|
setAudioFocusState(AUDIO_FOCUS_STATE_NOT_REQUESTED);
|
||||||
|
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
||||||
|
}
|
||||||
|
if (playWhenReady) {
|
||||||
|
return requestAudioFocus();
|
||||||
|
}
|
||||||
|
switch (audioFocusState) {
|
||||||
|
case AUDIO_FOCUS_STATE_NO_FOCUS:
|
||||||
|
return PLAYER_COMMAND_DO_NOT_PLAY;
|
||||||
|
case AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
|
||||||
|
return PLAYER_COMMAND_WAIT_FOR_CALLBACK;
|
||||||
|
default:
|
||||||
|
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
||||||
}
|
}
|
||||||
return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -231,6 +246,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public void release() {
|
public void release() {
|
||||||
playerControl = null;
|
playerControl = null;
|
||||||
abandonAudioFocusIfHeld();
|
abandonAudioFocusIfHeld();
|
||||||
|
setAudioFocusState(AUDIO_FOCUS_STATE_NOT_REQUESTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
@ -240,8 +256,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return focusListener;
|
return focusListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldAbandonAudioFocusIfHeld(@Player.State int playbackState) {
|
private boolean shouldHandleAudioFocus(@Player.State int playbackState) {
|
||||||
return playbackState == Player.STATE_IDLE || focusGainToRequest != AUDIOFOCUS_GAIN;
|
return playbackState != Player.STATE_IDLE && focusGainToRequest == AUDIOFOCUS_GAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @PlayerCommand int requestAudioFocus() {
|
private @PlayerCommand int requestAudioFocus() {
|
||||||
@ -259,7 +275,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void abandonAudioFocusIfHeld() {
|
private void abandonAudioFocusIfHeld() {
|
||||||
if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
|
if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS
|
||||||
|
|| audioFocusState == AUDIO_FOCUS_STATE_NOT_REQUESTED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Util.SDK_INT >= 26) {
|
if (Util.SDK_INT >= 26) {
|
||||||
@ -267,7 +284,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
} else {
|
} else {
|
||||||
abandonAudioFocusDefault();
|
abandonAudioFocusDefault();
|
||||||
}
|
}
|
||||||
setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int requestAudioFocusDefault() {
|
private int requestAudioFocusDefault() {
|
||||||
@ -417,6 +433,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
case AudioManager.AUDIOFOCUS_LOSS:
|
case AudioManager.AUDIOFOCUS_LOSS:
|
||||||
executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
|
executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
|
||||||
abandonAudioFocusIfHeld();
|
abandonAudioFocusIfHeld();
|
||||||
|
setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
|
||||||
return;
|
return;
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||||
|
@ -69,7 +69,7 @@ public class AudioFocusManagerTest {
|
|||||||
audioFocusManager.setAudioAttributes(/* audioAttributes= */ null);
|
audioFocusManager.setAudioAttributes(/* audioAttributes= */ null);
|
||||||
|
|
||||||
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
|
||||||
.isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
|
||||||
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
ShadowAudioManager.AudioFocusRequest request =
|
ShadowAudioManager.AudioFocusRequest request =
|
||||||
@ -188,7 +188,7 @@ public class AudioFocusManagerTest {
|
|||||||
|
|
||||||
// Audio focus should not be requested yet, because playWhenReady is false.
|
// Audio focus should not be requested yet, because playWhenReady is false.
|
||||||
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
|
||||||
.isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull();
|
assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull();
|
||||||
|
|
||||||
// Audio focus should be requested now that playWhenReady is true.
|
// Audio focus should be requested now that playWhenReady is true.
|
||||||
@ -241,6 +241,84 @@ public class AudioFocusManagerTest {
|
|||||||
assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f);
|
assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateAudioFocus_toPausedBeforeRequestingFocus_setsPlayerCommandPlayWhenReady() {
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
|
||||||
|
|
||||||
|
@AudioFocusManager.PlayerCommand
|
||||||
|
int playerCommand =
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY);
|
||||||
|
|
||||||
|
assertThat(playerCommand).isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateAudioFocus_toPausedWithFocus_setsPlayerCommandPlayWhenReady() {
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY);
|
||||||
|
|
||||||
|
@AudioFocusManager.PlayerCommand
|
||||||
|
int playerCommand =
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY);
|
||||||
|
|
||||||
|
assertThat(playerCommand).isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateAudioFocus_toPausedWithFocusLoss_setsPlayerCommandDoNotPlay() {
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY);
|
||||||
|
audioFocusManager.getFocusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
@AudioFocusManager.PlayerCommand
|
||||||
|
int playerCommand =
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY);
|
||||||
|
|
||||||
|
assertThat(playerCommand).isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateAudioFocus_toPausedWithTransientFocusLoss_setsPlayerCommandWaitForCallback() {
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY);
|
||||||
|
audioFocusManager.getFocusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
@AudioFocusManager.PlayerCommand
|
||||||
|
int playerCommand =
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY);
|
||||||
|
|
||||||
|
assertThat(playerCommand).isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
updateAudioFocus_toPausedWithTransientFocusLossCanDuck_setsPlayerCommandPlayWhenReady() {
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY);
|
||||||
|
audioFocusManager
|
||||||
|
.getFocusListener()
|
||||||
|
.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
@AudioFocusManager.PlayerCommand
|
||||||
|
int playerCommand =
|
||||||
|
audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY);
|
||||||
|
|
||||||
|
assertThat(playerCommand).isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateAudioFocus_abandonFocusWhenDucked_restoresFullVolume() {
|
public void updateAudioFocus_abandonFocusWhenDucked_restoresFullVolume() {
|
||||||
Shadows.shadowOf(audioManager)
|
Shadows.shadowOf(audioManager)
|
||||||
@ -313,14 +391,14 @@ public class AudioFocusManagerTest {
|
|||||||
audioFocusManager.setAudioAttributes(null);
|
audioFocusManager.setAudioAttributes(null);
|
||||||
|
|
||||||
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
|
||||||
.isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
|
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
|
||||||
ShadowAudioManager.AudioFocusRequest request =
|
ShadowAudioManager.AudioFocusRequest request =
|
||||||
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
|
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
|
||||||
assertThat(request).isNull();
|
assertThat(request).isNull();
|
||||||
|
|
||||||
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
|
||||||
.isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
|
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,14 +410,14 @@ public class AudioFocusManagerTest {
|
|||||||
audioFocusManager.setAudioAttributes(null);
|
audioFocusManager.setAudioAttributes(null);
|
||||||
|
|
||||||
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
|
||||||
.isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
|
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
|
||||||
ShadowAudioManager.AudioFocusRequest request =
|
ShadowAudioManager.AudioFocusRequest request =
|
||||||
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
|
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
|
||||||
assertThat(request).isNull();
|
assertThat(request).isNull();
|
||||||
|
|
||||||
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
|
||||||
.isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
|
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user