From 05c928f96d761dbf88dbf5f6c2d8fc943c79aa33 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 10 Dec 2020 19:31:47 +0000 Subject: [PATCH] Add live configuration to Timeline.Window Issue: #5011 PiperOrigin-RevId: 346828103 --- .../exoplayer2/ext/cast/CastTimeline.java | 6 +- .../google/android/exoplayer2/Timeline.java | 37 +++++--- .../google/android/exoplayer2/BasePlayer.java | 2 +- .../exoplayer2/DefaultControlDispatcher.java | 2 +- .../exoplayer2/ExoPlayerImplInternal.java | 7 +- .../com/google/android/exoplayer2/Player.java | 2 +- .../exoplayer2/offline/DownloadHelper.java | 2 +- .../exoplayer2/source/MaskingMediaSource.java | 2 +- .../source/ProgressiveMediaSource.java | 2 +- .../exoplayer2/source/SilenceMediaSource.java | 2 +- .../source/SinglePeriodTimeline.java | 35 +++---- .../source/SingleSampleMediaSource.java | 2 +- .../exoplayer2/MediaPeriodQueueTest.java | 2 +- .../android/exoplayer2/TimelineTest.java | 5 +- .../source/ClippingMediaSourceTest.java | 36 +++---- .../source/SinglePeriodTimelineTest.java | 2 +- .../source/ads/AdsMediaSourceTest.java | 4 +- .../source/dash/DashMediaSource.java | 59 ++++++------ .../source/dash/DashMediaSourceTest.java | 93 ++++++++++--------- .../exoplayer2/source/hls/HlsMediaSource.java | 25 ++--- .../source/hls/HlsMediaSourceTest.java | 22 ++--- .../source/smoothstreaming/SsMediaSource.java | 6 +- .../exoplayer2/testutil/FakeTimeline.java | 2 +- 23 files changed, 190 insertions(+), 167 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java index 6552c4da08..1a03b779f6 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java @@ -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, diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 69bccfea73..842862fee8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -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; *

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). * *

Live stream with indefinite availability

* @@ -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. - * - *

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)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java index 150a13e288..d99a320e32 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java index 25c468330c..26fbdd3bcb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java @@ -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; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 7c3dd553b9..875f548a86 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -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)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 36d64ec07e..c121e11159 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -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(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 0b8a668200..60df8413b1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -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, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java index 5d2e1c6fb7..f9cf5af50d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java @@ -374,7 +374,7 @@ public final class MaskingMediaSource extends CompositeMediaSource { /* isSeekable= */ false, // Dynamic window to indicate pending timeline updates. /* isDynamic= */ true, - /* isLive= */ false, + /* liveConfiguration= */ null, /* defaultPositionUs= */ 0, /* durationUs= */ C.TIME_UNSET, /* firstPeriodIndex= */ 0, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java index 19f09fde22..e18028571f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java @@ -336,7 +336,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, - /* isLive= */ timelineIsLive, + /* useLiveConfiguration= */ timelineIsLive, /* manifest= */ null, mediaItem); if (timelineIsPlaceholder) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java index 26b783f970..15861a1922 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java @@ -132,7 +132,7 @@ public final class SilenceMediaSource extends BaseMediaSource { durationUs, /* isSeekable= */ true, /* isDynamic= */ false, - /* isLive= */ false, + /* useLiveConfiguration= */ false, /* manifest= */ null, mediaItem)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java index 54230a8b4f..a24dedea03 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java @@ -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, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 6cb8a451b3..70d2f925ce 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -291,7 +291,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { durationUs, /* isSeekable= */ true, /* isDynamic= */ false, - /* isLive= */ false, + /* useLiveConfiguration= */ false, /* manifest= */ null, mediaItem); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index ef845f5daa..6a6a2dcf42 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -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; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java b/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java index e8585399cb..9dfd643712 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java @@ -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, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index 8fce3b25ac..dda55e4f47 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -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 = diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java index 4fce17e336..09ac9b6df2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java @@ -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. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java index 83386673af..b5748aaa42 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java @@ -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 = diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 4dc0921b0a..8c4aaaa66e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -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, diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java index 57f451b668..d1269e18a1 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java @@ -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) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 61a8095176..bb5ce6c509 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -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 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; } } diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java index 13ef9d75a4..fd2744280a 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java @@ -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); } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index d24394edbc..3297bc0311 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -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); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java index 0f68783560..a440dd745d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java @@ -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],