From d8d3bd7b3fb30da8fe4dad6159674470ea2f5353 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 2 Mar 2018 00:46:10 -0800 Subject: [PATCH] Make the period and initial window positions match for all HLS streams Before this change, HlsMediaSource timelines had a period starting at the epoch. For VOD streams the window position in the period was the program date time. This change makes period and initial window positions match. For live streams the window position advances as segments are removed, so its position in the period is the difference between the initial program date time and the program date time of the latest playlist. This also makes it possible to insert ads in VOD HLS content with program date time, as the period and window are now aligned. Issue: #3865 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=187590948 --- RELEASENOTES.md | 12 +++++- .../exoplayer2/source/hls/HlsChunkSource.java | 14 +++++-- .../exoplayer2/source/hls/HlsMediaSource.java | 40 ++++++++++++++----- .../hls/playlist/HlsPlaylistTracker.java | 9 ++++- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f9a03390efc..f21ad7f392d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,15 @@ # Release notes # +### 2.7.1 ### + +* HlsMediaSource: make HLS periods start at zero instead of the epoch. + Applications that rely on HLS timelines having a period starting at + the epoch will need to update their handling of HLS timelines. The program + date time is still available via the informational + `Timeline.Window.windowStartTimeMs` field + ([#3865](https://github.com/google/ExoPlayer/issues/3865), + [#3888](https://github.com/google/ExoPlayer/issues/3888)). + ### 2.7.0 ### * Player interface: @@ -21,7 +31,7 @@ * Add `ExoPlayer.setSeekParameters` for controlling how seek operations are performed. The `SeekParameters` class contains defaults for exact seeking and seeking to the closest sync points before, either side or after specified seek - positions. `SeekParameters` are not currently supported when playing HLS + positions. `SeekParameters` are not currently supported when playing HLS streams. * DefaultTrackSelector: * Replace `DefaultTrackSelector.Parameters` copy methods with a builder. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index db0db47aee9..d8496a63d2c 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -261,9 +261,13 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, long l // If the playlist is too old to contain the chunk, we need to refresh it. chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(); } else { - chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, - targetPositionUs - mediaPlaylist.startTimeUs, true, - !playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence; + chunkMediaSequence = + Util.binarySearchFloor( + mediaPlaylist.segments, + targetPositionUs, + /* inclusive= */ true, + /* stayInBounds= */ !playlistTracker.isLive() || previous == null) + + mediaPlaylist.mediaSequence; if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) { // We try getting the next chunk without adapting in case that's the reason for falling // behind the live window. @@ -320,7 +324,9 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, long l } // Compute start time of the next chunk. - long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs; + long offsetFromInitialStartTimeUs = + mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs(); + long startTimeUs = offsetFromInitialStartTimeUs + segment.relativeStartTimeUs; int discontinuitySequence = mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence; TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster( 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 5113bef6e04..1fe0d72ea1e 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 @@ -366,28 +366,50 @@ public void releaseSource() { @Override public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { SinglePeriodTimeline timeline; - long presentationStartTimeMs = playlist.hasProgramDateTime ? 0 : C.TIME_UNSET; long windowStartTimeMs = playlist.hasProgramDateTime ? C.usToMs(playlist.startTimeUs) : C.TIME_UNSET; + // For playlist types EVENT and VOD we know segments are never removed, so the presentation + // started at the same time as the window. Otherwise, we don't know the presentation start time. + long presentationStartTimeMs = + playlist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_EVENT + || playlist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_VOD + ? windowStartTimeMs + : C.TIME_UNSET; long windowDefaultStartPositionUs = playlist.startOffsetUs; if (playlistTracker.isLive()) { - long periodDurationUs = playlist.hasEndTag ? (playlist.startTimeUs + playlist.durationUs) - : C.TIME_UNSET; + long offsetFromInitialStartTimeUs = + playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); + long periodDurationUs = + playlist.hasEndTag ? offsetFromInitialStartTimeUs + playlist.durationUs : C.TIME_UNSET; List segments = playlist.segments; if (windowDefaultStartPositionUs == C.TIME_UNSET) { windowDefaultStartPositionUs = segments.isEmpty() ? 0 : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs; } - timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, - periodDurationUs, playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, - true, !playlist.hasEndTag); + timeline = + new SinglePeriodTimeline( + presentationStartTimeMs, + windowStartTimeMs, + periodDurationUs, + /* windowDurationUs= */ playlist.durationUs, + /* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs, + windowDefaultStartPositionUs, + /* isSeekable= */ true, + /* isDynamic= */ !playlist.hasEndTag); } else /* not live */ { if (windowDefaultStartPositionUs == C.TIME_UNSET) { windowDefaultStartPositionUs = 0; } - timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, - playlist.startTimeUs + playlist.durationUs, playlist.durationUs, playlist.startTimeUs, - windowDefaultStartPositionUs, true, false); + timeline = + new SinglePeriodTimeline( + presentationStartTimeMs, + windowStartTimeMs, + /* periodDurationUs= */ playlist.durationUs, + /* windowDurationUs= */ playlist.durationUs, + /* windowPositionInPeriodUs= */ 0, + windowDefaultStartPositionUs, + /* isSeekable= */ true, + /* isDynamic= */ false); } sourceListener.onSourceInfoRefreshed(this, timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist)); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index d2f16b5c272..2e565c322a9 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -83,7 +83,6 @@ public interface PrimaryPlaylistListener { * @param mediaPlaylist The primary playlist new snapshot. */ void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist); - } /** @@ -128,6 +127,7 @@ public interface PlaylistEventListener { private HlsUrl primaryHlsUrl; private HlsMediaPlaylist primaryUrlSnapshot; private boolean isLive; + private long initialStartTimeUs; /** * @param initialPlaylistUri Uri for the initial playlist of the stream. Can refer a media @@ -153,6 +153,7 @@ public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourc initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist"); playlistBundles = new IdentityHashMap<>(); playlistRefreshHandler = new Handler(); + initialStartTimeUs = C.TIME_UNSET; } /** @@ -208,6 +209,11 @@ public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) { return snapshot; } + /** Returns the start time of the first loaded primary playlist. */ + public long getInitialStartTimeUs() { + return initialStartTimeUs; + } + /** * Returns whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is * valid, meaning all the segments referenced by the playlist are expected to be available. If the @@ -371,6 +377,7 @@ private void onPlaylistUpdated(HlsUrl url, HlsMediaPlaylist newSnapshot) { if (primaryUrlSnapshot == null) { // This is the first primary url snapshot. isLive = !newSnapshot.hasEndTag; + initialStartTimeUs = newSnapshot.startTimeUs; } primaryUrlSnapshot = newSnapshot; primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot);