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
|
||||
issue that could cause UHD Dolby Vision playbacks to fail on some
|
||||
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)
|
||||
|
||||
|
@ -95,6 +95,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
COMMAND_SEEK_TO_DEFAULT_POSITION,
|
||||
COMMAND_SEEK_TO_WINDOW,
|
||||
COMMAND_SET_REPEAT_MODE,
|
||||
COMMAND_SET_SPEED_AND_PITCH,
|
||||
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||
COMMAND_GET_TIMELINE,
|
||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
||||
@ -102,6 +103,9 @@ public final class CastPlayer extends BasePlayer {
|
||||
COMMAND_CHANGE_MEDIA_ITEMS)
|
||||
.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 int RENDERER_COUNT = 3;
|
||||
@ -132,6 +136,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
// Internal state.
|
||||
private final StateHolder<Boolean> playWhenReady;
|
||||
private final StateHolder<Integer> repeatMode;
|
||||
private final StateHolder<PlaybackParameters> playbackParameters;
|
||||
@Nullable private RemoteMediaClient remoteMediaClient;
|
||||
private CastTimeline currentTimeline;
|
||||
private TrackGroupArray currentTrackGroups;
|
||||
@ -208,6 +213,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
(listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags)));
|
||||
playWhenReady = new StateHolder<>(false);
|
||||
repeatMode = new StateHolder<>(REPEAT_MODE_OFF);
|
||||
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
|
||||
playbackState = STATE_IDLE;
|
||||
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
||||
@ -463,14 +469,9 @@ public final class CastPlayer extends BasePlayer {
|
||||
return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||
// Unsupported by the RemoteMediaClient API. Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
return PlaybackParameters.DEFAULT;
|
||||
return playbackParameters.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -489,6 +490,32 @@ public final class CastPlayer extends BasePlayer {
|
||||
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
|
||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||
if (remoteMediaClient == null) {
|
||||
@ -761,6 +788,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying));
|
||||
}
|
||||
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
|
||||
updatePlaybackRateAndNotifyIfChanged(/* resultCallback= */ null);
|
||||
boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
|
||||
Timeline currentTimeline = getCurrentTimeline();
|
||||
currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
|
||||
@ -844,6 +872,22 @@ public final class CastPlayer extends BasePlayer {
|
||||
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")
|
||||
private void updateRepeatModeAndNotifyIfChanged(@Nullable ResultCallback<?> 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")
|
||||
private void setPlayerStateAndNotifyIfChanged(
|
||||
boolean playWhenReady,
|
||||
|
@ -61,6 +61,7 @@ import android.net.Uri;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
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:
|
||||
when(mockRemoteMediaClient.isPaused()).thenReturn(true);
|
||||
when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_OFF);
|
||||
when(mockMediaStatus.getPlaybackRate()).thenReturn(1.0d);
|
||||
castPlayer = new CastPlayer(mockCastContext);
|
||||
castPlayer.addListener(mockListener);
|
||||
verify(mockRemoteMediaClient).registerCallback(callbackArgumentCaptor.capture());
|
||||
@ -208,6 +210,93 @@ public class CastPlayerTest {
|
||||
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
|
||||
public void setRepeatMode_masksRemoteState() {
|
||||
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_BACK)).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_REPEAT_MODE)).isTrue();
|
||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)).isTrue();
|
||||
|
Loading…
x
Reference in New Issue
Block a user