Guard playWhenReady masking against regular status updates
PiperOrigin-RevId: 275863101
This commit is contained in:
parent
dbfc101e04
commit
a0843d0e2f
@ -16,7 +16,6 @@
|
|||||||
package com.google.android.exoplayer2.ext.cast;
|
package com.google.android.exoplayer2.ext.cast;
|
||||||
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.BasePlayer;
|
import com.google.android.exoplayer2.BasePlayer;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
@ -99,6 +98,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
@Nullable private SessionAvailabilityListener sessionAvailabilityListener;
|
@Nullable private SessionAvailabilityListener sessionAvailabilityListener;
|
||||||
|
|
||||||
// Internal state.
|
// Internal state.
|
||||||
|
private final StateHolder<Boolean> playWhenReady;
|
||||||
@Nullable private RemoteMediaClient remoteMediaClient;
|
@Nullable private RemoteMediaClient remoteMediaClient;
|
||||||
private CastTimeline currentTimeline;
|
private CastTimeline currentTimeline;
|
||||||
private TrackGroupArray currentTrackGroups;
|
private TrackGroupArray currentTrackGroups;
|
||||||
@ -106,7 +106,6 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
@Player.State private int playbackState;
|
@Player.State private int playbackState;
|
||||||
private int repeatMode;
|
private int repeatMode;
|
||||||
private int currentWindowIndex;
|
private int currentWindowIndex;
|
||||||
private boolean playWhenReady;
|
|
||||||
private long lastReportedPositionMs;
|
private long lastReportedPositionMs;
|
||||||
private int pendingSeekCount;
|
private int pendingSeekCount;
|
||||||
private int pendingSeekWindowIndex;
|
private int pendingSeekWindowIndex;
|
||||||
@ -131,6 +130,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
CastSession session = sessionManager.getCurrentCastSession();
|
CastSession session = sessionManager.getCurrentCastSession();
|
||||||
remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;
|
remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;
|
||||||
|
|
||||||
|
playWhenReady = new StateHolder<>(false);
|
||||||
playbackState = STATE_IDLE;
|
playbackState = STATE_IDLE;
|
||||||
repeatMode = REPEAT_MODE_OFF;
|
repeatMode = REPEAT_MODE_OFF;
|
||||||
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||||
@ -357,18 +357,22 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
flushNotifications();
|
flushNotifications();
|
||||||
PendingResult<MediaChannelResult> pendingResult =
|
PendingResult<MediaChannelResult> pendingResult =
|
||||||
playWhenReady ? remoteMediaClient.play() : remoteMediaClient.pause();
|
playWhenReady ? remoteMediaClient.play() : remoteMediaClient.pause();
|
||||||
pendingResult.setResultCallback(
|
this.playWhenReady.pendingResultCallback =
|
||||||
mediaChannelResult -> {
|
new ResultCallback<MediaChannelResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(MediaChannelResult mediaChannelResult) {
|
||||||
if (remoteMediaClient != null) {
|
if (remoteMediaClient != null) {
|
||||||
updatePlayerStateAndNotifyIfChanged();
|
updatePlayerStateAndNotifyIfChanged(this);
|
||||||
flushNotifications();
|
flushNotifications();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
pendingResult.setResultCallback(this.playWhenReady.pendingResultCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getPlayWhenReady() {
|
public boolean getPlayWhenReady() {
|
||||||
return playWhenReady;
|
return playWhenReady.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -571,9 +575,9 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
// There is no session. We leave the state of the player as it is now.
|
// There is no session. We leave the state of the player as it is now.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady;
|
boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
|
||||||
updatePlayerStateAndNotifyIfChanged();
|
updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null);
|
||||||
boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady;
|
boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
|
||||||
if (wasPlaying != isPlaying) {
|
if (wasPlaying != isPlaying) {
|
||||||
notificationsBatch.add(
|
notificationsBatch.add(
|
||||||
new ListenerNotificationTask(listener -> listener.onIsPlayingChanged(isPlaying)));
|
new ListenerNotificationTask(listener -> listener.onIsPlayingChanged(isPlaying)));
|
||||||
@ -605,10 +609,22 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
flushNotifications();
|
flushNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates {@link #playWhenReady} and {@link #playbackState} to match the Cast {@code
|
||||||
|
* remoteMediaClient} state, and notifies listeners of any state changes.
|
||||||
|
*
|
||||||
|
* <p>This method will only update values whose {@link StateHolder#pendingResultCallback} matches
|
||||||
|
* the given {@code resultCallback}.
|
||||||
|
*/
|
||||||
@RequiresNonNull("remoteMediaClient")
|
@RequiresNonNull("remoteMediaClient")
|
||||||
private void updatePlayerStateAndNotifyIfChanged() {
|
private void updatePlayerStateAndNotifyIfChanged(ResultCallback<?> resultCallback) {
|
||||||
setPlayerStateAndNotifyIfChanged(
|
boolean newPlayWhenReadyValue = playWhenReady.value;
|
||||||
!remoteMediaClient.isPaused(), fetchPlaybackState(remoteMediaClient));
|
if (playWhenReady.acceptsUpdate(resultCallback)) {
|
||||||
|
newPlayWhenReadyValue = !remoteMediaClient.isPaused();
|
||||||
|
playWhenReady.clearPendingResultCallback();
|
||||||
|
}
|
||||||
|
// We do not mask the playback state, so try setting it regardless of the playWhenReady masking.
|
||||||
|
setPlayerStateAndNotifyIfChanged(newPlayWhenReadyValue, fetchPlaybackState(remoteMediaClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull("remoteMediaClient")
|
@RequiresNonNull("remoteMediaClient")
|
||||||
@ -700,8 +716,8 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
private void setPlayerStateAndNotifyIfChanged(
|
private void setPlayerStateAndNotifyIfChanged(
|
||||||
boolean playWhenReady, @Player.State int playbackState) {
|
boolean playWhenReady, @Player.State int playbackState) {
|
||||||
if (this.playWhenReady != playWhenReady || this.playbackState != playbackState) {
|
if (this.playWhenReady.value != playWhenReady || this.playbackState != playbackState) {
|
||||||
this.playWhenReady = playWhenReady;
|
this.playWhenReady.value = playWhenReady;
|
||||||
this.playbackState = playbackState;
|
this.playbackState = playbackState;
|
||||||
notificationsBatch.add(
|
notificationsBatch.add(
|
||||||
new ListenerNotificationTask(
|
new ListenerNotificationTask(
|
||||||
@ -918,7 +934,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
|
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResult(@NonNull MediaChannelResult result) {
|
public void onResult(MediaChannelResult result) {
|
||||||
int statusCode = result.getStatus().getStatusCode();
|
int statusCode = result.getStatus().getStatusCode();
|
||||||
if (statusCode != CastStatusCodes.SUCCESS && statusCode != CastStatusCodes.REPLACED) {
|
if (statusCode != CastStatusCodes.SUCCESS && statusCode != CastStatusCodes.REPLACED) {
|
||||||
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
||||||
@ -933,6 +949,37 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds the value and the masking status of a specific part of the {@link CastPlayer} state. */
|
||||||
|
private static final class StateHolder<T> {
|
||||||
|
|
||||||
|
/** The user-facing value of a specific part of the {@link CastPlayer} state. */
|
||||||
|
public T value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #value} is being masked, holds the result callback for the operation that triggered
|
||||||
|
* the masking. Or null if {@link #value} is not being masked.
|
||||||
|
*/
|
||||||
|
@Nullable public ResultCallback<MediaChannelResult> pendingResultCallback;
|
||||||
|
|
||||||
|
public StateHolder(T initialValue) {
|
||||||
|
value = initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearPendingResultCallback() {
|
||||||
|
pendingResultCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether {@link #value} is being masked by the operation associated to the given
|
||||||
|
* result callback.
|
||||||
|
*
|
||||||
|
* @param resultCallback A result callback.
|
||||||
|
*/
|
||||||
|
public boolean acceptsUpdate(ResultCallback<?> resultCallback) {
|
||||||
|
return pendingResultCallback == resultCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class ListenerNotificationTask {
|
private final class ListenerNotificationTask {
|
||||||
|
|
||||||
private final Iterator<ListenerHolder> listenersSnapshot;
|
private final Iterator<ListenerHolder> listenersSnapshot;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user