mirror of
https://github.com/samsonjs/media.git
synced 2026-04-22 14:05:55 +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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
|||
|
|
@ -175,11 +175,16 @@ public class TimelineTest {
|
|||
otherPeriod.durationUs = 11L;
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue