Fix audio focus handling in ExoPlayerImpl

Some cases are not handled correctly at the moment:
 - Pausing during suppressed playback should not clear the
   suppression state.
 - Transient focus loss while paused should be reported as
   a playback suppression.

Issue: androidx/media#1436
#cherrypick
PiperOrigin-RevId: 644971218
This commit is contained in:
tonihei 2024-06-20 03:59:03 -07:00 committed by Copybara-Service
parent 1d26d1891e
commit e84bb0d21c
3 changed files with 37 additions and 14 deletions

View File

@ -4,6 +4,9 @@
* Common Library: * Common Library:
* ExoPlayer: * ExoPlayer:
* Fix some audio focus inconsistencies, e.g. not reporting full or
transient focus loss while the player is paused
([#1436](https://github.com/androidx/media/issues/1436)).
* Transformer: * Transformer:
* Track Selection: * Track Selection:
* Extractors: * Extractors:

View File

@ -550,8 +550,7 @@ import java.util.concurrent.TimeoutException;
boolean playWhenReady = getPlayWhenReady(); boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING); int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING);
updatePlayWhenReady( updatePlayWhenReady(playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
if (playbackInfo.playbackState != Player.STATE_IDLE) { if (playbackInfo.playbackState != Player.STATE_IDLE) {
return; return;
} }
@ -831,8 +830,7 @@ import java.util.concurrent.TimeoutException;
verifyApplicationThread(); verifyApplicationThread();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady( updatePlayWhenReady(playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
} }
@Override @Override
@ -1500,8 +1498,7 @@ import java.util.concurrent.TimeoutException;
boolean playWhenReady = getPlayWhenReady(); boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady( updatePlayWhenReady(playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
listeners.flushEvents(); listeners.flushEvents();
} }
@ -2836,7 +2833,7 @@ import java.util.concurrent.TimeoutException;
@PlaybackSuppressionReason @PlaybackSuppressionReason
private int computePlaybackSuppressionReason( private int computePlaybackSuppressionReason(
boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand) { boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand) {
if (playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY) { if (playerCommand == AudioFocusManager.PLAYER_COMMAND_WAIT_FOR_CALLBACK) {
return Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS; return Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
} }
if (suppressPlaybackOnUnsuitableOutput) { if (suppressPlaybackOnUnsuitableOutput) {
@ -3005,8 +3002,8 @@ import java.util.concurrent.TimeoutException;
.build(); .build();
} }
private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) { private static int getPlayWhenReadyChangeReason(int playerCommand) {
return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY return playerCommand == AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY
? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS ? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
: PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; : PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
} }
@ -3291,7 +3288,7 @@ import java.util.concurrent.TimeoutException;
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) { public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
boolean playWhenReady = getPlayWhenReady(); boolean playWhenReady = getPlayWhenReady();
updatePlayWhenReady( updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
} }
// AudioBecomingNoisyManager.EventListener implementation. // AudioBecomingNoisyManager.EventListener implementation.

View File

@ -4504,10 +4504,12 @@ public class ExoPlayerTest {
run(player).untilPendingCommandsAreFullyHandled(); run(player).untilPendingCommandsAreFullyHandled();
player.pause(); player.pause();
boolean playWhenReady = player.getPlayWhenReady(); boolean playWhenReady = player.getPlayWhenReady();
@Player.PlaybackSuppressionReason int suppressionReason = player.getPlaybackSuppressionReason();
player.release(); player.release();
assertThat(playWhenReady).isFalse(); assertThat(playWhenReady).isFalse();
// TODO: Fix behavior and assert that suppression reason if transient audio focus loss. assertThat(suppressionReason)
.isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
InOrder inOrder = inOrder(listener); InOrder inOrder = inOrder(listener);
inOrder inOrder
.verify(listener) .verify(listener)
@ -4607,7 +4609,10 @@ public class ExoPlayerTest {
.verify(listener) .verify(listener)
.onPlayWhenReadyChanged( .onPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
// TODO: Fix behavior and assert that audio focus loss is reported via onPlayWhenReadyChanged. inOrder
.verify(listener)
.onPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS);
} }
@Test @Test
@ -4629,17 +4634,22 @@ public class ExoPlayerTest {
.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT); .onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
run(player).untilPendingCommandsAreFullyHandled(); run(player).untilPendingCommandsAreFullyHandled();
boolean playWhenReady = player.getPlayWhenReady(); boolean playWhenReady = player.getPlayWhenReady();
@Player.PlaybackSuppressionReason int suppressionReason = player.getPlaybackSuppressionReason();
shadowOf(audioManager) shadowOf(audioManager)
.getLastAudioFocusRequest() .getLastAudioFocusRequest()
.listener .listener
.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN); .onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN);
run(player).untilPendingCommandsAreFullyHandled(); run(player).untilPendingCommandsAreFullyHandled();
boolean playWhenReadyAfterGain = player.getPlayWhenReady(); boolean playWhenReadyAfterGain = player.getPlayWhenReady();
@Player.PlaybackSuppressionReason
int suppressionReasonAfterGain = player.getPlaybackSuppressionReason();
player.release(); player.release();
assertThat(playWhenReady).isFalse(); assertThat(playWhenReady).isFalse();
assertThat(playWhenReadyAfterGain).isFalse(); assertThat(playWhenReadyAfterGain).isFalse();
// TODO: Fix behavior and assert that suppression reason is transient audio focus loss. assertThat(suppressionReason)
.isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
assertThat(suppressionReasonAfterGain).isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
InOrder inOrder = inOrder(listener); InOrder inOrder = inOrder(listener);
inOrder inOrder
.verify(listener) .verify(listener)
@ -4649,6 +4659,13 @@ public class ExoPlayerTest {
.verify(listener) .verify(listener)
.onPlayWhenReadyChanged( .onPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
inOrder
.verify(listener)
.onPlaybackSuppressionReasonChanged(
Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
inOrder
.verify(listener)
.onPlaybackSuppressionReasonChanged(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
verify(listener, never()) verify(listener, never())
.onPlayWhenReadyChanged( .onPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS); /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS);
@ -4679,7 +4696,6 @@ public class ExoPlayerTest {
assertThat(playWhenReady).isTrue(); assertThat(playWhenReady).isTrue();
assertThat(suppressionReason).isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE); assertThat(suppressionReason).isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
// TODO: Fix behavior and assert that suppression reason is transient audio focus loss.
InOrder inOrder = inOrder(listener); InOrder inOrder = inOrder(listener);
inOrder inOrder
.verify(listener) .verify(listener)
@ -4689,10 +4705,17 @@ public class ExoPlayerTest {
.verify(listener) .verify(listener)
.onPlayWhenReadyChanged( .onPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
inOrder
.verify(listener)
.onPlaybackSuppressionReasonChanged(
Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
inOrder inOrder
.verify(listener) .verify(listener)
.onPlayWhenReadyChanged( .onPlayWhenReadyChanged(
/* playWhenReady= */ true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); /* playWhenReady= */ true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
inOrder
.verify(listener)
.onPlaybackSuppressionReasonChanged(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
verify(listener, never()) verify(listener, never())
.onPlayWhenReadyChanged( .onPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS); /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS);