diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 575106f784f..10e12f0ec6d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -104,17 +104,23 @@ public void releaseSource() { @Override public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { SinglePeriodTimeline timeline; + long windowDefaultStartPositionUs = playlist.startOffsetUs; if (playlistTracker.isLive()) { long periodDurationUs = playlist.hasEndTag ? (playlist.startTimeUs + playlist.durationUs) : C.TIME_UNSET; List segments = playlist.segments; - long windowDefaultStartPositionUs = segments.isEmpty() ? 0 - : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs; + if (windowDefaultStartPositionUs == C.TIME_UNSET) { + windowDefaultStartPositionUs = segments.isEmpty() ? 0 + : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs; + } timeline = new SinglePeriodTimeline(periodDurationUs, playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag); } else /* not live */ { + if (windowDefaultStartPositionUs == C.TIME_UNSET) { + windowDefaultStartPositionUs = 0; + } timeline = new SinglePeriodTimeline(playlist.startTimeUs + playlist.durationUs, - playlist.durationUs, playlist.startTimeUs, 0, true, false); + playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, true, false); } sourceListener.onSourceInfoRefreshed(timeline, playlist); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 4f30e018ae8..079d4001fb4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -65,6 +65,7 @@ public int compareTo(Long relativeStartTimeUs) { } + public final long startOffsetUs; public final long startTimeUs; public final int mediaSequence; public final int version; @@ -75,7 +76,7 @@ public int compareTo(Long relativeStartTimeUs) { public final List segments; public final long durationUs; - public HlsMediaPlaylist(String baseUri, long startTimeUs, int mediaSequence, + public HlsMediaPlaylist(String baseUri, long startOffsetUs, long startTimeUs, int mediaSequence, int version, long targetDurationUs, boolean hasEndTag, boolean hasProgramDateTime, Segment initializationSegment, List segments) { super(baseUri, HlsPlaylist.TYPE_MEDIA); @@ -87,13 +88,14 @@ public HlsMediaPlaylist(String baseUri, long startTimeUs, int mediaSequence, this.hasProgramDateTime = hasProgramDateTime; this.initializationSegment = initializationSegment; this.segments = Collections.unmodifiableList(segments); - if (!segments.isEmpty()) { Segment last = segments.get(segments.size() - 1); durationUs = last.relativeStartTimeUs + last.durationUs; } else { durationUs = 0; } + this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET + : startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs; } /** @@ -132,8 +134,8 @@ public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) { if (this.startTimeUs == startTimeUs) { return this; } - return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, targetDurationUs, - hasEndTag, hasProgramDateTime, initializationSegment, segments); + return new HlsMediaPlaylist(baseUri, startOffsetUs, startTimeUs, mediaSequence, version, + targetDurationUs, hasEndTag, hasProgramDateTime, initializationSegment, segments); } /** @@ -146,8 +148,8 @@ public HlsMediaPlaylist copyWithEndTag() { if (this.hasEndTag) { return this; } - return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, targetDurationUs, - true, hasProgramDateTime, initializationSegment, segments); + return new HlsMediaPlaylist(baseUri, startOffsetUs, startTimeUs, mediaSequence, version, + targetDurationUs, true, hasProgramDateTime, initializationSegment, segments); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 460c4576bd7..e9bd5b6696e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -58,13 +58,14 @@ public UnrecognizedInputFormatException(Uri inputUri) { private static final String TAG_VERSION = "#EXT-X-VERSION"; private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF"; private static final String TAG_MEDIA = "#EXT-X-MEDIA"; + private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION"; private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; private static final String TAG_DISCONTINUITY_SEQUENCE = "#EXT-X-DISCONTINUITY-SEQUENCE"; private static final String TAG_PROGRAM_DATE_TIME = "#EXT-X-PROGRAM-DATE-TIME"; private static final String TAG_INIT_SEGMENT = "#EXT-X-MAP"; private static final String TAG_MEDIA_DURATION = "#EXTINF"; private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE"; - private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION"; + private static final String TAG_START = "#EXT-X-START"; private static final String TAG_ENDLIST = "#EXT-X-ENDLIST"; private static final String TAG_KEY = "#EXT-X-KEY"; private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE"; @@ -90,6 +91,7 @@ public UnrecognizedInputFormatException(Uri inputUri) { + ":(\\d+)\\b"); private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION + ":([\\d\\.]+)\\b"); + private static final Pattern REGEX_TIME_OFFSET = Pattern.compile("TIME-OFFSET=([\\d\\.]+)\\b"); private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE + ":(\\d+(?:@\\d+)?)\\b"); private static final Pattern REGEX_ATTR_BYTERANGE = @@ -255,6 +257,7 @@ private static int parseSelectionFlags(String line) { private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) throws IOException { + long startOffsetUs = C.TIME_UNSET; int mediaSequence = 0; int version = 1; // Default version == 1. long targetDurationUs = C.TIME_UNSET; @@ -277,7 +280,9 @@ private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String String line; while (iterator.hasNext()) { line = iterator.next(); - if (line.startsWith(TAG_INIT_SEGMENT)) { + if (line.startsWith(TAG_START)) { + startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND); + } else if (line.startsWith(TAG_INIT_SEGMENT)) { String uri = parseStringAttr(line, REGEX_URI); String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE); if (byteRange != null) { @@ -353,7 +358,7 @@ private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String hasEndTag = true; } } - return new HlsMediaPlaylist(baseUri, playlistStartTimeUs, mediaSequence, version, + return new HlsMediaPlaylist(baseUri, startOffsetUs, playlistStartTimeUs, mediaSequence, version, targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments); }