mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Audio focus: Restore full volume if focus is abandoned when ducked
If we're in the ducked state and updateAudioFocus is called with a new state for which focus is no longer required, we should restore the player back to full volume. Issue: #7182 PiperOrigin-RevId: 305232155
This commit is contained in:
parent
5a7dbae18c
commit
20cadd6e04
4 changed files with 134 additions and 64 deletions
|
|
@ -167,16 +167,18 @@
|
||||||
* Allow missing hours and milliseconds in SubRip (.srt) timecodes
|
* Allow missing hours and milliseconds in SubRip (.srt) timecodes
|
||||||
([#7122](https://github.com/google/ExoPlayer/issues/7122)).
|
([#7122](https://github.com/google/ExoPlayer/issues/7122)).
|
||||||
* Audio:
|
* Audio:
|
||||||
* Prevent case where another app spuriously holding transient audio focus
|
|
||||||
could prevent ExoPlayer from acquiring audio focus for an indefinite period
|
|
||||||
of time ([#7182](https://github.com/google/ExoPlayer/issues/7182).
|
|
||||||
* Workaround issue that could cause slower than realtime playback of AAC on
|
|
||||||
Android 10 ([#6671](https://github.com/google/ExoPlayer/issues/6671).
|
|
||||||
* Enable playback speed adjustment and silence skipping for floating point PCM
|
* Enable playback speed adjustment and silence skipping for floating point PCM
|
||||||
audio, via resampling to 16-bit integer PCM. To output the original floating
|
audio, via resampling to 16-bit integer PCM. To output the original floating
|
||||||
point audio without adjustment, pass `enableFloatOutput=true` to the
|
point audio without adjustment, pass `enableFloatOutput=true` to the
|
||||||
`DefaultAudioSink` constructor
|
`DefaultAudioSink` constructor
|
||||||
([#7134](https://github.com/google/ExoPlayer/issues/7134)).
|
([#7134](https://github.com/google/ExoPlayer/issues/7134)).
|
||||||
|
* Workaround issue that could cause slower than realtime playback of AAC on
|
||||||
|
Android 10 ([#6671](https://github.com/google/ExoPlayer/issues/6671).
|
||||||
|
* Fix case where another app spuriously holding transient audio focus could
|
||||||
|
prevent ExoPlayer from acquiring audio focus for an indefinite period of
|
||||||
|
time ([#7182](https://github.com/google/ExoPlayer/issues/7182).
|
||||||
|
* Fix case where the player volume could be permanently ducked if audio focus
|
||||||
|
was released whilst ducking.
|
||||||
* Fix playback of WAV files with trailing non-media bytes
|
* Fix playback of WAV files with trailing non-media bytes
|
||||||
([#7129](https://github.com/google/ExoPlayer/issues/7129)).
|
([#7129](https://github.com/google/ExoPlayer/issues/7129)).
|
||||||
* Fix playback of ADTS files with mid-stream ID3 metadata.
|
* Fix playback of ADTS files with mid-stream ID3 metadata.
|
||||||
|
|
|
||||||
|
|
@ -75,15 +75,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({
|
@IntDef({
|
||||||
AUDIO_FOCUS_STATE_LOST_FOCUS,
|
|
||||||
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,
|
||||||
AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK
|
AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK
|
||||||
})
|
})
|
||||||
private @interface AudioFocusState {}
|
private @interface AudioFocusState {}
|
||||||
/** No audio focus was held, but has been lost by another app taking it permanently. */
|
|
||||||
private static final int AUDIO_FOCUS_STATE_LOST_FOCUS = -1;
|
|
||||||
/** 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 = 0;
|
||||||
/** The requested audio focus is currently held. */
|
/** The requested audio focus is currently held. */
|
||||||
|
|
@ -100,7 +97,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
private final AudioManager audioManager;
|
private final AudioManager audioManager;
|
||||||
private final AudioFocusListener focusListener;
|
private final AudioFocusListener focusListener;
|
||||||
private final PlayerControl playerControl;
|
@Nullable private PlayerControl playerControl;
|
||||||
@Nullable private AudioAttributes audioAttributes;
|
@Nullable private AudioAttributes audioAttributes;
|
||||||
|
|
||||||
@AudioFocusState private int audioFocusState;
|
@AudioFocusState private int audioFocusState;
|
||||||
|
|
@ -165,6 +162,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY;
|
return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the manager is no longer required. Audio focus will be released without making any
|
||||||
|
* calls to the {@link PlayerControl}.
|
||||||
|
*/
|
||||||
|
public void release() {
|
||||||
|
playerControl = null;
|
||||||
|
abandonAudioFocus();
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|
@ -183,10 +189,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
int requestResult = Util.SDK_INT >= 26 ? requestAudioFocusV26() : requestAudioFocusDefault();
|
int requestResult = Util.SDK_INT >= 26 ? requestAudioFocusV26() : requestAudioFocusDefault();
|
||||||
if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_HAVE_FOCUS;
|
setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
|
||||||
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
||||||
} else {
|
} else {
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS;
|
setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
|
||||||
return PLAYER_COMMAND_DO_NOT_PLAY;
|
return PLAYER_COMMAND_DO_NOT_PLAY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +206,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
} else {
|
} else {
|
||||||
abandonAudioFocusDefault();
|
abandonAudioFocusDefault();
|
||||||
}
|
}
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS;
|
setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int requestAudioFocusDefault() {
|
private int requestAudioFocusDefault() {
|
||||||
|
|
@ -325,63 +331,55 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAudioFocusChange(int focusChange) {
|
private void setAudioFocusState(@AudioFocusState int audioFocusState) {
|
||||||
// Convert the platform focus change to internal state.
|
if (this.audioFocusState == audioFocusState) {
|
||||||
switch (focusChange) {
|
return;
|
||||||
case AudioManager.AUDIOFOCUS_LOSS:
|
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_LOST_FOCUS;
|
|
||||||
break;
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
|
|
||||||
break;
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
|
||||||
if (willPauseWhenDucked()) {
|
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
|
|
||||||
} else {
|
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AudioManager.AUDIOFOCUS_GAIN:
|
|
||||||
audioFocusState = AUDIO_FOCUS_STATE_HAVE_FOCUS;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.w(TAG, "Unknown focus change type: " + focusChange);
|
|
||||||
// Early return.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the internal state (change).
|
|
||||||
switch (audioFocusState) {
|
|
||||||
case AUDIO_FOCUS_STATE_NO_FOCUS:
|
|
||||||
// Focus was not requested; nothing to do.
|
|
||||||
break;
|
|
||||||
case AUDIO_FOCUS_STATE_LOST_FOCUS:
|
|
||||||
playerControl.executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
|
|
||||||
abandonAudioFocus();
|
|
||||||
break;
|
|
||||||
case AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
|
|
||||||
playerControl.executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
|
|
||||||
break;
|
|
||||||
case AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK:
|
|
||||||
// Volume will be adjusted by the code below.
|
|
||||||
break;
|
|
||||||
case AUDIO_FOCUS_STATE_HAVE_FOCUS:
|
|
||||||
playerControl.executePlayerCommand(PLAYER_COMMAND_PLAY_WHEN_READY);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unknown audio focus state: " + audioFocusState);
|
|
||||||
}
|
}
|
||||||
|
this.audioFocusState = audioFocusState;
|
||||||
|
|
||||||
float volumeMultiplier =
|
float volumeMultiplier =
|
||||||
(audioFocusState == AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK)
|
(audioFocusState == AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK)
|
||||||
? AudioFocusManager.VOLUME_MULTIPLIER_DUCK
|
? AudioFocusManager.VOLUME_MULTIPLIER_DUCK
|
||||||
: AudioFocusManager.VOLUME_MULTIPLIER_DEFAULT;
|
: AudioFocusManager.VOLUME_MULTIPLIER_DEFAULT;
|
||||||
if (this.volumeMultiplier != volumeMultiplier) {
|
if (this.volumeMultiplier == volumeMultiplier) {
|
||||||
this.volumeMultiplier = volumeMultiplier;
|
return;
|
||||||
|
}
|
||||||
|
this.volumeMultiplier = volumeMultiplier;
|
||||||
|
if (playerControl != null) {
|
||||||
playerControl.setVolumeMultiplier(volumeMultiplier);
|
playerControl.setVolumeMultiplier(volumeMultiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handlePlatformAudioFocusChange(int focusChange) {
|
||||||
|
switch (focusChange) {
|
||||||
|
case AudioManager.AUDIOFOCUS_GAIN:
|
||||||
|
setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
|
||||||
|
executePlayerCommand(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
return;
|
||||||
|
case AudioManager.AUDIOFOCUS_LOSS:
|
||||||
|
executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
|
||||||
|
abandonAudioFocus();
|
||||||
|
return;
|
||||||
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||||
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||||
|
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || willPauseWhenDucked()) {
|
||||||
|
executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
|
||||||
|
setAudioFocusState(AUDIO_FOCUS_STATE_LOSS_TRANSIENT);
|
||||||
|
} else {
|
||||||
|
setAudioFocusState(AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
Log.w(TAG, "Unknown focus change type: " + focusChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executePlayerCommand(@PlayerCommand int playerCommand) {
|
||||||
|
if (playerControl != null) {
|
||||||
|
playerControl.executePlayerCommand(playerCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Internal audio focus listener.
|
// Internal audio focus listener.
|
||||||
|
|
||||||
private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
|
private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
|
||||||
|
|
@ -393,7 +391,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioFocusChange(int focusChange) {
|
public void onAudioFocusChange(int focusChange) {
|
||||||
eventHandler.post(() -> handleAudioFocusChange(focusChange));
|
eventHandler.post(() -> handlePlatformAudioFocusChange(focusChange));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1565,9 +1565,9 @@ public class SimpleExoPlayer extends BasePlayer
|
||||||
public void release() {
|
public void release() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
audioBecomingNoisyManager.setEnabled(false);
|
audioBecomingNoisyManager.setEnabled(false);
|
||||||
audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE);
|
|
||||||
wakeLockManager.setStayAwake(false);
|
wakeLockManager.setStayAwake(false);
|
||||||
wifiLockManager.setStayAwake(false);
|
wifiLockManager.setStayAwake(false);
|
||||||
|
audioFocusManager.release();
|
||||||
player.release();
|
player.release();
|
||||||
removeSurfaceCallbacks();
|
removeSurfaceCallbacks();
|
||||||
if (surface != null) {
|
if (surface != null) {
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,54 @@ public class AudioFocusManagerTest {
|
||||||
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateAudioFocus_pausedToPlaying_withTransientDuck_setsPlayerCommandPlayWhenReady() {
|
||||||
|
AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(media);
|
||||||
|
|
||||||
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
|
||||||
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
|
||||||
|
// Simulate transient ducking.
|
||||||
|
audioFocusManager
|
||||||
|
.getFocusListener()
|
||||||
|
.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
|
||||||
|
assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
|
||||||
|
|
||||||
|
// Focus should be re-requested, rather than staying in a state of transient ducking. This
|
||||||
|
// should restore the volume to 1.0.
|
||||||
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
|
||||||
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateAudioFocus_abandonFocusWhenDucked_restoresFullVolume() {
|
||||||
|
AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(media);
|
||||||
|
|
||||||
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
|
||||||
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
|
||||||
|
// Simulate transient ducking.
|
||||||
|
audioFocusManager
|
||||||
|
.getFocusListener()
|
||||||
|
.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
|
||||||
|
assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
|
||||||
|
|
||||||
|
// Configure the manager to no longer handle audio focus.
|
||||||
|
audioFocusManager.setAudioAttributes(null);
|
||||||
|
|
||||||
|
// Focus should be abandoned, which should restore the volume to 1.0.
|
||||||
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
|
||||||
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(maxSdk = 25)
|
@Config(maxSdk = 25)
|
||||||
public void updateAudioFocus_readyToIdle_abandonsAudioFocus() {
|
public void updateAudioFocus_readyToIdle_abandonsAudioFocus() {
|
||||||
|
|
@ -318,6 +366,28 @@ public class AudioFocusManagerTest {
|
||||||
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
|
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void release_doesNotCallPlayerControlToRestoreVolume() {
|
||||||
|
AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
|
||||||
|
Shadows.shadowOf(audioManager)
|
||||||
|
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
audioFocusManager.setAudioAttributes(media);
|
||||||
|
|
||||||
|
assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
|
||||||
|
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
|
||||||
|
|
||||||
|
// Simulate transient ducking.
|
||||||
|
audioFocusManager
|
||||||
|
.getFocusListener()
|
||||||
|
.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
|
||||||
|
assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
|
||||||
|
|
||||||
|
audioFocusManager.release();
|
||||||
|
|
||||||
|
// PlaybackController.setVolumeMultiplier should not have been called to restore the volume.
|
||||||
|
assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onAudioFocusChange_withDuckEnabled_volumeReducedAndRestored() {
|
public void onAudioFocusChange_withDuckEnabled_volumeReducedAndRestored() {
|
||||||
// Ensure that the volume multiplier is adjusted when audio focus is lost to
|
// Ensure that the volume multiplier is adjusted when audio focus is lost to
|
||||||
|
|
@ -367,7 +437,7 @@ public class AudioFocusManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onAudioFocusChange_withTransientLost_sendsCommandWaitForCallback() {
|
public void onAudioFocusChange_withTransientLoss_sendsCommandWaitForCallback() {
|
||||||
// Ensure that the player is commanded to pause when audio focus is lost with
|
// Ensure that the player is commanded to pause when audio focus is lost with
|
||||||
// AUDIOFOCUS_LOSS_TRANSIENT.
|
// AUDIOFOCUS_LOSS_TRANSIENT.
|
||||||
AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
|
AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
|
||||||
|
|
@ -385,7 +455,7 @@ public class AudioFocusManagerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(maxSdk = 25)
|
@Config(maxSdk = 25)
|
||||||
public void onAudioFocusChange_withAudioFocusLost_sendsDoNotPlayAndAbandondsFocus() {
|
public void onAudioFocusChange_withFocusLoss_sendsDoNotPlayAndAbandonsFocus() {
|
||||||
// Ensure that AUDIOFOCUS_LOSS causes AudioFocusManager to pause playback and abandon audio
|
// Ensure that AUDIOFOCUS_LOSS causes AudioFocusManager to pause playback and abandon audio
|
||||||
// focus.
|
// focus.
|
||||||
AudioAttributes media =
|
AudioAttributes media =
|
||||||
|
|
@ -411,7 +481,7 @@ public class AudioFocusManagerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(minSdk = 26, maxSdk = TARGET_SDK)
|
@Config(minSdk = 26, maxSdk = TARGET_SDK)
|
||||||
public void onAudioFocusChange_withAudioFocusLost_sendsDoNotPlayAndAbandondsFocus_v26() {
|
public void onAudioFocusChange_withFocusLoss_sendsDoNotPlayAndAbandonsFocus_v26() {
|
||||||
// Ensure that AUDIOFOCUS_LOSS causes AudioFocusManager to pause playback and abandon audio
|
// Ensure that AUDIOFOCUS_LOSS causes AudioFocusManager to pause playback and abandon audio
|
||||||
// focus.
|
// focus.
|
||||||
AudioAttributes media =
|
AudioAttributes media =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue