Commit 09d37641 by tonihei Committed by Tianyi Feng

Add playlist and seek operations to SimpleBasePlayer

These are the remaining setter operations. They all share the same
logic that handles playlist and/or position changes. The logic to
create the placeholder state is mostly copied from ExoPlayerImpl's
maskTimelineAndPosition and getPeriodPositonUsAfterTimelineChanged.

PiperOrigin-RevId: 496364712
parent a032da58
...@@ -22,6 +22,7 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; ...@@ -22,6 +22,7 @@ import static com.google.android.exoplayer2.util.Util.castNonNull;
import static com.google.android.exoplayer2.util.Util.msToUs; import static com.google.android.exoplayer2.util.Util.msToUs;
import static com.google.android.exoplayer2.util.Util.usToMs; import static com.google.android.exoplayer2.util.Util.usToMs;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Looper; import android.os.Looper;
...@@ -52,6 +53,7 @@ import com.google.common.util.concurrent.Futures; ...@@ -52,6 +53,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.ForOverride; import com.google.errorprone.annotations.ForOverride;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
...@@ -2021,33 +2023,118 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2021,33 +2023,118 @@ public abstract class SimpleBasePlayer extends BasePlayer {
@Override @Override
public final void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) { public final void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
// TODO: implement. verifyApplicationThreadAndInitState();
throw new IllegalStateException(); int startIndex = resetPosition ? C.INDEX_UNSET : state.currentMediaItemIndex;
long startPositionMs = resetPosition ? C.TIME_UNSET : state.contentPositionMsSupplier.get();
setMediaItemsInternal(mediaItems, startIndex, startPositionMs);
} }
@Override @Override
public final void setMediaItems( public final void setMediaItems(
List<MediaItem> mediaItems, int startIndex, long startPositionMs) { List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
// TODO: implement. verifyApplicationThreadAndInitState();
throw new IllegalStateException(); if (startIndex == C.INDEX_UNSET) {
startIndex = state.currentMediaItemIndex;
startPositionMs = state.contentPositionMsSupplier.get();
}
setMediaItemsInternal(mediaItems, startIndex, startPositionMs);
}
@RequiresNonNull("state")
private void setMediaItemsInternal(
List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
checkArgument(startIndex == C.INDEX_UNSET || startIndex >= 0);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS)
&& (mediaItems.size() != 1 || !shouldHandleCommand(Player.COMMAND_SET_MEDIA_ITEM))) {
return;
}
updateStateForPendingOperation(
/* pendingOperation= */ handleSetMediaItems(mediaItems, startIndex, startPositionMs),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>();
for (int i = 0; i < mediaItems.size(); i++) {
placeholderPlaylist.add(getPlaceholderMediaItemData(mediaItems.get(i)));
}
return getStateWithNewPlaylistAndPosition(
state, placeholderPlaylist, startIndex, startPositionMs);
});
} }
@Override @Override
public final void addMediaItems(int index, List<MediaItem> mediaItems) { public final void addMediaItems(int index, List<MediaItem> mediaItems) {
// TODO: implement. verifyApplicationThreadAndInitState();
throw new IllegalStateException(); checkArgument(index >= 0);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
int playlistSize = state.playlist.size();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || mediaItems.isEmpty()) {
return;
}
int correctedIndex = min(index, playlistSize);
updateStateForPendingOperation(
/* pendingOperation= */ handleAddMediaItems(correctedIndex, mediaItems),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
for (int i = 0; i < mediaItems.size(); i++) {
placeholderPlaylist.add(
i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
}
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
});
} }
@Override @Override
public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) { public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
// TODO: implement. verifyApplicationThreadAndInitState();
throw new IllegalStateException(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex && newIndex >= 0);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
int playlistSize = state.playlist.size();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS)
|| playlistSize == 0
|| fromIndex >= playlistSize) {
return;
}
int correctedToIndex = min(toIndex, playlistSize);
int correctedNewIndex = min(newIndex, state.playlist.size() - (correctedToIndex - fromIndex));
if (fromIndex == correctedToIndex || correctedNewIndex == fromIndex) {
return;
}
updateStateForPendingOperation(
/* pendingOperation= */ handleMoveMediaItems(
fromIndex, correctedToIndex, correctedNewIndex),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
Util.moveItems(placeholderPlaylist, fromIndex, correctedToIndex, correctedNewIndex);
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
});
} }
@Override @Override
public final void removeMediaItems(int fromIndex, int toIndex) { public final void removeMediaItems(int fromIndex, int toIndex) {
// TODO: implement. verifyApplicationThreadAndInitState();
throw new IllegalStateException(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
int playlistSize = state.playlist.size();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS)
|| playlistSize == 0
|| fromIndex >= playlistSize) {
return;
}
int correctedToIndex = min(toIndex, playlistSize);
if (fromIndex == correctedToIndex) {
return;
}
updateStateForPendingOperation(
/* pendingOperation= */ handleRemoveMediaItems(fromIndex, correctedToIndex),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
});
} }
@Override @Override
...@@ -2141,8 +2228,21 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2141,8 +2228,21 @@ public abstract class SimpleBasePlayer extends BasePlayer {
long positionMs, long positionMs,
@Player.Command int seekCommand, @Player.Command int seekCommand,
boolean isRepeatingCurrentItem) { boolean isRepeatingCurrentItem) {
// TODO: implement. verifyApplicationThreadAndInitState();
throw new IllegalStateException(); checkArgument(mediaItemIndex >= 0);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
if (!shouldHandleCommand(seekCommand)
|| isPlayingAd()
|| (!state.playlist.isEmpty() && mediaItemIndex >= state.playlist.size())) {
return;
}
updateStateForPendingOperation(
/* pendingOperation= */ handleSeek(mediaItemIndex, positionMs, seekCommand),
/* placeholderStateSupplier= */ () ->
getStateWithNewPlaylistAndPosition(state, state.playlist, mediaItemIndex, positionMs),
/* seeked= */ true,
isRepeatingCurrentItem);
} }
@Override @Override
...@@ -2617,7 +2717,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2617,7 +2717,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
if (!pendingOperations.isEmpty() || released) { if (!pendingOperations.isEmpty() || released) {
return; return;
} }
updateStateAndInformListeners(getState()); updateStateAndInformListeners(
getState(), /* seeked= */ false, /* isRepeatingCurrentItem= */ false);
} }
/** /**
...@@ -2654,6 +2755,26 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2654,6 +2755,26 @@ public abstract class SimpleBasePlayer extends BasePlayer {
} }
/** /**
* Returns the placeholder {@link MediaItemData} used for a new {@link MediaItem} added to the
* playlist.
*
* <p>An implementation only needs to override this method if it can determine a more accurate
* placeholder state than the default.
*
* @param mediaItem The {@link MediaItem} added to the playlist.
* @return The {@link MediaItemData} used as placeholder while adding the item to the playlist is
* in progress.
*/
@ForOverride
protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) {
return new MediaItemData.Builder(new PlaceholderUid())
.setMediaItem(mediaItem)
.setIsDynamic(true)
.setIsPlaceholder(true)
.build();
}
/**
* Handles calls to {@link Player#setPlayWhenReady}, {@link Player#play} and {@link Player#pause}. * Handles calls to {@link Player#setPlayWhenReady}, {@link Player#play} and {@link Player#pause}.
* *
* <p>Will only be called if {@link Player#COMMAND_PLAY_PAUSE} is available. * <p>Will only be called if {@link Player#COMMAND_PLAY_PAUSE} is available.
...@@ -2877,6 +2998,101 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2877,6 +2998,101 @@ public abstract class SimpleBasePlayer extends BasePlayer {
throw new IllegalStateException(); throw new IllegalStateException();
} }
/**
* Handles calls to {@link Player#setMediaItem} and {@link Player#setMediaItems}.
*
* <p>Will only be called if {@link Player#COMMAND_SET_MEDIA_ITEM} or {@link
* Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. If only {@link Player#COMMAND_SET_MEDIA_ITEM}
* is available, the list of media items will always contain exactly one item.
*
* @param mediaItems The media items to add.
* @param startIndex The index at which to start playback from, or {@link C#INDEX_UNSET} to start
* at the default item.
* @param startPositionMs The position in milliseconds to start playback from, or {@link
* C#TIME_UNSET} to start at the default position in the media item.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ForOverride
protected ListenableFuture<?> handleSetMediaItems(
List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
throw new IllegalStateException();
}
/**
* Handles calls to {@link Player#addMediaItem} and {@link Player#addMediaItems}.
*
* <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.
*
* @param index The index at which to add the items. The index is in the range 0 &lt;= {@code
* index} &lt;= {@link #getMediaItemCount()}.
* @param mediaItems The media items to add.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ForOverride
protected ListenableFuture<?> handleAddMediaItems(int index, List<MediaItem> mediaItems) {
throw new IllegalStateException();
}
/**
* Handles calls to {@link Player#moveMediaItem} and {@link Player#moveMediaItems}.
*
* <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.
*
* @param fromIndex The start index of the items to move. The index is in the range 0 &lt;= {@code
* fromIndex} &lt; {@link #getMediaItemCount()}.
* @param toIndex The index of the first item not to be included in the move (exclusive). The
* index is in the range {@code fromIndex} &lt; {@code toIndex} &lt;= {@link
* #getMediaItemCount()}.
* @param newIndex The new index of the first moved item. The index is in the range {@code 0}
* &lt;= {@code newIndex} &lt; {@link #getMediaItemCount() - (toIndex - fromIndex)}.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ForOverride
protected ListenableFuture<?> handleMoveMediaItems(int fromIndex, int toIndex, int newIndex) {
throw new IllegalStateException();
}
/**
* Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}.
*
* <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.
*
* @param fromIndex The index at which to start removing media items. The index is in the range 0
* &lt;= {@code fromIndex} &lt; {@link #getMediaItemCount()}.
* @param toIndex The index of the first item to be kept (exclusive). The index is in the range
* {@code fromIndex} &lt; {@code toIndex} &lt;= {@link #getMediaItemCount()}.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ForOverride
protected ListenableFuture<?> handleRemoveMediaItems(int fromIndex, int toIndex) {
throw new IllegalStateException();
}
/**
* Handles calls to {@link Player#seekTo} and other seek operations (for example, {@link
* Player#seekToNext}).
*
* <p>Will only be called if the appropriate {@link Player.Command}, for example {@link
* Player#COMMAND_SEEK_TO_MEDIA_ITEM} or {@link Player#COMMAND_SEEK_TO_NEXT}, is available.
*
* @param mediaItemIndex The media item index to seek to. The index is in the range 0 &lt;= {@code
* mediaItemIndex} &lt; {@code mediaItems.size()}.
* @param positionMs The position in milliseconds to start playback from, or {@link C#TIME_UNSET}
* to start at the default position in the media item.
* @param seekCommand The {@link Player.Command} used to trigger the seek.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ForOverride
protected ListenableFuture<?> handleSeek(
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
throw new IllegalStateException();
}
@RequiresNonNull("state") @RequiresNonNull("state")
private boolean shouldHandleCommand(@Player.Command int commandCode) { private boolean shouldHandleCommand(@Player.Command int commandCode) {
return !released && state.availableCommands.contains(commandCode); return !released && state.availableCommands.contains(commandCode);
...@@ -2884,7 +3100,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2884,7 +3100,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
@SuppressWarnings("deprecation") // Calling deprecated listener methods. @SuppressWarnings("deprecation") // Calling deprecated listener methods.
@RequiresNonNull("state") @RequiresNonNull("state")
private void updateStateAndInformListeners(State newState) { private void updateStateAndInformListeners(
State newState, boolean seeked, boolean isRepeatingCurrentItem) {
State previousState = state; State previousState = state;
// Assign new state immediately such that all getters return the right values, but use a // Assign new state immediately such that all getters return the right values, but use a
// snapshot of the previous and new state so that listener invocations are triggered correctly. // snapshot of the previous and new state so that listener invocations are triggered correctly.
...@@ -2906,10 +3123,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2906,10 +3123,11 @@ public abstract class SimpleBasePlayer extends BasePlayer {
MediaMetadata previousMediaMetadata = getMediaMetadataInternal(previousState); MediaMetadata previousMediaMetadata = getMediaMetadataInternal(previousState);
MediaMetadata newMediaMetadata = getMediaMetadataInternal(newState); MediaMetadata newMediaMetadata = getMediaMetadataInternal(newState);
int positionDiscontinuityReason = int positionDiscontinuityReason =
getPositionDiscontinuityReason(previousState, newState, window, period); getPositionDiscontinuityReason(previousState, newState, seeked, window, period);
boolean timelineChanged = !previousState.timeline.equals(newState.timeline); boolean timelineChanged = !previousState.timeline.equals(newState.timeline);
int mediaItemTransitionReason = int mediaItemTransitionReason =
getMediaItemTransitionReason(previousState, newState, positionDiscontinuityReason, window); getMediaItemTransitionReason(
previousState, newState, positionDiscontinuityReason, isRepeatingCurrentItem, window);
if (timelineChanged) { if (timelineChanged) {
@Player.TimelineChangeReason @Player.TimelineChangeReason
...@@ -3093,7 +3311,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3093,7 +3311,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
listeners.queueEvent( listeners.queueEvent(
Player.EVENT_METADATA, listener -> listener.onMetadata(newState.timedMetadata)); Player.EVENT_METADATA, listener -> listener.onMetadata(newState.timedMetadata));
} }
if (false /* TODO: add flag to know when a seek request has been resolved */) { if (positionDiscontinuityReason == Player.DISCONTINUITY_REASON_SEEK) {
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed); listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed);
} }
if (!previousState.availableCommands.equals(newState.availableCommands)) { if (!previousState.availableCommands.equals(newState.availableCommands)) {
...@@ -3125,18 +3343,33 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3125,18 +3343,33 @@ public abstract class SimpleBasePlayer extends BasePlayer {
@RequiresNonNull("state") @RequiresNonNull("state")
private void updateStateForPendingOperation( private void updateStateForPendingOperation(
ListenableFuture<?> pendingOperation, Supplier<State> placeholderStateSupplier) { ListenableFuture<?> pendingOperation, Supplier<State> placeholderStateSupplier) {
updateStateForPendingOperation(
pendingOperation,
placeholderStateSupplier,
/* seeked= */ false,
/* isRepeatingCurrentItem= */ false);
}
@RequiresNonNull("state")
private void updateStateForPendingOperation(
ListenableFuture<?> pendingOperation,
Supplier<State> placeholderStateSupplier,
boolean seeked,
boolean isRepeatingCurrentItem) {
if (pendingOperation.isDone() && pendingOperations.isEmpty()) { if (pendingOperation.isDone() && pendingOperations.isEmpty()) {
updateStateAndInformListeners(getState()); updateStateAndInformListeners(getState(), seeked, isRepeatingCurrentItem);
} else { } else {
pendingOperations.add(pendingOperation); pendingOperations.add(pendingOperation);
State suggestedPlaceholderState = placeholderStateSupplier.get(); State suggestedPlaceholderState = placeholderStateSupplier.get();
updateStateAndInformListeners(getPlaceholderState(suggestedPlaceholderState)); updateStateAndInformListeners(
getPlaceholderState(suggestedPlaceholderState), seeked, isRepeatingCurrentItem);
pendingOperation.addListener( pendingOperation.addListener(
() -> { () -> {
castNonNull(state); // Already checked by method @RequiresNonNull pre-condition. castNonNull(state); // Already checked by method @RequiresNonNull pre-condition.
pendingOperations.remove(pendingOperation); pendingOperations.remove(pendingOperation);
if (pendingOperations.isEmpty() && !released) { if (pendingOperations.isEmpty() && !released) {
updateStateAndInformListeners(getState()); updateStateAndInformListeners(
getState(), /* seeked= */ false, /* isRepeatingCurrentItem= */ false);
} }
}, },
this::postOrRunOnApplicationHandler); this::postOrRunOnApplicationHandler);
...@@ -3221,7 +3454,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3221,7 +3454,11 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
} }
for (int i = 0; i < previousPlaylist.size(); i++) { for (int i = 0; i < previousPlaylist.size(); i++) {
if (!previousPlaylist.get(i).uid.equals(newPlaylist.get(i).uid)) { Object previousUid = previousPlaylist.get(i).uid;
Object newUid = newPlaylist.get(i).uid;
boolean resolvedAutoGeneratedPlaceholder =
previousUid instanceof PlaceholderUid && !(newUid instanceof PlaceholderUid);
if (!previousUid.equals(newUid) && !resolvedAutoGeneratedPlaceholder) {
return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
} }
} }
...@@ -3229,11 +3466,18 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3229,11 +3466,18 @@ public abstract class SimpleBasePlayer extends BasePlayer {
} }
private static int getPositionDiscontinuityReason( private static int getPositionDiscontinuityReason(
State previousState, State newState, Timeline.Window window, Timeline.Period period) { State previousState,
State newState,
boolean seeked,
Timeline.Window window,
Timeline.Period period) {
if (newState.hasPositionDiscontinuity) { if (newState.hasPositionDiscontinuity) {
// We were asked to report a discontinuity. // We were asked to report a discontinuity.
return newState.positionDiscontinuityReason; return newState.positionDiscontinuityReason;
} }
if (seeked) {
return Player.DISCONTINUITY_REASON_SEEK;
}
if (previousState.playlist.isEmpty()) { if (previousState.playlist.isEmpty()) {
// First change from an empty playlist is not reported as a discontinuity. // First change from an empty playlist is not reported as a discontinuity.
return C.INDEX_UNSET; return C.INDEX_UNSET;
...@@ -3247,6 +3491,10 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3247,6 +3491,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
getCurrentPeriodIndexInternal(previousState, window, period)); getCurrentPeriodIndexInternal(previousState, window, period));
Object newPeriodUid = Object newPeriodUid =
newState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(newState, window, period)); newState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(newState, window, period));
if (previousPeriodUid instanceof PlaceholderUid && !(newPeriodUid instanceof PlaceholderUid)) {
// An auto-generated placeholder was resolved to a real item.
return C.INDEX_UNSET;
}
if (!newPeriodUid.equals(previousPeriodUid) if (!newPeriodUid.equals(previousPeriodUid)
|| previousState.currentAdGroupIndex != newState.currentAdGroupIndex || previousState.currentAdGroupIndex != newState.currentAdGroupIndex
|| previousState.currentAdIndexInAdGroup != newState.currentAdIndexInAdGroup) { || previousState.currentAdIndexInAdGroup != newState.currentAdIndexInAdGroup) {
...@@ -3343,6 +3591,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3343,6 +3591,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
State previousState, State previousState,
State newState, State newState,
int positionDiscontinuityReason, int positionDiscontinuityReason,
boolean isRepeatingCurrentItem,
Timeline.Window window) { Timeline.Window window) {
Timeline previousTimeline = previousState.timeline; Timeline previousTimeline = previousState.timeline;
Timeline newTimeline = newState.timeline; Timeline newTimeline = newState.timeline;
...@@ -3356,6 +3605,10 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3356,6 +3605,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
.uid; .uid;
Object newWindowUid = Object newWindowUid =
newState.timeline.getWindow(getCurrentMediaItemIndexInternal(newState), window).uid; newState.timeline.getWindow(getCurrentMediaItemIndexInternal(newState), window).uid;
if (previousWindowUid instanceof PlaceholderUid && !(newWindowUid instanceof PlaceholderUid)) {
// An auto-generated placeholder was resolved to a real item.
return C.INDEX_UNSET;
}
if (!previousWindowUid.equals(newWindowUid)) { if (!previousWindowUid.equals(newWindowUid)) {
if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) {
return MEDIA_ITEM_TRANSITION_REASON_AUTO; return MEDIA_ITEM_TRANSITION_REASON_AUTO;
...@@ -3371,8 +3624,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3371,8 +3624,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
&& getContentPositionMsInternal(previousState) > getContentPositionMsInternal(newState)) { && getContentPositionMsInternal(previousState) > getContentPositionMsInternal(newState)) {
return MEDIA_ITEM_TRANSITION_REASON_REPEAT; return MEDIA_ITEM_TRANSITION_REASON_REPEAT;
} }
if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK && isRepeatingCurrentItem) {
&& /* TODO: mark repetition seeks to detect this case */ false) {
return MEDIA_ITEM_TRANSITION_REASON_SEEK; return MEDIA_ITEM_TRANSITION_REASON_SEEK;
} }
return C.INDEX_UNSET; return C.INDEX_UNSET;
...@@ -3385,4 +3637,139 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3385,4 +3637,139 @@ public abstract class SimpleBasePlayer extends BasePlayer {
Rect surfaceFrame = surfaceHolder.getSurfaceFrame(); Rect surfaceFrame = surfaceHolder.getSurfaceFrame();
return new Size(surfaceFrame.width(), surfaceFrame.height()); return new Size(surfaceFrame.width(), surfaceFrame.height());
} }
private static int getMediaItemIndexInNewPlaylist(
List<MediaItemData> oldPlaylist,
Timeline newPlaylistTimeline,
int oldMediaItemIndex,
Timeline.Period period) {
if (oldPlaylist.isEmpty()) {
return oldMediaItemIndex < newPlaylistTimeline.getWindowCount()
? oldMediaItemIndex
: C.INDEX_UNSET;
}
Object oldFirstPeriodUid =
oldPlaylist.get(oldMediaItemIndex).getPeriodUid(/* periodIndexInMediaItem= */ 0);
if (newPlaylistTimeline.getIndexOfPeriod(oldFirstPeriodUid) == C.INDEX_UNSET) {
return C.INDEX_UNSET;
}
return newPlaylistTimeline.getPeriodByUid(oldFirstPeriodUid, period).windowIndex;
}
private static State getStateWithNewPlaylist(
State oldState, List<MediaItemData> newPlaylist, Timeline.Period period) {
State.Builder stateBuilder = oldState.buildUpon();
stateBuilder.setPlaylist(newPlaylist);
Timeline newTimeline = stateBuilder.timeline;
long oldPositionMs = oldState.contentPositionMsSupplier.get();
int oldIndex = getCurrentMediaItemIndexInternal(oldState);
int newIndex = getMediaItemIndexInNewPlaylist(oldState.playlist, newTimeline, oldIndex, period);
long newPositionMs = newIndex == C.INDEX_UNSET ? C.TIME_UNSET : oldPositionMs;
// If the current item no longer exists, try to find a matching subsequent item.
for (int i = oldIndex + 1; newIndex == C.INDEX_UNSET && i < oldState.playlist.size(); i++) {
// TODO: Use shuffle order to iterate.
newIndex =
getMediaItemIndexInNewPlaylist(
oldState.playlist, newTimeline, /* oldMediaItemIndex= */ i, period);
}
// If this fails, transition to ENDED state.
if (oldState.playbackState != Player.STATE_IDLE && newIndex == C.INDEX_UNSET) {
stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false);
}
return buildStateForNewPosition(
stateBuilder,
oldState,
oldPositionMs,
newPlaylist,
newIndex,
newPositionMs,
/* keepAds= */ true);
}
private static State getStateWithNewPlaylistAndPosition(
State oldState, List<MediaItemData> newPlaylist, int newIndex, long newPositionMs) {
State.Builder stateBuilder = oldState.buildUpon();
stateBuilder.setPlaylist(newPlaylist);
if (oldState.playbackState != Player.STATE_IDLE) {
if (newPlaylist.isEmpty()) {
stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false);
} else {
stateBuilder.setPlaybackState(Player.STATE_BUFFERING);
}
}
long oldPositionMs = oldState.contentPositionMsSupplier.get();
return buildStateForNewPosition(
stateBuilder,
oldState,
oldPositionMs,
newPlaylist,
newIndex,
newPositionMs,
/* keepAds= */ false);
}
private static State buildStateForNewPosition(
State.Builder stateBuilder,
State oldState,
long oldPositionMs,
List<MediaItemData> newPlaylist,
int newIndex,
long newPositionMs,
boolean keepAds) {
// Resolve unset or invalid index and position.
oldPositionMs = getPositionOrDefaultInMediaItem(oldPositionMs, oldState);
if (!newPlaylist.isEmpty() && (newIndex == C.INDEX_UNSET || newIndex >= newPlaylist.size())) {
newIndex = 0; // TODO: Use shuffle order to get first index.
newPositionMs = C.TIME_UNSET;
}
if (!newPlaylist.isEmpty() && newPositionMs == C.TIME_UNSET) {
newPositionMs = usToMs(newPlaylist.get(newIndex).defaultPositionUs);
}
boolean oldOrNewPlaylistEmpty = oldState.playlist.isEmpty() || newPlaylist.isEmpty();
boolean mediaItemChanged =
!oldOrNewPlaylistEmpty
&& !oldState
.playlist
.get(getCurrentMediaItemIndexInternal(oldState))
.uid
.equals(newPlaylist.get(newIndex).uid);
if (oldOrNewPlaylistEmpty || mediaItemChanged || newPositionMs < oldPositionMs) {
// New item or seeking back. Assume no buffer and no ad playback persists.
stateBuilder
.setCurrentMediaItemIndex(newIndex)
.setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET)
.setContentPositionMs(newPositionMs)
.setContentBufferedPositionMs(PositionSupplier.getConstant(newPositionMs))
.setTotalBufferedDurationMs(PositionSupplier.ZERO);
} else if (newPositionMs == oldPositionMs) {
// Unchanged position. Assume ad playback and buffer in current item persists.
stateBuilder.setCurrentMediaItemIndex(newIndex);
if (oldState.currentAdGroupIndex != C.INDEX_UNSET && keepAds) {
stateBuilder.setTotalBufferedDurationMs(
PositionSupplier.getConstant(
oldState.adBufferedPositionMsSupplier.get() - oldState.adPositionMsSupplier.get()));
} else {
stateBuilder
.setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET)
.setTotalBufferedDurationMs(
PositionSupplier.getConstant(
getContentBufferedPositionMsInternal(oldState) - oldPositionMs));
}
} else {
// Seeking forward. Assume remaining buffer in current item persist, but no ad playback.
long contentBufferedDurationMs =
max(getContentBufferedPositionMsInternal(oldState), newPositionMs);
long totalBufferedDurationMs =
max(0, oldState.totalBufferedDurationMsSupplier.get() - (newPositionMs - oldPositionMs));
stateBuilder
.setCurrentMediaItemIndex(newIndex)
.setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET)
.setContentPositionMs(newPositionMs)
.setContentBufferedPositionMs(PositionSupplier.getConstant(contentBufferedDurationMs))
.setTotalBufferedDurationMs(PositionSupplier.getConstant(totalBufferedDurationMs));
}
return stateBuilder.build();
}
private static final class PlaceholderUid {}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment