Commit c455bad8 by tonihei Committed by marcbaechinger

Add Period.isPlaceholder to fix preparation issues for concatenation.

We added a source that allows mixed placeholder and non-placeholder
periods, but have no way to denote that in the Timeline because the
placeholder flag only exists on Window level. This causes a bug if
the first item in a concatenation has a window-period offset and the
player can't detect whether it's still a placeholder or not.

Adding this flag to Period allows the player to detect this reliably.
In addition we need to make sure that re-resolving pending positions
only happens for the first placeholder period where the window-offset
can actually change. As all subsequent periods have to start at position
0, so they don't need to be re-resolved (and shouldn't).

PiperOrigin-RevId: 367171518
parent ce4c655c
...@@ -589,6 +589,12 @@ public abstract class Timeline implements Bundleable { ...@@ -589,6 +589,12 @@ public abstract class Timeline implements Bundleable {
*/ */
public long positionInWindowUs; public long positionInWindowUs;
/**
* Whether this period contains placeholder information because the real information has yet to
* be loaded.
*/
public boolean isPlaceholder;
private AdPlaybackState adPlaybackState; private AdPlaybackState adPlaybackState;
/** Creates a new instance with no ad playback state. */ /** Creates a new instance with no ad playback state. */
...@@ -650,6 +656,7 @@ public abstract class Timeline implements Bundleable { ...@@ -650,6 +656,7 @@ public abstract class Timeline implements Bundleable {
this.durationUs = durationUs; this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs; this.positionInWindowUs = positionInWindowUs;
this.adPlaybackState = adPlaybackState; this.adPlaybackState = adPlaybackState;
this.isPlaceholder = false;
return this; return this;
} }
...@@ -814,6 +821,7 @@ public abstract class Timeline implements Bundleable { ...@@ -814,6 +821,7 @@ public abstract class Timeline implements Bundleable {
&& windowIndex == that.windowIndex && windowIndex == that.windowIndex
&& durationUs == that.durationUs && durationUs == that.durationUs
&& positionInWindowUs == that.positionInWindowUs && positionInWindowUs == that.positionInWindowUs
&& isPlaceholder == that.isPlaceholder
&& Util.areEqual(adPlaybackState, that.adPlaybackState); && Util.areEqual(adPlaybackState, that.adPlaybackState);
} }
...@@ -825,6 +833,7 @@ public abstract class Timeline implements Bundleable { ...@@ -825,6 +833,7 @@ public abstract class Timeline implements Bundleable {
result = 31 * result + windowIndex; result = 31 * result + windowIndex;
result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
result = 31 * result + (int) (positionInWindowUs ^ (positionInWindowUs >>> 32)); result = 31 * result + (int) (positionInWindowUs ^ (positionInWindowUs >>> 32));
result = 31 * result + (isPlaceholder ? 1 : 0);
result = 31 * result + adPlaybackState.hashCode(); result = 31 * result + adPlaybackState.hashCode();
return result; return result;
} }
...@@ -837,6 +846,7 @@ public abstract class Timeline implements Bundleable { ...@@ -837,6 +846,7 @@ public abstract class Timeline implements Bundleable {
FIELD_WINDOW_INDEX, FIELD_WINDOW_INDEX,
FIELD_DURATION_US, FIELD_DURATION_US,
FIELD_POSITION_IN_WINDOW_US, FIELD_POSITION_IN_WINDOW_US,
FIELD_PLACEHOLDER,
FIELD_AD_PLAYBACK_STATE FIELD_AD_PLAYBACK_STATE
}) })
private @interface FieldNumber {} private @interface FieldNumber {}
...@@ -844,7 +854,8 @@ public abstract class Timeline implements Bundleable { ...@@ -844,7 +854,8 @@ public abstract class Timeline implements Bundleable {
private static final int FIELD_WINDOW_INDEX = 0; private static final int FIELD_WINDOW_INDEX = 0;
private static final int FIELD_DURATION_US = 1; private static final int FIELD_DURATION_US = 1;
private static final int FIELD_POSITION_IN_WINDOW_US = 2; private static final int FIELD_POSITION_IN_WINDOW_US = 2;
private static final int FIELD_AD_PLAYBACK_STATE = 3; private static final int FIELD_PLACEHOLDER = 3;
private static final int FIELD_AD_PLAYBACK_STATE = 4;
/** /**
* {@inheritDoc} * {@inheritDoc}
...@@ -859,6 +870,7 @@ public abstract class Timeline implements Bundleable { ...@@ -859,6 +870,7 @@ public abstract class Timeline implements Bundleable {
bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex); bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex);
bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); bundle.putLong(keyForField(FIELD_DURATION_US), durationUs);
bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs); bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs);
bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder);
bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle()); bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle());
return bundle; return bundle;
} }
...@@ -876,6 +888,7 @@ public abstract class Timeline implements Bundleable { ...@@ -876,6 +888,7 @@ public abstract class Timeline implements Bundleable {
bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET); bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
long positionInWindowUs = long positionInWindowUs =
bundle.getLong(keyForField(FIELD_POSITION_IN_WINDOW_US), /* defaultValue= */ 0); bundle.getLong(keyForField(FIELD_POSITION_IN_WINDOW_US), /* defaultValue= */ 0);
boolean isPlaceholder = bundle.getBoolean(keyForField(FIELD_PLACEHOLDER));
@Nullable @Nullable
Bundle adPlaybackStateBundle = bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATE)); Bundle adPlaybackStateBundle = bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATE));
AdPlaybackState adPlaybackState = AdPlaybackState adPlaybackState =
...@@ -884,13 +897,15 @@ public abstract class Timeline implements Bundleable { ...@@ -884,13 +897,15 @@ public abstract class Timeline implements Bundleable {
: AdPlaybackState.NONE; : AdPlaybackState.NONE;
Period period = new Period(); Period period = new Period();
return period.set( period.set(
/* id= */ null, /* id= */ null,
/* uid= */ null, /* uid= */ null,
windowIndex, windowIndex,
durationUs, durationUs,
positionInWindowUs, positionInWindowUs,
adPlaybackState); adPlaybackState);
period.isPlaceholder = isPlaceholder;
return period;
} }
private static String keyForField(@Period.FieldNumber int field) { private static String keyForField(@Period.FieldNumber int field) {
...@@ -1469,8 +1484,9 @@ public abstract class Timeline implements Bundleable { ...@@ -1469,8 +1484,9 @@ public abstract class Timeline implements Bundleable {
@Override @Override
public Period getPeriod(int periodIndex, Period period, boolean ignoredSetIds) { public Period getPeriod(int periodIndex, Period period, boolean ignoredSetIds) {
Period p = periods.get(periodIndex); Period p = periods.get(periodIndex);
return period.set( period.set(p.id, p.uid, p.windowIndex, p.durationUs, p.positionInWindowUs, p.adPlaybackState);
p.id, p.uid, p.windowIndex, p.durationUs, p.positionInWindowUs, p.adPlaybackState); period.isPlaceholder = p.isPlaceholder;
return period;
} }
@Override @Override
......
...@@ -176,10 +176,15 @@ public class TimelineTest { ...@@ -176,10 +176,15 @@ public class TimelineTest {
assertThat(period).isNotEqualTo(otherPeriod); assertThat(period).isNotEqualTo(otherPeriod);
otherPeriod = new Timeline.Period(); otherPeriod = new Timeline.Period();
otherPeriod.isPlaceholder = true;
assertThat(period).isNotEqualTo(otherPeriod);
otherPeriod = new Timeline.Period();
period.id = new Object(); period.id = new Object();
period.uid = new Object(); period.uid = new Object();
period.windowIndex = 1; period.windowIndex = 1;
period.durationUs = 123L; period.durationUs = 123L;
period.isPlaceholder = true;
otherPeriod = otherPeriod =
otherPeriod.set( otherPeriod.set(
period.id, period.id,
...@@ -187,6 +192,7 @@ public class TimelineTest { ...@@ -187,6 +192,7 @@ public class TimelineTest {
period.windowIndex, period.windowIndex,
period.durationUs, period.durationUs,
/* positionInWindowUs= */ 0); /* positionInWindowUs= */ 0);
otherPeriod.isPlaceholder = true;
assertThat(period).isEqualTo(otherPeriod); assertThat(period).isEqualTo(otherPeriod);
} }
...@@ -323,6 +329,7 @@ public class TimelineTest { ...@@ -323,6 +329,7 @@ public class TimelineTest {
period.windowIndex = 1; period.windowIndex = 1;
period.durationUs = 123_000; period.durationUs = 123_000;
period.positionInWindowUs = 4_000; period.positionInWindowUs = 4_000;
period.isPlaceholder = true;
Timeline.Period restoredPeriod = Timeline.Period.CREATOR.fromBundle(period.toBundle()); Timeline.Period restoredPeriod = Timeline.Period.CREATOR.fromBundle(period.toBundle());
......
...@@ -1369,7 +1369,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1369,7 +1369,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodId mediaPeriodId = playbackInfo.periodId; MediaPeriodId mediaPeriodId = playbackInfo.periodId;
long startPositionUs = playbackInfo.positionUs; long startPositionUs = playbackInfo.positionUs;
long requestedContentPositionUs = long requestedContentPositionUs =
shouldUseRequestedContentPosition(playbackInfo, period, window) shouldUseRequestedContentPosition(playbackInfo, period)
? playbackInfo.requestedContentPositionUs ? playbackInfo.requestedContentPositionUs
: playbackInfo.positionUs; : playbackInfo.positionUs;
boolean resetTrackInfo = false; boolean resetTrackInfo = false;
...@@ -1821,9 +1821,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1821,9 +1821,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
periodPositionChanged periodPositionChanged
&& isSourceRefresh && isSourceRefresh
&& !oldTimeline.isEmpty() && !oldTimeline.isEmpty()
&& !oldTimeline.getWindow( && !oldTimeline.getPeriodByUid(oldPeriodUid, period).isPlaceholder;
oldTimeline.getPeriodByUid(oldPeriodUid, period).windowIndex, window)
.isPlaceholder;
playbackInfo = playbackInfo =
handlePositionDiscontinuity( handlePositionDiscontinuity(
newPeriodId, newPeriodId,
...@@ -2478,7 +2476,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2478,7 +2476,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodId oldPeriodId = playbackInfo.periodId; MediaPeriodId oldPeriodId = playbackInfo.periodId;
Object newPeriodUid = oldPeriodId.periodUid; Object newPeriodUid = oldPeriodId.periodUid;
boolean shouldUseRequestedContentPosition = boolean shouldUseRequestedContentPosition =
shouldUseRequestedContentPosition(playbackInfo, period, window); shouldUseRequestedContentPosition(playbackInfo, period);
long oldContentPositionUs = long oldContentPositionUs =
shouldUseRequestedContentPosition shouldUseRequestedContentPosition
? playbackInfo.requestedContentPositionUs ? playbackInfo.requestedContentPositionUs
...@@ -2551,12 +2549,17 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2551,12 +2549,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
timeline.getPeriodByUid(newPeriodUid, period).windowIndex; timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
} else { } else {
playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period); playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period);
if (playbackInfo.timeline.getWindow(period.windowIndex, window).firstPeriodIndex
== playbackInfo.timeline.getIndexOfPeriod(oldPeriodId.periodUid)) {
// Only need to resolve the first period in a window because subsequent periods must start
// at position 0 and don't need to be resolved.
long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs(); long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs();
int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex; int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
Pair<Object, Long> periodPosition = Pair<Object, Long> periodPosition =
timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
newPeriodUid = periodPosition.first; newPeriodUid = periodPosition.first;
newContentPositionUs = periodPosition.second; newContentPositionUs = periodPosition.second;
}
// Use an explicitly requested content position as new target live offset. // Use an explicitly requested content position as new target live offset.
setTargetLiveOffset = true; setTargetLiveOffset = true;
} }
...@@ -2616,16 +2619,14 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2616,16 +2619,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private static boolean shouldUseRequestedContentPosition( private static boolean shouldUseRequestedContentPosition(
PlaybackInfo playbackInfo, Timeline.Period period, Timeline.Window window) { PlaybackInfo playbackInfo, Timeline.Period period) {
// Only use the actual position as content position if it's not an ad and we already have // Only use the actual position as content position if it's not an ad and we already have
// prepared media information. Otherwise use the requested position. // prepared media information. Otherwise use the requested position.
MediaPeriodId periodId = playbackInfo.periodId; MediaPeriodId periodId = playbackInfo.periodId;
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
return periodId.isAd() return periodId.isAd()
|| timeline.isEmpty() || timeline.isEmpty()
|| timeline.getWindow( || timeline.getPeriodByUid(periodId.periodUid, period).isPlaceholder;
timeline.getPeriodByUid(periodId.periodUid, period).windowIndex, window)
.isPlaceholder;
} }
/** /**
...@@ -2691,9 +2692,12 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2691,9 +2692,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
pendingMessageInfo.resolvedPeriodIndex = index; pendingMessageInfo.resolvedPeriodIndex = index;
previousTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period); previousTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period);
if (previousTimeline.getWindow(period.windowIndex, window).isPlaceholder) { if (period.isPlaceholder
&& previousTimeline.getWindow(period.windowIndex, window).firstPeriodIndex
== previousTimeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid)) {
// The position needs to be re-resolved because the window in the previous timeline wasn't // The position needs to be re-resolved because the window in the previous timeline wasn't
// fully prepared. // fully prepared. Only resolve the first period in a window because subsequent periods must
// start at position 0 and don't need to be resolved.
long windowPositionUs = long windowPositionUs =
pendingMessageInfo.resolvedPeriodTimeUs + period.getPositionInWindowUs(); pendingMessageInfo.resolvedPeriodTimeUs + period.getPositionInWindowUs();
int windowIndex = int windowIndex =
...@@ -2768,10 +2772,12 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2768,10 +2772,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
int periodIndex = timeline.getIndexOfPeriod(periodPosition.first); int periodIndex = timeline.getIndexOfPeriod(periodPosition.first);
if (periodIndex != C.INDEX_UNSET) { if (periodIndex != C.INDEX_UNSET) {
// We successfully located the period in the internal timeline. // We successfully located the period in the internal timeline.
seekTimeline.getPeriodByUid(periodPosition.first, period); if (seekTimeline.getPeriodByUid(periodPosition.first, period).isPlaceholder
if (seekTimeline.getWindow(period.windowIndex, window).isPlaceholder) { && seekTimeline.getWindow(period.windowIndex, window).firstPeriodIndex
== seekTimeline.getIndexOfPeriod(periodPosition.first)) {
// The seek timeline was using a placeholder, so we need to re-resolve using the updated // The seek timeline was using a placeholder, so we need to re-resolve using the updated
// timeline in case the resolved position changed. // timeline in case the resolved position changed. Only resolve the first period in a window
// because subsequent periods must start at position 0 and don't need to be resolved.
int newWindowIndex = timeline.getPeriodByUid(periodPosition.first, period).windowIndex; int newWindowIndex = timeline.getPeriodByUid(periodPosition.first, period).windowIndex;
periodPosition = periodPosition =
timeline.getPeriodPosition( timeline.getPeriodPosition(
......
...@@ -395,12 +395,14 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> { ...@@ -395,12 +395,14 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
@Override @Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) { public Period getPeriod(int periodIndex, Period period, boolean setIds) {
return period.set( period.set(
/* id= */ setIds ? 0 : null, /* id= */ setIds ? 0 : null,
/* uid= */ setIds ? MaskingTimeline.MASKING_EXTERNAL_PERIOD_UID : null, /* uid= */ setIds ? MaskingTimeline.MASKING_EXTERNAL_PERIOD_UID : null,
/* windowIndex= */ 0, /* windowIndex= */ 0,
/* durationUs = */ C.TIME_UNSET, /* durationUs = */ C.TIME_UNSET,
/* positionInWindowUs= */ 0); /* positionInWindowUs= */ 0);
period.isPlaceholder = true;
return period;
} }
@Override @Override
......
...@@ -387,6 +387,13 @@ public final class ProgressiveMediaSource extends BaseMediaSource ...@@ -387,6 +387,13 @@ public final class ProgressiveMediaSource extends BaseMediaSource
window.isPlaceholder = true; window.isPlaceholder = true;
return window; return window;
} }
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
super.getPeriod(periodIndex, period, setIds);
period.isPlaceholder = true;
return period;
}
}; };
} }
refreshSourceInfo(timeline); refreshSourceInfo(timeline);
......
...@@ -410,13 +410,15 @@ public final class FakeTimeline extends Timeline { ...@@ -410,13 +410,15 @@ public final class FakeTimeline extends Timeline {
} else { } else {
positionInWindowUs = periodDurationUs * windowPeriodIndex; positionInWindowUs = periodDurationUs * windowPeriodIndex;
} }
return period.set( period.set(
id, id,
uid, uid,
windowIndex, windowIndex,
periodDurationUs, periodDurationUs,
positionInWindowUs, positionInWindowUs,
windowDefinition.adPlaybackState); windowDefinition.adPlaybackState);
period.isPlaceholder = windowDefinition.isPlaceholder;
return period;
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment