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 {
*/
public long positionInWindowUs;
/**
* Whether this period contains placeholder information because the real information has yet to
* be loaded.
*/
public boolean isPlaceholder;
private AdPlaybackState adPlaybackState;
/** Creates a new instance with no ad playback state. */
......@@ -650,6 +656,7 @@ public abstract class Timeline implements Bundleable {
this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs;
this.adPlaybackState = adPlaybackState;
this.isPlaceholder = false;
return this;
}
......@@ -814,6 +821,7 @@ public abstract class Timeline implements Bundleable {
&& windowIndex == that.windowIndex
&& durationUs == that.durationUs
&& positionInWindowUs == that.positionInWindowUs
&& isPlaceholder == that.isPlaceholder
&& Util.areEqual(adPlaybackState, that.adPlaybackState);
}
......@@ -825,6 +833,7 @@ public abstract class Timeline implements Bundleable {
result = 31 * result + windowIndex;
result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
result = 31 * result + (int) (positionInWindowUs ^ (positionInWindowUs >>> 32));
result = 31 * result + (isPlaceholder ? 1 : 0);
result = 31 * result + adPlaybackState.hashCode();
return result;
}
......@@ -837,6 +846,7 @@ public abstract class Timeline implements Bundleable {
FIELD_WINDOW_INDEX,
FIELD_DURATION_US,
FIELD_POSITION_IN_WINDOW_US,
FIELD_PLACEHOLDER,
FIELD_AD_PLAYBACK_STATE
})
private @interface FieldNumber {}
......@@ -844,7 +854,8 @@ public abstract class Timeline implements Bundleable {
private static final int FIELD_WINDOW_INDEX = 0;
private static final int FIELD_DURATION_US = 1;
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}
......@@ -859,6 +870,7 @@ public abstract class Timeline implements Bundleable {
bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex);
bundle.putLong(keyForField(FIELD_DURATION_US), durationUs);
bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs);
bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder);
bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle());
return bundle;
}
......@@ -876,6 +888,7 @@ public abstract class Timeline implements Bundleable {
bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
long positionInWindowUs =
bundle.getLong(keyForField(FIELD_POSITION_IN_WINDOW_US), /* defaultValue= */ 0);
boolean isPlaceholder = bundle.getBoolean(keyForField(FIELD_PLACEHOLDER));
@Nullable
Bundle adPlaybackStateBundle = bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATE));
AdPlaybackState adPlaybackState =
......@@ -884,13 +897,15 @@ public abstract class Timeline implements Bundleable {
: AdPlaybackState.NONE;
Period period = new Period();
return period.set(
period.set(
/* id= */ null,
/* uid= */ null,
windowIndex,
durationUs,
positionInWindowUs,
adPlaybackState);
period.isPlaceholder = isPlaceholder;
return period;
}
private static String keyForField(@Period.FieldNumber int field) {
......@@ -1469,8 +1484,9 @@ public abstract class Timeline implements Bundleable {
@Override
public Period getPeriod(int periodIndex, Period period, boolean ignoredSetIds) {
Period p = periods.get(periodIndex);
return period.set(
p.id, p.uid, p.windowIndex, p.durationUs, p.positionInWindowUs, p.adPlaybackState);
period.set(p.id, p.uid, p.windowIndex, p.durationUs, p.positionInWindowUs, p.adPlaybackState);
period.isPlaceholder = p.isPlaceholder;
return period;
}
@Override
......
......@@ -176,10 +176,15 @@ public class TimelineTest {
assertThat(period).isNotEqualTo(otherPeriod);
otherPeriod = new Timeline.Period();
otherPeriod.isPlaceholder = true;
assertThat(period).isNotEqualTo(otherPeriod);
otherPeriod = new Timeline.Period();
period.id = new Object();
period.uid = new Object();
period.windowIndex = 1;
period.durationUs = 123L;
period.isPlaceholder = true;
otherPeriod =
otherPeriod.set(
period.id,
......@@ -187,6 +192,7 @@ public class TimelineTest {
period.windowIndex,
period.durationUs,
/* positionInWindowUs= */ 0);
otherPeriod.isPlaceholder = true;
assertThat(period).isEqualTo(otherPeriod);
}
......@@ -323,6 +329,7 @@ public class TimelineTest {
period.windowIndex = 1;
period.durationUs = 123_000;
period.positionInWindowUs = 4_000;
period.isPlaceholder = true;
Timeline.Period restoredPeriod = Timeline.Period.CREATOR.fromBundle(period.toBundle());
......
......@@ -1369,7 +1369,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodId mediaPeriodId = playbackInfo.periodId;
long startPositionUs = playbackInfo.positionUs;
long requestedContentPositionUs =
shouldUseRequestedContentPosition(playbackInfo, period, window)
shouldUseRequestedContentPosition(playbackInfo, period)
? playbackInfo.requestedContentPositionUs
: playbackInfo.positionUs;
boolean resetTrackInfo = false;
......@@ -1821,9 +1821,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
periodPositionChanged
&& isSourceRefresh
&& !oldTimeline.isEmpty()
&& !oldTimeline.getWindow(
oldTimeline.getPeriodByUid(oldPeriodUid, period).windowIndex, window)
.isPlaceholder;
&& !oldTimeline.getPeriodByUid(oldPeriodUid, period).isPlaceholder;
playbackInfo =
handlePositionDiscontinuity(
newPeriodId,
......@@ -2478,7 +2476,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodId oldPeriodId = playbackInfo.periodId;
Object newPeriodUid = oldPeriodId.periodUid;
boolean shouldUseRequestedContentPosition =
shouldUseRequestedContentPosition(playbackInfo, period, window);
shouldUseRequestedContentPosition(playbackInfo, period);
long oldContentPositionUs =
shouldUseRequestedContentPosition
? playbackInfo.requestedContentPositionUs
......@@ -2551,12 +2549,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
} else {
playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period);
long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs();
int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
Pair<Object, Long> periodPosition =
timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
newPeriodUid = periodPosition.first;
newContentPositionUs = periodPosition.second;
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();
int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
Pair<Object, Long> periodPosition =
timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
newPeriodUid = periodPosition.first;
newContentPositionUs = periodPosition.second;
}
// Use an explicitly requested content position as new target live offset.
setTargetLiveOffset = true;
}
......@@ -2616,16 +2619,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
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
// prepared media information. Otherwise use the requested position.
MediaPeriodId periodId = playbackInfo.periodId;
Timeline timeline = playbackInfo.timeline;
return periodId.isAd()
|| timeline.isEmpty()
|| timeline.getWindow(
timeline.getPeriodByUid(periodId.periodUid, period).windowIndex, window)
.isPlaceholder;
|| timeline.getPeriodByUid(periodId.periodUid, period).isPlaceholder;
}
/**
......@@ -2691,9 +2692,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
pendingMessageInfo.resolvedPeriodIndex = index;
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
// 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 =
pendingMessageInfo.resolvedPeriodTimeUs + period.getPositionInWindowUs();
int windowIndex =
......@@ -2768,10 +2772,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
int periodIndex = timeline.getIndexOfPeriod(periodPosition.first);
if (periodIndex != C.INDEX_UNSET) {
// We successfully located the period in the internal timeline.
seekTimeline.getPeriodByUid(periodPosition.first, period);
if (seekTimeline.getWindow(period.windowIndex, window).isPlaceholder) {
if (seekTimeline.getPeriodByUid(periodPosition.first, period).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
// 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;
periodPosition =
timeline.getPeriodPosition(
......
......@@ -395,12 +395,14 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
return period.set(
period.set(
/* id= */ setIds ? 0 : null,
/* uid= */ setIds ? MaskingTimeline.MASKING_EXTERNAL_PERIOD_UID : null,
/* windowIndex= */ 0,
/* durationUs = */ C.TIME_UNSET,
/* positionInWindowUs= */ 0);
period.isPlaceholder = true;
return period;
}
@Override
......
......@@ -387,6 +387,13 @@ public final class ProgressiveMediaSource extends BaseMediaSource
window.isPlaceholder = true;
return window;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
super.getPeriod(periodIndex, period, setIds);
period.isPlaceholder = true;
return period;
}
};
}
refreshSourceInfo(timeline);
......
......@@ -410,13 +410,15 @@ public final class FakeTimeline extends Timeline {
} else {
positionInWindowUs = periodDurationUs * windowPeriodIndex;
}
return period.set(
period.set(
id,
uid,
windowIndex,
periodDurationUs,
positionInWindowUs,
windowDefinition.adPlaybackState);
period.isPlaceholder = windowDefinition.isPlaceholder;
return period;
}
@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