mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
ce4c655c83
commit
c455bad8f8
6 changed files with 66 additions and 26 deletions
|
|
@ -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 {
|
||||||
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 {
|
||||||
&& 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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
: 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 {
|
||||||
@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
|
||||||
|
|
|
||||||
|
|
@ -175,11 +175,16 @@ public class TimelineTest {
|
||||||
otherPeriod.durationUs = 11L;
|
otherPeriod.durationUs = 11L;
|
||||||
assertThat(period).isNotEqualTo(otherPeriod);
|
assertThat(period).isNotEqualTo(otherPeriod);
|
||||||
|
|
||||||
|
otherPeriod = new Timeline.Period();
|
||||||
|
otherPeriod.isPlaceholder = true;
|
||||||
|
assertThat(period).isNotEqualTo(otherPeriod);
|
||||||
|
|
||||||
otherPeriod = new Timeline.Period();
|
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 {
|
||||||
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 {
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
|
timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
|
||||||
} else {
|
} else {
|
||||||
playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period);
|
playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period);
|
||||||
long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs();
|
if (playbackInfo.timeline.getWindow(period.windowIndex, window).firstPeriodIndex
|
||||||
int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
|
== playbackInfo.timeline.getIndexOfPeriod(oldPeriodId.periodUid)) {
|
||||||
Pair<Object, Long> periodPosition =
|
// Only need to resolve the first period in a window because subsequent periods must start
|
||||||
timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
|
// at position 0 and don't need to be resolved.
|
||||||
newPeriodUid = periodPosition.first;
|
long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs();
|
||||||
newContentPositionUs = periodPosition.second;
|
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.
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
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;
|
||||||
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> {
|
||||||
|
|
||||||
@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
|
||||||
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 {
|
||||||
} 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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue