Commit 501f54a8 by aquilescanta Committed by Oliver Woodman

Add #EXT-X-PROGRAM-DATE-TIME support for HLS media playlists

Issue:#747

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=140525595
parent 91c58627
...@@ -212,7 +212,8 @@ import java.util.Locale; ...@@ -212,7 +212,8 @@ import java.util.Locale;
// 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, targetPositionUs, true, chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments,
targetPositionUs - mediaPlaylist.startTimeUs, true,
!playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence; !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
...@@ -259,16 +260,6 @@ import java.util.Locale; ...@@ -259,16 +260,6 @@ import java.util.Locale;
clearEncryptionData(); clearEncryptionData();
} }
// Compute start time and sequence number of the next chunk.
long startTimeUs = segment.startTimeUs;
if (previous != null && !switchingVariant) {
startTimeUs = previous.getAdjustedEndTimeUs();
}
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
DataSpec initDataSpec = null; DataSpec initDataSpec = null;
Segment initSegment = mediaPlaylist.initializationSegment; Segment initSegment = mediaPlaylist.initializationSegment;
if (initSegment != null) { if (initSegment != null) {
...@@ -277,13 +268,20 @@ import java.util.Locale; ...@@ -277,13 +268,20 @@ import java.util.Locale;
initSegment.byterangeLength, null); initSegment.byterangeLength, null);
} }
// Compute start time of the next chunk.
long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
// Configure the data source and spec for the chunk. // Configure the data source and spec for the chunk.
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null); null);
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex], out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segment, trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
chunkMediaSequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence,
encryptionIv); segment.discontinuitySequenceNumber, isTimestampMaster, timestampAdjuster, previous,
encryptionKey, encryptionIv);
} }
/** /**
......
...@@ -28,7 +28,6 @@ import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; ...@@ -28,7 +28,6 @@ import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -89,8 +88,10 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -89,8 +88,10 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param hlsUrl The url of the playlist from which this chunk was obtained. * @param hlsUrl The url of the playlist from which this chunk was obtained.
* @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionReason See {@link #trackSelectionReason}.
* @param trackSelectionData See {@link #trackSelectionData}. * @param trackSelectionData See {@link #trackSelectionData}.
* @param segment The {@link Segment} for which this media chunk is created. * @param startTimeUs The start time of the chunk in microseconds.
* @param endTimeUs The end time of the chunk in microseconds.
* @param chunkIndex The media sequence number of the chunk. * @param chunkIndex The media sequence number of the chunk.
* @param discontinuitySequenceNumber The discontinuity sequence number of the chunk.
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
...@@ -98,21 +99,21 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -98,21 +99,21 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param encryptionIv For AES encryption chunks, the encryption initialization vector. * @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/ */
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec, public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment, HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, long startTimeUs,
int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, long endTimeUs, int chunkIndex, int discontinuitySequenceNumber,
boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster,
HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) { HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, segment.startTimeUs, trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex);
segment.startTimeUs + segment.durationUs, chunkIndex);
this.initDataSpec = initDataSpec; this.initDataSpec = initDataSpec;
this.hlsUrl = hlsUrl; this.hlsUrl = hlsUrl;
this.isMasterTimestampSource = isMasterTimestampSource; this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.previousChunk = previousChunk; this.previousChunk = previousChunk;
// Note: this.dataSource and dataSource may be different. // Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource; this.isEncrypted = this.dataSource instanceof Aes128DataSource;
initDataSource = dataSource; initDataSource = dataSource;
discontinuitySequenceNumber = segment.discontinuitySequenceNumber;
adjustedEndTimeUs = endTimeUs; adjustedEndTimeUs = endTimeUs;
uid = UID_SOURCE.getAndIncrement(); uid = UID_SOURCE.getAndIncrement();
} }
...@@ -136,7 +137,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -136,7 +137,7 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
/** /**
* Returns the presentation time in microseconds of the last sample in the chunk * Returns the presentation time in microseconds of the last sample in the chunk.
*/ */
public long getAdjustedEndTimeUs() { public long getAdjustedEndTimeUs() {
return adjustedEndTimeUs; return adjustedEndTimeUs;
...@@ -231,8 +232,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -231,8 +232,8 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
private void maybeLoadInitData() throws IOException, InterruptedException { private void maybeLoadInitData() throws IOException, InterruptedException {
if (previousChunk == null || previousChunk.extractor != extractor || initLoadCompleted if ((previousChunk != null && previousChunk.extractor == extractor)
|| initDataSpec == null) { || initLoadCompleted || initDataSpec == null) {
return; return;
} }
DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded); DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded);
......
...@@ -103,10 +103,10 @@ public final class HlsMediaSource implements MediaSource, ...@@ -103,10 +103,10 @@ public final class HlsMediaSource implements MediaSource,
SinglePeriodTimeline timeline; SinglePeriodTimeline timeline;
if (playlistTracker.isLive()) { if (playlistTracker.isLive()) {
// TODO: fix windowPositionInPeriodUs when playlist is empty. // TODO: fix windowPositionInPeriodUs when playlist is empty.
long windowPositionInPeriodUs = playlist.getStartTimeUs(); long windowPositionInPeriodUs = playlist.startTimeUs;
List<HlsMediaPlaylist.Segment> segments = playlist.segments; List<HlsMediaPlaylist.Segment> segments = playlist.segments;
long windowDefaultStartPositionUs = segments.isEmpty() ? 0 long windowDefaultStartPositionUs = segments.isEmpty() ? 0
: segments.get(Math.max(0, segments.size() - 3)).startTimeUs - windowPositionInPeriodUs; : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs;
timeline = new SinglePeriodTimeline(C.TIME_UNSET, playlist.durationUs, timeline = new SinglePeriodTimeline(C.TIME_UNSET, playlist.durationUs,
windowPositionInPeriodUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag); windowPositionInPeriodUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag);
} else /* not live */ { } else /* not live */ {
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.hls.playlist; package com.google.android.exoplayer2.source.hls.playlist;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -33,7 +32,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -33,7 +32,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final String url; public final String url;
public final long durationUs; public final long durationUs;
public final int discontinuitySequenceNumber; public final int discontinuitySequenceNumber;
public final long startTimeUs; public final long relativeStartTimeUs;
public final boolean isEncrypted; public final boolean isEncrypted;
public final String encryptionKeyUri; public final String encryptionKeyUri;
public final String encryptionIV; public final String encryptionIV;
...@@ -45,12 +44,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -45,12 +44,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
} }
public Segment(String uri, long durationUs, int discontinuitySequenceNumber, public Segment(String uri, long durationUs, int discontinuitySequenceNumber,
long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, long relativeStartTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV,
long byterangeOffset, long byterangeLength) { long byterangeOffset, long byterangeLength) {
this.url = uri; this.url = uri;
this.durationUs = durationUs; this.durationUs = durationUs;
this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.startTimeUs = startTimeUs; this.relativeStartTimeUs = relativeStartTimeUs;
this.isEncrypted = isEncrypted; this.isEncrypted = isEncrypted;
this.encryptionKeyUri = encryptionKeyUri; this.encryptionKeyUri = encryptionKeyUri;
this.encryptionIV = encryptionIV; this.encryptionIV = encryptionIV;
...@@ -59,64 +58,55 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -59,64 +58,55 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
} }
@Override @Override
public int compareTo(Long startTimeUs) { public int compareTo(Long relativeStartTimeUs) {
return this.startTimeUs > startTimeUs ? 1 : (this.startTimeUs < startTimeUs ? -1 : 0); return this.relativeStartTimeUs > relativeStartTimeUs
} ? 1 : (this.relativeStartTimeUs < relativeStartTimeUs ? -1 : 0);
public Segment copyWithStartTimeUs(long startTimeUs) {
return new Segment(url, durationUs, discontinuitySequenceNumber, startTimeUs, isEncrypted,
encryptionKeyUri, encryptionIV, byterangeOffset, byterangeLength);
} }
} }
public final long startTimeUs;
public final int mediaSequence; public final int mediaSequence;
public final int version; public final int version;
public final Segment initializationSegment; public final Segment initializationSegment;
public final List<Segment> segments; public final List<Segment> segments;
public final boolean hasEndTag; public final boolean hasEndTag;
public final boolean hasProgramDateTime;
public final long durationUs; public final long durationUs;
public HlsMediaPlaylist(String baseUri, int mediaSequence, int version, public HlsMediaPlaylist(String baseUri, long startTimeUs, int mediaSequence, int version,
boolean hasEndTag, Segment initializationSegment, List<Segment> segments) { boolean hasEndTag, boolean hasProgramDateTime, Segment initializationSegment,
List<Segment> segments) {
super(baseUri, HlsPlaylist.TYPE_MEDIA); super(baseUri, HlsPlaylist.TYPE_MEDIA);
this.startTimeUs = startTimeUs;
this.mediaSequence = mediaSequence; this.mediaSequence = mediaSequence;
this.version = version; this.version = version;
this.hasEndTag = hasEndTag; this.hasEndTag = hasEndTag;
this.hasProgramDateTime = hasProgramDateTime;
this.initializationSegment = initializationSegment; this.initializationSegment = initializationSegment;
this.segments = Collections.unmodifiableList(segments); this.segments = Collections.unmodifiableList(segments);
if (!segments.isEmpty()) { if (!segments.isEmpty()) {
Segment first = segments.get(0);
Segment last = segments.get(segments.size() - 1); Segment last = segments.get(segments.size() - 1);
durationUs = last.startTimeUs + last.durationUs - first.startTimeUs; durationUs = last.relativeStartTimeUs + last.durationUs;
} else { } else {
durationUs = 0; durationUs = 0;
} }
} }
public long getStartTimeUs() { public boolean isNewerThan(HlsMediaPlaylist other) {
return segments.isEmpty() ? 0 : segments.get(0).startTimeUs; return other == null || mediaSequence > other.mediaSequence
|| (mediaSequence == other.mediaSequence && segments.size() > other.segments.size())
|| (hasEndTag && !other.hasEndTag);
} }
public long getEndTimeUs() { public long getEndTimeUs() {
return getStartTimeUs() + durationUs; return startTimeUs + durationUs;
}
public HlsMediaPlaylist copyWithStartTimeUs(long newStartTimeUs) {
long startTimeOffsetUs = newStartTimeUs - getStartTimeUs();
int segmentsSize = segments.size();
List<Segment> newSegments = new ArrayList<>(segmentsSize);
for (int i = 0; i < segmentsSize; i++) {
Segment segment = segments.get(i);
newSegments.add(segment.copyWithStartTimeUs(segment.startTimeUs + startTimeOffsetUs));
}
return copyWithSegments(newSegments);
} }
public HlsMediaPlaylist copyWithSegments(List<Segment> segments) { public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) {
return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag, return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, hasEndTag,
initializationSegment, segments); hasProgramDateTime, initializationSegment, segments);
} }
} }
...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.ParserException; ...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
...@@ -43,6 +44,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -43,6 +44,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String TAG_MEDIA = "#EXT-X-MEDIA"; private static final String TAG_MEDIA = "#EXT-X-MEDIA";
private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; 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_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_INIT_SEGMENT = "#EXT-X-MAP";
private static final String TAG_MEDIA_DURATION = "#EXTINF"; private static final String TAG_MEDIA_DURATION = "#EXTINF";
private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE"; private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE";
...@@ -62,17 +64,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -62,17 +64,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String BOOLEAN_TRUE = "YES"; private static final String BOOLEAN_TRUE = "YES";
private static final String BOOLEAN_FALSE = "NO"; private static final String BOOLEAN_FALSE = "NO";
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\"");
private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\"");
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
private static final Pattern REGEX_CLOSED_CAPTIONS = Pattern.compile("CLOSED-CAPTIONS=\"(.+?)\"");
private static final Pattern REGEX_SUBTITLES = Pattern.compile("SUBTITLES=\"(.+?)\"");
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b"); private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b");
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\""); private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)"); private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b"); private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b");
private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION
+ ":(\\d+)\\b");
private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE
+ ":(\\d+)\\b"); + ":(\\d+)\\b");
private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION
...@@ -211,7 +206,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -211,7 +206,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
throws IOException { throws IOException {
int mediaSequence = 0; int mediaSequence = 0;
int targetDurationSecs = 0;
int version = 1; // Default version == 1. int version = 1; // Default version == 1.
boolean hasEndTag = false; boolean hasEndTag = false;
Segment initializationSegment = null; Segment initializationSegment = null;
...@@ -219,6 +213,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -219,6 +213,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
long segmentDurationUs = 0; long segmentDurationUs = 0;
int discontinuitySequenceNumber = 0; int discontinuitySequenceNumber = 0;
long playlistStartTimeUs = 0;
long segmentStartTimeUs = 0; long segmentStartTimeUs = 0;
long segmentByteRangeOffset = 0; long segmentByteRangeOffset = 0;
long segmentByteRangeLength = C.LENGTH_UNSET; long segmentByteRangeLength = C.LENGTH_UNSET;
...@@ -244,8 +239,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -244,8 +239,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength); initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength);
segmentByteRangeOffset = 0; segmentByteRangeOffset = 0;
segmentByteRangeLength = C.LENGTH_UNSET; segmentByteRangeLength = C.LENGTH_UNSET;
} else if (line.startsWith(TAG_TARGET_DURATION)) {
targetDurationSecs = parseIntAttr(line, REGEX_TARGET_DURATION);
} else if (line.startsWith(TAG_MEDIA_SEQUENCE)) { } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE); mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE);
segmentMediaSequence = mediaSequence; segmentMediaSequence = mediaSequence;
...@@ -275,6 +268,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -275,6 +268,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1)); discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1));
} else if (line.equals(TAG_DISCONTINUITY)) { } else if (line.equals(TAG_DISCONTINUITY)) {
discontinuitySequenceNumber++; discontinuitySequenceNumber++;
} else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) {
if (playlistStartTimeUs == 0) {
long programDatetimeUs =
C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
}
} else if (!line.startsWith("#")) { } else if (!line.startsWith("#")) {
String segmentEncryptionIV; String segmentEncryptionIV;
if (!isEncrypted) { if (!isEncrypted) {
...@@ -301,8 +300,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -301,8 +300,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
hasEndTag = true; hasEndTag = true;
} }
} }
return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag, return new HlsMediaPlaylist(baseUri, playlistStartTimeUs, mediaSequence, version, hasEndTag,
initializationSegment, segments); playlistStartTimeUs != 0, initializationSegment, segments);
} }
private static String parseStringAttr(String line, Pattern pattern) throws ParserException { private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
......
...@@ -292,46 +292,39 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -292,46 +292,39 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
*/ */
private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist, private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist,
HlsMediaPlaylist newPlaylist) { HlsMediaPlaylist newPlaylist) {
if (newPlaylist.hasProgramDateTime) {
if (newPlaylist.isNewerThan(oldPlaylist)) {
return newPlaylist;
} else {
return oldPlaylist;
}
}
HlsMediaPlaylist primaryPlaylistSnapshot = HlsMediaPlaylist primaryPlaylistSnapshot =
playlistBundles.get(primaryHlsUrl).latestPlaylistSnapshot; playlistBundles.get(primaryHlsUrl).latestPlaylistSnapshot;
if (oldPlaylist == null) { if (oldPlaylist == null) {
if (primaryPlaylistSnapshot == null) { if (primaryPlaylistSnapshot == null
// Playback has just started so no adjustment is needed. || primaryPlaylistSnapshot.startTimeUs == newPlaylist.startTimeUs) {
// Playback has just started or is VOD so no adjustment is needed.
return newPlaylist; return newPlaylist;
} else { } else {
return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.getStartTimeUs()); return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.startTimeUs);
} }
} }
List<HlsMediaPlaylist.Segment> oldSegments = oldPlaylist.segments; List<Segment> oldSegments = oldPlaylist.segments;
int oldPlaylistSize = oldSegments.size(); int oldPlaylistSize = oldSegments.size();
int newPlaylistSize = newPlaylist.segments.size(); if (!newPlaylist.isNewerThan(oldPlaylist)) {
int mediaSequenceOffset = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
if (newPlaylistSize == oldPlaylistSize && mediaSequenceOffset == 0
&& oldPlaylist.hasEndTag == newPlaylist.hasEndTag) {
// Playlist has not changed. // Playlist has not changed.
return oldPlaylist; return oldPlaylist;
} }
if (mediaSequenceOffset < 0) { int mediaSequenceOffset = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
// Playlist has changed but media sequence has regressed.
return oldPlaylist;
}
if (mediaSequenceOffset <= oldPlaylistSize) { if (mediaSequenceOffset <= oldPlaylistSize) {
// We can extrapolate the start time of new segments from the segments of the old snapshot. long adjustedNewPlaylistStartTimeUs = mediaSequenceOffset == oldPlaylistSize
ArrayList<HlsMediaPlaylist.Segment> newSegments = new ArrayList<>(newPlaylistSize); ? oldPlaylist.getEndTimeUs()
for (int i = mediaSequenceOffset; i < oldPlaylistSize; i++) { : oldPlaylist.startTimeUs + oldSegments.get(mediaSequenceOffset).relativeStartTimeUs;
newSegments.add(oldSegments.get(i)); return newPlaylist.copyWithStartTimeUs(adjustedNewPlaylistStartTimeUs);
}
HlsMediaPlaylist.Segment lastSegment = oldSegments.get(oldPlaylistSize - 1);
for (int i = newSegments.size(); i < newPlaylistSize; i++) {
lastSegment = newPlaylist.segments.get(i).copyWithStartTimeUs(
lastSegment.startTimeUs + lastSegment.durationUs);
newSegments.add(lastSegment);
}
return newPlaylist.copyWithSegments(newSegments);
} else {
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.getStartTimeUs());
} }
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.startTimeUs);
} }
/** /**
...@@ -375,31 +368,19 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -375,31 +368,19 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
public void adjustTimestampsOfPlaylist(int chunkMediaSequence, long adjustedStartTimeUs) { public void adjustTimestampsOfPlaylist(int chunkMediaSequence, long adjustedStartTimeUs) {
ArrayList<Segment> segments = new ArrayList<>(latestPlaylistSnapshot.segments);
int indexOfChunk = chunkMediaSequence - latestPlaylistSnapshot.mediaSequence; int indexOfChunk = chunkMediaSequence - latestPlaylistSnapshot.mediaSequence;
if (indexOfChunk < 0) { if (latestPlaylistSnapshot.hasProgramDateTime || indexOfChunk < 0) {
return; return;
} }
Segment actualSegment = segments.get(indexOfChunk); Segment actualSegment = latestPlaylistSnapshot.segments.get(indexOfChunk);
long timestampDriftUs = Math.abs(actualSegment.startTimeUs - adjustedStartTimeUs); long segmentAbsoluteStartTimeUs =
actualSegment.relativeStartTimeUs + latestPlaylistSnapshot.startTimeUs;
long timestampDriftUs = Math.abs(segmentAbsoluteStartTimeUs - adjustedStartTimeUs);
if (timestampDriftUs < TIMESTAMP_ADJUSTMENT_THRESHOLD_US) { if (timestampDriftUs < TIMESTAMP_ADJUSTMENT_THRESHOLD_US) {
return; return;
} }
segments.set(indexOfChunk, actualSegment.copyWithStartTimeUs(adjustedStartTimeUs)); latestPlaylistSnapshot = latestPlaylistSnapshot.copyWithStartTimeUs(
// Propagate the adjustment backwards. adjustedStartTimeUs - actualSegment.relativeStartTimeUs);
for (int i = indexOfChunk - 1; i >= 0; i--) {
Segment segment = segments.get(i);
segments.set(i,
segment.copyWithStartTimeUs(segments.get(i + 1).startTimeUs - segment.durationUs));
}
// Propagate the adjustment forward.
int segmentsSize = segments.size();
for (int i = indexOfChunk + 1; i < segmentsSize; i++) {
Segment segment = segments.get(i);
segments.set(i,
segment.copyWithStartTimeUs(segments.get(i - 1).startTimeUs + segment.durationUs));
}
latestPlaylistSnapshot = latestPlaylistSnapshot.copyWithSegments(segments);
} }
// Loader.Callback implementation. // Loader.Callback implementation.
......
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