Commit dd462e8c by christosts

Filter what PlaybackStateCompat actions are advertised

PlayerWrapper advertises PlaybackStateCompat actions to the legacy
MediaSession based on the player's available commands.

PiperOrigin-RevId: 502559162
(cherry picked from commit 39f4a17a)
parent 79fd80f8
......@@ -917,33 +917,11 @@ import java.util.List;
int state =
MediaUtils.convertToPlaybackStateCompatState(
playerError, getPlaybackState(), getPlayWhenReady());
long allActions =
PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_REWIND
| PlaybackStateCompat.ACTION_FAST_FORWARD
| PlaybackStateCompat.ACTION_SET_RATING
| PlaybackStateCompat.ACTION_SEEK_TO
| PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
| PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
| PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
| PlaybackStateCompat.ACTION_PLAY_FROM_URI
| PlaybackStateCompat.ACTION_PREPARE
| PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID
| PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH
| PlaybackStateCompat.ACTION_PREPARE_FROM_URI
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE
| PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
| PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED;
if (getAvailableCommands().contains(COMMAND_SEEK_TO_PREVIOUS)
|| getAvailableCommands().contains(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)) {
allActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
}
if (getAvailableCommands().contains(COMMAND_SEEK_TO_NEXT)
|| getAvailableCommands().contains(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) {
allActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
// Always advertise ACTION_SET_RATING.
long actions = PlaybackStateCompat.ACTION_SET_RATING;
Commands availableCommands = getAvailableCommands();
for (int i = 0; i < availableCommands.size(); i++) {
actions |= convertCommandToPlaybackStateActions(availableCommands.get(i));
}
long queueItemId =
isCommandAvailable(COMMAND_GET_TIMELINE)
......@@ -964,7 +942,7 @@ import java.util.List;
PlaybackStateCompat.Builder builder =
new PlaybackStateCompat.Builder()
.setState(state, compatPosition, sessionPlaybackSpeed, SystemClock.elapsedRealtime())
.setActions(allActions)
.setActions(actions)
.setActiveQueueItemId(queueItemId)
.setBufferedPosition(compatBufferedPosition)
.setExtras(extras);
......@@ -1127,4 +1105,67 @@ import java.util.List;
private void verifyApplicationThread() {
checkState(Looper.myLooper() == getApplicationLooper());
}
@SuppressWarnings("deprecation") // Uses deprecated PlaybackStateCompat actions.
private static long convertCommandToPlaybackStateActions(@Command int command) {
switch (command) {
case Player.COMMAND_PLAY_PAUSE:
return PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PLAY_PAUSE;
case Player.COMMAND_PREPARE:
return PlaybackStateCompat.ACTION_PREPARE;
case Player.COMMAND_SEEK_BACK:
return PlaybackStateCompat.ACTION_REWIND;
case Player.COMMAND_SEEK_FORWARD:
return PlaybackStateCompat.ACTION_FAST_FORWARD;
case Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM:
return PlaybackStateCompat.ACTION_SEEK_TO;
case Player.COMMAND_SEEK_TO_MEDIA_ITEM:
return PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
case Player.COMMAND_SEEK_TO_NEXT:
case Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM:
return PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
case Player.COMMAND_SEEK_TO_PREVIOUS:
case Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM:
return PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
case Player.COMMAND_SET_MEDIA_ITEM:
return PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
| PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
| PlaybackStateCompat.ACTION_PLAY_FROM_URI
| PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID
| PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH
| PlaybackStateCompat.ACTION_PREPARE_FROM_URI;
case Player.COMMAND_SET_REPEAT_MODE:
return PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
case Player.COMMAND_SET_SPEED_AND_PITCH:
return PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED;
case Player.COMMAND_SET_SHUFFLE_MODE:
return PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
| PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE_ENABLED;
case Player.COMMAND_STOP:
return PlaybackStateCompat.ACTION_STOP;
case Player.COMMAND_ADJUST_DEVICE_VOLUME:
case Player.COMMAND_CHANGE_MEDIA_ITEMS:
// TODO(b/227346735): Handle this through
// MediaSessionCompat.setFlags(FLAG_HANDLES_QUEUE_COMMANDS)
case Player.COMMAND_GET_AUDIO_ATTRIBUTES:
case Player.COMMAND_GET_CURRENT_MEDIA_ITEM:
case Player.COMMAND_GET_DEVICE_VOLUME:
case Player.COMMAND_GET_MEDIA_ITEMS_METADATA:
case Player.COMMAND_GET_TEXT:
case Player.COMMAND_GET_TIMELINE:
case Player.COMMAND_GET_TRACKS:
case Player.COMMAND_GET_VOLUME:
case Player.COMMAND_INVALID:
case Player.COMMAND_SEEK_TO_DEFAULT_POSITION:
case Player.COMMAND_SET_DEVICE_VOLUME:
case Player.COMMAND_SET_MEDIA_ITEMS_METADATA:
case Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS:
case Player.COMMAND_SET_VIDEO_SURFACE:
case Player.COMMAND_SET_VOLUME:
default:
return 0;
}
}
}
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.session;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ForwardingPlayer;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Consumer;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.test.session.common.HandlerThreadTestRule;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests that {@link MediaControllerCompat} receives the expected {@link
* PlaybackStateCompat.Actions} when connected to a {@link MediaSession}.
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest {
private static final String TAG = "MCCPSActionWithMS3";
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
@Test
public void playerWithCommandPlayPause_actionsPlayAndPauseAndPlayPauseAdvertised()
throws Exception {
Player player =
createPlayerWithAvailableCommand(createDefaultPlayer(), Player.COMMAND_PLAY_PAUSE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
long actions =
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions();
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_PAUSE).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PLAY).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PAUSE).isNotEqualTo(0);
CountDownLatch latch = new CountDownLatch(2);
List<Boolean> receivedPlayWhenReady = new ArrayList<>();
Player.Listener listener =
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
receivedPlayWhenReady.add(playWhenReady);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().play();
controllerCompat.getTransportControls().pause();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedPlayWhenReady).containsExactly(true, false).inOrder();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandPlayPause_actionsPlayAndPauseAndPlayPauseNotAdvertised()
throws Exception {
Player player =
createPlayerWithExcludedCommand(createDefaultPlayer(), Player.COMMAND_PLAY_PAUSE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
long actions =
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions();
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_PAUSE).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PLAY).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PAUSE).isEqualTo(0);
AtomicInteger playWhenReadyCalled = new AtomicInteger();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
playWhenReadyCalled.incrementAndGet();
}
@Override
public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_ENDED) {
latch.countDown();
}
}
};
player.addListener(listener);
// play() & pause() should be a no-op
controllerCompat.getTransportControls().play();
controllerCompat.getTransportControls().pause();
// prepare() should transition the player to STATE_ENDED
controllerCompat.getTransportControls().prepare();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playWhenReadyCalled.get()).isEqualTo(0);
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandPrepare_actionPrepareAdvertised() throws Exception {
Player player = createPlayerWithAvailableCommand(createDefaultPlayer(), Player.COMMAND_PREPARE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_PREPARE)
.isNotEqualTo(0);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_ENDED) {
latch.countDown();
}
}
};
player.addListener(listener);
// prepare() should transition the player to STATE_ENDED.
controllerCompat.getTransportControls().prepare();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandPrepare_actionPrepareNotAdvertised() throws Exception {
Player player = createPlayerWithExcludedCommand(createDefaultPlayer(), Player.COMMAND_PREPARE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_PREPARE)
.isEqualTo(0);
AtomicInteger playbackStateChanges = new AtomicInteger();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPlaybackStateChanged(@Player.State int playbackState) {
playbackStateChanges.incrementAndGet();
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// prepare() should be no-op
controllerCompat.getTransportControls().prepare();
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateChanges.get()).isEqualTo(0);
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSeekBack_actionRewindAdvertised() throws Exception {
Player player =
createPlayerWithAvailableCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItem(
MediaItem.fromUri("asset://media/wav/sample.wav"),
/* startPositionMs= */ 500);
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_BACK);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_REWIND)
.isNotEqualTo(0);
AtomicInteger discontinuityReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPositionDiscontinuity(
Player.PositionInfo oldPosition,
Player.PositionInfo newPosition,
@Player.DiscontinuityReason int reason) {
discontinuityReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().rewind();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(discontinuityReason.get()).isEqualTo(Player.DISCONTINUITY_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSeekBack_actionRewindNotAdvertised() throws Exception {
Player player =
createPlayerWithExcludedCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItem(
MediaItem.fromUri("asset://media/wav/sample.wav"),
/* startPositionMs= */ 500);
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_BACK);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_REWIND)
.isEqualTo(0);
AtomicBoolean receivedOnPositionDiscontinuity = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPositionDiscontinuity(
Player.PositionInfo oldPosition,
Player.PositionInfo newPosition,
@Player.DiscontinuityReason int reason) {
receivedOnPositionDiscontinuity.set(true);
}
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
latch.countDown();
}
};
player.addListener(listener);
// rewind() should be no-op.
controllerCompat.getTransportControls().rewind();
controllerCompat.getTransportControls().play();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedOnPositionDiscontinuity.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSeekForward_actionFastForwardAdvertised() throws Exception {
Player player =
createPlayerWithAvailableCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItem(MediaItem.fromUri("asset://media/wav/sample.wav"));
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_FORWARD);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_FAST_FORWARD)
.isNotEqualTo(0);
AtomicInteger discontinuityReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPositionDiscontinuity(
Player.PositionInfo oldPosition,
Player.PositionInfo newPosition,
@Player.DiscontinuityReason int reason) {
discontinuityReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().fastForward();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(discontinuityReason.get()).isEqualTo(Player.DISCONTINUITY_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSeekForward_actionFastForwardNotAdvertised() throws Exception {
Player player =
createPlayerWithExcludedCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItem(MediaItem.fromUri("asset://media/wav/sample.wav"));
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_FORWARD);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_FAST_FORWARD)
.isEqualTo(0);
AtomicBoolean receivedOnPositionDiscontinuity = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPositionDiscontinuity(
Player.PositionInfo oldPosition,
Player.PositionInfo newPosition,
@Player.DiscontinuityReason int reason) {
receivedOnPositionDiscontinuity.set(true);
}
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
latch.countDown();
}
};
player.addListener(listener);
// fastForward() should be no-op
controllerCompat.getTransportControls().fastForward();
controllerCompat.getTransportControls().play();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedOnPositionDiscontinuity.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSeekInCurrentMediaItem_actionSeekToAdvertised() throws Exception {
Player player =
createPlayerWithAvailableCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItem(MediaItem.fromUri("asset://media/wav/sample.wav"));
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SEEK_TO)
.isNotEqualTo(0);
AtomicInteger discontinuityReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPositionDiscontinuity(
Player.PositionInfo oldPosition,
Player.PositionInfo newPosition,
@Player.DiscontinuityReason int reason) {
discontinuityReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().seekTo(100);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(discontinuityReason.get()).isEqualTo(Player.DISCONTINUITY_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSeekInCurrentMediaItem_actionSeekToNotAdvertised()
throws Exception {
Player player =
createPlayerWithExcludedCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItem(MediaItem.fromUri("asset://media/wav/sample.wav"));
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SEEK_TO)
.isEqualTo(0);
AtomicBoolean receiovedOnPositionDiscontinuity = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onPositionDiscontinuity(
Player.PositionInfo oldPosition,
Player.PositionInfo newPosition,
@Player.DiscontinuityReason int reason) {
receiovedOnPositionDiscontinuity.set(true);
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// seekTo() should be no-op.
controllerCompat.getTransportControls().seekTo(100);
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receiovedOnPositionDiscontinuity.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSeekToMediaItem_actionSkipToQueueItemAdvertised() throws Exception {
Player player =
createPlayerWithAvailableCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")));
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_TO_MEDIA_ITEM);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM)
.isNotEqualTo(0);
AtomicInteger mediaItemTransitionReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
mediaItemTransitionReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().skipToNext();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(mediaItemTransitionReason.get()).isEqualTo(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSeekToMediaItem_actionSkipToQueueItemNotAdvertised()
throws Exception {
Player player =
createPlayerWithExcludedCommand(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")));
createdPlayer.prepare();
}),
Player.COMMAND_SEEK_TO_MEDIA_ITEM);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM)
.isEqualTo(0);
AtomicBoolean receivedOnMediaItemTransition = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
receivedOnMediaItemTransition.set(true);
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// skipToQueueItem() should be no-op.
controllerCompat.getTransportControls().skipToQueueItem(1);
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedOnMediaItemTransition.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void
playerWithCommandSeekToNext_withoutCommandSeeKToNextMediaItem_actionSkipToNextAdvertised()
throws Exception {
Player player =
createPlayerWithCommands(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")));
createdPlayer.prepare();
}),
/* availableCommands= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_NEXT)
.build(),
/* excludedCommand= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
.build());
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
.isNotEqualTo(0);
AtomicInteger mediaItemTransitionReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
mediaItemTransitionReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().skipToNext();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(mediaItemTransitionReason.get()).isEqualTo(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void
playerWithCommandSeekToNextMediaItem_withoutCommandSeekToNext_actionSkipToNextAdvertised()
throws Exception {
Player player =
createPlayerWithCommands(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")));
createdPlayer.prepare();
}),
/* availableCommands= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
.build(),
/* excludedCommands= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_NEXT)
.build());
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
.isNotEqualTo(0);
AtomicInteger mediaItemTransitionReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
mediaItemTransitionReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().skipToNext();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(mediaItemTransitionReason.get()).isEqualTo(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void
playerWithoutCommandSeekToNextAndCommandSeekToNextMediaItem_actionSkipToNextNotAdvertised()
throws Exception {
Player player =
createPlayerWithCommands(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")));
createdPlayer.prepare();
}),
/* availableCommands= */ Player.Commands.EMPTY,
/* excludedCommands= */ new Player.Commands.Builder()
.addAll(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, Player.COMMAND_SEEK_TO_NEXT)
.build());
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
.isEqualTo(0);
AtomicBoolean receivedOnMediaItemTransition = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
receivedOnMediaItemTransition.set(true);
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// skipToNext() should be no-op.
controllerCompat.getTransportControls().skipToNext();
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedOnMediaItemTransition.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void
playerWithCommandSeekToPrevious_withoutCommandSeekToPreviousMediaItem_actionSkipToPreviousAdvertised()
throws Exception {
Player player =
createPlayerWithCommands(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")),
/* startIndex= */ 1,
/* startPositionMs= */ C.TIME_UNSET);
createdPlayer.prepare();
}),
/* availableCommands= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_PREVIOUS)
.build(),
/* excludedCommands= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
.build());
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
.isNotEqualTo(0);
AtomicInteger mediaItemTransitionReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
mediaItemTransitionReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().skipToPrevious();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(mediaItemTransitionReason.get()).isEqualTo(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void
playerWithCommandSeekToPreviousMediaItem_withoutCommandSeekToPrevious_actionSkipToPreviousAdvertised()
throws Exception {
Player player =
createPlayerWithCommands(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")),
/* startIndex= */ 1,
/* startPositionMs= */ C.TIME_UNSET);
createdPlayer.prepare();
}),
/* availableCommands= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
.build(),
/* excludedCommands= */ new Player.Commands.Builder()
.add(Player.COMMAND_SEEK_TO_PREVIOUS)
.build());
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
.isNotEqualTo(0);
AtomicInteger mediaItemTransitionReason = new AtomicInteger(-1);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
mediaItemTransitionReason.set(reason);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().skipToPrevious();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(mediaItemTransitionReason.get()).isEqualTo(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
mediaSession.release();
releasePlayer(player);
}
@Test
public void
playerWithoutCommandSeekToPreviousAndCommandSeekToPreviousMediaItem_actionSkipToPreviousNotAdvertised()
throws Exception {
Player player =
createPlayerWithCommands(
createPlayer(
/* onPostCreationTask= */ createdPlayer -> {
createdPlayer.setMediaItems(
ImmutableList.of(
MediaItem.fromUri("asset://media/wav/sample.wav"),
MediaItem.fromUri("asset://media/wav/sample_rf64.wav")),
/* startIndex= */ 1,
/* startPositionMs= */ C.TIME_UNSET);
createdPlayer.prepare();
}),
/* availableCommands= */ Player.Commands.EMPTY,
/* excludedCommands= */ new Player.Commands.Builder()
.addAll(Player.COMMAND_SEEK_TO_PREVIOUS, Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
.build());
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
.isEqualTo(0);
AtomicBoolean receivedOnMediaItemTransition = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
receivedOnMediaItemTransition.set(true);
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// skipToPrevious() should be no-op.
controllerCompat.getTransportControls().skipToPrevious();
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedOnMediaItemTransition.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSetMediaItem_actionsPlayFromXAndPrepareFromXAdvertised()
throws Exception {
Player player =
createPlayerWithAvailableCommand(createDefaultPlayer(), Player.COMMAND_SET_MEDIA_ITEM);
MediaSession mediaSession =
createMediaSession(
player,
new MediaSession.Callback() {
@Override
public ListenableFuture<List<MediaItem>> onAddMediaItems(
MediaSession mediaSession,
MediaSession.ControllerInfo controller,
List<MediaItem> mediaItems) {
return Futures.immediateFuture(
ImmutableList.of(MediaItem.fromUri("asset://media/wav/sample.wav")));
}
});
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
long actions =
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions();
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_FROM_URI).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PREPARE_FROM_URI).isNotEqualTo(0);
ConditionVariable conditionVariable = new ConditionVariable();
Player.Listener listener =
new Player.Listener() {
@Override
public void onTimelineChanged(Timeline timeline, int reason) {
conditionVariable.open();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().playFromMediaId(/* mediaId= */ "mediaId", Bundle.EMPTY);
assertThat(conditionVariable.block(TIMEOUT_MS)).isTrue();
conditionVariable.close();
controllerCompat
.getTransportControls()
.playFromUri(Uri.parse("https://example.invalid"), Bundle.EMPTY);
assertThat(conditionVariable.block(TIMEOUT_MS)).isTrue();
conditionVariable.close();
controllerCompat.getTransportControls().playFromSearch(/* query= */ "search", Bundle.EMPTY);
assertThat(conditionVariable.block(TIMEOUT_MS)).isTrue();
conditionVariable.close();
controllerCompat
.getTransportControls()
.prepareFromMediaId(/* mediaId= */ "mediaId", Bundle.EMPTY);
assertThat(conditionVariable.block(TIMEOUT_MS)).isTrue();
conditionVariable.close();
controllerCompat
.getTransportControls()
.prepareFromUri(Uri.parse("https://example.invalid"), Bundle.EMPTY);
assertThat(conditionVariable.block(TIMEOUT_MS)).isTrue();
conditionVariable.close();
controllerCompat.getTransportControls().prepareFromSearch(/* query= */ "search", Bundle.EMPTY);
assertThat(conditionVariable.block(TIMEOUT_MS)).isTrue();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSetMediaItem_actionsPlayFromXAndPrepareFromXNotAdvertised()
throws Exception {
Player player =
createPlayerWithExcludedCommand(createDefaultPlayer(), Player.COMMAND_SET_MEDIA_ITEM);
MediaSession mediaSession =
createMediaSession(
player,
new MediaSession.Callback() {
@Override
public ListenableFuture<List<MediaItem>> onAddMediaItems(
MediaSession mediaSession,
MediaSession.ControllerInfo controller,
List<MediaItem> mediaItems) {
return Futures.immediateFuture(
ImmutableList.of(MediaItem.fromUri("asset://media/wav/sample.wav")));
}
});
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
long actions =
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions();
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PLAY_FROM_URI).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_PREPARE_FROM_URI).isEqualTo(0);
AtomicBoolean receivedOnTimelineChanged = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onTimelineChanged(Timeline timeline, int reason) {
receivedOnTimelineChanged.set(true);
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// prepareFrom and playFrom methods should be no-op.
MediaControllerCompat.TransportControls transportControls =
controllerCompat.getTransportControls();
transportControls.prepareFromMediaId(/* mediaId= */ "mediaId", Bundle.EMPTY);
transportControls.prepareFromSearch(/* query= */ "search", Bundle.EMPTY);
transportControls.prepareFromUri(Uri.parse("https://example.invalid"), Bundle.EMPTY);
transportControls.playFromMediaId(/* mediaId= */ "mediaId", Bundle.EMPTY);
transportControls.playFromSearch(/* query= */ "search", Bundle.EMPTY);
transportControls.playFromUri(Uri.parse("https://example.invalid"), Bundle.EMPTY);
transportControls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedOnTimelineChanged.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSetRepeatMode_actionSetRepeatModeAdvertised() throws Exception {
Player player =
createPlayerWithAvailableCommand(createDefaultPlayer(), Player.COMMAND_SET_REPEAT_MODE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SET_REPEAT_MODE)
.isNotEqualTo(0);
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onRepeatModeChanged(int repeatMode) {
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSetRepeatMode_actionSetRepeatModeNotAdvertised()
throws Exception {
Player player =
createPlayerWithExcludedCommand(createDefaultPlayer(), Player.COMMAND_SET_REPEAT_MODE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SET_REPEAT_MODE)
.isEqualTo(0);
AtomicBoolean repeatModeChanged = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
Player.Listener listener =
new Player.Listener() {
@Override
public void onRepeatModeChanged(int repeatMode) {
repeatModeChanged.set(true);
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// setRepeatMode() should be no-op
controllerCompat.getTransportControls().setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ALL);
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(repeatModeChanged.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSetSpeedAndPitch_actionSetPlaybackSpeedAdvertised()
throws Exception {
Player player =
createPlayerWithAvailableCommand(createDefaultPlayer(), Player.COMMAND_SET_SPEED_AND_PITCH);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED)
.isNotEqualTo(0);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<PlaybackParameters> playbackParametersRef = new AtomicReference<>();
Player.Listener listener =
new Player.Listener() {
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
playbackParametersRef.set(playbackParameters);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().setPlaybackSpeed(0.5f);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackParametersRef.get().speed).isEqualTo(0.5f);
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSetSpeedAndPitch_actionSetPlaybackSpeedNotAdvertised()
throws Exception {
Player player =
createPlayerWithExcludedCommand(createDefaultPlayer(), Player.COMMAND_SET_SPEED_AND_PITCH);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
assertThat(
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions()
& PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED)
.isEqualTo(0);
CountDownLatch latch = new CountDownLatch(1);
AtomicBoolean receivedPlaybackParameters = new AtomicBoolean();
Player.Listener listener =
new Player.Listener() {
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
receivedPlaybackParameters.set(true);
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
latch.countDown();
}
};
player.addListener(listener);
// setPlaybackSpeed() should be no-op.
controllerCompat.getTransportControls().setPlaybackSpeed(0.5f);
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedPlaybackParameters.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithCommandSetShuffleMode_actionSetShuffleModeAdvertised() throws Exception {
Player player =
createPlayerWithAvailableCommand(createDefaultPlayer(), Player.COMMAND_SET_SHUFFLE_MODE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
long actions =
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions();
assertThat(actions & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE).isNotEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE_ENABLED).isNotEqualTo(0);
CountDownLatch latch = new CountDownLatch(1);
AtomicBoolean receivedShuffleModeEnabled = new AtomicBoolean();
Player.Listener listener =
new Player.Listener() {
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
receivedShuffleModeEnabled.set(shuffleModeEnabled);
latch.countDown();
}
};
player.addListener(listener);
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedShuffleModeEnabled.get()).isTrue();
mediaSession.release();
releasePlayer(player);
}
@Test
public void playerWithoutCommandSetShuffleMode_actionSetShuffleModeNotAdvertised()
throws Exception {
Player player =
createPlayerWithExcludedCommand(createDefaultPlayer(), Player.COMMAND_SET_SHUFFLE_MODE);
MediaSession mediaSession = createMediaSession(player);
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
long actions =
getFirstPlaybackState(controllerCompat, threadTestRule.getHandler()).getActions();
assertThat(actions & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE).isEqualTo(0);
assertThat(actions & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE_ENABLED).isEqualTo(0);
CountDownLatch latch = new CountDownLatch(1);
AtomicBoolean receivedShuffleModeEnabled = new AtomicBoolean();
Player.Listener listener =
new Player.Listener() {
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
receivedShuffleModeEnabled.set(shuffleModeEnabled);
}
@Override
public void onRepeatModeChanged(int repeatMode) {
latch.countDown();
}
};
player.addListener(listener);
// setShuffleMode() should be no-op
controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
controllerCompat.getTransportControls().setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ALL);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedShuffleModeEnabled.get()).isFalse();
mediaSession.release();
releasePlayer(player);
}
private PlaybackStateCompat getFirstPlaybackState(
MediaControllerCompat mediaControllerCompat, Handler handler) throws InterruptedException {
LinkedBlockingDeque<PlaybackStateCompat> playbackStateCompats = new LinkedBlockingDeque<>();
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
playbackStateCompats.add(state);
}
};
mediaControllerCompat.registerCallback(callback, handler);
PlaybackStateCompat playbackStateCompat = playbackStateCompats.take();
mediaControllerCompat.unregisterCallback(callback);
return playbackStateCompat;
}
/**
* Creates a default {@link ExoPlayer} instance on the main thread. Use {@link
* #releasePlayer(Player)} to release the returned instance on the main thread.
*/
private static Player createDefaultPlayer() {
return createPlayer(/* onPostCreationTask= */ player -> {});
}
/**
* Creates a player on the main thread. After the player is created, {@code onPostCreationTask} is
* called from the main thread to set any initial state on the player.
*/
private static Player createPlayer(Consumer<Player> onPostCreationTask) {
AtomicReference<Player> playerRef = new AtomicReference<>();
getInstrumentation()
.runOnMainSync(
() -> {
ExoPlayer exoPlayer =
new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build();
onPostCreationTask.accept(exoPlayer);
playerRef.set(exoPlayer);
});
return playerRef.get();
}
private static MediaSession createMediaSession(Player player) {
return createMediaSession(player, null);
}
private static MediaSession createMediaSession(
Player player, @Nullable MediaSession.Callback callback) {
MediaSession.Builder session =
new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player);
if (callback != null) {
session.setCallback(callback);
}
return session.build();
}
private static MediaControllerCompat createMediaControllerCompat(MediaSession mediaSession) {
return new MediaControllerCompat(
ApplicationProvider.getApplicationContext(),
mediaSession.getSessionCompat().getSessionToken());
}
/** Releases the {@code player} on the main thread. */
private static void releasePlayer(Player player) {
getInstrumentation().runOnMainSync(player::release);
}
/**
* Returns an {@link Player} where {@code availableCommand} is always included in the {@linkplain
* Player#getAvailableCommands() available commands}.
*/
private static Player createPlayerWithAvailableCommand(
Player player, @Player.Command int availableCommand) {
return createPlayerWithCommands(
player, new Player.Commands.Builder().add(availableCommand).build(), Player.Commands.EMPTY);
}
/**
* Returns a {@link Player} where {@code excludedCommand} is always excluded from the {@linkplain
* Player#getAvailableCommands() available commands}.
*/
private static Player createPlayerWithExcludedCommand(
Player player, @Player.Command int excludedCommand) {
return createPlayerWithCommands(
player, Player.Commands.EMPTY, new Player.Commands.Builder().add(excludedCommand).build());
}
/**
* Returns an {@link Player} where {@code availableCommands} are always included and {@code
* excludedCommands} are always excluded from the {@linkplain Player#getAvailableCommands()
* available commands}.
*/
private static Player createPlayerWithCommands(
Player player, Player.Commands availableCommands, Player.Commands excludedCommands) {
return new ForwardingPlayer(player) {
@Override
public Commands getAvailableCommands() {
Commands.Builder commands =
super.getAvailableCommands().buildUpon().addAll(availableCommands);
for (int i = 0; i < excludedCommands.size(); i++) {
commands.remove(excludedCommands.get(i));
}
return commands.build();
}
@Override
public boolean isCommandAvailable(int command) {
return getAvailableCommands().contains(command);
}
};
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment