From 3f5dbf2ef3fe19f392d46027b7ffce0783d28427 Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 19 Jul 2021 21:34:41 +0100 Subject: [PATCH] Add flag to SinglePeriodTimeline to suppress projection Issue: #9037 #minor-release PiperOrigin-RevId: 385630065 --- RELEASENOTES.md | 3 ++ .../source/SinglePeriodTimeline.java | 46 ++++++++++++++++++- .../android/exoplayer2/ExoPlayerTest.java | 1 + .../exoplayer2/source/hls/HlsMediaSource.java | 5 ++ .../source/hls/playlist/HlsMediaPlaylist.java | 6 +++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index baf4502a2e..d94b35b1ce 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -138,6 +138,9 @@ ([#9004](https://github.com/google/ExoPlayer/issues/9004)). * Forward the `FRAME-RATE` value from the master playlist to renditions. ([#8960](https://github.com/google/ExoPlayer/issues/8960)). + * Fix issue where HLS events would start at positions greater than + specified by an `EXT-X-START` tag when placed in a playlist + ([#9037](https://github.com/google/ExoPlayer/issues/9037)). * Ad playback: * Support changing ad break positions in the player logic ([#5067](https://github.com/google/ExoPlayer/issues/5067). 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 8d70fdc102..142bc13755 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 @@ -40,6 +40,7 @@ public final class SinglePeriodTimeline extends Timeline { private final long windowDefaultStartPositionUs; private final boolean isSeekable; private final boolean isDynamic; + private final boolean suppressPositionProjection; @Nullable private final Object manifest; @Nullable private final MediaItem mediaItem; @Nullable private final MediaItem.LiveConfiguration liveConfiguration; @@ -169,6 +170,7 @@ public final class SinglePeriodTimeline extends Timeline { windowDefaultStartPositionUs, isSeekable, isDynamic, + /* suppressPositionProjection= */ false, manifest, mediaItem, useLiveConfiguration ? mediaItem.liveConfiguration : null); @@ -176,7 +178,7 @@ public final class SinglePeriodTimeline extends Timeline { /** * @deprecated Use {@link #SinglePeriodTimeline(long, long, long, long, long, long, long, boolean, - * boolean, Object, MediaItem, MediaItem.LiveConfiguration)} instead. + * boolean, boolean, Object, MediaItem, MediaItem.LiveConfiguration)} instead. */ @Deprecated public SinglePeriodTimeline( @@ -202,11 +204,46 @@ public final class SinglePeriodTimeline extends Timeline { windowDefaultStartPositionUs, isSeekable, isDynamic, + /* suppressPositionProjection= */ false, manifest, MEDIA_ITEM.buildUpon().setTag(tag).build(), isLive ? MEDIA_ITEM.liveConfiguration : null); } + /** + * @deprecated Use {@link #SinglePeriodTimeline(long, long, long, long, long, long, long, boolean, + * boolean, boolean, Object, MediaItem, MediaItem.LiveConfiguration)} instead. + */ + @Deprecated + public SinglePeriodTimeline( + long presentationStartTimeMs, + long windowStartTimeMs, + long elapsedRealtimeEpochOffsetMs, + long periodDurationUs, + long windowDurationUs, + long windowPositionInPeriodUs, + long windowDefaultStartPositionUs, + boolean isSeekable, + boolean isDynamic, + @Nullable Object manifest, + MediaItem mediaItem, + @Nullable MediaItem.LiveConfiguration liveConfiguration) { + this( + presentationStartTimeMs, + windowStartTimeMs, + elapsedRealtimeEpochOffsetMs, + periodDurationUs, + windowDurationUs, + windowPositionInPeriodUs, + windowDefaultStartPositionUs, + isSeekable, + isDynamic, + /* suppressPositionProjection= */ false, + manifest, + mediaItem, + liveConfiguration); + } + /** * Creates a timeline with one period, and a window of known duration starting at a specified * position in the period. @@ -226,6 +263,9 @@ 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 suppressPositionProjection Whether {@link #getWindow(int, Window, long) position + * projection} in a playlist should be suppressed. This only applies for dynamic timelines and + * is ignored otherwise. * @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 @@ -241,6 +281,7 @@ public final class SinglePeriodTimeline extends Timeline { long windowDefaultStartPositionUs, boolean isSeekable, boolean isDynamic, + boolean suppressPositionProjection, @Nullable Object manifest, MediaItem mediaItem, @Nullable MediaItem.LiveConfiguration liveConfiguration) { @@ -253,6 +294,7 @@ public final class SinglePeriodTimeline extends Timeline { this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; this.isSeekable = isSeekable; this.isDynamic = isDynamic; + this.suppressPositionProjection = suppressPositionProjection; this.manifest = manifest; this.mediaItem = checkNotNull(mediaItem); this.liveConfiguration = liveConfiguration; @@ -268,7 +310,7 @@ public final class SinglePeriodTimeline extends Timeline { public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { Assertions.checkIndex(windowIndex, 0, 1); long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; - if (isDynamic && defaultPositionProjectionUs != 0) { + if (isDynamic && !suppressPositionProjection && defaultPositionProjectionUs != 0) { if (windowDurationUs == C.TIME_UNSET) { // Don't allow projection into a window that has an unknown duration. windowDefaultStartPositionUs = C.TIME_UNSET; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index dbd8ab845e..2d32923a5a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -9428,6 +9428,7 @@ public final class ExoPlayerTest { /* windowDefaultStartPositionUs= */ 0, /* isSeekable= */ true, /* isDynamic= */ true, + /* suppressPositionProjection= */ false, /* manifest= */ null, mediaItem, mediaItem.liveConfiguration); 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 6b454ebf50..d5c3c10d4d 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 @@ -548,6 +548,9 @@ public final class HlsMediaSource extends BaseMediaSource maybeUpdateLiveConfiguration(targetLiveOffsetUs); long windowDefaultStartPositionUs = getLiveWindowDefaultStartPositionUs(playlist, liveEdgeOffsetUs); + boolean suppressPositionProjection = + playlist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_EVENT + && playlist.hasPositiveStartOffset; return new SinglePeriodTimeline( presentationStartTimeMs, windowStartTimeMs, @@ -558,6 +561,7 @@ public final class HlsMediaSource extends BaseMediaSource windowDefaultStartPositionUs, /* isSeekable= */ true, /* isDynamic= */ !playlist.hasEndTag, + suppressPositionProjection, manifest, mediaItem, liveConfiguration); @@ -590,6 +594,7 @@ public final class HlsMediaSource extends BaseMediaSource windowDefaultStartPositionUs, /* isSeekable= */ true, /* isDynamic= */ false, + /* suppressPositionProjection= */ true, manifest, mediaItem, /* liveConfiguration= */ null); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 695f41fbee..c0f867dc76 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -400,6 +400,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * {@link #durationUs}, inclusive. */ public final long startOffsetUs; + /** + * Whether the {@link #startOffsetUs} was explicitly defined by #EXT-X-START as a positive value + * or zero. + */ + public final boolean hasPositiveStartOffset; /** Whether the start position should be precise, as defined by #EXT-X-START. */ public final boolean preciseStart; /** @@ -526,6 +531,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { : startOffsetUs >= 0 ? min(durationUs, startOffsetUs) : max(0, durationUs + startOffsetUs); + this.hasPositiveStartOffset = startOffsetUs >= 0; this.serverControl = serverControl; }