Add live configuration to Timeline.Window
Issue: #5011 PiperOrigin-RevId: 346828103
This commit is contained in:
parent
392b3ab573
commit
05c928f96d
@ -126,16 +126,18 @@ import java.util.Arrays;
|
||||
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||
long durationUs = durationsUs[windowIndex];
|
||||
boolean isDynamic = durationUs == C.TIME_UNSET;
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(ids[windowIndex]).build();
|
||||
return window.set(
|
||||
/* uid= */ ids[windowIndex],
|
||||
/* mediaItem= */ new MediaItem.Builder().setUri(Uri.EMPTY).setTag(ids[windowIndex]).build(),
|
||||
/* mediaItem= */ mediaItem,
|
||||
/* manifest= */ null,
|
||||
/* presentationStartTimeMs= */ C.TIME_UNSET,
|
||||
/* windowStartTimeMs= */ C.TIME_UNSET,
|
||||
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
|
||||
/* isSeekable= */ !isDynamic,
|
||||
isDynamic,
|
||||
isLive[windowIndex],
|
||||
isLive[windowIndex] ? mediaItem.liveConfiguration : null,
|
||||
defaultPositionsUs[windowIndex],
|
||||
durationUs,
|
||||
/* firstPeriodIndex= */ windowIndex,
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
@ -74,10 +76,10 @@ import com.google.android.exoplayer2.util.Util;
|
||||
* <p>A timeline for a live stream consists of a period whose duration is unknown, since it's
|
||||
* continually extending as more content is broadcast. If content only remains available for a
|
||||
* limited period of time then the window may start at a non-zero position, defining the region of
|
||||
* content that can still be played. The window will have {@link Window#isLive} set to true to
|
||||
* indicate it's a live stream and {@link Window#isDynamic} set to true as long as we expect changes
|
||||
* to the live window. Its default position is typically near to the live edge (indicated by the
|
||||
* black dot in the figure above).
|
||||
* content that can still be played. The window will return true from {@link Window#isLive()} to
|
||||
* indicate it's a live stream and {@link Window#isDynamic} will be set to true as long as we expect
|
||||
* changes to the live window. Its default position is typically near to the live edge (indicated by
|
||||
* the black dot in the figure above).
|
||||
*
|
||||
* <h3>Live stream with indefinite availability</h3>
|
||||
*
|
||||
@ -191,12 +193,14 @@ public abstract class Timeline {
|
||||
/** Whether this window may change when the timeline is updated. */
|
||||
public boolean isDynamic;
|
||||
|
||||
/** @deprecated Use {@link #isLive()} instead. */
|
||||
@Deprecated public boolean isLive;
|
||||
|
||||
/**
|
||||
* Whether the media in this window is live. For informational purposes only.
|
||||
*
|
||||
* <p>Check {@link #isDynamic} to know whether this window may still change.
|
||||
* The {@link MediaItem.LiveConfiguration} that is used or null if {@link #isLive()} returns
|
||||
* false.
|
||||
*/
|
||||
public boolean isLive;
|
||||
@Nullable public MediaItem.LiveConfiguration liveConfiguration;
|
||||
|
||||
/**
|
||||
* Whether this window contains placeholder information because the real information has yet to
|
||||
@ -248,7 +252,7 @@ public abstract class Timeline {
|
||||
long elapsedRealtimeEpochOffsetMs,
|
||||
boolean isSeekable,
|
||||
boolean isDynamic,
|
||||
boolean isLive,
|
||||
@Nullable MediaItem.LiveConfiguration liveConfiguration,
|
||||
long defaultPositionUs,
|
||||
long durationUs,
|
||||
int firstPeriodIndex,
|
||||
@ -266,7 +270,8 @@ public abstract class Timeline {
|
||||
this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs;
|
||||
this.isSeekable = isSeekable;
|
||||
this.isDynamic = isDynamic;
|
||||
this.isLive = isLive;
|
||||
this.isLive = liveConfiguration != null;
|
||||
this.liveConfiguration = liveConfiguration;
|
||||
this.defaultPositionUs = defaultPositionUs;
|
||||
this.durationUs = durationUs;
|
||||
this.firstPeriodIndex = firstPeriodIndex;
|
||||
@ -336,6 +341,14 @@ public abstract class Timeline {
|
||||
return Util.getNowUnixTimeMs(elapsedRealtimeEpochOffsetMs);
|
||||
}
|
||||
|
||||
/** Returns whether this is a live stream. */
|
||||
// Verifies whether the deprecated isLive member field is in a correct state.
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean isLive() {
|
||||
checkState(isLive == (liveConfiguration != null));
|
||||
return liveConfiguration != null;
|
||||
}
|
||||
|
||||
// Provide backward compatibility for tag.
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
@ -349,12 +362,12 @@ public abstract class Timeline {
|
||||
return Util.areEqual(uid, that.uid)
|
||||
&& Util.areEqual(mediaItem, that.mediaItem)
|
||||
&& Util.areEqual(manifest, that.manifest)
|
||||
&& Util.areEqual(liveConfiguration, that.liveConfiguration)
|
||||
&& presentationStartTimeMs == that.presentationStartTimeMs
|
||||
&& windowStartTimeMs == that.windowStartTimeMs
|
||||
&& elapsedRealtimeEpochOffsetMs == that.elapsedRealtimeEpochOffsetMs
|
||||
&& isSeekable == that.isSeekable
|
||||
&& isDynamic == that.isDynamic
|
||||
&& isLive == that.isLive
|
||||
&& isPlaceholder == that.isPlaceholder
|
||||
&& defaultPositionUs == that.defaultPositionUs
|
||||
&& durationUs == that.durationUs
|
||||
@ -370,6 +383,7 @@ public abstract class Timeline {
|
||||
result = 31 * result + uid.hashCode();
|
||||
result = 31 * result + mediaItem.hashCode();
|
||||
result = 31 * result + (manifest == null ? 0 : manifest.hashCode());
|
||||
result = 31 * result + (liveConfiguration == null ? 0 : liveConfiguration.hashCode());
|
||||
result = 31 * result + (int) (presentationStartTimeMs ^ (presentationStartTimeMs >>> 32));
|
||||
result = 31 * result + (int) (windowStartTimeMs ^ (windowStartTimeMs >>> 32));
|
||||
result =
|
||||
@ -377,7 +391,6 @@ public abstract class Timeline {
|
||||
+ (int) (elapsedRealtimeEpochOffsetMs ^ (elapsedRealtimeEpochOffsetMs >>> 32));
|
||||
result = 31 * result + (isSeekable ? 1 : 0);
|
||||
result = 31 * result + (isDynamic ? 1 : 0);
|
||||
result = 31 * result + (isLive ? 1 : 0);
|
||||
result = 31 * result + (isPlaceholder ? 1 : 0);
|
||||
result = 31 * result + (int) (defaultPositionUs ^ (defaultPositionUs >>> 32));
|
||||
result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
|
||||
|
@ -214,7 +214,7 @@ public abstract class BasePlayer implements Player {
|
||||
@Override
|
||||
public final boolean isCurrentWindowLive() {
|
||||
Timeline timeline = getCurrentTimeline();
|
||||
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isLive;
|
||||
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isLive();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,7 +99,7 @@ public class DefaultControlDispatcher implements ControlDispatcher {
|
||||
int nextWindowIndex = player.getNextWindowIndex();
|
||||
if (nextWindowIndex != C.INDEX_UNSET) {
|
||||
player.seekTo(nextWindowIndex, C.TIME_UNSET);
|
||||
} else if (timeline.getWindow(windowIndex, window).isLive) {
|
||||
} else if (timeline.getWindow(windowIndex, window).isLive()) {
|
||||
player.seekTo(windowIndex, C.TIME_UNSET);
|
||||
}
|
||||
return true;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@ -1050,7 +1051,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private long getLiveOffsetUs(Timeline timeline, Object periodUid, long periodPositionUs) {
|
||||
int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex;
|
||||
timeline.getWindow(windowIndex, window);
|
||||
if (window.windowStartTimeMs == C.TIME_UNSET || !window.isLive || !window.isDynamic) {
|
||||
if (window.windowStartTimeMs == C.TIME_UNSET || !window.isLive() || !window.isDynamic) {
|
||||
return C.TIME_UNSET;
|
||||
}
|
||||
return C.msToUs(window.getCurrentUnixTimeMs() - window.windowStartTimeMs)
|
||||
@ -1067,7 +1068,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
int windowIndex = timeline.getPeriodByUid(mediaPeriodId.periodUid, period).windowIndex;
|
||||
timeline.getWindow(windowIndex, window);
|
||||
return window.isLive && window.isDynamic;
|
||||
return window.isLive() && window.isDynamic;
|
||||
}
|
||||
|
||||
private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
|
||||
@ -1838,7 +1839,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
int windowIndex = newTimeline.getPeriodByUid(newPeriodId.periodUid, period).windowIndex;
|
||||
newTimeline.getWindow(windowIndex, window);
|
||||
livePlaybackSpeedControl.setLiveConfiguration(window.mediaItem.liveConfiguration);
|
||||
livePlaybackSpeedControl.setLiveConfiguration(castNonNull(window.liveConfiguration));
|
||||
if (positionForTargetOffsetOverrideUs != C.TIME_UNSET) {
|
||||
livePlaybackSpeedControl.setTargetLiveOffsetOverrideUs(
|
||||
getLiveOffsetUs(newTimeline, newPeriodId.periodUid, positionForTargetOffsetOverrideUs));
|
||||
|
@ -1570,7 +1570,7 @@ public interface Player {
|
||||
/**
|
||||
* Returns whether the current window is live, or {@code false} if the {@link Timeline} is empty.
|
||||
*
|
||||
* @see Timeline.Window#isLive
|
||||
* @see Timeline.Window#isLive()
|
||||
*/
|
||||
boolean isCurrentWindowLive();
|
||||
|
||||
|
@ -1003,7 +1003,7 @@ public final class DownloadHelper {
|
||||
// Ignore dynamic updates.
|
||||
return;
|
||||
}
|
||||
if (timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()).isLive) {
|
||||
if (timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()).isLive()) {
|
||||
downloadHelperHandler
|
||||
.obtainMessage(
|
||||
DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED,
|
||||
|
@ -374,7 +374,7 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
|
||||
/* isSeekable= */ false,
|
||||
// Dynamic window to indicate pending timeline updates.
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ false,
|
||||
/* liveConfiguration= */ null,
|
||||
/* defaultPositionUs= */ 0,
|
||||
/* durationUs= */ C.TIME_UNSET,
|
||||
/* firstPeriodIndex= */ 0,
|
||||
|
@ -336,7 +336,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
timelineDurationUs,
|
||||
timelineIsSeekable,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ timelineIsLive,
|
||||
/* useLiveConfiguration= */ timelineIsLive,
|
||||
/* manifest= */ null,
|
||||
mediaItem);
|
||||
if (timelineIsPlaceholder) {
|
||||
|
@ -132,7 +132,7 @@ public final class SilenceMediaSource extends BaseMediaSource {
|
||||
durationUs,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
mediaItem));
|
||||
}
|
||||
|
@ -45,9 +45,9 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
private final long windowDefaultStartPositionUs;
|
||||
private final boolean isSeekable;
|
||||
private final boolean isDynamic;
|
||||
private final boolean isLive;
|
||||
@Nullable private final Object manifest;
|
||||
@Nullable private final MediaItem mediaItem;
|
||||
@Nullable private final MediaItem.LiveConfiguration liveConfiguration;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #SinglePeriodTimeline(long, boolean, boolean, boolean, Object,
|
||||
@ -81,7 +81,8 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
* @param durationUs The duration of the period, in microseconds.
|
||||
* @param isSeekable Whether seeking is supported within the period.
|
||||
* @param isDynamic Whether the window may change when the timeline is updated.
|
||||
* @param isLive Whether the window is live.
|
||||
* @param useLiveConfiguration Whether the window is live and {@link MediaItem#liveConfiguration}
|
||||
* is used to configure live playback behaviour.
|
||||
* @param manifest The manifest. May be {@code null}.
|
||||
* @param mediaItem A media item used for {@link Window#mediaItem}.
|
||||
*/
|
||||
@ -89,7 +90,7 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
long durationUs,
|
||||
boolean isSeekable,
|
||||
boolean isDynamic,
|
||||
boolean isLive,
|
||||
boolean useLiveConfiguration,
|
||||
@Nullable Object manifest,
|
||||
MediaItem mediaItem) {
|
||||
this(
|
||||
@ -99,7 +100,7 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
/* windowDefaultStartPositionUs= */ 0,
|
||||
isSeekable,
|
||||
isDynamic,
|
||||
isLive,
|
||||
useLiveConfiguration,
|
||||
manifest,
|
||||
mediaItem);
|
||||
}
|
||||
@ -148,7 +149,8 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
* which to begin playback, in microseconds.
|
||||
* @param isSeekable Whether seeking is supported within the window.
|
||||
* @param isDynamic Whether the window may change when the timeline is updated.
|
||||
* @param isLive Whether the window is live.
|
||||
* @param useLiveConfiguration Whether the window is live and {@link MediaItem#liveConfiguration}
|
||||
* is used to configure live playback behaviour.
|
||||
* @param manifest The manifest. May be (@code null}.
|
||||
* @param mediaItem A media item used for {@link Timeline.Window#mediaItem}.
|
||||
*/
|
||||
@ -159,7 +161,7 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
long windowDefaultStartPositionUs,
|
||||
boolean isSeekable,
|
||||
boolean isDynamic,
|
||||
boolean isLive,
|
||||
boolean useLiveConfiguration,
|
||||
@Nullable Object manifest,
|
||||
MediaItem mediaItem) {
|
||||
this(
|
||||
@ -172,14 +174,14 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
windowDefaultStartPositionUs,
|
||||
isSeekable,
|
||||
isDynamic,
|
||||
isLive,
|
||||
manifest,
|
||||
mediaItem);
|
||||
mediaItem,
|
||||
useLiveConfiguration ? mediaItem.liveConfiguration : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #SinglePeriodTimeline(long, long, long, long, long, long, long, boolean,
|
||||
* boolean, boolean, Object, MediaItem)} instead.
|
||||
* boolean, Object, MediaItem, MediaItem.LiveConfiguration)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public SinglePeriodTimeline(
|
||||
@ -205,9 +207,9 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
windowDefaultStartPositionUs,
|
||||
isSeekable,
|
||||
isDynamic,
|
||||
isLive,
|
||||
manifest,
|
||||
MEDIA_ITEM.buildUpon().setTag(tag).build());
|
||||
MEDIA_ITEM.buildUpon().setTag(tag).build(),
|
||||
isLive ? MEDIA_ITEM.liveConfiguration : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -229,9 +231,10 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
* which to begin playback, in microseconds.
|
||||
* @param isSeekable Whether seeking is supported within the window.
|
||||
* @param isDynamic Whether the window may change when the timeline is updated.
|
||||
* @param isLive Whether the window is live.
|
||||
* @param manifest The manifest. May be {@code null}.
|
||||
* @param mediaItem A media item used for {@link Timeline.Window#mediaItem}.
|
||||
* @param liveConfiguration The configuration for live playback behaviour, or {@code null} if the
|
||||
* window is not live.
|
||||
*/
|
||||
public SinglePeriodTimeline(
|
||||
long presentationStartTimeMs,
|
||||
@ -243,9 +246,9 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
long windowDefaultStartPositionUs,
|
||||
boolean isSeekable,
|
||||
boolean isDynamic,
|
||||
boolean isLive,
|
||||
@Nullable Object manifest,
|
||||
MediaItem mediaItem) {
|
||||
MediaItem mediaItem,
|
||||
@Nullable MediaItem.LiveConfiguration liveConfiguration) {
|
||||
this.presentationStartTimeMs = presentationStartTimeMs;
|
||||
this.windowStartTimeMs = windowStartTimeMs;
|
||||
this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs;
|
||||
@ -255,9 +258,9 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
|
||||
this.isSeekable = isSeekable;
|
||||
this.isDynamic = isDynamic;
|
||||
this.isLive = isLive;
|
||||
this.manifest = manifest;
|
||||
this.mediaItem = checkNotNull(mediaItem);
|
||||
this.liveConfiguration = liveConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -291,7 +294,7 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
elapsedRealtimeEpochOffsetMs,
|
||||
isSeekable,
|
||||
isDynamic,
|
||||
isLive,
|
||||
liveConfiguration,
|
||||
windowDefaultStartPositionUs,
|
||||
windowDurationUs,
|
||||
/* firstPeriodIndex= */ 0,
|
||||
|
@ -291,7 +291,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
||||
durationUs,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
mediaItem);
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ public final class MediaPeriodQueueTest {
|
||||
CONTENT_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
private static final Uri AD_URI = Uri.EMPTY;
|
||||
|
@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.MediaItem.LiveConfiguration;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||
import com.google.android.exoplayer2.testutil.TimelineAsserts;
|
||||
@ -91,7 +92,7 @@ public class TimelineTest {
|
||||
assertThat(window).isNotEqualTo(otherWindow);
|
||||
|
||||
otherWindow = new Timeline.Window();
|
||||
otherWindow.isLive = true;
|
||||
otherWindow.liveConfiguration = LiveConfiguration.UNSET;
|
||||
assertThat(window).isNotEqualTo(otherWindow);
|
||||
|
||||
otherWindow = new Timeline.Window();
|
||||
@ -129,7 +130,7 @@ public class TimelineTest {
|
||||
window.elapsedRealtimeEpochOffsetMs,
|
||||
window.isSeekable,
|
||||
window.isDynamic,
|
||||
window.isLive,
|
||||
window.liveConfiguration,
|
||||
window.defaultPositionUs,
|
||||
window.durationUs,
|
||||
window.firstPeriodIndex,
|
||||
|
@ -69,7 +69,7 @@ public final class ClippingMediaSourceTest {
|
||||
TEST_PERIOD_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -90,7 +90,7 @@ public final class ClippingMediaSourceTest {
|
||||
TEST_PERIOD_DURATION_US,
|
||||
/* isSeekable= */ false,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -112,7 +112,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* durationUs= */ C.TIME_UNSET,
|
||||
/* isSeekable= */ false,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -134,7 +134,7 @@ public final class ClippingMediaSourceTest {
|
||||
TEST_PERIOD_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -153,7 +153,7 @@ public final class ClippingMediaSourceTest {
|
||||
TEST_PERIOD_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -189,7 +189,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* durationUs= */ TEST_PERIOD_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -208,7 +208,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* durationUs= */ C.TIME_UNSET,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -226,7 +226,7 @@ public final class ClippingMediaSourceTest {
|
||||
TEST_PERIOD_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -249,7 +249,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -272,7 +272,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
Timeline timeline2 =
|
||||
@ -283,7 +283,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -323,7 +323,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
Timeline timeline2 =
|
||||
@ -334,7 +334,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -374,7 +374,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
Timeline timeline2 =
|
||||
@ -385,7 +385,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -426,7 +426,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
Timeline timeline2 =
|
||||
@ -437,7 +437,7 @@ public final class ClippingMediaSourceTest {
|
||||
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
@ -556,7 +556,7 @@ public final class ClippingMediaSourceTest {
|
||||
TEST_PERIOD_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
FakeMediaSource fakeMediaSource =
|
||||
|
@ -71,7 +71,7 @@ public final class SinglePeriodTimelineTest {
|
||||
/* windowDefaultStartPositionUs= */ 0,
|
||||
/* isSeekable= */ false,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
// Should return null with a positive position projection beyond window duration.
|
||||
|
@ -58,7 +58,7 @@ public final class AdsMediaSourceTest {
|
||||
PREROLL_AD_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
private static final Object PREROLL_AD_PERIOD_UID =
|
||||
@ -70,7 +70,7 @@ public final class AdsMediaSourceTest {
|
||||
CONTENT_DURATION_US,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
/* manifest= */ null,
|
||||
MediaItem.fromUri(Uri.EMPTY));
|
||||
private static final Object CONTENT_PERIOD_UID =
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.source.dash;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
@ -427,7 +428,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
|
||||
private static final String TAG = "DashMediaSource";
|
||||
|
||||
private final MediaItem originalMediaItem;
|
||||
private final MediaItem mediaItem;
|
||||
private final boolean sideloadedManifest;
|
||||
private final DataSource.Factory manifestDataSourceFactory;
|
||||
private final DashChunkSource.Factory chunkSourceFactory;
|
||||
@ -452,7 +453,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
private IOException manifestFatalError;
|
||||
private Handler handler;
|
||||
|
||||
private MediaItem updatedMediaItem;
|
||||
private MediaItem.LiveConfiguration liveConfiguration;
|
||||
private Uri manifestUri;
|
||||
private Uri initialManifestUri;
|
||||
private DashManifest manifest;
|
||||
@ -476,8 +477,8 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
DrmSessionManager drmSessionManager,
|
||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||
long fallbackTargetLiveOffsetMs) {
|
||||
this.originalMediaItem = mediaItem;
|
||||
this.updatedMediaItem = mediaItem;
|
||||
this.mediaItem = mediaItem;
|
||||
this.liveConfiguration = mediaItem.liveConfiguration;
|
||||
this.manifestUri = checkNotNull(mediaItem.playbackProperties).uri;
|
||||
this.initialManifestUri = mediaItem.playbackProperties.uri;
|
||||
this.manifest = manifest;
|
||||
@ -531,12 +532,12 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getTag() {
|
||||
return castNonNull(updatedMediaItem.playbackProperties).tag;
|
||||
return castNonNull(mediaItem.playbackProperties).tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaItem getMediaItem() {
|
||||
return updatedMediaItem;
|
||||
return mediaItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -938,8 +939,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
/* windowStartPeriodTimeUs= */ currentStartTimeUs,
|
||||
/* windowEndPeriodTimeUs= */ currentEndTimeUs);
|
||||
windowDefaultStartPositionUs =
|
||||
nowUnixTimeUs
|
||||
- C.msToUs(windowStartTimeMs + updatedMediaItem.liveConfiguration.targetOffsetMs);
|
||||
nowUnixTimeUs - C.msToUs(windowStartTimeMs + liveConfiguration.targetOffsetMs);
|
||||
long minimumDefaultStartPositionUs =
|
||||
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
|
||||
if (windowDefaultStartPositionUs < minimumDefaultStartPositionUs) {
|
||||
@ -959,7 +959,8 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
windowDurationUs,
|
||||
windowDefaultStartPositionUs,
|
||||
manifest,
|
||||
updatedMediaItem);
|
||||
mediaItem,
|
||||
manifest.dynamic ? liveConfiguration : null);
|
||||
refreshSourceInfo(timeline);
|
||||
|
||||
if (!sideloadedManifest) {
|
||||
@ -996,8 +997,8 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
private void updateMediaItemLiveConfiguration(
|
||||
long nowPeriodTimeUs, long windowStartPeriodTimeUs, long windowEndPeriodTimeUs) {
|
||||
long maxLiveOffsetMs;
|
||||
if (originalMediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) {
|
||||
maxLiveOffsetMs = originalMediaItem.liveConfiguration.maxOffsetMs;
|
||||
if (mediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) {
|
||||
maxLiveOffsetMs = mediaItem.liveConfiguration.maxOffsetMs;
|
||||
} else if (manifest.serviceDescription != null
|
||||
&& manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) {
|
||||
maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs;
|
||||
@ -1005,8 +1006,8 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
maxLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowStartPeriodTimeUs);
|
||||
}
|
||||
long minLiveOffsetMs;
|
||||
if (originalMediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) {
|
||||
minLiveOffsetMs = originalMediaItem.liveConfiguration.minOffsetMs;
|
||||
if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) {
|
||||
minLiveOffsetMs = mediaItem.liveConfiguration.minOffsetMs;
|
||||
} else if (manifest.serviceDescription != null
|
||||
&& manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) {
|
||||
minLiveOffsetMs = manifest.serviceDescription.minOffsetMs;
|
||||
@ -1022,9 +1023,9 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
}
|
||||
}
|
||||
long targetOffsetMs;
|
||||
if (updatedMediaItem.liveConfiguration.targetOffsetMs != C.TIME_UNSET) {
|
||||
if (liveConfiguration.targetOffsetMs != C.TIME_UNSET) {
|
||||
// Keep existing target offset even if the media configuration changes.
|
||||
targetOffsetMs = updatedMediaItem.liveConfiguration.targetOffsetMs;
|
||||
targetOffsetMs = liveConfiguration.targetOffsetMs;
|
||||
} else if (manifest.serviceDescription != null
|
||||
&& manifest.serviceDescription.targetOffsetMs != C.TIME_UNSET) {
|
||||
targetOffsetMs = manifest.serviceDescription.targetOffsetMs;
|
||||
@ -1048,26 +1049,20 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs);
|
||||
}
|
||||
float minPlaybackSpeed = C.RATE_UNSET;
|
||||
if (originalMediaItem.liveConfiguration.minPlaybackSpeed != C.RATE_UNSET) {
|
||||
minPlaybackSpeed = originalMediaItem.liveConfiguration.minPlaybackSpeed;
|
||||
if (mediaItem.liveConfiguration.minPlaybackSpeed != C.RATE_UNSET) {
|
||||
minPlaybackSpeed = mediaItem.liveConfiguration.minPlaybackSpeed;
|
||||
} else if (manifest.serviceDescription != null) {
|
||||
minPlaybackSpeed = manifest.serviceDescription.minPlaybackSpeed;
|
||||
}
|
||||
float maxPlaybackSpeed = C.RATE_UNSET;
|
||||
if (originalMediaItem.liveConfiguration.maxPlaybackSpeed != C.RATE_UNSET) {
|
||||
maxPlaybackSpeed = originalMediaItem.liveConfiguration.maxPlaybackSpeed;
|
||||
if (mediaItem.liveConfiguration.maxPlaybackSpeed != C.RATE_UNSET) {
|
||||
maxPlaybackSpeed = mediaItem.liveConfiguration.maxPlaybackSpeed;
|
||||
} else if (manifest.serviceDescription != null) {
|
||||
maxPlaybackSpeed = manifest.serviceDescription.maxPlaybackSpeed;
|
||||
}
|
||||
updatedMediaItem =
|
||||
originalMediaItem
|
||||
.buildUpon()
|
||||
.setLiveTargetOffsetMs(targetOffsetMs)
|
||||
.setLiveMinOffsetMs(minLiveOffsetMs)
|
||||
.setLiveMaxOffsetMs(maxLiveOffsetMs)
|
||||
.setLiveMinPlaybackSpeed(minPlaybackSpeed)
|
||||
.setLiveMaxPlaybackSpeed(maxPlaybackSpeed)
|
||||
.build();
|
||||
liveConfiguration =
|
||||
new MediaItem.LiveConfiguration(
|
||||
targetOffsetMs, minLiveOffsetMs, maxLiveOffsetMs, minPlaybackSpeed, maxPlaybackSpeed);
|
||||
}
|
||||
|
||||
private void scheduleManifestRefresh(long delayUntilNextLoadMs) {
|
||||
@ -1232,6 +1227,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
private final long windowDefaultStartPositionUs;
|
||||
private final DashManifest manifest;
|
||||
private final MediaItem mediaItem;
|
||||
@Nullable private final MediaItem.LiveConfiguration liveConfiguration;
|
||||
|
||||
public DashTimeline(
|
||||
long presentationStartTimeMs,
|
||||
@ -1242,7 +1238,9 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
long windowDurationUs,
|
||||
long windowDefaultStartPositionUs,
|
||||
DashManifest manifest,
|
||||
MediaItem mediaItem) {
|
||||
MediaItem mediaItem,
|
||||
@Nullable MediaItem.LiveConfiguration liveConfiguration) {
|
||||
checkState(manifest.dynamic == (liveConfiguration != null));
|
||||
this.presentationStartTimeMs = presentationStartTimeMs;
|
||||
this.windowStartTimeMs = windowStartTimeMs;
|
||||
this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs;
|
||||
@ -1252,6 +1250,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
|
||||
this.manifest = manifest;
|
||||
this.mediaItem = mediaItem;
|
||||
this.liveConfiguration = liveConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1288,7 +1287,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
elapsedRealtimeEpochOffsetMs,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ isMovingLiveWindow(manifest),
|
||||
/* isLive= */ manifest.dynamic,
|
||||
liveConfiguration,
|
||||
windowDefaultStartPositionUs,
|
||||
windowDurationUs,
|
||||
/* firstPeriodIndex= */ 0,
|
||||
|
@ -287,14 +287,15 @@ public final class DashMediaSourceTest {
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION))
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
MediaItem.LiveConfiguration liveConfiguration =
|
||||
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetOffsetMs)
|
||||
assertThat(liveConfiguration.targetOffsetMs)
|
||||
.isEqualTo(DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minOffsetMs).isEqualTo(0L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L);
|
||||
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
|
||||
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -306,13 +307,14 @@ public final class DashMediaSourceTest {
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
MediaItem.LiveConfiguration liveConfiguration =
|
||||
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetOffsetMs).isEqualTo(1234L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minOffsetMs).isEqualTo(0L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(liveConfiguration.targetOffsetMs).isEqualTo(1234L);
|
||||
assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L);
|
||||
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
|
||||
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -333,13 +335,10 @@ public final class DashMediaSourceTest {
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(mediaItem);
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
MediaItem.LiveConfiguration liveConfiguration =
|
||||
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetOffsetMs).isEqualTo(876L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minOffsetMs).isEqualTo(500L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxOffsetMs).isEqualTo(20_000L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
assertThat(liveConfiguration).isEqualTo(mediaItem.liveConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -353,13 +352,14 @@ public final class DashMediaSourceTest {
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItem = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
MediaItem.LiveConfiguration liveConfiguration =
|
||||
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
|
||||
|
||||
assertThat(mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(2_000L);
|
||||
assertThat(mediaItem.liveConfiguration.minOffsetMs).isEqualTo(500L);
|
||||
assertThat(mediaItem.liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
|
||||
assertThat(mediaItem.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(mediaItem.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(liveConfiguration.targetOffsetMs).isEqualTo(2_000L);
|
||||
assertThat(liveConfiguration.minOffsetMs).isEqualTo(500L);
|
||||
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
|
||||
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -383,13 +383,14 @@ public final class DashMediaSourceTest {
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(mediaItem);
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
MediaItem.LiveConfiguration liveConfiguration =
|
||||
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetOffsetMs).isEqualTo(876L);
|
||||
assertThat(mediaItem.liveConfiguration.minOffsetMs).isEqualTo(200L);
|
||||
assertThat(mediaItem.liveConfiguration.maxOffsetMs).isEqualTo(999L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
assertThat(liveConfiguration.targetOffsetMs).isEqualTo(876L);
|
||||
assertThat(liveConfiguration.minOffsetMs).isEqualTo(200L);
|
||||
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(999L);
|
||||
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -401,13 +402,14 @@ public final class DashMediaSourceTest {
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItem = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
MediaItem.LiveConfiguration liveConfiguration =
|
||||
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
|
||||
|
||||
assertThat(mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(4_000L);
|
||||
assertThat(mediaItem.liveConfiguration.minOffsetMs).isEqualTo(2_000L);
|
||||
assertThat(mediaItem.liveConfiguration.maxOffsetMs).isEqualTo(6_000L);
|
||||
assertThat(mediaItem.liveConfiguration.minPlaybackSpeed).isEqualTo(0.96f);
|
||||
assertThat(mediaItem.liveConfiguration.maxPlaybackSpeed).isEqualTo(1.04f);
|
||||
assertThat(liveConfiguration.targetOffsetMs).isEqualTo(4_000L);
|
||||
assertThat(liveConfiguration.minOffsetMs).isEqualTo(2_000L);
|
||||
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(6_000L);
|
||||
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(0.96f);
|
||||
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(1.04f);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -428,13 +430,14 @@ public final class DashMediaSourceTest {
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(mediaItem);
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
MediaItem.LiveConfiguration liveConfiguration =
|
||||
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetOffsetMs).isEqualTo(876L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minOffsetMs).isEqualTo(100L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxOffsetMs).isEqualTo(999L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
assertThat(liveConfiguration.targetOffsetMs).isEqualTo(876L);
|
||||
assertThat(liveConfiguration.minOffsetMs).isEqualTo(100L);
|
||||
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(999L);
|
||||
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -448,7 +451,7 @@ public final class DashMediaSourceTest {
|
||||
Window window = prepareAndWaitForTimelineRefresh(mediaSource);
|
||||
|
||||
// Expect the target live offset as defined in the manifest.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(3000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(3000);
|
||||
// Expect the default position at the first segment start before the live edge.
|
||||
assertThat(window.getDefaultPositionMs()).isEqualTo(2_000);
|
||||
}
|
||||
@ -466,7 +469,7 @@ public final class DashMediaSourceTest {
|
||||
// Expect the default position at the first segment start below the minimum live start position.
|
||||
assertThat(window.getDefaultPositionMs()).isEqualTo(4_000);
|
||||
// Expect the target live offset reaching from now time to the minimum live start position.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(9000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(9000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -483,7 +486,7 @@ public final class DashMediaSourceTest {
|
||||
// Expect the default position at the start of the last segment.
|
||||
assertThat(window.getDefaultPositionMs()).isEqualTo(12_000);
|
||||
// Expect the target live offset reaching from now time to the end of the window.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(60_000 - 16_000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(60_000 - 16_000);
|
||||
}
|
||||
|
||||
private static Window prepareAndWaitForTimelineRefresh(MediaSource mediaSource)
|
||||
|
@ -405,8 +405,9 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
private final boolean useSessionKeys;
|
||||
private final HlsPlaylistTracker playlistTracker;
|
||||
private final long elapsedRealTimeOffsetMs;
|
||||
private final MediaItem mediaItem;
|
||||
|
||||
private MediaItem mediaItem;
|
||||
private MediaItem.LiveConfiguration liveConfiguration;
|
||||
@Nullable private TransferListener mediaTransferListener;
|
||||
|
||||
private HlsMediaSource(
|
||||
@ -423,6 +424,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
boolean useSessionKeys) {
|
||||
this.playbackProperties = checkNotNull(mediaItem.playbackProperties);
|
||||
this.mediaItem = mediaItem;
|
||||
this.liveConfiguration = mediaItem.liveConfiguration;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.extractorFactory = extractorFactory;
|
||||
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
||||
@ -515,8 +517,8 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
if (playlistTracker.isLive()) {
|
||||
long liveEdgeOffsetUs = getLiveEdgeOffsetUs(playlist);
|
||||
long targetLiveOffsetUs =
|
||||
mediaItem.liveConfiguration.targetOffsetMs != C.TIME_UNSET
|
||||
? C.msToUs(mediaItem.liveConfiguration.targetOffsetMs)
|
||||
liveConfiguration.targetOffsetMs != C.TIME_UNSET
|
||||
? C.msToUs(liveConfiguration.targetOffsetMs)
|
||||
: getTargetLiveOffsetUs(playlist, liveEdgeOffsetUs);
|
||||
// Ensure target live offset is within the live window and greater than the live edge offset.
|
||||
targetLiveOffsetUs =
|
||||
@ -546,9 +548,9 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
windowDefaultStartPositionUs,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ !playlist.hasEndTag,
|
||||
/* isLive= */ true,
|
||||
manifest,
|
||||
mediaItem);
|
||||
mediaItem,
|
||||
liveConfiguration);
|
||||
} else /* not live */ {
|
||||
if (windowDefaultStartPositionUs == C.TIME_UNSET) {
|
||||
windowDefaultStartPositionUs = 0;
|
||||
@ -564,9 +566,9 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
windowDefaultStartPositionUs,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
manifest,
|
||||
mediaItem);
|
||||
mediaItem,
|
||||
/* liveConfiguration= */ null);
|
||||
}
|
||||
refreshSourceInfo(timeline);
|
||||
}
|
||||
@ -581,9 +583,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
List<HlsMediaPlaylist.Segment> segments = playlist.segments;
|
||||
int segmentIndex = segments.size() - 1;
|
||||
long minStartPositionUs =
|
||||
playlist.durationUs
|
||||
+ liveEdgeOffsetUs
|
||||
- C.msToUs(mediaItem.liveConfiguration.targetOffsetMs);
|
||||
playlist.durationUs + liveEdgeOffsetUs - C.msToUs(liveConfiguration.targetOffsetMs);
|
||||
while (segmentIndex > 0
|
||||
&& segments.get(segmentIndex).relativeStartTimeUs > minStartPositionUs) {
|
||||
segmentIndex--;
|
||||
@ -593,8 +593,9 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
|
||||
private void maybeUpdateMediaItem(long targetLiveOffsetUs) {
|
||||
long targetLiveOffsetMs = C.usToMs(targetLiveOffsetUs);
|
||||
if (targetLiveOffsetMs != mediaItem.liveConfiguration.targetOffsetMs) {
|
||||
mediaItem = mediaItem.buildUpon().setLiveTargetOffsetMs(targetLiveOffsetMs).build();
|
||||
if (targetLiveOffsetMs != liveConfiguration.targetOffsetMs) {
|
||||
liveConfiguration =
|
||||
mediaItem.buildUpon().setLiveTargetOffsetMs(targetLiveOffsetMs).build().liveConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ public class HlsMediaSourceTest {
|
||||
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
|
||||
// The target live offset is picked from target duration (3 * 4 = 12 seconds) and then expressed
|
||||
// in relation to the live edge (12 + 1 seconds).
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(13000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000);
|
||||
assertThat(window.defaultPositionUs).isEqualTo(4000000);
|
||||
}
|
||||
|
||||
@ -219,7 +219,7 @@ public class HlsMediaSourceTest {
|
||||
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
|
||||
// The target live offset is picked from hold back and then expressed in relation to the live
|
||||
// edge (+1 seconds).
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(13000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000);
|
||||
assertThat(window.defaultPositionUs).isEqualTo(4000000);
|
||||
}
|
||||
|
||||
@ -257,7 +257,7 @@ public class HlsMediaSourceTest {
|
||||
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
|
||||
// The target live offset is picked from hold back and then expressed in relation to the live
|
||||
// edge (+1 seconds).
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(13000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000);
|
||||
assertThat(window.defaultPositionUs).isEqualTo(4000000);
|
||||
}
|
||||
|
||||
@ -288,7 +288,7 @@ public class HlsMediaSourceTest {
|
||||
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
|
||||
// The target live offset is picked from part hold back and then expressed in relation to the
|
||||
// live edge (+1 seconds).
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(4000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(4000);
|
||||
assertThat(window.defaultPositionUs).isEqualTo(0);
|
||||
}
|
||||
|
||||
@ -318,7 +318,7 @@ public class HlsMediaSourceTest {
|
||||
|
||||
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
|
||||
// The target live offset is picked from the media item and not adjusted.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(1000);
|
||||
assertThat(window.liveConfiguration).isEqualTo(mediaItem.liveConfiguration);
|
||||
assertThat(window.defaultPositionUs).isEqualTo(0);
|
||||
}
|
||||
|
||||
@ -351,7 +351,7 @@ public class HlsMediaSourceTest {
|
||||
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
|
||||
assertThat(mediaItem.liveConfiguration.targetOffsetMs)
|
||||
.isGreaterThan(C.usToMs(window.durationUs));
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(9000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(9000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -385,7 +385,7 @@ public class HlsMediaSourceTest {
|
||||
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
|
||||
// The target live offset is not adjusted to the live edge because the list does not have
|
||||
// program date time.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetOffsetMs).isEqualTo(12000);
|
||||
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(12000);
|
||||
assertThat(window.defaultPositionUs).isEqualTo(4000000);
|
||||
}
|
||||
|
||||
@ -475,13 +475,13 @@ public class HlsMediaSourceTest {
|
||||
runMainLooperUntil(() -> timelines.size() == 4);
|
||||
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
assertThat(timelines.get(0).getWindow(0, window).mediaItem.liveConfiguration.targetOffsetMs)
|
||||
assertThat(timelines.get(0).getWindow(0, window).liveConfiguration.targetOffsetMs)
|
||||
.isEqualTo(12000);
|
||||
assertThat(timelines.get(1).getWindow(0, window).mediaItem.liveConfiguration.targetOffsetMs)
|
||||
assertThat(timelines.get(1).getWindow(0, window).liveConfiguration.targetOffsetMs)
|
||||
.isEqualTo(12000);
|
||||
assertThat(timelines.get(2).getWindow(0, window).mediaItem.liveConfiguration.targetOffsetMs)
|
||||
assertThat(timelines.get(2).getWindow(0, window).liveConfiguration.targetOffsetMs)
|
||||
.isEqualTo(8000);
|
||||
assertThat(timelines.get(3).getWindow(0, window).mediaItem.liveConfiguration.targetOffsetMs)
|
||||
assertThat(timelines.get(3).getWindow(0, window).liveConfiguration.targetOffsetMs)
|
||||
.isEqualTo(8000);
|
||||
}
|
||||
|
||||
|
@ -594,7 +594,7 @@ public final class SsMediaSource extends BaseMediaSource
|
||||
/* windowDefaultStartPositionUs= */ 0,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ manifest.isLive,
|
||||
/* isLive= */ manifest.isLive,
|
||||
/* useLiveConfiguration= */ manifest.isLive,
|
||||
manifest,
|
||||
mediaItem);
|
||||
} else if (manifest.isLive) {
|
||||
@ -617,7 +617,7 @@ public final class SsMediaSource extends BaseMediaSource
|
||||
defaultStartPositionUs,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
manifest,
|
||||
mediaItem);
|
||||
} else {
|
||||
@ -631,7 +631,7 @@ public final class SsMediaSource extends BaseMediaSource
|
||||
/* windowDefaultStartPositionUs= */ 0,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* useLiveConfiguration= */ false,
|
||||
manifest,
|
||||
mediaItem);
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ public final class FakeTimeline extends Timeline {
|
||||
/* elapsedRealtimeEpochOffsetMs= */ windowDefinition.isLive ? 0 : C.TIME_UNSET,
|
||||
windowDefinition.isSeekable,
|
||||
windowDefinition.isDynamic,
|
||||
windowDefinition.isLive,
|
||||
windowDefinition.isLive ? windowDefinition.mediaItem.liveConfiguration : null,
|
||||
windowDefinition.defaultPositionUs,
|
||||
windowDefinition.durationUs,
|
||||
periodOffsets[windowIndex],
|
||||
|
Loading…
x
Reference in New Issue
Block a user