mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
697165ce56
commit
68c401b53e
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user