mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Implement setPlaybackParameters for CastPlayer
Issue: #6784 PiperOrigin-RevId: 393374139
This commit is contained in:
parent
8909f2017f
commit
d930e07a0e
@ -12,6 +12,10 @@
|
|||||||
* Request smaller decoder input buffers for Dolby Vision. This fixes an
|
* Request smaller decoder input buffers for Dolby Vision. This fixes an
|
||||||
issue that could cause UHD Dolby Vision playbacks to fail on some
|
issue that could cause UHD Dolby Vision playbacks to fail on some
|
||||||
devices, including Amazon Fire TV 4K.
|
devices, including Amazon Fire TV 4K.
|
||||||
|
* Cast extension:
|
||||||
|
* Implement `CastPlayer.setPlaybackParameters(PlaybackParameters)` to
|
||||||
|
support setting the playback speed
|
||||||
|
([#6784](https://github.com/google/ExoPlayer/issues/6784)).
|
||||||
|
|
||||||
### 2.15.0 (2021-08-10)
|
### 2.15.0 (2021-08-10)
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
COMMAND_SEEK_TO_DEFAULT_POSITION,
|
COMMAND_SEEK_TO_DEFAULT_POSITION,
|
||||||
COMMAND_SEEK_TO_WINDOW,
|
COMMAND_SEEK_TO_WINDOW,
|
||||||
COMMAND_SET_REPEAT_MODE,
|
COMMAND_SET_REPEAT_MODE,
|
||||||
|
COMMAND_SET_SPEED_AND_PITCH,
|
||||||
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||||
COMMAND_GET_TIMELINE,
|
COMMAND_GET_TIMELINE,
|
||||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
||||||
@ -102,6 +103,9 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
COMMAND_CHANGE_MEDIA_ITEMS)
|
COMMAND_CHANGE_MEDIA_ITEMS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static final float MIN_SPEED_SUPPORTED = 0.5f;
|
||||||
|
public static final float MAX_SPEED_SUPPORTED = 2.0f;
|
||||||
|
|
||||||
private static final String TAG = "CastPlayer";
|
private static final String TAG = "CastPlayer";
|
||||||
|
|
||||||
private static final int RENDERER_COUNT = 3;
|
private static final int RENDERER_COUNT = 3;
|
||||||
@ -132,6 +136,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
// Internal state.
|
// Internal state.
|
||||||
private final StateHolder<Boolean> playWhenReady;
|
private final StateHolder<Boolean> playWhenReady;
|
||||||
private final StateHolder<Integer> repeatMode;
|
private final StateHolder<Integer> repeatMode;
|
||||||
|
private final StateHolder<PlaybackParameters> playbackParameters;
|
||||||
@Nullable private RemoteMediaClient remoteMediaClient;
|
@Nullable private RemoteMediaClient remoteMediaClient;
|
||||||
private CastTimeline currentTimeline;
|
private CastTimeline currentTimeline;
|
||||||
private TrackGroupArray currentTrackGroups;
|
private TrackGroupArray currentTrackGroups;
|
||||||
@ -208,6 +213,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
(listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags)));
|
(listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags)));
|
||||||
playWhenReady = new StateHolder<>(false);
|
playWhenReady = new StateHolder<>(false);
|
||||||
repeatMode = new StateHolder<>(REPEAT_MODE_OFF);
|
repeatMode = new StateHolder<>(REPEAT_MODE_OFF);
|
||||||
|
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
|
||||||
playbackState = STATE_IDLE;
|
playbackState = STATE_IDLE;
|
||||||
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
currentTrackGroups = TrackGroupArray.EMPTY;
|
||||||
@ -463,14 +469,9 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
|
return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
|
||||||
// Unsupported by the RemoteMediaClient API. Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PlaybackParameters getPlaybackParameters() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return PlaybackParameters.DEFAULT;
|
return playbackParameters.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -489,6 +490,32 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
sessionManager.endCurrentSession(false);
|
sessionManager.endCurrentSession(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
|
if (remoteMediaClient == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PlaybackParameters actualPlaybackParameters =
|
||||||
|
new PlaybackParameters(
|
||||||
|
Util.constrainValue(
|
||||||
|
playbackParameters.speed, MIN_SPEED_SUPPORTED, MAX_SPEED_SUPPORTED));
|
||||||
|
setPlaybackParametersAndNotifyIfChanged(actualPlaybackParameters);
|
||||||
|
listeners.flushEvents();
|
||||||
|
PendingResult<MediaChannelResult> pendingResult =
|
||||||
|
remoteMediaClient.setPlaybackRate(actualPlaybackParameters.speed, /* customData= */ null);
|
||||||
|
this.playbackParameters.pendingResultCallback =
|
||||||
|
new ResultCallback<MediaChannelResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(MediaChannelResult mediaChannelResult) {
|
||||||
|
if (remoteMediaClient != null) {
|
||||||
|
updatePlaybackRateAndNotifyIfChanged(this);
|
||||||
|
listeners.flushEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pendingResult.setResultCallback(this.playbackParameters.pendingResultCallback);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||||
if (remoteMediaClient == null) {
|
if (remoteMediaClient == null) {
|
||||||
@ -761,6 +788,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying));
|
Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying));
|
||||||
}
|
}
|
||||||
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
|
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
|
||||||
|
updatePlaybackRateAndNotifyIfChanged(/* resultCallback= */ null);
|
||||||
boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
|
boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
|
||||||
Timeline currentTimeline = getCurrentTimeline();
|
Timeline currentTimeline = getCurrentTimeline();
|
||||||
currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
|
currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
|
||||||
@ -844,6 +872,22 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
newPlayWhenReadyValue, playWhenReadyChangeReason, fetchPlaybackState(remoteMediaClient));
|
newPlayWhenReadyValue, playWhenReadyChangeReason, fetchPlaybackState(remoteMediaClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull("remoteMediaClient")
|
||||||
|
private void updatePlaybackRateAndNotifyIfChanged(@Nullable ResultCallback<?> resultCallback) {
|
||||||
|
if (playbackParameters.acceptsUpdate(resultCallback)) {
|
||||||
|
@Nullable MediaStatus mediaStatus = remoteMediaClient.getMediaStatus();
|
||||||
|
float speed =
|
||||||
|
mediaStatus != null
|
||||||
|
? (float) mediaStatus.getPlaybackRate()
|
||||||
|
: PlaybackParameters.DEFAULT.speed;
|
||||||
|
if (speed > 0.0f) {
|
||||||
|
// Set the speed if not paused.
|
||||||
|
setPlaybackParametersAndNotifyIfChanged(new PlaybackParameters(speed));
|
||||||
|
}
|
||||||
|
playbackParameters.clearPendingResultCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresNonNull("remoteMediaClient")
|
@RequiresNonNull("remoteMediaClient")
|
||||||
private void updateRepeatModeAndNotifyIfChanged(@Nullable ResultCallback<?> resultCallback) {
|
private void updateRepeatModeAndNotifyIfChanged(@Nullable ResultCallback<?> resultCallback) {
|
||||||
if (repeatMode.acceptsUpdate(resultCallback)) {
|
if (repeatMode.acceptsUpdate(resultCallback)) {
|
||||||
@ -1100,6 +1144,17 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setPlaybackParametersAndNotifyIfChanged(PlaybackParameters playbackParameters) {
|
||||||
|
if (this.playbackParameters.value.equals(playbackParameters)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.playbackParameters.value = playbackParameters;
|
||||||
|
listeners.queueEvent(
|
||||||
|
Player.EVENT_PLAYBACK_PARAMETERS_CHANGED,
|
||||||
|
listener -> listener.onPlaybackParametersChanged(playbackParameters));
|
||||||
|
updateAvailableCommandsAndNotifyIfChanged();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void setPlayerStateAndNotifyIfChanged(
|
private void setPlayerStateAndNotifyIfChanged(
|
||||||
boolean playWhenReady,
|
boolean playWhenReady,
|
||||||
|
@ -61,6 +61,7 @@ import android.net.Uri;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -126,6 +127,7 @@ public class CastPlayerTest {
|
|||||||
// Make the remote media client present the same default values as ExoPlayer:
|
// Make the remote media client present the same default values as ExoPlayer:
|
||||||
when(mockRemoteMediaClient.isPaused()).thenReturn(true);
|
when(mockRemoteMediaClient.isPaused()).thenReturn(true);
|
||||||
when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_OFF);
|
when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_OFF);
|
||||||
|
when(mockMediaStatus.getPlaybackRate()).thenReturn(1.0d);
|
||||||
castPlayer = new CastPlayer(mockCastContext);
|
castPlayer = new CastPlayer(mockCastContext);
|
||||||
castPlayer.addListener(mockListener);
|
castPlayer.addListener(mockListener);
|
||||||
verify(mockRemoteMediaClient).registerCallback(callbackArgumentCaptor.capture());
|
verify(mockRemoteMediaClient).registerCallback(callbackArgumentCaptor.capture());
|
||||||
@ -208,6 +210,93 @@ public class CastPlayerTest {
|
|||||||
assertThat(castPlayer.getPlayWhenReady()).isTrue();
|
assertThat(castPlayer.getPlayWhenReady()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void playbackParameters_defaultPlaybackSpeed_isUnitSpeed() {
|
||||||
|
assertThat(castPlayer.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void playbackParameters_onStatusUpdated_setsRemotePlaybackSpeed() {
|
||||||
|
PlaybackParameters expectedPlaybackParameters = new PlaybackParameters(/* speed= */ 1.234f);
|
||||||
|
when(mockMediaStatus.getPlaybackRate()).thenReturn(1.234d);
|
||||||
|
|
||||||
|
remoteMediaClientCallback.onStatusUpdated();
|
||||||
|
|
||||||
|
assertThat(castPlayer.getPlaybackParameters()).isEqualTo(expectedPlaybackParameters);
|
||||||
|
verify(mockListener).onPlaybackParametersChanged(expectedPlaybackParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void playbackParameters_onStatusUpdated_ignoreInPausedState() {
|
||||||
|
when(mockMediaStatus.getPlaybackRate()).thenReturn(0.0d);
|
||||||
|
|
||||||
|
remoteMediaClientCallback.onStatusUpdated();
|
||||||
|
|
||||||
|
assertThat(castPlayer.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
|
||||||
|
verifyNoMoreInteractions(mockListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setPlaybackParameters_speedOutOfRange_valueIsConstraintToMinAndMax() {
|
||||||
|
when(mockRemoteMediaClient.setPlaybackRate(eq(2d), any())).thenReturn(mockPendingResult);
|
||||||
|
when(mockRemoteMediaClient.setPlaybackRate(eq(0.5d), any())).thenReturn(mockPendingResult);
|
||||||
|
PlaybackParameters expectedMaxValuePlaybackParameters = new PlaybackParameters(/* speed= */ 2f);
|
||||||
|
PlaybackParameters expectedMinValuePlaybackParameters =
|
||||||
|
new PlaybackParameters(/* speed= */ 0.5f);
|
||||||
|
|
||||||
|
castPlayer.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.001f));
|
||||||
|
verify(mockListener).onPlaybackParametersChanged(expectedMaxValuePlaybackParameters);
|
||||||
|
castPlayer.setPlaybackParameters(new PlaybackParameters(/* speed= */ 0.499f));
|
||||||
|
verify(mockListener).onPlaybackParametersChanged(expectedMinValuePlaybackParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setPlaybackParameters_masksPendingState() {
|
||||||
|
PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.234f);
|
||||||
|
when(mockRemoteMediaClient.setPlaybackRate(eq((double) 1.234f), any()))
|
||||||
|
.thenReturn(mockPendingResult);
|
||||||
|
|
||||||
|
castPlayer.setPlaybackParameters(playbackParameters);
|
||||||
|
|
||||||
|
verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
|
||||||
|
assertThat(castPlayer.getPlaybackParameters().speed).isEqualTo(1.234f);
|
||||||
|
verify(mockListener).onPlaybackParametersChanged(playbackParameters);
|
||||||
|
|
||||||
|
// Simulate a status update while the update is pending that must not override the masked speed.
|
||||||
|
when(mockMediaStatus.getPlaybackRate()).thenReturn(99.0d);
|
||||||
|
remoteMediaClientCallback.onStatusUpdated();
|
||||||
|
assertThat(castPlayer.getPlaybackParameters().speed).isEqualTo(1.234f);
|
||||||
|
|
||||||
|
// Call the captured result callback when the device responds. The listener must not be called.
|
||||||
|
when(mockMediaStatus.getPlaybackRate()).thenReturn(1.234d);
|
||||||
|
setResultCallbackArgumentCaptor
|
||||||
|
.getValue()
|
||||||
|
.onResult(mock(RemoteMediaClient.MediaChannelResult.class));
|
||||||
|
assertThat(castPlayer.getPlaybackParameters().speed).isEqualTo(1.234f);
|
||||||
|
verifyNoMoreInteractions(mockListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setPlaybackParameters_speedChangeNotSupported_resetOnResultCallback() {
|
||||||
|
when(mockRemoteMediaClient.setPlaybackRate(eq((double) 1.234f), any()))
|
||||||
|
.thenReturn(mockPendingResult);
|
||||||
|
PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.234f);
|
||||||
|
|
||||||
|
// Change the playback speed and and capture the result callback.
|
||||||
|
castPlayer.setPlaybackParameters(playbackParameters);
|
||||||
|
verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
|
||||||
|
verify(mockListener).onPlaybackParametersChanged(new PlaybackParameters(/* speed= */ 1.234f));
|
||||||
|
|
||||||
|
// The device does not support speed changes and returns unit speed to the result callback.
|
||||||
|
when(mockMediaStatus.getPlaybackRate()).thenReturn(1.0d);
|
||||||
|
setResultCallbackArgumentCaptor
|
||||||
|
.getValue()
|
||||||
|
.onResult(mock(RemoteMediaClient.MediaChannelResult.class));
|
||||||
|
assertThat(castPlayer.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
|
||||||
|
verify(mockListener).onPlaybackParametersChanged(PlaybackParameters.DEFAULT);
|
||||||
|
verifyNoMoreInteractions(mockListener);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setRepeatMode_masksRemoteState() {
|
public void setRepeatMode_masksRemoteState() {
|
||||||
when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), any())).thenReturn(mockPendingResult);
|
when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), any())).thenReturn(mockPendingResult);
|
||||||
@ -1215,7 +1304,7 @@ public class CastPlayerTest {
|
|||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_WINDOW)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_WINDOW)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_BACK)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_BACK)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_FORWARD)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_FORWARD)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)).isFalse();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_REPEAT_MODE)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_REPEAT_MODE)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)).isTrue();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user