Implement set/getPlaybackSpeed
This CL implements set/getPlaybackSpeed in MediaSession2/Controller2,
and also adds tests.
Bug: 77241129
Test: ./gradlew media:check media:connectedCheck
Change-Id: I4e689aa24afc4abb1015911bd176ed679fe2fd45
diff --git a/media/src/androidTest/java/androidx/media/MediaController2Test.java b/media/src/androidTest/java/androidx/media/MediaController2Test.java
index 3cd7a2a..9e54276 100644
--- a/media/src/androidTest/java/androidx/media/MediaController2Test.java
+++ b/media/src/androidTest/java/androidx/media/MediaController2Test.java
@@ -16,8 +16,7 @@
package androidx.media;
-import static junit.framework.Assert.assertEquals;
-
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -222,7 +221,7 @@
long time2 = System.currentTimeMillis();
assertEquals(state, controller.getPlayerState());
assertEquals(bufferedPosition, controller.getBufferedPosition());
- assertEquals(speed, controller.getPlaybackSpeed());
+ assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
long positionLowerBound = (long) (position + speed * (System.currentTimeMillis() - time2));
long currentPosition = controller.getCurrentPosition();
long positionUpperBound = (long) (position + speed * (System.currentTimeMillis() - time1));
@@ -348,6 +347,15 @@
}
}
+ @Test
+ public void testSetPlaybackSpeed() throws Exception {
+ prepareLooper();
+ final float speed = 1.5f;
+ mController.setPlaybackSpeed(speed);
+ assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
+ }
+
/**
* Test whether {@link MediaSession2#setPlaylist(List, MediaMetadata2)} is notified
* through the
diff --git a/media/src/androidTest/java/androidx/media/MediaSession2Test.java b/media/src/androidTest/java/androidx/media/MediaSession2Test.java
index d738c9f..e2cfedf 100644
--- a/media/src/androidTest/java/androidx/media/MediaSession2Test.java
+++ b/media/src/androidTest/java/androidx/media/MediaSession2Test.java
@@ -248,6 +248,47 @@
}
}
+ /**
+ * This also tests {@link ControllerCallback#onPlaybackSpeedChanged(MediaController2, float)}
+ * and {@link MediaController2#getPlaybackSpeed()}.
+ */
+ @Test
+ public void testPlaybackSpeedChanged() throws Exception {
+ prepareLooper();
+ final float speed = 1.5f;
+ mPlayer.setPlaybackSpeed(speed);
+
+ final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
+ .setId("testPlaybackSpeedChanged")
+ .setSessionCallback(sHandlerExecutor, new SessionCallback() {
+ @Override
+ public void onPlaybackSpeedChanged(MediaSession2 session,
+ MediaPlayerBase player, float speedOut) {
+ assertEquals(speed, speedOut, 0.0f);
+ latchForSessionCallback.countDown();
+ }
+ }).build()) {
+
+ final CountDownLatch latchForControllerCallback = new CountDownLatch(1);
+ final MediaController2 controller =
+ createController(mSession.getToken(), true, new ControllerCallback() {
+ @Override
+ public void onPlaybackSpeedChanged(MediaController2 controller,
+ float speedOut) {
+ assertEquals(speed, speedOut, 0.0f);
+ latchForControllerCallback.countDown();
+ }
+ });
+
+ mPlayer.notifyPlaybackSpeedChanged(speed);
+ assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertTrue(latchForControllerCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
+ }
+ }
+
@Test
public void testUpdatePlayer() throws Exception {
prepareLooper();
@@ -381,6 +422,23 @@
}
@Test
+ public void testSetPlaybackSpeed() throws Exception {
+ prepareLooper();
+ final float speed = 1.5f;
+ mSession.setPlaybackSpeed(speed);
+ assertTrue(mPlayer.mSetPlaybackSpeedCalled);
+ assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
+ }
+
+ @Test
+ public void testGetPlaybackSpeed() throws Exception {
+ prepareLooper();
+ final float speed = 1.5f;
+ mPlayer.setPlaybackSpeed(speed);
+ assertEquals(speed, mSession.getPlaybackSpeed(), 0.0f);
+ }
+
+ @Test
public void testSkipToPreviousItem() {
prepareLooper();
mSession.skipToPreviousItem();
diff --git a/media/src/androidTest/java/androidx/media/MockPlayer.java b/media/src/androidTest/java/androidx/media/MockPlayer.java
index 5c0dd5f..ce940d8 100644
--- a/media/src/androidTest/java/androidx/media/MockPlayer.java
+++ b/media/src/androidTest/java/androidx/media/MockPlayer.java
@@ -35,6 +35,7 @@
public boolean mResetCalled;
public boolean mPrepareCalled;
public boolean mSeekToCalled;
+ public boolean mSetPlaybackSpeedCalled;
public long mSeekPosition;
public long mCurrentPosition;
public long mBufferedPosition;
@@ -194,6 +195,19 @@
}
}
+ public void notifyPlaybackSpeedChanged(final float speed) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ final PlayerEventCallback callback = mCallbacks.keyAt(i);
+ final Executor executor = mCallbacks.valueAt(i);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ callback.onPlaybackSpeedChanged(MockPlayer.this, speed);
+ }
+ });
+ }
+ }
+
public void notifyError(int what) {
for (int i = 0; i < mCallbacks.size(); i++) {
final PlayerEventCallback callback = mCallbacks.keyAt(i);
@@ -241,7 +255,11 @@
@Override
public void setPlaybackSpeed(float speed) {
+ mSetPlaybackSpeedCalled = true;
mPlaybackSpeed = speed;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
}
@Override
diff --git a/media/src/main/java/androidx/media/MediaConstants2.java b/media/src/main/java/androidx/media/MediaConstants2.java
index 5de31c0..2a168eb 100644
--- a/media/src/main/java/androidx/media/MediaConstants2.java
+++ b/media/src/main/java/androidx/media/MediaConstants2.java
@@ -29,6 +29,8 @@
"androidx.media.session.event.ON_ROUTES_INFO_CHANGED";
static final String SESSION_EVENT_ON_PLAYBACK_INFO_CHANGED =
"androidx.media.session.event.ON_PLAYBACK_INFO_CHANGED";
+ static final String SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED =
+ "androidx.media.session.event.ON_PLAYBACK_SPEED_CHANGED";
static final String SESSION_EVENT_ON_REPEAT_MODE_CHANGED =
"androidx.media.session.event.ON_REPEAT_MODE_CHANGED";
static final String SESSION_EVENT_ON_SHUFFLE_MODE_CHANGED =
@@ -59,6 +61,7 @@
static final String ARGUMENT_ALLOWED_COMMANDS = "androidx.media.argument.ALLOWED_COMMANDS";
static final String ARGUMENT_SEEK_POSITION = "androidx.media.argument.SEEK_POSITION";
static final String ARGUMENT_PLAYER_STATE = "androidx.media.argument.PLAYER_STATE";
+ static final String ARGUMENT_PLAYBACK_SPEED = "androidx.media.argument.PLAYBACK_SPEED";
static final String ARGUMENT_ERROR_CODE = "androidx.media.argument.ERROR_CODE";
static final String ARGUMENT_REPEAT_MODE = "androidx.media.argument.REPEAT_MODE";
static final String ARGUMENT_SHUFFLE_MODE = "androidx.media.argument.SHUFFLE_MODE";
diff --git a/media/src/main/java/androidx/media/MediaController2.java b/media/src/main/java/androidx/media/MediaController2.java
index 40c23ce..6ca8267 100644
--- a/media/src/main/java/androidx/media/MediaController2.java
+++ b/media/src/main/java/androidx/media/MediaController2.java
@@ -32,6 +32,7 @@
import static androidx.media.MediaConstants2.ARGUMENT_PACKAGE_NAME;
import static androidx.media.MediaConstants2.ARGUMENT_PID;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_INFO;
+import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_SPEED;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_STATE_COMPAT;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYER_STATE;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST;
@@ -58,6 +59,7 @@
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ALLOWED_COMMANDS_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ERROR;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_INFO_CHANGED;
+import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYER_STATE_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_METADATA_CHANGED;
@@ -73,6 +75,7 @@
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_RESET;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO;
+import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SET_SPEED;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
@@ -617,6 +620,20 @@
mPlaybackInfo = info;
}
mCallback.onPlaybackInfoChanged(MediaController2.this, info);
+ break;
+ }
+ case SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED: {
+ PlaybackStateCompat state =
+ extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT);
+ if (state == null) {
+ return;
+ }
+ synchronized (mLock) {
+ mPlaybackStateCompat = state;
+ }
+ mCallback.onPlaybackSpeedChanged(
+ MediaController2.this, state.getPlaybackSpeed());
+ break;
}
}
}
@@ -1171,7 +1188,15 @@
* Set the playback speed.
*/
public void setPlaybackSpeed(float speed) {
- // TODO(jaewan): implement this (b/74093080)
+ synchronized (mLock) {
+ if (!mConnected) {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putFloat(ARGUMENT_PLAYBACK_SPEED, speed);
+ sendCommand(COMMAND_CODE_PLAYBACK_SET_SPEED, args);
+ }
}
/**
diff --git a/media/src/main/java/androidx/media/MediaSession2ImplBase.java b/media/src/main/java/androidx/media/MediaSession2ImplBase.java
index ccba2fc..b7c32c2 100644
--- a/media/src/main/java/androidx/media/MediaSession2ImplBase.java
+++ b/media/src/main/java/androidx/media/MediaSession2ImplBase.java
@@ -1022,6 +1022,21 @@
});
}
+ @Override
+ public void onPlaybackSpeedChanged(final MediaPlayerBase mpb, final float speed) {
+ final MediaSession2ImplBase session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ session.getCallback().onPlaybackSpeedChanged(session.getInstance(), mpb, speed);
+ session.getSession2Stub().notifyPlaybackSpeedChanged(speed);
+ }
+ });
+ }
+
private MediaSession2ImplBase getSession() {
final MediaSession2ImplBase session = mSession.get();
if (session == null && DEBUG) {
diff --git a/media/src/main/java/androidx/media/MediaSession2StubImplBase.java b/media/src/main/java/androidx/media/MediaSession2StubImplBase.java
index 1e3d3f3..9978e4c 100644
--- a/media/src/main/java/androidx/media/MediaSession2StubImplBase.java
+++ b/media/src/main/java/androidx/media/MediaSession2StubImplBase.java
@@ -29,6 +29,7 @@
import static androidx.media.MediaConstants2.ARGUMENT_PACKAGE_NAME;
import static androidx.media.MediaConstants2.ARGUMENT_PID;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_INFO;
+import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_SPEED;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_STATE_COMPAT;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYER_STATE;
import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST;
@@ -55,6 +56,7 @@
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ALLOWED_COMMANDS_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ERROR;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_INFO_CHANGED;
+import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYER_STATE_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_CHANGED;
import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_METADATA_CHANGED;
@@ -68,6 +70,7 @@
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_RESET;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO;
+import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SET_SPEED;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
@@ -415,6 +418,12 @@
Bundle route = extras.getBundle(ARGUMENT_ROUTE_BUNDLE);
mSession.getCallback().onSelectRoute(
mSession.getInstance(), controller, route);
+ break;
+ }
+ case COMMAND_CODE_PLAYBACK_SET_SPEED: {
+ float speed = extras.getFloat(ARGUMENT_PLAYBACK_SPEED);
+ mSession.setPlaybackSpeed(speed);
+ break;
}
}
}
@@ -541,6 +550,19 @@
});
}
+ void notifyPlaybackSpeedChanged(final float speed) {
+ notifyAll(new Session2Runnable() {
+ @Override
+ public void run(ControllerInfo controller) throws RemoteException {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ ARGUMENT_PLAYBACK_STATE_COMPAT, mSession.getPlaybackStateCompat());
+ controller.getControllerBinder().onEvent(
+ SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED, bundle);
+ }
+ });
+ }
+
void notifyError(final int errorCode, final Bundle extras) {
notifyAll(new Session2Runnable() {
@Override
diff --git a/media/src/main/java/androidx/media/SessionCommand2.java b/media/src/main/java/androidx/media/SessionCommand2.java
index a07799b..aca8234 100644
--- a/media/src/main/java/androidx/media/SessionCommand2.java
+++ b/media/src/main/java/androidx/media/SessionCommand2.java
@@ -315,6 +315,15 @@
*/
public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 35;
+ /**
+ * Command code for {@link MediaController2#setPlaybackSpeed(float)}}.
+ * <p>
+ * Command would be sent directly to the player if the session doesn't reject the request
+ * through the {@link SessionCallback#onCommandRequest(MediaSession2, ControllerInfo,
+ * SessionCommand2)}.
+ */
+ public static final int COMMAND_CODE_PLAYBACK_SET_SPEED = 39;
+
private static final String KEY_COMMAND_CODE =
"android.media.media_session2.command.command_code";
private static final String KEY_COMMAND_CUSTOM_COMMAND =