Mark initial progressive source Timeline as placeholder

This is more accurate since it's just a placeholder and none of the
values is provided by the media.

It also allows to fix a problem in ClippingMediaSource where we
couldn't detect a clipping error because we didn't know if the
timeline is a placeholder or not.

Issue:#5924
PiperOrigin-RevId: 297813606
This commit is contained in:
tonihei 2020-02-28 12:11:48 +00:00 committed by Oliver Woodman
parent 697165ce56
commit 68c401b53e
3 changed files with 54 additions and 15 deletions

View File

@ -319,14 +319,14 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
} }
Window window = timeline.getWindow(0, new Window()); Window window = timeline.getWindow(0, new Window());
startUs = Math.max(0, startUs); startUs = Math.max(0, startUs);
if (!window.isPlaceholder && startUs != 0 && !window.isSeekable) {
throw new IllegalClippingException(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START);
}
long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : Math.max(0, endUs); long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : Math.max(0, endUs);
if (window.durationUs != C.TIME_UNSET) { if (window.durationUs != C.TIME_UNSET) {
if (resolvedEndUs > window.durationUs) { if (resolvedEndUs > window.durationUs) {
resolvedEndUs = window.durationUs; resolvedEndUs = window.durationUs;
} }
if (startUs != 0 && !window.isSeekable) {
throw new IllegalClippingException(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START);
}
if (startUs > resolvedEndUs) { if (startUs > resolvedEndUs) {
throw new IllegalClippingException(IllegalClippingException.REASON_START_EXCEEDS_END); throw new IllegalClippingException(IllegalClippingException.REASON_START_EXCEEDS_END);
} }

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
@ -204,6 +205,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
private final int continueLoadingCheckIntervalBytes; private final int continueLoadingCheckIntervalBytes;
@Nullable private final Object tag; @Nullable private final Object tag;
private boolean timelineIsPlaceholder;
private long timelineDurationUs; private long timelineDurationUs;
private boolean timelineIsSeekable; private boolean timelineIsSeekable;
private boolean timelineIsLive; private boolean timelineIsLive;
@ -226,6 +228,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy; this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy;
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
this.timelineIsPlaceholder = true;
this.timelineDurationUs = C.TIME_UNSET; this.timelineDurationUs = C.TIME_UNSET;
this.tag = tag; this.tag = tag;
} }
@ -240,7 +243,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
transferListener = mediaTransferListener; transferListener = mediaTransferListener;
drmSessionManager.prepare(); drmSessionManager.prepare();
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable, timelineIsLive); notifySourceInfoRefreshed();
} }
@Override @Override
@ -283,30 +286,47 @@ public final class ProgressiveMediaSource extends BaseMediaSource
public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) { public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
// If we already have the duration from a previous source info refresh, use it. // If we already have the duration from a previous source info refresh, use it.
durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs; durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs;
if (timelineDurationUs == durationUs if (!timelineIsPlaceholder
&& timelineDurationUs == durationUs
&& timelineIsSeekable == isSeekable && timelineIsSeekable == isSeekable
&& timelineIsLive == isLive) { && timelineIsLive == isLive) {
// Suppress no-op source info changes. // Suppress no-op source info changes.
return; return;
} }
notifySourceInfoRefreshed(durationUs, isSeekable, isLive); timelineDurationUs = durationUs;
timelineIsSeekable = isSeekable;
timelineIsLive = isLive;
timelineIsPlaceholder = false;
notifySourceInfoRefreshed();
} }
// Internal methods. // Internal methods.
private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) { private void notifySourceInfoRefreshed() {
timelineDurationUs = durationUs;
timelineIsSeekable = isSeekable;
timelineIsLive = isLive;
// TODO: Split up isDynamic into multiple fields to indicate which values may change. Then // TODO: Split up isDynamic into multiple fields to indicate which values may change. Then
// indicate that the duration may change until it's known. See [internal: b/69703223]. // indicate that the duration may change until it's known. See [internal: b/69703223].
refreshSourceInfo( Timeline timeline =
new SinglePeriodTimeline( new SinglePeriodTimeline(
timelineDurationUs, timelineDurationUs,
timelineIsSeekable, timelineIsSeekable,
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ timelineIsLive, /* isLive= */ timelineIsLive,
/* manifest= */ null, /* manifest= */ null,
tag)); tag);
if (timelineIsPlaceholder) {
// TODO: Actually prepare the extractors during prepatation so that we don't need a
// placeholder. See https://github.com/google/ExoPlayer/issues/4727.
timeline =
new ForwardingTimeline(timeline) {
@Override
public Window getWindow(
int windowIndex, Window window, long defaultPositionProjectionUs) {
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
window.isPlaceholder = true;
return window;
}
};
}
refreshSourceInfo(timeline);
} }
} }

View File

@ -26,6 +26,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException; import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException;
import com.google.android.exoplayer2.source.MaskingMediaSource.DummyTimeline;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.testutil.FakeMediaPeriod; import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
@ -100,6 +101,26 @@ public final class ClippingMediaSourceTest {
} }
} }
@Test
public void clippingUnseekableWindowWithUnknownDurationThrows() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
/* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* isLive= */ false);
// If the unseekable window isn't clipped, clipping succeeds.
getClippedTimeline(timeline, /* startUs= */ 0, TEST_PERIOD_DURATION_US);
try {
// If the unseekable window is clipped, clipping fails.
getClippedTimeline(timeline, /* startUs= */ 1, TEST_PERIOD_DURATION_US);
fail("Expected clipping to fail.");
} catch (IllegalClippingException e) {
assertThat(e.reason).isEqualTo(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START);
}
}
@Test @Test
public void clippingStart() throws IOException { public void clippingStart() throws IOException {
Timeline timeline = Timeline timeline =
@ -139,9 +160,7 @@ public final class ClippingMediaSourceTest {
// Timeline that's dynamic and not seekable. A child source might report such a timeline prior // Timeline that's dynamic and not seekable. A child source might report such a timeline prior
// to it having loaded sufficient data to establish its duration and seekability. Such timelines // to it having loaded sufficient data to establish its duration and seekability. Such timelines
// should not result in clipping failure. // should not result in clipping failure.
Timeline timeline = Timeline timeline = new DummyTimeline(/* tag= */ null);
new SinglePeriodTimeline(
C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true, /* isLive= */ true);
Timeline clippedTimeline = Timeline clippedTimeline =
getClippedTimeline( getClippedTimeline(