diff --git a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java b/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java index acda89a97c..edabd55812 100644 --- a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java +++ b/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java @@ -931,6 +931,38 @@ public class SessionPlayerConnectorTest { assertThat(onPlaylistChangedLatch.getCount()).isEqualTo(1); } + @Test + @LargeTest + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) + public void movePlaylistItem_calledOnlyOnce_notifiesPlaylistChangeOnlyOnce() throws Exception { + List playlist = new ArrayList<>(); + playlist.add(TestUtils.createMediaItem(R.raw.video_1)); + playlist.add(TestUtils.createMediaItem(R.raw.video_2)); + playlist.add(TestUtils.createMediaItem(R.raw.video_3)); + assertPlayerResultSuccess(sessionPlayerConnector.setPlaylist(playlist, /* metadata= */ null)); + assertPlayerResultSuccess(sessionPlayerConnector.prepare()); + + CountDownLatch onPlaylistChangedLatch = new CountDownLatch(2); + int moveFromIndex = 0; + int moveToIndex = 2; + playlist.add(moveToIndex, playlist.remove(moveFromIndex)); + sessionPlayerConnector.registerPlayerCallback( + executor, + new SessionPlayer.PlayerCallback() { + @Override + public void onPlaylistChanged( + SessionPlayer player, + @Nullable List list, + @Nullable MediaMetadata metadata) { + assertThat(list).isEqualTo(playlist); + onPlaylistChangedLatch.countDown(); + } + }); + sessionPlayerConnector.movePlaylistItem(moveFromIndex, moveToIndex); + assertThat(onPlaylistChangedLatch.await(PLAYLIST_CHANGE_WAIT_TIME_MS, MILLISECONDS)).isFalse(); + assertThat(onPlaylistChangedLatch.getCount()).isEqualTo(1); + } + // TODO(b/168860979): De-flake and re-enable. @Ignore @Test diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java index fc80c85856..dd5784ee53 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java @@ -97,6 +97,8 @@ import java.util.concurrent.Callable; /** Command code for {@link SessionPlayer#removePlaylistItem(int)} */ public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 16; + /** Command code for {@link SessionPlayer#movePlaylistItem(int, int)} */ + public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 17; /** List of session commands whose result would be set after the command is finished. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -119,6 +121,7 @@ import java.util.concurrent.Callable; COMMAND_CODE_PLAYER_SET_PLAYLIST, COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM, COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM, + COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM, }) public @interface CommandCode {} diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java index 21659637ab..9be6f23865 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java @@ -223,6 +223,18 @@ import java.util.List; return true; } + public boolean movePlaylistItem(@IntRange(from = 0) int fromIndex, @IntRange(from = 0) int toIndex) { + int itemCount = player.getMediaItemCount(); + if (!(fromIndex < itemCount && toIndex < itemCount)) { + return false; + } + if (fromIndex == toIndex) { + return true; + } + player.moveMediaItem(fromIndex, toIndex); + return true; + } + public boolean skipToPreviousPlaylistItem() { Timeline timeline = player.getCurrentTimeline(); Assertions.checkState(!timeline.isEmpty()); diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java index 7ea89ad537..e238aee7a0 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java @@ -226,7 +226,7 @@ import java.util.concurrent.TimeoutException; build = new SessionCommandGroup.Builder(); } - build.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1); + build.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_2); // TODO(internal b/142848015): Use removeCommand(int) when it's added. if (mediaItemProvider == null) { build.removeCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM)); @@ -234,6 +234,7 @@ import java.util.concurrent.TimeoutException; build.removeCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM)); build.removeCommand( new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM)); + build.removeCommand(new SessionCommand(SessionCommand.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM)); } if (ratingCallback == null) { build.removeCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)); diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java index 0415a5cf38..9a3dc09b07 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java @@ -324,6 +324,17 @@ public final class SessionPlayerConnector extends SessionPlayer { return result; } + @Override + public ListenableFuture movePlaylistItem(int fromIndex, int toIndex) { + Assertions.checkArgument(fromIndex >= 0); + Assertions.checkArgument(toIndex >= 0); + ListenableFuture result = + playerCommandQueue.addCommand( + PlayerCommandQueue.COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM, + /* command= */ () -> player.movePlaylistItem(fromIndex, toIndex)); + return result; + } + @Override public ListenableFuture skipToPreviousPlaylistItem() { ListenableFuture result =