Allow ExoPlayer to opt into volume device control, forbidden by default

PiperOrigin-RevId: 532136692
(cherry picked from commit 1c6b894e8880480918babaf0a6f6b2038faf2e81)
This commit is contained in:
jbibik 2023-05-15 17:30:50 +01:00 committed by Tofunmi Adigun-Hameed
parent 7f1c1185e7
commit 27becc028d
5 changed files with 269 additions and 47 deletions

View File

@ -47,9 +47,17 @@
objects that are dispatched by the dispatcher. objects that are dispatched by the dispatcher.
* Rename `ExoTrackSelection.blacklist` to `excludeTrack` and * Rename `ExoTrackSelection.blacklist` to `excludeTrack` and
`isBlacklisted` to `isTrackExcluded`. `isBlacklisted` to `isTrackExcluded`.
* Deprecate `Player.COMMAND_GET_MEDIA_ITEMS_METADATA` and * Add commands to Player:
`COMMAND_SET_MEDIA_ITEMS_METADATA`. Use `COMMAND_GET_METADATA` and * `COMMAND_GET_METADATA`
`COMMAND_SET_PLAYLIST_METADATA` instead. * `COMMAND_SET_PLAYLIST_METADATA`
* `COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS`
* `COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS`
* Add overloaded methods to Player which allow users to specify volume
flags:
* `void setDeviceVolume(int, int)`
* `void increaseDeviceVolume(int)`
* `void decreaseDeviceVolume(int)`
* `void setDeviceMuted(boolean, int)`
* Add `Buffer.isLastSample()` that denotes if `Buffer` contains flag * Add `Buffer.isLastSample()` that denotes if `Buffer` contains flag
`C.BUFFER_FLAG_LAST_SAMPLE`. `C.BUFFER_FLAG_LAST_SAMPLE`.
* Fix issue where last frame may not be rendered if the last sample with * Fix issue where last frame may not be rendered if the last sample with
@ -64,13 +72,16 @@
* Fix parsing of H.265 SPS in MPEG-TS files by re-using the parsing logic * Fix parsing of H.265 SPS in MPEG-TS files by re-using the parsing logic
already used by RTSP and MP4 extractors already used by RTSP and MP4 extractors
([#303](https://github.com/androidx/media/issues/303)). ([#303](https://github.com/androidx/media/issues/303)).
* ExoPlayer:
* Allow ExoPlayer to have control of device volume methods only if
explicitly opted in. Use
`ExoPlayer.Builder.setDeviceVolumeControlEnabled` to have access to:
* `getDeviceVolume()`
* `isDeviceMuted()`
* `setDeviceVolume(int)` and `setDeviceVolume(int, int)`
* `increaseDeviceVolume(int)` and `increaseDeviceVolume(int, int)`
* `decreaseDeviceVolume(int)` and `decreaseDeviceVolume(int, int)`
* Session: * Session:
* Deprecate 4 volume-controlling methods in `Player` and add overloaded
methods which allow users to specify volume flags:
* `void setDeviceVolume(int, int)`
* `void increaseDeviceVolume(int)`
* `void decreaseDeviceVolume(int)`
* `void setDeviceMuted(boolean, int)`
* Fix issue where `MediaController` doesn't update its available commands * Fix issue where `MediaController` doesn't update its available commands
when connected to a legacy `MediaSessionCompat` that updates its when connected to a legacy `MediaSessionCompat` that updates its
actions. actions.

View File

@ -479,6 +479,7 @@ public interface ExoPlayer extends Player {
@C.WakeMode /* package */ int wakeMode; @C.WakeMode /* package */ int wakeMode;
/* package */ boolean handleAudioBecomingNoisy; /* package */ boolean handleAudioBecomingNoisy;
/* package */ boolean skipSilenceEnabled; /* package */ boolean skipSilenceEnabled;
/* package */ boolean deviceVolumeControlEnabled;
@C.VideoScalingMode /* package */ int videoScalingMode; @C.VideoScalingMode /* package */ int videoScalingMode;
@C.VideoChangeFrameRateStrategy /* package */ int videoChangeFrameRateStrategy; @C.VideoChangeFrameRateStrategy /* package */ int videoChangeFrameRateStrategy;
/* package */ boolean useLazyPreparation; /* package */ boolean useLazyPreparation;
@ -918,6 +919,21 @@ public interface ExoPlayer extends Player {
return this; return this;
} }
/**
* Sets whether the player is allowed to set, increase, decrease or mute device volume.
*
* @param deviceVolumeControlEnabled Whether controlling device volume is enabled.
* @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called.
*/
@CanIgnoreReturnValue
@UnstableApi
public Builder setDeviceVolumeControlEnabled(boolean deviceVolumeControlEnabled) {
checkState(!buildCalled);
this.deviceVolumeControlEnabled = deviceVolumeControlEnabled;
return this;
}
/** /**
* Sets the {@link C.VideoScalingMode} that will be used by the player. * Sets the {@link C.VideoScalingMode} that will be used by the player.
* *

View File

@ -168,7 +168,7 @@ import java.util.concurrent.TimeoutException;
private final FrameMetadataListener frameMetadataListener; private final FrameMetadataListener frameMetadataListener;
private final AudioBecomingNoisyManager audioBecomingNoisyManager; private final AudioBecomingNoisyManager audioBecomingNoisyManager;
private final AudioFocusManager audioFocusManager; private final AudioFocusManager audioFocusManager;
private final StreamVolumeManager streamVolumeManager; @Nullable private final StreamVolumeManager streamVolumeManager;
private final WakeLockManager wakeLockManager; private final WakeLockManager wakeLockManager;
private final WifiLockManager wifiLockManager; private final WifiLockManager wifiLockManager;
private final long detachSurfaceTimeoutMs; private final long detachSurfaceTimeoutMs;
@ -228,6 +228,7 @@ import java.util.concurrent.TimeoutException;
private long maskingWindowPositionMs; private long maskingWindowPositionMs;
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
@SuppressWarnings("deprecation") // Control flow for old volume commands
public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer) { public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer) {
constructorFinished = new ConditionVariable(); constructorFinished = new ConditionVariable();
try { try {
@ -306,17 +307,17 @@ import java.util.concurrent.TimeoutException;
COMMAND_GET_TRACKS, COMMAND_GET_TRACKS,
COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_AUDIO_ATTRIBUTES,
COMMAND_GET_VOLUME, COMMAND_GET_VOLUME,
COMMAND_GET_DEVICE_VOLUME,
COMMAND_SET_VOLUME, COMMAND_SET_VOLUME,
COMMAND_SET_DEVICE_VOLUME,
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_SET_VIDEO_SURFACE, COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT, COMMAND_GET_TEXT,
COMMAND_RELEASE) COMMAND_RELEASE)
.addIf( .addIf(
COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported())
.addIf(COMMAND_GET_DEVICE_VOLUME, builder.deviceVolumeControlEnabled)
.addIf(COMMAND_SET_DEVICE_VOLUME, builder.deviceVolumeControlEnabled)
.addIf(COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, builder.deviceVolumeControlEnabled)
.addIf(COMMAND_ADJUST_DEVICE_VOLUME, builder.deviceVolumeControlEnabled)
.addIf(COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, builder.deviceVolumeControlEnabled)
.build(); .build();
availableCommands = availableCommands =
new Commands.Builder() new Commands.Builder()
@ -381,9 +382,13 @@ import java.util.concurrent.TimeoutException;
audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy); audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy);
audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener); audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener);
audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null); audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null);
if (builder.deviceVolumeControlEnabled) {
streamVolumeManager = streamVolumeManager =
new StreamVolumeManager(builder.context, eventHandler, componentListener); new StreamVolumeManager(builder.context, eventHandler, componentListener);
streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage));
} else {
streamVolumeManager = null;
}
wakeLockManager = new WakeLockManager(builder.context); wakeLockManager = new WakeLockManager(builder.context);
wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE); wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE);
wifiLockManager = new WifiLockManager(builder.context); wifiLockManager = new WifiLockManager(builder.context);
@ -999,7 +1004,9 @@ import java.util.concurrent.TimeoutException;
keepSessionIdAudioTrack = null; keepSessionIdAudioTrack = null;
} }
audioBecomingNoisyManager.setEnabled(false); audioBecomingNoisyManager.setEnabled(false);
if (streamVolumeManager != null) {
streamVolumeManager.release(); streamVolumeManager.release();
}
wakeLockManager.setStayAwake(false); wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false); wifiLockManager.setStayAwake(false);
audioFocusManager.release(); audioFocusManager.release();
@ -1421,7 +1428,10 @@ import java.util.concurrent.TimeoutException;
if (!Util.areEqual(this.audioAttributes, newAudioAttributes)) { if (!Util.areEqual(this.audioAttributes, newAudioAttributes)) {
this.audioAttributes = newAudioAttributes; this.audioAttributes = newAudioAttributes;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, newAudioAttributes); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, newAudioAttributes);
streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(newAudioAttributes.usage)); if (streamVolumeManager != null) {
streamVolumeManager.setStreamType(
Util.getStreamTypeForAudioUsage(newAudioAttributes.usage));
}
// Queue event only and flush after updating playWhenReady in case both events are triggered. // Queue event only and flush after updating playWhenReady in case both events are triggered.
listeners.queueEvent( listeners.queueEvent(
EVENT_AUDIO_ATTRIBUTES_CHANGED, EVENT_AUDIO_ATTRIBUTES_CHANGED,
@ -1703,13 +1713,21 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public int getDeviceVolume() { public int getDeviceVolume() {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
return streamVolumeManager.getVolume(); return streamVolumeManager.getVolume();
} else {
return 0;
}
} }
@Override @Override
public boolean isDeviceMuted() { public boolean isDeviceMuted() {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
return streamVolumeManager.isMuted(); return streamVolumeManager.isMuted();
} else {
return false;
}
} }
/** /**
@ -1719,14 +1737,18 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void setDeviceVolume(int volume) { public void setDeviceVolume(int volume) {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.setVolume(volume, C.VOLUME_FLAG_SHOW_UI); streamVolumeManager.setVolume(volume, C.VOLUME_FLAG_SHOW_UI);
} }
}
@Override @Override
public void setDeviceVolume(int volume, @C.VolumeFlags int flags) { public void setDeviceVolume(int volume, @C.VolumeFlags int flags) {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.setVolume(volume, flags); streamVolumeManager.setVolume(volume, flags);
} }
}
/** /**
* @deprecated Use {@link #increaseDeviceVolume(int)} instead. * @deprecated Use {@link #increaseDeviceVolume(int)} instead.
@ -1735,14 +1757,18 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void increaseDeviceVolume() { public void increaseDeviceVolume() {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.increaseVolume(C.VOLUME_FLAG_SHOW_UI); streamVolumeManager.increaseVolume(C.VOLUME_FLAG_SHOW_UI);
} }
}
@Override @Override
public void increaseDeviceVolume(@C.VolumeFlags int flags) { public void increaseDeviceVolume(@C.VolumeFlags int flags) {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.increaseVolume(flags); streamVolumeManager.increaseVolume(flags);
} }
}
/** /**
* @deprecated Use {@link #decreaseDeviceVolume(int)} instead. * @deprecated Use {@link #decreaseDeviceVolume(int)} instead.
@ -1751,14 +1777,18 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void decreaseDeviceVolume() { public void decreaseDeviceVolume() {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.decreaseVolume(C.VOLUME_FLAG_SHOW_UI); streamVolumeManager.decreaseVolume(C.VOLUME_FLAG_SHOW_UI);
} }
}
@Override @Override
public void decreaseDeviceVolume(@C.VolumeFlags int flags) { public void decreaseDeviceVolume(@C.VolumeFlags int flags) {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.decreaseVolume(flags); streamVolumeManager.decreaseVolume(flags);
} }
}
/** /**
* @deprecated Use {@link #setDeviceMuted(boolean, int)} instead. * @deprecated Use {@link #setDeviceMuted(boolean, int)} instead.
@ -1767,14 +1797,18 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void setDeviceMuted(boolean muted) { public void setDeviceMuted(boolean muted) {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.setMuted(muted, C.VOLUME_FLAG_SHOW_UI); streamVolumeManager.setMuted(muted, C.VOLUME_FLAG_SHOW_UI);
} }
}
@Override @Override
public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) { public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
verifyApplicationThread(); verifyApplicationThread();
if (streamVolumeManager != null) {
streamVolumeManager.setMuted(muted, flags); streamVolumeManager.setMuted(muted, flags);
} }
}
@Override @Override
public boolean isTunnelingEnabled() { public boolean isTunnelingEnabled() {
@ -2797,10 +2831,10 @@ import java.util.concurrent.TimeoutException;
} }
} }
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) { private static DeviceInfo createDeviceInfo(@Nullable StreamVolumeManager streamVolumeManager) {
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL) return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL)
.setMinVolume(streamVolumeManager.getMinVolume()) .setMinVolume(streamVolumeManager != null ? streamVolumeManager.getMinVolume() : 0)
.setMaxVolume(streamVolumeManager.getMaxVolume()) .setMaxVolume(streamVolumeManager != null ? streamVolumeManager.getMaxVolume() : 0)
.build(); .build();
} }

View File

@ -100,6 +100,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.AdPlaybackState; import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.AudioAttributes; import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
@ -9155,6 +9156,7 @@ public final class ExoPlayerTest {
player.release(); player.release();
} }
@SuppressWarnings("deprecation") // Checking old volume commands
@Test @Test
public void isCommandAvailable_isTrueForAvailableCommands() { public void isCommandAvailable_isTrueForAvailableCommands() {
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
@ -9184,10 +9186,7 @@ public final class ExoPlayerTest {
assertThat(player.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_GET_VOLUME)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_GET_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_VOLUME)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SET_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_GET_TEXT)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_GET_TEXT)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)).isTrue();
@ -9195,6 +9194,33 @@ public final class ExoPlayerTest {
assertThat(player.isCommandAvailable(COMMAND_RELEASE)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_RELEASE)).isTrue();
} }
@SuppressWarnings("deprecation") // Checking old volume commands
@Test
public void isCommandAvailable_withDeviceVolumeControlEnabled_isTrueForDeviceVolumeCommands() {
ExoPlayer player =
new TestExoPlayerBuilder(context).setDeviceVolumeControlEnabled(true).build();
assertThat(player.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)).isTrue();
}
@SuppressWarnings("deprecation") // Checking old volume commands
@Test
public void
isCommandAvailable_withoutDeviceVolumeControlEnabled_isFalseForDeviceVolumeCommands() {
ExoPlayer player =
new TestExoPlayerBuilder(context).setDeviceVolumeControlEnabled(false).build();
assertThat(player.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isFalse();
assertThat(player.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME)).isFalse();
assertThat(player.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)).isFalse();
assertThat(player.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)).isFalse();
assertThat(player.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)).isFalse();
}
@Test @Test
public void isCommandAvailable_duringAd_isFalseForSeekCommands() throws Exception { public void isCommandAvailable_duringAd_isFalseForSeekCommands() throws Exception {
AdPlaybackState adPlaybackState = AdPlaybackState adPlaybackState =
@ -9529,7 +9555,9 @@ public final class ExoPlayerTest {
Player.Commands defaultCommands = createWithDefaultCommands(); Player.Commands defaultCommands = createWithDefaultCommands();
Player.Commands commandsWithSeekToNextWindow = Player.Commands commandsWithSeekToNextWindow =
createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT); createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT);
Player.Commands emptyTimelineCommands = createWithDefaultCommands(/* isTimelineEmpty= */ true); Player.Commands emptyTimelineCommands =
createWithDefaultCommands(
/* isTimelineEmpty= */ true, /* allowDeviceVolumeControl= */ false);
Player.Listener mockListener = mock(Player.Listener.class); Player.Listener mockListener = mock(Player.Listener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
@ -9557,7 +9585,9 @@ public final class ExoPlayerTest {
Player.Commands defaultCommands = createWithDefaultCommands(); Player.Commands defaultCommands = createWithDefaultCommands();
Player.Commands commandsWithSeekToPreviousWindow = Player.Commands commandsWithSeekToPreviousWindow =
createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.Commands emptyTimelineCommands = createWithDefaultCommands(/* isTimelineEmpty= */ true); Player.Commands emptyTimelineCommands =
createWithDefaultCommands(
/* isTimelineEmpty= */ true, /* allowDeviceVolumeControl= */ false);
Player.Listener mockListener = mock(Player.Listener.class); Player.Listener mockListener = mock(Player.Listener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
@ -12323,7 +12353,9 @@ public final class ExoPlayerTest {
@Test @Test
public void releaseAfterVolumeChanges_triggerPendingDeviceVolumeEventsInListener() { public void releaseAfterVolumeChanges_triggerPendingDeviceVolumeEventsInListener() {
ExoPlayer player = ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build(); new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setDeviceVolumeControlEnabled(true)
.build();
Player.Listener listener = mock(Player.Listener.class); Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener); player.addListener(listener);
@ -12343,6 +12375,113 @@ public final class ExoPlayerTest {
verify(listener, atLeast(2)).onDeviceVolumeChanged(anyInt(), anyBoolean()); verify(listener, atLeast(2)).onDeviceVolumeChanged(anyInt(), anyBoolean());
} }
@Test
public void setDeviceMutedWithoutDeviceVolumeControl_noEffectDeviceRemainsUnmuted() {
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setDeviceVolumeControlEnabled(false)
.build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
player.setDeviceMuted(/* muted= */ true, /* flags= */ 0); // no volume control, no effect
boolean isActuallyMuted = player.isDeviceMuted();
player.release();
assertThat(isActuallyMuted).isFalse();
verify(listener, times(0)).onDeviceVolumeChanged(anyInt(), anyBoolean());
}
@Test
public void setDeviceMutedWithDeviceVolumeControl_deviceGetsMuted() {
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setDeviceVolumeControlEnabled(true)
.build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
player.setDeviceMuted(/* muted= */ true, /* flags= */ 0);
boolean isActuallyMuted = player.isDeviceMuted();
player.release();
assertThat(isActuallyMuted).isTrue();
verify(listener).onDeviceVolumeChanged(anyInt(), anyBoolean());
}
@Test
public void increaseDeviceVolumeWithoutDeviceVolumeControl_deviceVolumeUnchanged() {
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setDeviceVolumeControlEnabled(false)
.build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
player.increaseDeviceVolume(/* flags= */ 0); // no volume control, no effect
player.release();
verify(listener, times(0)).onDeviceVolumeChanged(anyInt(), anyBoolean());
}
@Test
public void decreaseDeviceVolumeWithoutDeviceVolumeControl_deviceVolumeUnchanged() {
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setDeviceVolumeControlEnabled(false)
.build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
player.decreaseDeviceVolume(/* flags= */ 0); // no volume control, no effect
player.release();
verify(listener, times(0)).onDeviceVolumeChanged(anyInt(), anyBoolean());
}
@Test
public void getDeviceVolumeWithoutDeviceVolumeControl_returnsZero() {
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setDeviceVolumeControlEnabled(false)
.build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
int initialDeviceVolume = player.getDeviceVolume();
player.setDeviceVolume(10, /* flags= */ 0);
int setDeviceVolumeAt10 = player.getDeviceVolume();
player.increaseDeviceVolume(/* flags= */ 0);
player.increaseDeviceVolume(/* flags= */ 0);
int setDeviceVolumeAt12 = player.getDeviceVolume();
player.decreaseDeviceVolume(/* flags= */ 0);
int setDeviceVolumeAt11 = player.getDeviceVolume();
player.release();
assertThat(initialDeviceVolume).isEqualTo(0);
assertThat(setDeviceVolumeAt10).isEqualTo(0);
assertThat(setDeviceVolumeAt12).isEqualTo(0);
assertThat(setDeviceVolumeAt11).isEqualTo(0);
verify(listener, times(0)).onDeviceVolumeChanged(anyInt(), anyBoolean());
}
@Test
public void getDeviceInfoWithoutDeviceVolumeControl_returnsZeroForMinMaxVolume() {
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setDeviceVolumeControlEnabled(false)
.build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
DeviceInfo deviceInfo = player.getDeviceInfo();
int minVolume = deviceInfo.minVolume;
int maxVolume = deviceInfo.maxVolume;
assertThat(minVolume).isEqualTo(0);
assertThat(maxVolume).isEqualTo(0);
}
@Test @Test
public void loadControlBackBuffer_withInsufficientMemoryLimits_stillContinuesPlayback() public void loadControlBackBuffer_withInsufficientMemoryLimits_stillContinuesPlayback()
throws Exception { throws Exception {
@ -12482,8 +12621,11 @@ public final class ExoPlayerTest {
return false; return false;
} }
@SuppressWarnings("deprecation") // Control flow for the old volume commands
private static Player.Commands createWithDefaultCommands( private static Player.Commands createWithDefaultCommands(
boolean isTimelineEmpty, @Player.Command int... additionalCommands) { boolean isTimelineEmpty,
boolean allowDeviceVolumeControl,
@Player.Command int... additionalCommands) {
Player.Commands.Builder builder = new Player.Commands.Builder(); Player.Commands.Builder builder = new Player.Commands.Builder();
builder.addAll( builder.addAll(
COMMAND_PLAY_PAUSE, COMMAND_PLAY_PAUSE,
@ -12502,12 +12644,7 @@ public final class ExoPlayerTest {
COMMAND_SET_MEDIA_ITEM, COMMAND_SET_MEDIA_ITEM,
COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_AUDIO_ATTRIBUTES,
COMMAND_GET_VOLUME, COMMAND_GET_VOLUME,
COMMAND_GET_DEVICE_VOLUME,
COMMAND_SET_VOLUME, COMMAND_SET_VOLUME,
COMMAND_SET_DEVICE_VOLUME,
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_SET_VIDEO_SURFACE, COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT, COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS, COMMAND_SET_TRACK_SELECTION_PARAMETERS,
@ -12516,13 +12653,22 @@ public final class ExoPlayerTest {
if (!isTimelineEmpty) { if (!isTimelineEmpty) {
builder.add(COMMAND_SEEK_TO_PREVIOUS); builder.add(COMMAND_SEEK_TO_PREVIOUS);
} }
if (allowDeviceVolumeControl) {
builder.addAll(
COMMAND_GET_DEVICE_VOLUME,
COMMAND_SET_DEVICE_VOLUME,
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS);
}
builder.addAll(additionalCommands); builder.addAll(additionalCommands);
return builder.build(); return builder.build();
} }
private static Player.Commands createWithDefaultCommands( private static Player.Commands createWithDefaultCommands(
@Player.Command int... additionalCommands) { @Player.Command int... additionalCommands) {
return createWithDefaultCommands(/* isTimelineEmpty= */ false, additionalCommands); return createWithDefaultCommands(
/* isTimelineEmpty= */ false, /* allowDeviceVolumeControl= */ false, additionalCommands);
} }
// Internal classes. // Internal classes.

View File

@ -54,6 +54,7 @@ public class TestExoPlayerBuilder {
private @MonotonicNonNull Looper looper; private @MonotonicNonNull Looper looper;
private long seekBackIncrementMs; private long seekBackIncrementMs;
private long seekForwardIncrementMs; private long seekForwardIncrementMs;
private boolean deviceVolumeControlEnabled;
public TestExoPlayerBuilder(Context context) { public TestExoPlayerBuilder(Context context) {
this.context = context; this.context = context;
@ -67,6 +68,7 @@ public class TestExoPlayerBuilder {
} }
seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS; seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS;
seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS; seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS;
deviceVolumeControlEnabled = false;
} }
/** /**
@ -282,6 +284,18 @@ public class TestExoPlayerBuilder {
return this; return this;
} }
/**
* Sets the variable controlling player's ability to get/set device volume.
*
* @param deviceVolumeControlEnabled Whether the player can get/set device volume.
* @return This builder.
*/
@CanIgnoreReturnValue
public TestExoPlayerBuilder setDeviceVolumeControlEnabled(boolean deviceVolumeControlEnabled) {
this.deviceVolumeControlEnabled = deviceVolumeControlEnabled;
return this;
}
/** Returns the seek forward increment used by the player. */ /** Returns the seek forward increment used by the player. */
public long getSeekForwardIncrementMs() { public long getSeekForwardIncrementMs() {
return seekForwardIncrementMs; return seekForwardIncrementMs;
@ -322,7 +336,8 @@ public class TestExoPlayerBuilder {
.setUseLazyPreparation(useLazyPreparation) .setUseLazyPreparation(useLazyPreparation)
.setLooper(looper) .setLooper(looper)
.setSeekBackIncrementMs(seekBackIncrementMs) .setSeekBackIncrementMs(seekBackIncrementMs)
.setSeekForwardIncrementMs(seekForwardIncrementMs); .setSeekForwardIncrementMs(seekForwardIncrementMs)
.setDeviceVolumeControlEnabled(deviceVolumeControlEnabled);
if (mediaSourceFactory != null) { if (mediaSourceFactory != null) {
builder.setMediaSourceFactory(mediaSourceFactory); builder.setMediaSourceFactory(mediaSourceFactory);
} }