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
This commit is contained in:
andrewlewis 2018-03-02 00:46:10 -08:00 committed by Oliver Woodman
parent 452e4debcc
commit d8d3bd7b3f
4 changed files with 60 additions and 15 deletions

View file

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

View file

@ -261,9 +261,13 @@ import java.util.List;
// 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 @@ import java.util.List;
}
// 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(

View file

@ -366,28 +366,50 @@ public final class HlsMediaSource implements MediaSource,
@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<HlsMediaPlaylist.Segment> 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));

View file

@ -83,7 +83,6 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
* @param mediaPlaylist The primary playlist new snapshot.
*/
void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist);
}
/**
@ -128,6 +127,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
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 final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist");
playlistBundles = new IdentityHashMap<>();
playlistRefreshHandler = new Handler();
initialStartTimeUs = C.TIME_UNSET;
}
/**
@ -208,6 +209,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
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 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
if (primaryUrlSnapshot == null) {
// This is the first primary url snapshot.
isLive = !newSnapshot.hasEndTag;
initialStartTimeUs = newSnapshot.startTimeUs;
}
primaryUrlSnapshot = newSnapshot;
primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot);