Commit 992cfdec by aquilescanta Committed by Oliver Woodman

Feed timestamps from loaded chunks back to the playlist tracker

This is the first step towards allowing discontinuities in the
playlist tracking. Also changed durationSecs for durationUs in
MediaPlaylist.Segment.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=138207732
parent a5a2bc89
...@@ -72,7 +72,6 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -72,7 +72,6 @@ public class HlsMediaPlaylistParserTest extends TestCase {
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertEquals(2679, mediaPlaylist.mediaSequence); assertEquals(2679, mediaPlaylist.mediaSequence);
assertEquals(8, mediaPlaylist.targetDurationSecs);
assertEquals(3, mediaPlaylist.version); assertEquals(3, mediaPlaylist.version);
assertEquals(true, mediaPlaylist.hasEndTag); assertEquals(true, mediaPlaylist.hasEndTag);
List<HlsMediaPlaylist.Segment> segments = mediaPlaylist.segments; List<HlsMediaPlaylist.Segment> segments = mediaPlaylist.segments;
...@@ -80,7 +79,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -80,7 +79,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(5, segments.size()); assertEquals(5, segments.size());
assertEquals(4, segments.get(0).discontinuitySequenceNumber); assertEquals(4, segments.get(0).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(0).durationSecs); assertEquals(7975000, segments.get(0).durationUs);
assertEquals(false, segments.get(0).isEncrypted); assertEquals(false, segments.get(0).isEncrypted);
assertEquals(null, segments.get(0).encryptionKeyUri); assertEquals(null, segments.get(0).encryptionKeyUri);
assertEquals(null, segments.get(0).encryptionIV); assertEquals(null, segments.get(0).encryptionIV);
...@@ -89,7 +88,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -89,7 +88,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals("https://priv.example.com/fileSequence2679.ts", segments.get(0).url); assertEquals("https://priv.example.com/fileSequence2679.ts", segments.get(0).url);
assertEquals(4, segments.get(1).discontinuitySequenceNumber); assertEquals(4, segments.get(1).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(1).durationSecs); assertEquals(7975000, segments.get(1).durationUs);
assertEquals(true, segments.get(1).isEncrypted); assertEquals(true, segments.get(1).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri); assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri);
assertEquals("0x1566B", segments.get(1).encryptionIV); assertEquals("0x1566B", segments.get(1).encryptionIV);
...@@ -98,7 +97,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -98,7 +97,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals("https://priv.example.com/fileSequence2680.ts", segments.get(1).url); assertEquals("https://priv.example.com/fileSequence2680.ts", segments.get(1).url);
assertEquals(4, segments.get(2).discontinuitySequenceNumber); assertEquals(4, segments.get(2).discontinuitySequenceNumber);
assertEquals(7.941, segments.get(2).durationSecs); assertEquals(7941000, segments.get(2).durationUs);
assertEquals(false, segments.get(2).isEncrypted); assertEquals(false, segments.get(2).isEncrypted);
assertEquals(null, segments.get(2).encryptionKeyUri); assertEquals(null, segments.get(2).encryptionKeyUri);
assertEquals(null, segments.get(2).encryptionIV); assertEquals(null, segments.get(2).encryptionIV);
...@@ -107,7 +106,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -107,7 +106,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals("https://priv.example.com/fileSequence2681.ts", segments.get(2).url); assertEquals("https://priv.example.com/fileSequence2681.ts", segments.get(2).url);
assertEquals(5, segments.get(3).discontinuitySequenceNumber); assertEquals(5, segments.get(3).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(3).durationSecs); assertEquals(7975000, segments.get(3).durationUs);
assertEquals(true, segments.get(3).isEncrypted); assertEquals(true, segments.get(3).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri); assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri);
// 0xA7A == 2682. // 0xA7A == 2682.
...@@ -118,7 +117,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -118,7 +117,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals("https://priv.example.com/fileSequence2682.ts", segments.get(3).url); assertEquals("https://priv.example.com/fileSequence2682.ts", segments.get(3).url);
assertEquals(5, segments.get(4).discontinuitySequenceNumber); assertEquals(5, segments.get(4).discontinuitySequenceNumber);
assertEquals(7.975, segments.get(4).durationSecs); assertEquals(7975000, segments.get(4).durationUs);
assertEquals(true, segments.get(4).isEncrypted); assertEquals(true, segments.get(4).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri); assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri);
// 0xA7B == 2683. // 0xA7B == 2683.
......
...@@ -267,7 +267,7 @@ import java.util.Locale; ...@@ -267,7 +267,7 @@ import java.util.Locale;
if (previous != null && !switchingVariant) { if (previous != null && !switchingVariant) {
startTimeUs = previous.getAdjustedEndTimeUs(); startTimeUs = previous.getAdjustedEndTimeUs();
} }
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); long endTimeUs = startTimeUs + segment.durationUs;
Format format = variants[newVariantIndex].format; Format format = variants[newVariantIndex].format;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
...@@ -322,7 +322,7 @@ import java.util.Locale; ...@@ -322,7 +322,7 @@ import java.util.Locale;
// This flag ensures the change of pid between streams does not affect the sample queues. // This flag ensures the change of pid between streams does not affect the sample queues.
@DefaultTsPayloadReaderFactory.Flags @DefaultTsPayloadReaderFactory.Flags
int esReaderFactoryFlags = 0; int esReaderFactoryFlags = 0;
String codecs = variants[newVariantIndex].format.codecs; String codecs = format.codecs;
if (!TextUtils.isEmpty(codecs)) { if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can // exist. If we know from the codec attribute that they don't exist, then we can
...@@ -353,7 +353,7 @@ import java.util.Locale; ...@@ -353,7 +353,7 @@ import java.util.Locale;
// Configure the data source and spec for the chunk. // Configure the data source and spec for the chunk.
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, format, out.chunk = new HlsMediaChunk(dataSource, dataSpec, variants[newVariantIndex],
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber,
isTimestampMaster, timestampAdjuster, extractor, extractorNeedsInit, switchingVariant, isTimestampMaster, timestampAdjuster, extractor, extractorNeedsInit, switchingVariant,
...@@ -367,6 +367,11 @@ import java.util.Locale; ...@@ -367,6 +367,11 @@ import java.util.Locale;
* @param chunk The chunk whose load has been completed. * @param chunk The chunk whose load has been completed.
*/ */
public void onChunkLoadCompleted(Chunk chunk) { public void onChunkLoadCompleted(Chunk chunk) {
if (chunk instanceof HlsMediaChunk) {
HlsMediaChunk mediaChunk = (HlsMediaChunk) chunk;
playlistTracker.onChunkLoaded(mediaChunk.hlsUrl, mediaChunk.chunkIndex,
mediaChunk.getAdjustedStartTimeUs());
}
if (chunk instanceof HlsInitializationChunk) { if (chunk instanceof HlsInitializationChunk) {
lastLoadedInitializationChunk = (HlsInitializationChunk) chunk; lastLoadedInitializationChunk = (HlsInitializationChunk) chunk;
} else if (chunk instanceof EncryptionKeyChunk) { } else if (chunk instanceof EncryptionKeyChunk) {
......
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.TimestampAdjuster;
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.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.Util; import com.google.android.exoplayer2.util.Util;
...@@ -49,6 +49,11 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -49,6 +49,11 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public final Extractor extractor; public final Extractor extractor;
/**
* The url of the playlist from which this chunk was obtained.
*/
public final HlsUrl hlsUrl;
private final boolean isEncrypted; private final boolean isEncrypted;
private final boolean extractorNeedsInit; private final boolean extractorNeedsInit;
private final boolean shouldSpliceIn; private final boolean shouldSpliceIn;
...@@ -64,7 +69,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -64,7 +69,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* @param dataSource The source from which the data should be loaded. * @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. * @param dataSpec Defines the data to be loaded.
* @param trackFormat See {@link #trackFormat}. * @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 startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
...@@ -81,13 +86,14 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -81,13 +86,14 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param encryptionKey For AES encryption chunks, the encryption key. * @param encryptionKey For AES encryption chunks, the encryption key.
* @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, Format trackFormat, public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, HlsUrl hlsUrl,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource, int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource,
TimestampAdjuster timestampAdjuster, Extractor extractor, boolean extractorNeedsInit, TimestampAdjuster timestampAdjuster, Extractor extractor, boolean extractorNeedsInit,
boolean shouldSpliceIn, byte[] encryptionKey, byte[] encryptionIv) { boolean shouldSpliceIn, byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trackFormat, super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex); trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex);
this.hlsUrl = hlsUrl;
this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.isMasterTimestampSource = isMasterTimestampSource; this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
......
...@@ -31,7 +31,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -31,7 +31,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public static final class Segment implements Comparable<Long> { public static final class Segment implements Comparable<Long> {
public final String url; public final String url;
public final double durationSecs; public final long durationUs;
public final int discontinuitySequenceNumber; public final int discontinuitySequenceNumber;
public final long startTimeUs; public final long startTimeUs;
public final boolean isEncrypted; public final boolean isEncrypted;
...@@ -44,11 +44,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -44,11 +44,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
this(uri, 0, -1, C.TIME_UNSET, false, null, null, byterangeOffset, byterangeLength); this(uri, 0, -1, C.TIME_UNSET, false, null, null, byterangeOffset, byterangeLength);
} }
public Segment(String uri, double durationSecs, int discontinuitySequenceNumber, public Segment(String uri, long durationUs, int discontinuitySequenceNumber,
long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV,
long byterangeOffset, long byterangeLength) { long byterangeOffset, long byterangeLength) {
this.url = uri; this.url = uri;
this.durationSecs = durationSecs; this.durationUs = durationUs;
this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.isEncrypted = isEncrypted; this.isEncrypted = isEncrypted;
...@@ -64,28 +64,23 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -64,28 +64,23 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
} }
public Segment copyWithStartTimeUs(long startTimeUs) { public Segment copyWithStartTimeUs(long startTimeUs) {
return new Segment(url, durationSecs, discontinuitySequenceNumber, startTimeUs, isEncrypted, return new Segment(url, durationUs, discontinuitySequenceNumber, startTimeUs, isEncrypted,
encryptionKeyUri, encryptionIV, byterangeOffset, byterangeLength); encryptionKeyUri, encryptionIV, byterangeOffset, byterangeLength);
} }
} }
public static final String ENCRYPTION_METHOD_NONE = "NONE";
public static final String ENCRYPTION_METHOD_AES_128 = "AES-128";
public final int mediaSequence; public final int mediaSequence;
public final int targetDurationSecs;
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 long durationUs; public final long durationUs;
public HlsMediaPlaylist(String baseUri, int mediaSequence, int targetDurationSecs, int version, public HlsMediaPlaylist(String baseUri, int mediaSequence, int version,
boolean hasEndTag, Segment initializationSegment, List<Segment> segments) { boolean hasEndTag, Segment initializationSegment, List<Segment> segments) {
super(baseUri, HlsPlaylist.TYPE_MEDIA); super(baseUri, HlsPlaylist.TYPE_MEDIA);
this.mediaSequence = mediaSequence; this.mediaSequence = mediaSequence;
this.targetDurationSecs = targetDurationSecs;
this.version = version; this.version = version;
this.hasEndTag = hasEndTag; this.hasEndTag = hasEndTag;
this.initializationSegment = initializationSegment; this.initializationSegment = initializationSegment;
...@@ -94,8 +89,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -94,8 +89,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
if (!segments.isEmpty()) { if (!segments.isEmpty()) {
Segment first = segments.get(0); Segment first = segments.get(0);
Segment last = segments.get(segments.size() - 1); Segment last = segments.get(segments.size() - 1);
durationUs = last.startTimeUs + (long) (last.durationSecs * C.MICROS_PER_SECOND) durationUs = last.startTimeUs + last.durationUs - first.startTimeUs;
- first.startTimeUs;
} else { } else {
durationUs = 0; durationUs = 0;
} }
...@@ -121,7 +115,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -121,7 +115,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
} }
public HlsMediaPlaylist copyWithSegments(List<Segment> segments) { public HlsMediaPlaylist copyWithSegments(List<Segment> segments) {
return new HlsMediaPlaylist(baseUri, mediaSequence, targetDurationSecs, version, hasEndTag, return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag,
initializationSegment, segments); initializationSegment, segments);
} }
......
...@@ -217,7 +217,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -217,7 +217,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
Segment initializationSegment = null; Segment initializationSegment = null;
List<Segment> segments = new ArrayList<>(); List<Segment> segments = new ArrayList<>();
double segmentDurationSecs = 0.0; long segmentDurationUs = 0;
int discontinuitySequenceNumber = 0; int discontinuitySequenceNumber = 0;
long segmentStartTimeUs = 0; long segmentStartTimeUs = 0;
long segmentByteRangeOffset = 0; long segmentByteRangeOffset = 0;
...@@ -252,7 +252,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -252,7 +252,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
} else if (line.startsWith(TAG_VERSION)) { } else if (line.startsWith(TAG_VERSION)) {
version = parseIntAttr(line, REGEX_VERSION); version = parseIntAttr(line, REGEX_VERSION);
} else if (line.startsWith(TAG_MEDIA_DURATION)) { } else if (line.startsWith(TAG_MEDIA_DURATION)) {
segmentDurationSecs = parseDoubleAttr(line, REGEX_MEDIA_DURATION); segmentDurationUs =
(long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
} else if (line.startsWith(TAG_KEY)) { } else if (line.startsWith(TAG_KEY)) {
String method = parseStringAttr(line, REGEX_METHOD); String method = parseStringAttr(line, REGEX_METHOD);
isEncrypted = METHOD_AES128.equals(method); isEncrypted = METHOD_AES128.equals(method);
...@@ -287,11 +288,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -287,11 +288,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
if (segmentByteRangeLength == C.LENGTH_UNSET) { if (segmentByteRangeLength == C.LENGTH_UNSET) {
segmentByteRangeOffset = 0; segmentByteRangeOffset = 0;
} }
segments.add(new Segment(line, segmentDurationSecs, discontinuitySequenceNumber, segments.add(new Segment(line, segmentDurationUs, discontinuitySequenceNumber,
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
segmentByteRangeOffset, segmentByteRangeLength)); segmentByteRangeOffset, segmentByteRangeLength));
segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND); segmentStartTimeUs += segmentDurationUs;
segmentDurationSecs = 0.0; segmentDurationUs = 0;
if (segmentByteRangeLength != C.LENGTH_UNSET) { if (segmentByteRangeLength != C.LENGTH_UNSET) {
segmentByteRangeOffset += segmentByteRangeLength; segmentByteRangeOffset += segmentByteRangeLength;
} }
...@@ -300,7 +301,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -300,7 +301,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
hasEndTag = true; hasEndTag = true;
} }
} }
return new HlsMediaPlaylist(baseUri, mediaSequence, targetDurationSecs, version, hasEndTag, return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag,
initializationSegment, segments); initializationSegment, segments);
} }
......
...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C; ...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
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.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
...@@ -63,6 +64,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -63,6 +64,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
/** /**
* Determines the minimum amount of time by which a media playlist segment's start time has to
* drift from the actual start time of the chunk it refers to for it to be adjusted.
*/
private static final long TIMESTAMP_ADJUSTMENT_THRESHOLD_US = 500000;
/**
* Period for refreshing playlists. * Period for refreshing playlists.
*/ */
private static final long PLAYLIST_REFRESH_PERIOD_MS = 5000; private static final long PLAYLIST_REFRESH_PERIOD_MS = 5000;
...@@ -182,6 +189,17 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -182,6 +189,17 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
return isLive; return isLive;
} }
/**
* Called when a chunk from a media playlist is loaded.
*
* @param hlsUrl The url of the playlist from which the chunk was obtained.
* @param chunkMediaSequence The media sequence number of the loaded chunk.
* @param adjustedStartTimeUs The adjusted start time of the loaded chunk.
*/
public void onChunkLoaded(HlsUrl hlsUrl, int chunkMediaSequence, long adjustedStartTimeUs) {
playlistBundles.get(hlsUrl).adjustTimestampsOfPlaylist(chunkMediaSequence, adjustedStartTimeUs);
}
// Loader.Callback implementation. // Loader.Callback implementation.
@Override @Override
...@@ -262,7 +280,6 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -262,7 +280,6 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
/** /**
* TODO: Allow chunks to feed adjusted timestamps back to the playlist tracker.
* TODO: Track discontinuities for media playlists that don't include the discontinuity number. * TODO: Track discontinuities for media playlists that don't include the discontinuity number.
*/ */
private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist, private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist,
...@@ -293,7 +310,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -293,7 +310,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
HlsMediaPlaylist.Segment lastSegment = oldSegments.get(oldPlaylistSize - 1); HlsMediaPlaylist.Segment lastSegment = oldSegments.get(oldPlaylistSize - 1);
for (int i = newPlaylistSize - newSegmentsCount; i < newPlaylistSize; i++) { for (int i = newPlaylistSize - newSegmentsCount; i < newPlaylistSize; i++) {
lastSegment = newPlaylist.segments.get(i).copyWithStartTimeUs( lastSegment = newPlaylist.segments.get(i).copyWithStartTimeUs(
lastSegment.startTimeUs + (long) lastSegment.durationSecs * C.MICROS_PER_SECOND); lastSegment.startTimeUs + lastSegment.durationUs);
newSegments.add(lastSegment); newSegments.add(lastSegment);
} }
return newPlaylist.copyWithSegments(newSegments); return newPlaylist.copyWithSegments(newSegments);
...@@ -343,6 +360,34 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -343,6 +360,34 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
this.callback = callback; this.callback = callback;
} }
public void adjustTimestampsOfPlaylist(int chunkMediaSequence, long adjustedStartTimeUs) {
ArrayList<Segment> segments = new ArrayList<>(latestPlaylistSnapshot.segments);
int indexOfChunk = chunkMediaSequence - latestPlaylistSnapshot.mediaSequence;
if (indexOfChunk < 0) {
return;
}
Segment actualSegment = segments.get(indexOfChunk);
long timestampDriftUs = Math.abs(actualSegment.startTimeUs - adjustedStartTimeUs);
if (timestampDriftUs < TIMESTAMP_ADJUSTMENT_THRESHOLD_US) {
return;
}
segments.set(indexOfChunk, actualSegment.copyWithStartTimeUs(adjustedStartTimeUs));
// Propagate the adjustment backwards.
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.
@Override @Override
......
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