Commit 4373e63f by andrewlewis Committed by Andrew Lewis

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
parent 73b6b20f
...@@ -22,6 +22,12 @@ ...@@ -22,6 +22,12 @@
* Allow clipping of child media sources where the period and window have a * Allow clipping of child media sources where the period and window have a
non-zero offset with `ClippingMediaSource` non-zero offset with `ClippingMediaSource`
([#3888](https://github.com/google/ExoPlayer/issues/3888)). ([#3888](https://github.com/google/ExoPlayer/issues/3888)).
* HlsMediaSource: make HLS periods start at zero instead of the epoch.
Note: 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)).
### 2.7.0 ### ### 2.7.0 ###
...@@ -44,7 +50,7 @@ ...@@ -44,7 +50,7 @@
* Add `ExoPlayer.setSeekParameters` for controlling how seek operations are * Add `ExoPlayer.setSeekParameters` for controlling how seek operations are
performed. The `SeekParameters` class contains defaults for exact seeking and performed. The `SeekParameters` class contains defaults for exact seeking and
seeking to the closest sync points before, either side or after specified seek 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. streams.
* DefaultTrackSelector: * DefaultTrackSelector:
* Replace `DefaultTrackSelector.Parameters` copy methods with a builder. * Replace `DefaultTrackSelector.Parameters` copy methods with a builder.
......
...@@ -261,9 +261,13 @@ import java.util.List; ...@@ -261,9 +261,13 @@ import java.util.List;
// If the playlist is too old to contain the chunk, we need to refresh it. // If the playlist is too old to contain the chunk, we need to refresh it.
chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(); chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();
} else { } else {
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, chunkMediaSequence =
targetPositionUs - mediaPlaylist.startTimeUs, true, Util.binarySearchFloor(
!playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence; mediaPlaylist.segments,
targetPositionUs,
/* inclusive= */ true,
/* stayInBounds= */ !playlistTracker.isLive() || previous == null)
+ mediaPlaylist.mediaSequence;
if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) { if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) {
// We try getting the next chunk without adapting in case that's the reason for falling // We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window. // behind the live window.
...@@ -320,7 +324,9 @@ import java.util.List; ...@@ -320,7 +324,9 @@ import java.util.List;
} }
// Compute start time of the next chunk. // 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 int discontinuitySequence = mediaPlaylist.discontinuitySequence
+ segment.relativeDiscontinuitySequence; + segment.relativeDiscontinuitySequence;
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster( TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
......
...@@ -362,28 +362,50 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -362,28 +362,50 @@ public final class HlsMediaSource extends BaseMediaSource
@Override @Override
public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) {
SinglePeriodTimeline timeline; SinglePeriodTimeline timeline;
long presentationStartTimeMs = playlist.hasProgramDateTime ? 0 : C.TIME_UNSET;
long windowStartTimeMs = playlist.hasProgramDateTime ? C.usToMs(playlist.startTimeUs) long windowStartTimeMs = playlist.hasProgramDateTime ? C.usToMs(playlist.startTimeUs)
: C.TIME_UNSET; : 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; long windowDefaultStartPositionUs = playlist.startOffsetUs;
if (playlistTracker.isLive()) { if (playlistTracker.isLive()) {
long periodDurationUs = playlist.hasEndTag ? (playlist.startTimeUs + playlist.durationUs) long offsetFromInitialStartTimeUs =
: C.TIME_UNSET; playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
long periodDurationUs =
playlist.hasEndTag ? offsetFromInitialStartTimeUs + playlist.durationUs : C.TIME_UNSET;
List<HlsMediaPlaylist.Segment> segments = playlist.segments; List<HlsMediaPlaylist.Segment> segments = playlist.segments;
if (windowDefaultStartPositionUs == C.TIME_UNSET) { if (windowDefaultStartPositionUs == C.TIME_UNSET) {
windowDefaultStartPositionUs = segments.isEmpty() ? 0 windowDefaultStartPositionUs = segments.isEmpty() ? 0
: segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs; : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs;
} }
timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, timeline =
periodDurationUs, playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, new SinglePeriodTimeline(
true, !playlist.hasEndTag); presentationStartTimeMs,
windowStartTimeMs,
periodDurationUs,
/* windowDurationUs= */ playlist.durationUs,
/* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs,
windowDefaultStartPositionUs,
/* isSeekable= */ true,
/* isDynamic= */ !playlist.hasEndTag);
} else /* not live */ { } else /* not live */ {
if (windowDefaultStartPositionUs == C.TIME_UNSET) { if (windowDefaultStartPositionUs == C.TIME_UNSET) {
windowDefaultStartPositionUs = 0; windowDefaultStartPositionUs = 0;
} }
timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, timeline =
playlist.startTimeUs + playlist.durationUs, playlist.durationUs, playlist.startTimeUs, new SinglePeriodTimeline(
windowDefaultStartPositionUs, true, false); presentationStartTimeMs,
windowStartTimeMs,
/* periodDurationUs= */ playlist.durationUs,
/* windowDurationUs= */ playlist.durationUs,
/* windowPositionInPeriodUs= */ 0,
windowDefaultStartPositionUs,
/* isSeekable= */ true,
/* isDynamic= */ false);
} }
refreshSourceInfo(timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist)); refreshSourceInfo(timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist));
} }
......
...@@ -83,7 +83,6 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -83,7 +83,6 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
* @param mediaPlaylist The primary playlist new snapshot. * @param mediaPlaylist The primary playlist new snapshot.
*/ */
void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist); void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist);
} }
/** /**
...@@ -128,6 +127,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -128,6 +127,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
private HlsUrl primaryHlsUrl; private HlsUrl primaryHlsUrl;
private HlsMediaPlaylist primaryUrlSnapshot; private HlsMediaPlaylist primaryUrlSnapshot;
private boolean isLive; private boolean isLive;
private long initialStartTimeUs;
/** /**
* @param initialPlaylistUri Uri for the initial playlist of the stream. Can refer a media * @param initialPlaylistUri Uri for the initial playlist of the stream. Can refer a media
...@@ -153,6 +153,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -153,6 +153,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist"); initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist");
playlistBundles = new IdentityHashMap<>(); playlistBundles = new IdentityHashMap<>();
playlistRefreshHandler = new Handler(); playlistRefreshHandler = new Handler();
initialStartTimeUs = C.TIME_UNSET;
} }
/** /**
...@@ -208,6 +209,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -208,6 +209,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
return snapshot; 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 * 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 * valid, meaning all the segments referenced by the playlist are expected to be available. If the
...@@ -371,6 +377,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -371,6 +377,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
if (primaryUrlSnapshot == null) { if (primaryUrlSnapshot == null) {
// This is the first primary url snapshot. // This is the first primary url snapshot.
isLive = !newSnapshot.hasEndTag; isLive = !newSnapshot.hasEndTag;
initialStartTimeUs = newSnapshot.startTimeUs;
} }
primaryUrlSnapshot = newSnapshot; primaryUrlSnapshot = newSnapshot;
primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot); primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment