No-op restructure of playlist update handling.
This restructure moves all the position resolving code to a static method and removes the dependency of the MediaPeriodQueue on having an up-to-date timeline. Both steps allow simplified reasoning about the code as the static method can't change the state of the player, and there is no risk the queue can use the wrong timeline. These propoerties allow to fix a bug causing inconsistent states in a follow-up step. PiperOrigin-RevId: 290616395
This commit is contained in:
parent
df41ae3f42
commit
21c76b012c
@ -839,7 +839,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
long startPositionUs = playbackInfo.positionUs;
|
long startPositionUs = playbackInfo.positionUs;
|
||||||
if (clearPlaylist) {
|
if (clearPlaylist) {
|
||||||
timeline = Timeline.EMPTY;
|
timeline = Timeline.EMPTY;
|
||||||
mediaPeriodId = playbackInfo.getDummyPeriodForEmptyTimeline();
|
mediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline();
|
||||||
contentPositionUs = C.TIME_UNSET;
|
contentPositionUs = C.TIME_UNSET;
|
||||||
startPositionUs = C.TIME_UNSET;
|
startPositionUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import androidx.annotation.CheckResult;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
|
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
|
||||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||||
|
import com.google.android.exoplayer2.Player.RepeatMode;
|
||||||
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||||
@ -663,7 +664,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private void setRepeatModeInternal(@Player.RepeatMode int repeatMode)
|
private void setRepeatModeInternal(@Player.RepeatMode int repeatMode)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
this.repeatMode = repeatMode;
|
this.repeatMode = repeatMode;
|
||||||
if (!queue.updateRepeatMode(repeatMode)) {
|
if (!queue.updateRepeatMode(playbackInfo.timeline, repeatMode)) {
|
||||||
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
||||||
}
|
}
|
||||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||||
@ -672,7 +673,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled)
|
private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
this.shuffleModeEnabled = shuffleModeEnabled;
|
this.shuffleModeEnabled = shuffleModeEnabled;
|
||||||
if (!queue.updateShuffleModeEnabled(shuffleModeEnabled)) {
|
if (!queue.updateShuffleModeEnabled(playbackInfo.timeline, shuffleModeEnabled)) {
|
||||||
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
||||||
}
|
}
|
||||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||||
@ -867,7 +868,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
boolean seekPositionAdjusted;
|
boolean seekPositionAdjusted;
|
||||||
@Nullable
|
@Nullable
|
||||||
Pair<Object, Long> resolvedSeekPosition =
|
Pair<Object, Long> resolvedSeekPosition =
|
||||||
resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true);
|
resolveSeekPosition(
|
||||||
|
playbackInfo.timeline,
|
||||||
|
seekPosition,
|
||||||
|
/* trySubsequentPeriods= */ true,
|
||||||
|
repeatMode,
|
||||||
|
shuffleModeEnabled,
|
||||||
|
window,
|
||||||
|
period);
|
||||||
if (resolvedSeekPosition == null) {
|
if (resolvedSeekPosition == null) {
|
||||||
// The seek position was valid for the timeline that it was performed into, but the
|
// The seek position was valid for the timeline that it was performed into, but the
|
||||||
// timeline has changed or is not ready and a suitable seek position could not be resolved.
|
// timeline has changed or is not ready and a suitable seek position could not be resolved.
|
||||||
@ -879,7 +887,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
// Update the resolved seek position to take ads into account.
|
// Update the resolved seek position to take ads into account.
|
||||||
Object periodUid = resolvedSeekPosition.first;
|
Object periodUid = resolvedSeekPosition.first;
|
||||||
contentPositionUs = resolvedSeekPosition.second;
|
contentPositionUs = resolvedSeekPosition.second;
|
||||||
periodId = queue.resolveMediaPeriodIdForAds(periodUid, contentPositionUs);
|
periodId =
|
||||||
|
queue.resolveMediaPeriodIdForAds(playbackInfo.timeline, periodUid, contentPositionUs);
|
||||||
if (periodId.isAd()) {
|
if (periodId.isAd()) {
|
||||||
periodPositionUs = 0;
|
periodPositionUs = 0;
|
||||||
seekPositionAdjusted = true;
|
seekPositionAdjusted = true;
|
||||||
@ -1131,7 +1140,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
Timeline timeline = playbackInfo.timeline;
|
Timeline timeline = playbackInfo.timeline;
|
||||||
if (clearPlaylist) {
|
if (clearPlaylist) {
|
||||||
timeline = playlist.clear(/* shuffleOrder= */ null);
|
timeline = playlist.clear(/* shuffleOrder= */ null);
|
||||||
queue.setTimeline(timeline);
|
|
||||||
for (PendingMessageInfo pendingMessageInfo : pendingMessages) {
|
for (PendingMessageInfo pendingMessageInfo : pendingMessages) {
|
||||||
pendingMessageInfo.message.markAsProcessed(/* isDelivered= */ false);
|
pendingMessageInfo.message.markAsProcessed(/* isDelivered= */ false);
|
||||||
}
|
}
|
||||||
@ -1144,7 +1152,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
if (resetPosition) {
|
if (resetPosition) {
|
||||||
mediaPeriodId =
|
mediaPeriodId =
|
||||||
timeline.isEmpty()
|
timeline.isEmpty()
|
||||||
? playbackInfo.getDummyPeriodForEmptyTimeline()
|
? PlaybackInfo.getDummyPeriodForEmptyTimeline()
|
||||||
: getDummyFirstMediaPeriodForAds();
|
: getDummyFirstMediaPeriodForAds();
|
||||||
contentPositionUs = C.TIME_UNSET;
|
contentPositionUs = C.TIME_UNSET;
|
||||||
if (!mediaPeriodId.equals(playbackInfo.periodId)) {
|
if (!mediaPeriodId.equals(playbackInfo.periodId)) {
|
||||||
@ -1175,11 +1183,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
private MediaPeriodId getDummyFirstMediaPeriodForAds() {
|
private MediaPeriodId getDummyFirstMediaPeriodForAds() {
|
||||||
MediaPeriodId dummyFirstMediaPeriodId =
|
MediaPeriodId dummyFirstMediaPeriodId =
|
||||||
playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window, period);
|
getDummyFirstMediaPeriodId(
|
||||||
|
playbackInfo.timeline, playbackInfo.periodId, shuffleModeEnabled, window, period);
|
||||||
if (!playbackInfo.timeline.isEmpty()) {
|
if (!playbackInfo.timeline.isEmpty()) {
|
||||||
// add ad metadata if any and propagate the window sequence number to new period id.
|
// add ad metadata if any and propagate the window sequence number to new period id.
|
||||||
dummyFirstMediaPeriodId =
|
dummyFirstMediaPeriodId =
|
||||||
queue.resolveMediaPeriodIdForAds(dummyFirstMediaPeriodId.periodUid, /* positionUs= */ 0);
|
queue.resolveMediaPeriodIdForAds(
|
||||||
|
playbackInfo.timeline, dummyFirstMediaPeriodId.periodUid, /* positionUs= */ 0);
|
||||||
}
|
}
|
||||||
return dummyFirstMediaPeriodId;
|
return dummyFirstMediaPeriodId;
|
||||||
}
|
}
|
||||||
@ -1260,13 +1270,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private boolean resolvePendingMessagePosition(PendingMessageInfo pendingMessageInfo) {
|
private boolean resolvePendingMessagePosition(PendingMessageInfo pendingMessageInfo) {
|
||||||
if (pendingMessageInfo.resolvedPeriodUid == null) {
|
if (pendingMessageInfo.resolvedPeriodUid == null) {
|
||||||
// Position is still unresolved. Try to find window in current timeline.
|
// Position is still unresolved. Try to find window in current timeline.
|
||||||
|
@Nullable
|
||||||
Pair<Object, Long> periodPosition =
|
Pair<Object, Long> periodPosition =
|
||||||
resolveSeekPosition(
|
resolveSeekPosition(
|
||||||
|
playbackInfo.timeline,
|
||||||
new SeekPosition(
|
new SeekPosition(
|
||||||
pendingMessageInfo.message.getTimeline(),
|
pendingMessageInfo.message.getTimeline(),
|
||||||
pendingMessageInfo.message.getWindowIndex(),
|
pendingMessageInfo.message.getWindowIndex(),
|
||||||
C.msToUs(pendingMessageInfo.message.getPositionMs())),
|
C.msToUs(pendingMessageInfo.message.getPositionMs())),
|
||||||
/* trySubsequentPeriods= */ false);
|
/* trySubsequentPeriods= */ false,
|
||||||
|
repeatMode,
|
||||||
|
shuffleModeEnabled,
|
||||||
|
window,
|
||||||
|
period);
|
||||||
if (periodPosition == null) {
|
if (periodPosition == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1509,100 +1525,53 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlaylistInfoRefreshed(Timeline timeline) throws ExoPlaybackException {
|
private void handlePlaylistInfoRefreshed(Timeline timeline) throws ExoPlaybackException {
|
||||||
Timeline oldTimeline = playbackInfo.timeline;
|
PositionUpdateForPlaylistChange positionUpdate =
|
||||||
queue.setTimeline(timeline);
|
resolvePositionForPlaylistChange(
|
||||||
|
timeline,
|
||||||
|
playbackInfo,
|
||||||
|
pendingInitialSeekPosition,
|
||||||
|
queue,
|
||||||
|
repeatMode,
|
||||||
|
shuffleModeEnabled,
|
||||||
|
window,
|
||||||
|
period);
|
||||||
|
|
||||||
playbackInfo = playbackInfo.copyWithTimeline(timeline);
|
playbackInfo = playbackInfo.copyWithTimeline(timeline);
|
||||||
resolvePendingMessagePositions();
|
resolvePendingMessagePositions();
|
||||||
if (timeline.isEmpty()) {
|
if (!timeline.isEmpty()) {
|
||||||
@Nullable SeekPosition pendingInitialSeekPosition = this.pendingInitialSeekPosition;
|
// Retain pending seek position only while the timeline is still empty.
|
||||||
handleEndOfPlaylist();
|
|
||||||
// Retain seek position if any.
|
|
||||||
this.pendingInitialSeekPosition = pendingInitialSeekPosition;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MediaPeriodId oldPeriodId = playbackInfo.periodId;
|
|
||||||
Object newPeriodUid = oldPeriodId.periodUid;
|
|
||||||
long oldContentPositionUs =
|
|
||||||
oldPeriodId.isAd() ? playbackInfo.contentPositionUs : playbackInfo.positionUs;
|
|
||||||
long newContentPositionUs = oldContentPositionUs;
|
|
||||||
boolean forceBufferingState = false;
|
|
||||||
if (pendingInitialSeekPosition != null) {
|
|
||||||
// Resolve initial seek position.
|
|
||||||
@Nullable
|
|
||||||
Pair<Object, Long> periodPosition =
|
|
||||||
resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true);
|
|
||||||
if (periodPosition == null) {
|
|
||||||
// The initial seek in the empty old timeline is invalid in the new timeline.
|
|
||||||
handleEndOfPlaylist();
|
|
||||||
// Use the period resulting from the reset.
|
|
||||||
newPeriodUid = playbackInfo.periodId.periodUid;
|
|
||||||
newContentPositionUs = C.TIME_UNSET;
|
|
||||||
} else {
|
|
||||||
// The pending seek has been resolved successfully in the new timeline.
|
|
||||||
newPeriodUid = periodPosition.first;
|
|
||||||
newContentPositionUs =
|
|
||||||
pendingInitialSeekPosition.windowPositionUs == C.TIME_UNSET
|
|
||||||
? C.TIME_UNSET
|
|
||||||
: periodPosition.second;
|
|
||||||
forceBufferingState = playbackInfo.playbackState == Player.STATE_ENDED;
|
|
||||||
}
|
|
||||||
pendingInitialSeekPosition = null;
|
pendingInitialSeekPosition = null;
|
||||||
} else if (oldTimeline.isEmpty()) {
|
|
||||||
// Resolve to default position if the old timeline is empty and no seek is requested above.
|
|
||||||
Pair<Object, Long> defaultPosition =
|
|
||||||
getPeriodPosition(
|
|
||||||
timeline,
|
|
||||||
timeline.getFirstWindowIndex(shuffleModeEnabled),
|
|
||||||
/* windowPositionUs= */ C.TIME_UNSET);
|
|
||||||
newPeriodUid = defaultPosition.first;
|
|
||||||
newContentPositionUs = C.TIME_UNSET;
|
|
||||||
} else if (timeline.getIndexOfPeriod(newPeriodUid) == C.INDEX_UNSET) {
|
|
||||||
// The current period isn't in the new timeline. Attempt to resolve a subsequent period whose
|
|
||||||
// window we can restart from.
|
|
||||||
@Nullable
|
|
||||||
Object subsequentPeriodUid =
|
|
||||||
resolveSubsequentPeriod(
|
|
||||||
window, period, repeatMode, shuffleModeEnabled, newPeriodUid, oldTimeline, timeline);
|
|
||||||
if (subsequentPeriodUid == null) {
|
|
||||||
// We failed to resolve a suitable restart position but the timeline is not empty.
|
|
||||||
handleEndOfPlaylist();
|
|
||||||
// Use period and position resulting from the reset.
|
|
||||||
newPeriodUid = playbackInfo.periodId.periodUid;
|
|
||||||
newContentPositionUs = C.TIME_UNSET;
|
|
||||||
} else {
|
|
||||||
// We resolved a subsequent period. Start at the default position in the corresponding
|
|
||||||
// window.
|
|
||||||
Pair<Object, Long> defaultPosition =
|
|
||||||
getPeriodPosition(
|
|
||||||
timeline,
|
|
||||||
timeline.getPeriodByUid(subsequentPeriodUid, period).windowIndex,
|
|
||||||
C.TIME_UNSET);
|
|
||||||
newPeriodUid = defaultPosition.first;
|
|
||||||
newContentPositionUs = C.TIME_UNSET;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure ad insertion metadata is up to date.
|
MediaPeriodId oldPeriodId = playbackInfo.periodId;
|
||||||
long contentPositionForAdResolution = newContentPositionUs;
|
long oldContentPositionUs =
|
||||||
if (contentPositionForAdResolution == C.TIME_UNSET) {
|
oldPeriodId.isAd() ? playbackInfo.contentPositionUs : playbackInfo.positionUs;
|
||||||
contentPositionForAdResolution =
|
MediaPeriodId newPeriodId = positionUpdate.periodId;
|
||||||
timeline.getWindow(timeline.getPeriodByUid(newPeriodUid, period).windowIndex, window)
|
long newContentPositionUs = positionUpdate.contentPositionUs;
|
||||||
.defaultPositionUs;
|
boolean forceBufferingState = positionUpdate.forceBufferingState;
|
||||||
|
long newPositionUs = newPeriodId.isAd() ? 0 : newContentPositionUs;
|
||||||
|
|
||||||
|
if (positionUpdate.endPlayback) {
|
||||||
|
if (playbackInfo.playbackState != Player.STATE_IDLE) {
|
||||||
|
setState(Player.STATE_ENDED);
|
||||||
|
}
|
||||||
|
playbackInfo = copyWithNewPosition(newPeriodId, newPositionUs, newContentPositionUs);
|
||||||
|
// Reset, but retain the playlist and new position.
|
||||||
|
resetInternal(
|
||||||
|
/* resetRenderers= */ false,
|
||||||
|
/* resetPosition= */ false,
|
||||||
|
/* releasePlaylist= */ false,
|
||||||
|
/* clearPlaylist= */ false,
|
||||||
|
/* resetError= */ true);
|
||||||
|
if (timeline.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MediaPeriodId periodIdWithAds =
|
|
||||||
queue.resolveMediaPeriodIdForAds(newPeriodUid, contentPositionForAdResolution);
|
|
||||||
boolean oldAndNewPeriodIdAreSame =
|
|
||||||
oldPeriodId.periodUid.equals(newPeriodUid)
|
|
||||||
&& !oldPeriodId.isAd()
|
|
||||||
&& !periodIdWithAds.isAd();
|
|
||||||
// Drop update if we keep playing the same content (MediaPeriod.periodUid are identical) and
|
|
||||||
// only MediaPeriodId.nextAdGroupIndex may have changed. This postpones a potential
|
|
||||||
// discontinuity until we reach the former next ad group position.
|
|
||||||
MediaPeriodId newPeriodId = oldAndNewPeriodIdAreSame ? oldPeriodId : periodIdWithAds;
|
|
||||||
|
|
||||||
if (oldPeriodId.equals(newPeriodId) && oldContentPositionUs == newContentPositionUs) {
|
if (oldPeriodId.equals(newPeriodId) && oldContentPositionUs == newContentPositionUs) {
|
||||||
// We can keep the current playing period. Update the rest of the queued periods.
|
// We can keep the current playing period. Update the rest of the queued periods.
|
||||||
if (!queue.updateQueuedPeriods(rendererPositionUs, getMaxRendererReadPositionUs())) {
|
if (!queue.updateQueuedPeriods(
|
||||||
|
timeline, rendererPositionUs, getMaxRendererReadPositionUs())) {
|
||||||
seekToCurrentPosition(/* sendDiscontinuity= */ false);
|
seekToCurrentPosition(/* sendDiscontinuity= */ false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1613,11 +1582,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
while (periodHolder.getNext() != null) {
|
while (periodHolder.getNext() != null) {
|
||||||
periodHolder = periodHolder.getNext();
|
periodHolder = periodHolder.getNext();
|
||||||
if (periodHolder.info.id.equals(newPeriodId)) {
|
if (periodHolder.info.id.equals(newPeriodId)) {
|
||||||
periodHolder.info = queue.getUpdatedMediaPeriodInfo(periodHolder.info);
|
periodHolder.info = queue.getUpdatedMediaPeriodInfo(timeline, periodHolder.info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long newPositionUs = newPeriodId.isAd() ? 0 : newContentPositionUs;
|
|
||||||
if (!newPeriodId.isAd() && newContentPositionUs == C.TIME_UNSET) {
|
if (!newPeriodId.isAd() && newContentPositionUs == C.TIME_UNSET) {
|
||||||
// Get the default position for the first new period that is not an ad.
|
// Get the default position for the first new period that is not an ad.
|
||||||
int windowIndex = timeline.getPeriodByUid(newPeriodId.periodUid, period).windowIndex;
|
int windowIndex = timeline.getPeriodByUid(newPeriodId.periodUid, period).windowIndex;
|
||||||
@ -1657,95 +1625,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
return maxReadPositionUs;
|
return maxReadPositionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEndOfPlaylist() {
|
|
||||||
if (playbackInfo.playbackState != Player.STATE_IDLE) {
|
|
||||||
setState(Player.STATE_ENDED);
|
|
||||||
}
|
|
||||||
// Reset, but retain the playlist so that it can still resume after a seek or be modified.
|
|
||||||
resetInternal(
|
|
||||||
/* resetRenderers= */ false,
|
|
||||||
/* resetPosition= */ true,
|
|
||||||
/* releasePlaylist= */ false,
|
|
||||||
/* clearPlaylist= */ false,
|
|
||||||
/* resetError= */ true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@link SeekPosition} into the corresponding (periodUid, periodPositionUs) for the
|
|
||||||
* internal timeline.
|
|
||||||
*
|
|
||||||
* @param seekPosition The position to resolve.
|
|
||||||
* @param trySubsequentPeriods Whether the position can be resolved to a subsequent matching
|
|
||||||
* period if the original period is no longer available.
|
|
||||||
* @return The resolved position, or null if resolution was not successful.
|
|
||||||
* @throws IllegalSeekPositionException If the window index of the seek position is outside the
|
|
||||||
* bounds of the timeline.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private Pair<Object, Long> resolveSeekPosition(
|
|
||||||
SeekPosition seekPosition, boolean trySubsequentPeriods) {
|
|
||||||
Timeline timeline = playbackInfo.timeline;
|
|
||||||
Timeline seekTimeline = seekPosition.timeline;
|
|
||||||
if (timeline.isEmpty()) {
|
|
||||||
// We don't have a valid timeline yet, so we can't resolve the position.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (seekTimeline.isEmpty()) {
|
|
||||||
// The application performed a blind seek with an empty timeline (most likely based on
|
|
||||||
// knowledge of what the future timeline will be). Use the internal timeline.
|
|
||||||
seekTimeline = timeline;
|
|
||||||
}
|
|
||||||
// Map the SeekPosition to a position in the corresponding timeline.
|
|
||||||
Pair<Object, Long> periodPosition;
|
|
||||||
try {
|
|
||||||
periodPosition =
|
|
||||||
seekTimeline.getPeriodPosition(
|
|
||||||
window, period, seekPosition.windowIndex, seekPosition.windowPositionUs);
|
|
||||||
} catch (IndexOutOfBoundsException e) {
|
|
||||||
// The window index of the seek position was outside the bounds of the timeline.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (timeline.equals(seekTimeline)) {
|
|
||||||
// Our internal timeline is the seek timeline, so the mapped position is correct.
|
|
||||||
return periodPosition;
|
|
||||||
}
|
|
||||||
// Attempt to find the mapped period in the internal timeline.
|
|
||||||
int periodIndex = timeline.getIndexOfPeriod(periodPosition.first);
|
|
||||||
if (periodIndex != C.INDEX_UNSET) {
|
|
||||||
// We successfully located the period in the internal timeline.
|
|
||||||
return periodPosition;
|
|
||||||
}
|
|
||||||
if (trySubsequentPeriods) {
|
|
||||||
// Try and find a subsequent period from the seek timeline in the internal timeline.
|
|
||||||
@Nullable
|
|
||||||
Object periodUid =
|
|
||||||
resolveSubsequentPeriod(
|
|
||||||
window,
|
|
||||||
period,
|
|
||||||
repeatMode,
|
|
||||||
shuffleModeEnabled,
|
|
||||||
periodPosition.first,
|
|
||||||
seekTimeline,
|
|
||||||
timeline);
|
|
||||||
if (periodUid != null) {
|
|
||||||
// We found one. Use the default position of the corresponding window.
|
|
||||||
return getPeriodPosition(
|
|
||||||
timeline, timeline.getPeriodByUid(periodUid, period).windowIndex, C.TIME_UNSET);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We didn't find one. Give up.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls {@link Timeline#getPeriodPosition(Timeline.Window, Timeline.Period, int, long)} using the
|
|
||||||
* current timeline.
|
|
||||||
*/
|
|
||||||
private Pair<Object, Long> getPeriodPosition(
|
|
||||||
Timeline timeline, int windowIndex, long windowPositionUs) {
|
|
||||||
return timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePeriods() throws ExoPlaybackException, IOException {
|
private void updatePeriods() throws ExoPlaybackException, IOException {
|
||||||
if (playbackInfo.timeline.isEmpty() || !playlist.isPrepared()) {
|
if (playbackInfo.timeline.isEmpty() || !playlist.isPrepared()) {
|
||||||
// We're waiting to get information about periods.
|
// We're waiting to get information about periods.
|
||||||
@ -2199,6 +2078,234 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
.sendToTarget();
|
.sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PositionUpdateForPlaylistChange resolvePositionForPlaylistChange(
|
||||||
|
Timeline timeline,
|
||||||
|
PlaybackInfo playbackInfo,
|
||||||
|
@Nullable SeekPosition pendingInitialSeekPosition,
|
||||||
|
MediaPeriodQueue queue,
|
||||||
|
@RepeatMode int repeatMode,
|
||||||
|
boolean shuffleModeEnabled,
|
||||||
|
Timeline.Window window,
|
||||||
|
Timeline.Period period) {
|
||||||
|
if (timeline.isEmpty()) {
|
||||||
|
return new PositionUpdateForPlaylistChange(
|
||||||
|
PlaybackInfo.getDummyPeriodForEmptyTimeline(),
|
||||||
|
/* contentPositionUs= */ C.TIME_UNSET,
|
||||||
|
/* forceBufferingState= */ false,
|
||||||
|
/* endPlayback= */ true);
|
||||||
|
}
|
||||||
|
MediaPeriodId oldPeriodId = playbackInfo.periodId;
|
||||||
|
Object newPeriodUid = oldPeriodId.periodUid;
|
||||||
|
long oldContentPositionUs =
|
||||||
|
oldPeriodId.isAd() ? playbackInfo.contentPositionUs : playbackInfo.positionUs;
|
||||||
|
long newContentPositionUs = oldContentPositionUs;
|
||||||
|
boolean forceBufferingState = false;
|
||||||
|
boolean endPlayback = false;
|
||||||
|
if (pendingInitialSeekPosition != null) {
|
||||||
|
// Resolve initial seek position.
|
||||||
|
@Nullable
|
||||||
|
Pair<Object, Long> periodPosition =
|
||||||
|
resolveSeekPosition(
|
||||||
|
timeline,
|
||||||
|
pendingInitialSeekPosition,
|
||||||
|
/* trySubsequentPeriods= */ true,
|
||||||
|
repeatMode,
|
||||||
|
shuffleModeEnabled,
|
||||||
|
window,
|
||||||
|
period);
|
||||||
|
if (periodPosition == null) {
|
||||||
|
// The initial seek in the empty old timeline is invalid in the new timeline.
|
||||||
|
endPlayback = true;
|
||||||
|
newPeriodUid =
|
||||||
|
getDummyFirstMediaPeriodId(
|
||||||
|
timeline, playbackInfo.periodId, shuffleModeEnabled, window, period)
|
||||||
|
.periodUid;
|
||||||
|
newContentPositionUs = C.TIME_UNSET;
|
||||||
|
} else {
|
||||||
|
// The pending seek has been resolved successfully in the new timeline.
|
||||||
|
newPeriodUid = periodPosition.first;
|
||||||
|
newContentPositionUs =
|
||||||
|
pendingInitialSeekPosition.windowPositionUs == C.TIME_UNSET
|
||||||
|
? C.TIME_UNSET
|
||||||
|
: periodPosition.second;
|
||||||
|
forceBufferingState = playbackInfo.playbackState == Player.STATE_ENDED;
|
||||||
|
}
|
||||||
|
} else if (playbackInfo.timeline.isEmpty()) {
|
||||||
|
// Resolve to default position if the old timeline is empty and no seek is requested above.
|
||||||
|
Pair<Object, Long> defaultPosition =
|
||||||
|
timeline.getPeriodPosition(
|
||||||
|
window,
|
||||||
|
period,
|
||||||
|
timeline.getFirstWindowIndex(shuffleModeEnabled),
|
||||||
|
/* windowPositionUs= */ C.TIME_UNSET);
|
||||||
|
newPeriodUid = defaultPosition.first;
|
||||||
|
newContentPositionUs = C.TIME_UNSET;
|
||||||
|
} else if (timeline.getIndexOfPeriod(newPeriodUid) == C.INDEX_UNSET) {
|
||||||
|
// The current period isn't in the new timeline. Attempt to resolve a subsequent period whose
|
||||||
|
// window we can restart from.
|
||||||
|
@Nullable
|
||||||
|
Object subsequentPeriodUid =
|
||||||
|
resolveSubsequentPeriod(
|
||||||
|
window,
|
||||||
|
period,
|
||||||
|
repeatMode,
|
||||||
|
shuffleModeEnabled,
|
||||||
|
newPeriodUid,
|
||||||
|
playbackInfo.timeline,
|
||||||
|
timeline);
|
||||||
|
if (subsequentPeriodUid == null) {
|
||||||
|
// We failed to resolve a suitable restart position but the timeline is not empty.
|
||||||
|
endPlayback = true;
|
||||||
|
newPeriodUid =
|
||||||
|
getDummyFirstMediaPeriodId(
|
||||||
|
timeline, playbackInfo.periodId, shuffleModeEnabled, window, period)
|
||||||
|
.periodUid;
|
||||||
|
newContentPositionUs = C.TIME_UNSET;
|
||||||
|
} else {
|
||||||
|
// We resolved a subsequent period. Start at the default position in the corresponding
|
||||||
|
// window.
|
||||||
|
Pair<Object, Long> defaultPosition =
|
||||||
|
timeline.getPeriodPosition(
|
||||||
|
window,
|
||||||
|
period,
|
||||||
|
timeline.getPeriodByUid(subsequentPeriodUid, period).windowIndex,
|
||||||
|
/* windowPositionUs= */ C.TIME_UNSET);
|
||||||
|
newPeriodUid = defaultPosition.first;
|
||||||
|
newContentPositionUs = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure ad insertion metadata is up to date.
|
||||||
|
long contentPositionForAdResolution = newContentPositionUs;
|
||||||
|
if (contentPositionForAdResolution == C.TIME_UNSET) {
|
||||||
|
contentPositionForAdResolution =
|
||||||
|
timeline.getWindow(timeline.getPeriodByUid(newPeriodUid, period).windowIndex, window)
|
||||||
|
.defaultPositionUs;
|
||||||
|
}
|
||||||
|
MediaPeriodId periodIdWithAds =
|
||||||
|
queue.resolveMediaPeriodIdForAds(timeline, newPeriodUid, contentPositionForAdResolution);
|
||||||
|
boolean oldAndNewPeriodIdAreSame =
|
||||||
|
oldPeriodId.periodUid.equals(newPeriodUid)
|
||||||
|
&& !oldPeriodId.isAd()
|
||||||
|
&& !periodIdWithAds.isAd();
|
||||||
|
// Drop update if we keep playing the same content (MediaPeriod.periodUid are identical) and
|
||||||
|
// only MediaPeriodId.nextAdGroupIndex may have changed. This postpones a potential
|
||||||
|
// discontinuity until we reach the former next ad group position.
|
||||||
|
MediaPeriodId newPeriodId = oldAndNewPeriodIdAreSame ? oldPeriodId : periodIdWithAds;
|
||||||
|
|
||||||
|
return new PositionUpdateForPlaylistChange(
|
||||||
|
newPeriodId, newContentPositionUs, forceBufferingState, endPlayback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a {@link SeekPosition} into the corresponding (periodUid, periodPositionUs) for the
|
||||||
|
* internal timeline.
|
||||||
|
*
|
||||||
|
* @param seekPosition The position to resolve.
|
||||||
|
* @param trySubsequentPeriods Whether the position can be resolved to a subsequent matching
|
||||||
|
* period if the original period is no longer available.
|
||||||
|
* @return The resolved position, or null if resolution was not successful.
|
||||||
|
* @throws IllegalSeekPositionException If the window index of the seek position is outside the
|
||||||
|
* bounds of the timeline.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Pair<Object, Long> resolveSeekPosition(
|
||||||
|
Timeline timeline,
|
||||||
|
SeekPosition seekPosition,
|
||||||
|
boolean trySubsequentPeriods,
|
||||||
|
@RepeatMode int repeatMode,
|
||||||
|
boolean shuffleModeEnabled,
|
||||||
|
Timeline.Window window,
|
||||||
|
Timeline.Period period) {
|
||||||
|
Timeline seekTimeline = seekPosition.timeline;
|
||||||
|
if (timeline.isEmpty()) {
|
||||||
|
// We don't have a valid timeline yet, so we can't resolve the position.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (seekTimeline.isEmpty()) {
|
||||||
|
// The application performed a blind seek with an empty timeline (most likely based on
|
||||||
|
// knowledge of what the future timeline will be). Use the internal timeline.
|
||||||
|
seekTimeline = timeline;
|
||||||
|
}
|
||||||
|
// Map the SeekPosition to a position in the corresponding timeline.
|
||||||
|
Pair<Object, Long> periodPosition;
|
||||||
|
try {
|
||||||
|
periodPosition =
|
||||||
|
seekTimeline.getPeriodPosition(
|
||||||
|
window, period, seekPosition.windowIndex, seekPosition.windowPositionUs);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
// The window index of the seek position was outside the bounds of the timeline.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (timeline.equals(seekTimeline)) {
|
||||||
|
// Our internal timeline is the seek timeline, so the mapped position is correct.
|
||||||
|
return periodPosition;
|
||||||
|
}
|
||||||
|
// Attempt to find the mapped period in the internal timeline.
|
||||||
|
int periodIndex = timeline.getIndexOfPeriod(periodPosition.first);
|
||||||
|
if (periodIndex != C.INDEX_UNSET) {
|
||||||
|
// We successfully located the period in the internal timeline.
|
||||||
|
return periodPosition;
|
||||||
|
}
|
||||||
|
if (trySubsequentPeriods) {
|
||||||
|
// Try and find a subsequent period from the seek timeline in the internal timeline.
|
||||||
|
@Nullable
|
||||||
|
Object periodUid =
|
||||||
|
resolveSubsequentPeriod(
|
||||||
|
window,
|
||||||
|
period,
|
||||||
|
repeatMode,
|
||||||
|
shuffleModeEnabled,
|
||||||
|
periodPosition.first,
|
||||||
|
seekTimeline,
|
||||||
|
timeline);
|
||||||
|
if (periodUid != null) {
|
||||||
|
// We found one. Use the default position of the corresponding window.
|
||||||
|
return timeline.getPeriodPosition(
|
||||||
|
window,
|
||||||
|
period,
|
||||||
|
timeline.getPeriodByUid(periodUid, period).windowIndex,
|
||||||
|
/* windowPositionUs= */ C.TIME_UNSET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We didn't find one. Give up.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns dummy media period id for the first-to-be-played period of the current timeline.
|
||||||
|
*
|
||||||
|
* @param timeline The timeline whose first-to-be-played period needs to be found.
|
||||||
|
* @param currentMediaPeriodId The current media period id, not guaranteed to be part of {@code
|
||||||
|
* timeline}.
|
||||||
|
* @param shuffleModeEnabled Whether shuffle mode is enabled.
|
||||||
|
* @param window A writable {@link Timeline.Window}.
|
||||||
|
* @param period A writable {@link Timeline.Period}.
|
||||||
|
* @return A dummy media period id for the first-to-be-played period of the current timeline.
|
||||||
|
*/
|
||||||
|
private static MediaPeriodId getDummyFirstMediaPeriodId(
|
||||||
|
Timeline timeline,
|
||||||
|
MediaPeriodId currentMediaPeriodId,
|
||||||
|
boolean shuffleModeEnabled,
|
||||||
|
Timeline.Window window,
|
||||||
|
Timeline.Period period) {
|
||||||
|
if (timeline.isEmpty()) {
|
||||||
|
return PlaybackInfo.getDummyPeriodForEmptyTimeline();
|
||||||
|
}
|
||||||
|
int firstWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
|
||||||
|
int firstPeriodIndex = timeline.getWindow(firstWindowIndex, window).firstPeriodIndex;
|
||||||
|
int currentPeriodIndex = timeline.getIndexOfPeriod(currentMediaPeriodId.periodUid);
|
||||||
|
long windowSequenceNumber = C.INDEX_UNSET;
|
||||||
|
if (currentPeriodIndex != C.INDEX_UNSET) {
|
||||||
|
int currentWindowIndex = timeline.getPeriod(currentPeriodIndex, period).windowIndex;
|
||||||
|
if (firstWindowIndex == currentWindowIndex) {
|
||||||
|
// Keep window sequence number if the new position is still in the same window.
|
||||||
|
windowSequenceNumber = currentMediaPeriodId.windowSequenceNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex), windowSequenceNumber);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a period index into an old timeline, finds the first subsequent period that also exists
|
* Given a period index into an old timeline, finds the first subsequent period that also exists
|
||||||
* in a new timeline. The uid of this period in the new timeline is returned.
|
* in a new timeline. The uid of this period in the new timeline is returned.
|
||||||
@ -2260,6 +2367,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class PositionUpdateForPlaylistChange {
|
||||||
|
public final MediaPeriodId periodId;
|
||||||
|
public final long contentPositionUs;
|
||||||
|
public final boolean forceBufferingState;
|
||||||
|
public final boolean endPlayback;
|
||||||
|
|
||||||
|
public PositionUpdateForPlaylistChange(
|
||||||
|
MediaPeriodId periodId,
|
||||||
|
long contentPositionUs,
|
||||||
|
boolean forceBufferingState,
|
||||||
|
boolean endPlayback) {
|
||||||
|
this.periodId = periodId;
|
||||||
|
this.contentPositionUs = contentPositionUs;
|
||||||
|
this.forceBufferingState = forceBufferingState;
|
||||||
|
this.endPlayback = endPlayback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class PendingMessageInfo implements Comparable<PendingMessageInfo> {
|
private static final class PendingMessageInfo implements Comparable<PendingMessageInfo> {
|
||||||
|
|
||||||
public final PlayerMessage message;
|
public final PlayerMessage message;
|
||||||
|
@ -43,7 +43,6 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
|
|
||||||
private long nextWindowSequenceNumber;
|
private long nextWindowSequenceNumber;
|
||||||
private Timeline timeline;
|
|
||||||
private @RepeatMode int repeatMode;
|
private @RepeatMode int repeatMode;
|
||||||
private boolean shuffleModeEnabled;
|
private boolean shuffleModeEnabled;
|
||||||
@Nullable private MediaPeriodHolder playing;
|
@Nullable private MediaPeriodHolder playing;
|
||||||
@ -57,33 +56,32 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
public MediaPeriodQueue() {
|
public MediaPeriodQueue() {
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
timeline = Timeline.EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link Timeline}. Call {@link #updateQueuedPeriods(long, long)} to update the queued
|
|
||||||
* media periods to take into account the new timeline.
|
|
||||||
*/
|
|
||||||
public void setTimeline(Timeline timeline) {
|
|
||||||
this.timeline = timeline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link RepeatMode} and returns whether the repeat mode change has been fully handled.
|
* Sets the {@link RepeatMode} and returns whether the repeat mode change has been fully handled.
|
||||||
* If not, it is necessary to seek to the current playback position.
|
* If not, it is necessary to seek to the current playback position.
|
||||||
|
*
|
||||||
|
* @param timeline The current timeline.
|
||||||
|
* @param repeatMode The new repeat mode.
|
||||||
|
* @return Whether the repeat mode change has been fully handled.
|
||||||
*/
|
*/
|
||||||
public boolean updateRepeatMode(@RepeatMode int repeatMode) {
|
public boolean updateRepeatMode(Timeline timeline, @RepeatMode int repeatMode) {
|
||||||
this.repeatMode = repeatMode;
|
this.repeatMode = repeatMode;
|
||||||
return updateForPlaybackModeChange();
|
return updateForPlaybackModeChange(timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether shuffling is enabled and returns whether the shuffle mode change has been fully
|
* Sets whether shuffling is enabled and returns whether the shuffle mode change has been fully
|
||||||
* handled. If not, it is necessary to seek to the current playback position.
|
* handled. If not, it is necessary to seek to the current playback position.
|
||||||
|
*
|
||||||
|
* @param timeline The current timeline.
|
||||||
|
* @param shuffleModeEnabled Whether shuffling mode is enabled.
|
||||||
|
* @return Whether the shuffle mode change has been fully handled.
|
||||||
*/
|
*/
|
||||||
public boolean updateShuffleModeEnabled(boolean shuffleModeEnabled) {
|
public boolean updateShuffleModeEnabled(Timeline timeline, boolean shuffleModeEnabled) {
|
||||||
this.shuffleModeEnabled = shuffleModeEnabled;
|
this.shuffleModeEnabled = shuffleModeEnabled;
|
||||||
return updateForPlaybackModeChange();
|
return updateForPlaybackModeChange(timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether {@code mediaPeriod} is the current loading media period. */
|
/** Returns whether {@code mediaPeriod} is the current loading media period. */
|
||||||
@ -124,7 +122,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
long rendererPositionUs, PlaybackInfo playbackInfo) {
|
long rendererPositionUs, PlaybackInfo playbackInfo) {
|
||||||
return loading == null
|
return loading == null
|
||||||
? getFirstMediaPeriodInfo(playbackInfo)
|
? getFirstMediaPeriodInfo(playbackInfo)
|
||||||
: getFollowingMediaPeriodInfo(loading, rendererPositionUs);
|
: getFollowingMediaPeriodInfo(playbackInfo.timeline, loading, rendererPositionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -286,13 +284,15 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
* current playback position. The method assumes that the first media period in the queue is still
|
* current playback position. The method assumes that the first media period in the queue is still
|
||||||
* consistent with the new timeline.
|
* consistent with the new timeline.
|
||||||
*
|
*
|
||||||
|
* @param timeline The new timeline.
|
||||||
* @param rendererPositionUs The current renderer position in microseconds.
|
* @param rendererPositionUs The current renderer position in microseconds.
|
||||||
* @param maxRendererReadPositionUs The maximum renderer position up to which renderers have read
|
* @param maxRendererReadPositionUs The maximum renderer position up to which renderers have read
|
||||||
* the current reading media period in microseconds, or {@link C#TIME_END_OF_SOURCE} if they
|
* the current reading media period in microseconds, or {@link C#TIME_END_OF_SOURCE} if they
|
||||||
* have read to the end.
|
* have read to the end.
|
||||||
* @return Whether the timeline change has been handled completely.
|
* @return Whether the timeline change has been handled completely.
|
||||||
*/
|
*/
|
||||||
public boolean updateQueuedPeriods(long rendererPositionUs, long maxRendererReadPositionUs) {
|
public boolean updateQueuedPeriods(
|
||||||
|
Timeline timeline, long rendererPositionUs, long maxRendererReadPositionUs) {
|
||||||
// TODO: Merge this into setTimeline so that the queue gets updated as soon as the new timeline
|
// TODO: Merge this into setTimeline so that the queue gets updated as soon as the new timeline
|
||||||
// is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be
|
// is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be
|
||||||
// handled here.
|
// handled here.
|
||||||
@ -307,9 +307,10 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
// The id and start position of the first period have already been verified by
|
// The id and start position of the first period have already been verified by
|
||||||
// ExoPlayerImplInternal.handleSourceInfoRefreshed. Just update duration, isLastInTimeline
|
// ExoPlayerImplInternal.handleSourceInfoRefreshed. Just update duration, isLastInTimeline
|
||||||
// and isLastInPeriod flags.
|
// and isLastInPeriod flags.
|
||||||
newPeriodInfo = getUpdatedMediaPeriodInfo(oldPeriodInfo);
|
newPeriodInfo = getUpdatedMediaPeriodInfo(timeline, oldPeriodInfo);
|
||||||
} else {
|
} else {
|
||||||
newPeriodInfo = getFollowingMediaPeriodInfo(previousPeriodHolder, rendererPositionUs);
|
newPeriodInfo =
|
||||||
|
getFollowingMediaPeriodInfo(timeline, previousPeriodHolder, rendererPositionUs);
|
||||||
if (newPeriodInfo == null) {
|
if (newPeriodInfo == null) {
|
||||||
// We've loaded a next media period that is not in the new timeline.
|
// We've loaded a next media period that is not in the new timeline.
|
||||||
return !removeAfter(previousPeriodHolder);
|
return !removeAfter(previousPeriodHolder);
|
||||||
@ -349,13 +350,14 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
* account the current timeline. This method must only be called if the period is still part of
|
* account the current timeline. This method must only be called if the period is still part of
|
||||||
* the current timeline.
|
* the current timeline.
|
||||||
*
|
*
|
||||||
|
* @param timeline The current timeline used to update the media period.
|
||||||
* @param info Media period info for a media period based on an old timeline.
|
* @param info Media period info for a media period based on an old timeline.
|
||||||
* @return The updated media period info for the current timeline.
|
* @return The updated media period info for the current timeline.
|
||||||
*/
|
*/
|
||||||
public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info) {
|
public MediaPeriodInfo getUpdatedMediaPeriodInfo(Timeline timeline, MediaPeriodInfo info) {
|
||||||
MediaPeriodId id = info.id;
|
MediaPeriodId id = info.id;
|
||||||
boolean isLastInPeriod = isLastInPeriod(id);
|
boolean isLastInPeriod = isLastInPeriod(id);
|
||||||
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
|
boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod);
|
||||||
timeline.getPeriodByUid(info.id.periodUid, period);
|
timeline.getPeriodByUid(info.id.periodUid, period);
|
||||||
long durationUs =
|
long durationUs =
|
||||||
id.isAd()
|
id.isAd()
|
||||||
@ -378,13 +380,16 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
* played, returning an identifier for an ad group if one needs to be played before the specified
|
* played, returning an identifier for an ad group if one needs to be played before the specified
|
||||||
* position, or an identifier for a content media period if not.
|
* position, or an identifier for a content media period if not.
|
||||||
*
|
*
|
||||||
|
* @param timeline The timeline the period is part of.
|
||||||
* @param periodUid The uid of the timeline period to play.
|
* @param periodUid The uid of the timeline period to play.
|
||||||
* @param positionUs The next content position in the period to play.
|
* @param positionUs The next content position in the period to play.
|
||||||
* @return The identifier for the first media period to play, taking into account unplayed ads.
|
* @return The identifier for the first media period to play, taking into account unplayed ads.
|
||||||
*/
|
*/
|
||||||
public MediaPeriodId resolveMediaPeriodIdForAds(Object periodUid, long positionUs) {
|
public MediaPeriodId resolveMediaPeriodIdForAds(
|
||||||
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(periodUid);
|
Timeline timeline, Object periodUid, long positionUs) {
|
||||||
return resolveMediaPeriodIdForAds(periodUid, positionUs, windowSequenceNumber);
|
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
|
||||||
|
return resolveMediaPeriodIdForAds(
|
||||||
|
timeline, periodUid, positionUs, windowSequenceNumber, period);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
@ -394,14 +399,20 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
* played, returning an identifier for an ad group if one needs to be played before the specified
|
* played, returning an identifier for an ad group if one needs to be played before the specified
|
||||||
* position, or an identifier for a content media period if not.
|
* position, or an identifier for a content media period if not.
|
||||||
*
|
*
|
||||||
|
* @param timeline The timeline the period is part of.
|
||||||
* @param periodUid The uid of the timeline period to play.
|
* @param periodUid The uid of the timeline period to play.
|
||||||
* @param positionUs The next content position in the period to play.
|
* @param positionUs The next content position in the period to play.
|
||||||
* @param windowSequenceNumber The sequence number of the window in the buffered sequence of
|
* @param windowSequenceNumber The sequence number of the window in the buffered sequence of
|
||||||
* windows this period is part of.
|
* windows this period is part of.
|
||||||
|
* @param period A scratch {@link Timeline.Period}.
|
||||||
* @return The identifier for the first media period to play, taking into account unplayed ads.
|
* @return The identifier for the first media period to play, taking into account unplayed ads.
|
||||||
*/
|
*/
|
||||||
private MediaPeriodId resolveMediaPeriodIdForAds(
|
private static MediaPeriodId resolveMediaPeriodIdForAds(
|
||||||
Object periodUid, long positionUs, long windowSequenceNumber) {
|
Timeline timeline,
|
||||||
|
Object periodUid,
|
||||||
|
long positionUs,
|
||||||
|
long windowSequenceNumber,
|
||||||
|
Timeline.Period period) {
|
||||||
timeline.getPeriodByUid(periodUid, period);
|
timeline.getPeriodByUid(periodUid, period);
|
||||||
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
|
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
|
||||||
if (adGroupIndex == C.INDEX_UNSET) {
|
if (adGroupIndex == C.INDEX_UNSET) {
|
||||||
@ -418,10 +429,11 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
* the window sequence number of an existing matching media period or by creating a new window
|
* the window sequence number of an existing matching media period or by creating a new window
|
||||||
* sequence number.
|
* sequence number.
|
||||||
*
|
*
|
||||||
|
* @param timeline The timeline the period is part of.
|
||||||
* @param periodUid The uid of the timeline period.
|
* @param periodUid The uid of the timeline period.
|
||||||
* @return A window sequence number for a media period created for this timeline period.
|
* @return A window sequence number for a media period created for this timeline period.
|
||||||
*/
|
*/
|
||||||
private long resolvePeriodIndexToWindowSequenceNumber(Object periodUid) {
|
private long resolvePeriodIndexToWindowSequenceNumber(Timeline timeline, Object periodUid) {
|
||||||
int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex;
|
int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex;
|
||||||
if (oldFrontPeriodUid != null) {
|
if (oldFrontPeriodUid != null) {
|
||||||
int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid);
|
int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid);
|
||||||
@ -481,8 +493,10 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
/**
|
/**
|
||||||
* Updates the queue for any playback mode change, and returns whether the change was fully
|
* Updates the queue for any playback mode change, and returns whether the change was fully
|
||||||
* handled. If not, it is necessary to seek to the current playback position.
|
* handled. If not, it is necessary to seek to the current playback position.
|
||||||
|
*
|
||||||
|
* @param timeline The current timeline.
|
||||||
*/
|
*/
|
||||||
private boolean updateForPlaybackModeChange() {
|
private boolean updateForPlaybackModeChange(Timeline timeline) {
|
||||||
// Find the last existing period holder that matches the new period order.
|
// Find the last existing period holder that matches the new period order.
|
||||||
MediaPeriodHolder lastValidPeriodHolder = playing;
|
MediaPeriodHolder lastValidPeriodHolder = playing;
|
||||||
if (lastValidPeriodHolder == null) {
|
if (lastValidPeriodHolder == null) {
|
||||||
@ -514,7 +528,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder);
|
boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder);
|
||||||
|
|
||||||
// Update the period info for the last holder, as it may now be the last period in the timeline.
|
// Update the period info for the last holder, as it may now be the last period in the timeline.
|
||||||
lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info);
|
lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(timeline, lastValidPeriodHolder.info);
|
||||||
|
|
||||||
// If renderers may have read from a period that's been removed, it is necessary to restart.
|
// If renderers may have read from a period that's been removed, it is necessary to restart.
|
||||||
return !readingPeriodRemoved;
|
return !readingPeriodRemoved;
|
||||||
@ -525,13 +539,17 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
*/
|
*/
|
||||||
private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) {
|
private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) {
|
||||||
return getMediaPeriodInfo(
|
return getMediaPeriodInfo(
|
||||||
playbackInfo.periodId, playbackInfo.contentPositionUs, playbackInfo.startPositionUs);
|
playbackInfo.timeline,
|
||||||
|
playbackInfo.periodId,
|
||||||
|
playbackInfo.contentPositionUs,
|
||||||
|
playbackInfo.startPositionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link MediaPeriodInfo} for the media period following {@code mediaPeriodHolder}'s
|
* Returns the {@link MediaPeriodInfo} for the media period following {@code mediaPeriodHolder}'s
|
||||||
* media period.
|
* media period.
|
||||||
*
|
*
|
||||||
|
* @param timeline The current timeline.
|
||||||
* @param mediaPeriodHolder The media period holder.
|
* @param mediaPeriodHolder The media period holder.
|
||||||
* @param rendererPositionUs The current renderer position in microseconds.
|
* @param rendererPositionUs The current renderer position in microseconds.
|
||||||
* @return The following media period's info, or {@code null} if it is not yet possible to get the
|
* @return The following media period's info, or {@code null} if it is not yet possible to get the
|
||||||
@ -539,7 +557,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private MediaPeriodInfo getFollowingMediaPeriodInfo(
|
private MediaPeriodInfo getFollowingMediaPeriodInfo(
|
||||||
MediaPeriodHolder mediaPeriodHolder, long rendererPositionUs) {
|
Timeline timeline, MediaPeriodHolder mediaPeriodHolder, long rendererPositionUs) {
|
||||||
// TODO: This method is called repeatedly from ExoPlayerImplInternal.maybeUpdateLoadingPeriod
|
// TODO: This method is called repeatedly from ExoPlayerImplInternal.maybeUpdateLoadingPeriod
|
||||||
// but if the timeline is not ready to provide the next period it can't return a non-null value
|
// but if the timeline is not ready to provide the next period it can't return a non-null value
|
||||||
// until the timeline is updated. Store whether the next timeline period is ready when the
|
// until the timeline is updated. Store whether the next timeline period is ready when the
|
||||||
@ -571,6 +589,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
// want it to be from its default start position, so project the default start position
|
// want it to be from its default start position, so project the default start position
|
||||||
// forward by the duration of the buffer, and start buffering from this point.
|
// forward by the duration of the buffer, and start buffering from this point.
|
||||||
contentPositionUs = C.TIME_UNSET;
|
contentPositionUs = C.TIME_UNSET;
|
||||||
|
@Nullable
|
||||||
Pair<Object, Long> defaultPosition =
|
Pair<Object, Long> defaultPosition =
|
||||||
timeline.getPeriodPosition(
|
timeline.getPeriodPosition(
|
||||||
window,
|
window,
|
||||||
@ -595,8 +614,9 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
contentPositionUs = 0;
|
contentPositionUs = 0;
|
||||||
}
|
}
|
||||||
MediaPeriodId periodId =
|
MediaPeriodId periodId =
|
||||||
resolveMediaPeriodIdForAds(nextPeriodUid, startPositionUs, windowSequenceNumber);
|
resolveMediaPeriodIdForAds(
|
||||||
return getMediaPeriodInfo(periodId, contentPositionUs, startPositionUs);
|
timeline, nextPeriodUid, startPositionUs, windowSequenceNumber, period);
|
||||||
|
return getMediaPeriodInfo(timeline, periodId, contentPositionUs, startPositionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaPeriodId currentPeriodId = mediaPeriodInfo.id;
|
MediaPeriodId currentPeriodId = mediaPeriodInfo.id;
|
||||||
@ -614,6 +634,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup)
|
return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup)
|
||||||
? null
|
? null
|
||||||
: getMediaPeriodInfoForAd(
|
: getMediaPeriodInfoForAd(
|
||||||
|
timeline,
|
||||||
currentPeriodId.periodUid,
|
currentPeriodId.periodUid,
|
||||||
adGroupIndex,
|
adGroupIndex,
|
||||||
nextAdIndexInAdGroup,
|
nextAdIndexInAdGroup,
|
||||||
@ -625,6 +646,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
if (startPositionUs == C.TIME_UNSET) {
|
if (startPositionUs == C.TIME_UNSET) {
|
||||||
// If we're transitioning from an ad group to content starting from its default position,
|
// If we're transitioning from an ad group to content starting from its default position,
|
||||||
// project the start position forward as if this were a transition to a new window.
|
// project the start position forward as if this were a transition to a new window.
|
||||||
|
@Nullable
|
||||||
Pair<Object, Long> defaultPosition =
|
Pair<Object, Long> defaultPosition =
|
||||||
timeline.getPeriodPosition(
|
timeline.getPeriodPosition(
|
||||||
window,
|
window,
|
||||||
@ -638,7 +660,10 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
startPositionUs = defaultPosition.second;
|
startPositionUs = defaultPosition.second;
|
||||||
}
|
}
|
||||||
return getMediaPeriodInfoForContent(
|
return getMediaPeriodInfoForContent(
|
||||||
currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber);
|
timeline,
|
||||||
|
currentPeriodId.periodUid,
|
||||||
|
startPositionUs,
|
||||||
|
currentPeriodId.windowSequenceNumber);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Play the next ad group if it's available.
|
// Play the next ad group if it's available.
|
||||||
@ -646,6 +671,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
if (nextAdGroupIndex == C.INDEX_UNSET) {
|
if (nextAdGroupIndex == C.INDEX_UNSET) {
|
||||||
// The next ad group can't be played. Play content from the previous end position instead.
|
// The next ad group can't be played. Play content from the previous end position instead.
|
||||||
return getMediaPeriodInfoForContent(
|
return getMediaPeriodInfoForContent(
|
||||||
|
timeline,
|
||||||
currentPeriodId.periodUid,
|
currentPeriodId.periodUid,
|
||||||
/* startPositionUs= */ mediaPeriodInfo.durationUs,
|
/* startPositionUs= */ mediaPeriodInfo.durationUs,
|
||||||
currentPeriodId.windowSequenceNumber);
|
currentPeriodId.windowSequenceNumber);
|
||||||
@ -654,6 +680,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup)
|
return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup)
|
||||||
? null
|
? null
|
||||||
: getMediaPeriodInfoForAd(
|
: getMediaPeriodInfoForAd(
|
||||||
|
timeline,
|
||||||
currentPeriodId.periodUid,
|
currentPeriodId.periodUid,
|
||||||
nextAdGroupIndex,
|
nextAdGroupIndex,
|
||||||
adIndexInAdGroup,
|
adIndexInAdGroup,
|
||||||
@ -663,24 +690,27 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private MediaPeriodInfo getMediaPeriodInfo(
|
private MediaPeriodInfo getMediaPeriodInfo(
|
||||||
MediaPeriodId id, long contentPositionUs, long startPositionUs) {
|
Timeline timeline, MediaPeriodId id, long contentPositionUs, long startPositionUs) {
|
||||||
timeline.getPeriodByUid(id.periodUid, period);
|
timeline.getPeriodByUid(id.periodUid, period);
|
||||||
if (id.isAd()) {
|
if (id.isAd()) {
|
||||||
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
|
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return getMediaPeriodInfoForAd(
|
return getMediaPeriodInfoForAd(
|
||||||
|
timeline,
|
||||||
id.periodUid,
|
id.periodUid,
|
||||||
id.adGroupIndex,
|
id.adGroupIndex,
|
||||||
id.adIndexInAdGroup,
|
id.adIndexInAdGroup,
|
||||||
contentPositionUs,
|
contentPositionUs,
|
||||||
id.windowSequenceNumber);
|
id.windowSequenceNumber);
|
||||||
} else {
|
} else {
|
||||||
return getMediaPeriodInfoForContent(id.periodUid, startPositionUs, id.windowSequenceNumber);
|
return getMediaPeriodInfoForContent(
|
||||||
|
timeline, id.periodUid, startPositionUs, id.windowSequenceNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaPeriodInfo getMediaPeriodInfoForAd(
|
private MediaPeriodInfo getMediaPeriodInfoForAd(
|
||||||
|
Timeline timeline,
|
||||||
Object periodUid,
|
Object periodUid,
|
||||||
int adGroupIndex,
|
int adGroupIndex,
|
||||||
int adIndexInAdGroup,
|
int adIndexInAdGroup,
|
||||||
@ -707,11 +737,11 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private MediaPeriodInfo getMediaPeriodInfoForContent(
|
private MediaPeriodInfo getMediaPeriodInfoForContent(
|
||||||
Object periodUid, long startPositionUs, long windowSequenceNumber) {
|
Timeline timeline, Object periodUid, long startPositionUs, long windowSequenceNumber) {
|
||||||
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs);
|
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs);
|
||||||
MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex);
|
MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex);
|
||||||
boolean isLastInPeriod = isLastInPeriod(id);
|
boolean isLastInPeriod = isLastInPeriod(id);
|
||||||
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
|
boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod);
|
||||||
long endPositionUs =
|
long endPositionUs =
|
||||||
nextAdGroupIndex != C.INDEX_UNSET
|
nextAdGroupIndex != C.INDEX_UNSET
|
||||||
? period.getAdGroupTimeUs(nextAdGroupIndex)
|
? period.getAdGroupTimeUs(nextAdGroupIndex)
|
||||||
@ -734,7 +764,8 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
return !id.isAd() && id.nextAdGroupIndex == C.INDEX_UNSET;
|
return !id.isAd() && id.nextAdGroupIndex == C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
|
private boolean isLastInTimeline(
|
||||||
|
Timeline timeline, MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
|
||||||
int periodIndex = timeline.getIndexOfPeriod(id.periodUid);
|
int periodIndex = timeline.getIndexOfPeriod(id.periodUid);
|
||||||
int windowIndex = timeline.getPeriod(periodIndex, period).windowIndex;
|
int windowIndex = timeline.getPeriod(periodIndex, period).windowIndex;
|
||||||
return !timeline.getWindow(windowIndex, window).isDynamic
|
return !timeline.getWindow(windowIndex, window).isDynamic
|
||||||
|
@ -151,35 +151,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
this.positionUs = positionUs;
|
this.positionUs = positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns dummy media period id for the first-to-be-played period of the current timeline.
|
|
||||||
*
|
|
||||||
* @param shuffleModeEnabled Whether shuffle mode is enabled.
|
|
||||||
* @param window A writable {@link Timeline.Window}.
|
|
||||||
* @param period A writable {@link Timeline.Period}.
|
|
||||||
* @return A dummy media period id for the first-to-be-played period of the current timeline.
|
|
||||||
*/
|
|
||||||
public MediaPeriodId getDummyFirstMediaPeriodId(
|
|
||||||
boolean shuffleModeEnabled, Timeline.Window window, Timeline.Period period) {
|
|
||||||
if (timeline.isEmpty()) {
|
|
||||||
return getDummyPeriodForEmptyTimeline();
|
|
||||||
}
|
|
||||||
int firstWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
|
|
||||||
int firstPeriodIndex = timeline.getWindow(firstWindowIndex, window).firstPeriodIndex;
|
|
||||||
int currentPeriodIndex = timeline.getIndexOfPeriod(periodId.periodUid);
|
|
||||||
long windowSequenceNumber = C.INDEX_UNSET;
|
|
||||||
if (currentPeriodIndex != C.INDEX_UNSET) {
|
|
||||||
int currentWindowIndex = timeline.getPeriod(currentPeriodIndex, period).windowIndex;
|
|
||||||
if (firstWindowIndex == currentWindowIndex) {
|
|
||||||
// Keep window sequence number if the new position is still in the same window.
|
|
||||||
windowSequenceNumber = periodId.windowSequenceNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex), windowSequenceNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns dummy period id for an empty timeline. */
|
/** Returns dummy period id for an empty timeline. */
|
||||||
public MediaPeriodId getDummyPeriodForEmptyTimeline() {
|
public static MediaPeriodId getDummyPeriodForEmptyTimeline() {
|
||||||
return DUMMY_MEDIA_PERIOD_ID;
|
return DUMMY_MEDIA_PERIOD_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||||
boolean changeHandled =
|
boolean changeHandled =
|
||||||
mediaPeriodQueue.updateQueuedPeriods(
|
mediaPeriodQueue.updateQueuedPeriods(
|
||||||
/* rendererPositionUs= */ 0, /* maxRendererReadPositionUs= */ 0);
|
playbackInfo.timeline, /* rendererPositionUs= */ 0, /* maxRendererReadPositionUs= */ 0);
|
||||||
|
|
||||||
assertThat(changeHandled).isTrue();
|
assertThat(changeHandled).isTrue();
|
||||||
assertThat(getQueueLength()).isEqualTo(3);
|
assertThat(getQueueLength()).isEqualTo(3);
|
||||||
@ -228,7 +228,9 @@ public final class MediaPeriodQueueTest {
|
|||||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||||
boolean changeHandled =
|
boolean changeHandled =
|
||||||
mediaPeriodQueue.updateQueuedPeriods(
|
mediaPeriodQueue.updateQueuedPeriods(
|
||||||
/* rendererPositionUs= */ 0, /* maxRendererReadPositionUs= */ FIRST_AD_START_TIME_US);
|
playbackInfo.timeline,
|
||||||
|
/* rendererPositionUs= */ 0,
|
||||||
|
/* maxRendererReadPositionUs= */ FIRST_AD_START_TIME_US);
|
||||||
|
|
||||||
assertThat(changeHandled).isFalse();
|
assertThat(changeHandled).isFalse();
|
||||||
assertThat(getQueueLength()).isEqualTo(1);
|
assertThat(getQueueLength()).isEqualTo(1);
|
||||||
@ -256,6 +258,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
long readingPositionAtStartOfContentBetweenAds = FIRST_AD_START_TIME_US + AD_DURATION_US;
|
long readingPositionAtStartOfContentBetweenAds = FIRST_AD_START_TIME_US + AD_DURATION_US;
|
||||||
boolean changeHandled =
|
boolean changeHandled =
|
||||||
mediaPeriodQueue.updateQueuedPeriods(
|
mediaPeriodQueue.updateQueuedPeriods(
|
||||||
|
playbackInfo.timeline,
|
||||||
/* rendererPositionUs= */ 0,
|
/* rendererPositionUs= */ 0,
|
||||||
/* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds);
|
/* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds);
|
||||||
|
|
||||||
@ -285,6 +288,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
long readingPositionAtEndOfContentBetweenAds = SECOND_AD_START_TIME_US + AD_DURATION_US;
|
long readingPositionAtEndOfContentBetweenAds = SECOND_AD_START_TIME_US + AD_DURATION_US;
|
||||||
boolean changeHandled =
|
boolean changeHandled =
|
||||||
mediaPeriodQueue.updateQueuedPeriods(
|
mediaPeriodQueue.updateQueuedPeriods(
|
||||||
|
playbackInfo.timeline,
|
||||||
/* rendererPositionUs= */ 0,
|
/* rendererPositionUs= */ 0,
|
||||||
/* maxRendererReadPositionUs= */ readingPositionAtEndOfContentBetweenAds);
|
/* maxRendererReadPositionUs= */ readingPositionAtEndOfContentBetweenAds);
|
||||||
|
|
||||||
@ -313,7 +317,9 @@ public final class MediaPeriodQueueTest {
|
|||||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||||
boolean changeHandled =
|
boolean changeHandled =
|
||||||
mediaPeriodQueue.updateQueuedPeriods(
|
mediaPeriodQueue.updateQueuedPeriods(
|
||||||
/* rendererPositionUs= */ 0, /* maxRendererReadPositionUs= */ C.TIME_END_OF_SOURCE);
|
playbackInfo.timeline,
|
||||||
|
/* rendererPositionUs= */ 0,
|
||||||
|
/* maxRendererReadPositionUs= */ C.TIME_END_OF_SOURCE);
|
||||||
|
|
||||||
assertThat(changeHandled).isFalse();
|
assertThat(changeHandled).isFalse();
|
||||||
assertThat(getQueueLength()).isEqualTo(3);
|
assertThat(getQueueLength()).isEqualTo(3);
|
||||||
@ -332,12 +338,11 @@ public final class MediaPeriodQueueTest {
|
|||||||
|
|
||||||
Timeline timeline = createPlaylistTimeline();
|
Timeline timeline = createPlaylistTimeline();
|
||||||
periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0);
|
periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0);
|
||||||
mediaPeriodQueue.setTimeline(timeline);
|
|
||||||
|
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
new PlaybackInfo(
|
new PlaybackInfo(
|
||||||
timeline,
|
timeline,
|
||||||
mediaPeriodQueue.resolveMediaPeriodIdForAds(periodUid, /* positionUs= */ 0),
|
mediaPeriodQueue.resolveMediaPeriodIdForAds(timeline, periodUid, /* positionUs= */ 0),
|
||||||
/* startPositionUs= */ 0,
|
/* startPositionUs= */ 0,
|
||||||
/* contentPositionUs= */ 0,
|
/* contentPositionUs= */ 0,
|
||||||
Player.STATE_READY,
|
Player.STATE_READY,
|
||||||
@ -361,7 +366,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
SinglePeriodAdTimeline adTimeline =
|
SinglePeriodAdTimeline adTimeline =
|
||||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||||
fakeMediaSource.setNewSourceInfo(adTimeline, /* manifest */ null);
|
fakeMediaSource.setNewSourceInfo(adTimeline, /* manifest */ null);
|
||||||
mediaPeriodQueue.setTimeline(createPlaylistTimeline());
|
playbackInfo = playbackInfo.copyWithTimeline(createPlaylistTimeline());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Playlist.PlaylistTimeline createPlaylistTimeline() {
|
private Playlist.PlaylistTimeline createPlaylistTimeline() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user