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 (cherry picked from commit 09d37641d1ef3b8cf26dc39cfcd317ebe3ef5c79)
This commit is contained in:
parent
0ab7c752d7
commit
2c17c6ef04
@ -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;
|
|||||||
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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
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 {
|
|||||||
if (!pendingOperations.isEmpty() || released) {
|
if (!pendingOperations.isEmpty() || released) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStateAndInformListeners(getState());
|
updateStateAndInformListeners(
|
||||||
|
getState(), /* seeked= */ false, /* isRepeatingCurrentItem= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2653,6 +2754,26 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return suggestedPlaceholderState;
|
return suggestedPlaceholderState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}.
|
||||||
*
|
*
|
||||||
@ -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 <= {@code
|
||||||
|
* index} <= {@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 <= {@code
|
||||||
|
* fromIndex} < {@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} < {@code toIndex} <= {@link
|
||||||
|
* #getMediaItemCount()}.
|
||||||
|
* @param newIndex The new index of the first moved item. The index is in the range {@code 0}
|
||||||
|
* <= {@code newIndex} < {@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
|
||||||
|
* <= {@code fromIndex} < {@link #getMediaItemCount()}.
|
||||||
|
* @param toIndex The index of the first item to be kept (exclusive). The index is in the range
|
||||||
|
* {@code fromIndex} < {@code toIndex} <= {@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 <= {@code
|
||||||
|
* mediaItemIndex} < {@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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
@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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
.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 {
|
|||||||
&& 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 {
|
|||||||
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 {}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user