mirror of
https://github.com/samsonjs/media.git
synced 2026-04-10 12:05:47 +00:00
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
This commit is contained in:
parent
91c58627be
commit
501f54a8a6
6 changed files with 85 additions and 116 deletions
|
|
@ -212,7 +212,8 @@ import java.util.Locale;
|
|||
// 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, true,
|
||||
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments,
|
||||
targetPositionUs - mediaPlaylist.startTimeUs, true,
|
||||
!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
|
||||
|
|
@ -259,16 +260,6 @@ import java.util.Locale;
|
|||
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;
|
||||
Segment initSegment = mediaPlaylist.initializationSegment;
|
||||
if (initSegment != null) {
|
||||
|
|
@ -277,13 +268,20 @@ import java.util.Locale;
|
|||
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.
|
||||
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
|
||||
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
|
||||
null);
|
||||
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
|
||||
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segment,
|
||||
chunkMediaSequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey,
|
||||
encryptionIv);
|
||||
trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
|
||||
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence,
|
||||
segment.discontinuitySequenceNumber, isTimestampMaster, timestampAdjuster, previous,
|
||||
encryptionKey, encryptionIv);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.source.chunk.MediaChunk;
|
||||
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.DataSpec;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
|
@ -89,8 +88,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* @param hlsUrl The url of the playlist from which this chunk was obtained.
|
||||
* @param trackSelectionReason See {@link #trackSelectionReason}.
|
||||
* @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 discontinuitySequenceNumber The discontinuity sequence number of the chunk.
|
||||
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
|
||||
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
|
||||
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
|
||||
|
|
@ -98,21 +99,21 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* @param encryptionIv For AES encryption chunks, the encryption initialization vector.
|
||||
*/
|
||||
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
|
||||
HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment,
|
||||
int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster,
|
||||
HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, long startTimeUs,
|
||||
long endTimeUs, int chunkIndex, int discontinuitySequenceNumber,
|
||||
boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster,
|
||||
HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) {
|
||||
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format,
|
||||
trackSelectionReason, trackSelectionData, segment.startTimeUs,
|
||||
segment.startTimeUs + segment.durationUs, chunkIndex);
|
||||
trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex);
|
||||
this.initDataSpec = initDataSpec;
|
||||
this.hlsUrl = hlsUrl;
|
||||
this.isMasterTimestampSource = isMasterTimestampSource;
|
||||
this.timestampAdjuster = timestampAdjuster;
|
||||
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
|
||||
this.previousChunk = previousChunk;
|
||||
// Note: this.dataSource and dataSource may be different.
|
||||
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
|
||||
initDataSource = dataSource;
|
||||
discontinuitySequenceNumber = segment.discontinuitySequenceNumber;
|
||||
adjustedEndTimeUs = endTimeUs;
|
||||
uid = UID_SOURCE.getAndIncrement();
|
||||
}
|
||||
|
|
@ -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() {
|
||||
return adjustedEndTimeUs;
|
||||
|
|
@ -231,8 +232,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
}
|
||||
|
||||
private void maybeLoadInitData() throws IOException, InterruptedException {
|
||||
if (previousChunk == null || previousChunk.extractor != extractor || initLoadCompleted
|
||||
|| initDataSpec == null) {
|
||||
if ((previousChunk != null && previousChunk.extractor == extractor)
|
||||
|| initLoadCompleted || initDataSpec == null) {
|
||||
return;
|
||||
}
|
||||
DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded);
|
||||
|
|
|
|||
|
|
@ -103,10 +103,10 @@ public final class HlsMediaSource implements MediaSource,
|
|||
SinglePeriodTimeline timeline;
|
||||
if (playlistTracker.isLive()) {
|
||||
// TODO: fix windowPositionInPeriodUs when playlist is empty.
|
||||
long windowPositionInPeriodUs = playlist.getStartTimeUs();
|
||||
long windowPositionInPeriodUs = playlist.startTimeUs;
|
||||
List<HlsMediaPlaylist.Segment> segments = playlist.segments;
|
||||
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,
|
||||
windowPositionInPeriodUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag);
|
||||
} else /* not live */ {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
package com.google.android.exoplayer2.source.hls.playlist;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -33,7 +32,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
public final String url;
|
||||
public final long durationUs;
|
||||
public final int discontinuitySequenceNumber;
|
||||
public final long startTimeUs;
|
||||
public final long relativeStartTimeUs;
|
||||
public final boolean isEncrypted;
|
||||
public final String encryptionKeyUri;
|
||||
public final String encryptionIV;
|
||||
|
|
@ -45,12 +44,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
}
|
||||
|
||||
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) {
|
||||
this.url = uri;
|
||||
this.durationUs = durationUs;
|
||||
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.relativeStartTimeUs = relativeStartTimeUs;
|
||||
this.isEncrypted = isEncrypted;
|
||||
this.encryptionKeyUri = encryptionKeyUri;
|
||||
this.encryptionIV = encryptionIV;
|
||||
|
|
@ -59,64 +58,55 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Long startTimeUs) {
|
||||
return this.startTimeUs > startTimeUs ? 1 : (this.startTimeUs < startTimeUs ? -1 : 0);
|
||||
}
|
||||
|
||||
public Segment copyWithStartTimeUs(long startTimeUs) {
|
||||
return new Segment(url, durationUs, discontinuitySequenceNumber, startTimeUs, isEncrypted,
|
||||
encryptionKeyUri, encryptionIV, byterangeOffset, byterangeLength);
|
||||
public int compareTo(Long relativeStartTimeUs) {
|
||||
return this.relativeStartTimeUs > relativeStartTimeUs
|
||||
? 1 : (this.relativeStartTimeUs < relativeStartTimeUs ? -1 : 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public final long startTimeUs;
|
||||
public final int mediaSequence;
|
||||
public final int version;
|
||||
public final Segment initializationSegment;
|
||||
public final List<Segment> segments;
|
||||
public final boolean hasEndTag;
|
||||
public final boolean hasProgramDateTime;
|
||||
public final long durationUs;
|
||||
|
||||
public HlsMediaPlaylist(String baseUri, int mediaSequence, int version,
|
||||
boolean hasEndTag, Segment initializationSegment, List<Segment> segments) {
|
||||
public HlsMediaPlaylist(String baseUri, long startTimeUs, int mediaSequence, int version,
|
||||
boolean hasEndTag, boolean hasProgramDateTime, Segment initializationSegment,
|
||||
List<Segment> segments) {
|
||||
super(baseUri, HlsPlaylist.TYPE_MEDIA);
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.mediaSequence = mediaSequence;
|
||||
this.version = version;
|
||||
this.hasEndTag = hasEndTag;
|
||||
this.hasProgramDateTime = hasProgramDateTime;
|
||||
this.initializationSegment = initializationSegment;
|
||||
this.segments = Collections.unmodifiableList(segments);
|
||||
|
||||
if (!segments.isEmpty()) {
|
||||
Segment first = segments.get(0);
|
||||
Segment last = segments.get(segments.size() - 1);
|
||||
durationUs = last.startTimeUs + last.durationUs - first.startTimeUs;
|
||||
durationUs = last.relativeStartTimeUs + last.durationUs;
|
||||
} else {
|
||||
durationUs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public long getStartTimeUs() {
|
||||
return segments.isEmpty() ? 0 : segments.get(0).startTimeUs;
|
||||
public boolean isNewerThan(HlsMediaPlaylist other) {
|
||||
return other == null || mediaSequence > other.mediaSequence
|
||||
|| (mediaSequence == other.mediaSequence && segments.size() > other.segments.size())
|
||||
|| (hasEndTag && !other.hasEndTag);
|
||||
}
|
||||
|
||||
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) {
|
||||
return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag,
|
||||
initializationSegment, segments);
|
||||
public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) {
|
||||
return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, hasEndTag,
|
||||
hasProgramDateTime, initializationSegment, segments);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
|
@ -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_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";
|
||||
|
|
@ -62,17 +64,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
private static final String BOOLEAN_TRUE = "YES";
|
||||
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_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
|
||||
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_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION
|
||||
+ ":(\\d+)\\b");
|
||||
private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE
|
||||
+ ":(\\d+)\\b");
|
||||
private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION
|
||||
|
|
@ -211,7 +206,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
|
||||
throws IOException {
|
||||
int mediaSequence = 0;
|
||||
int targetDurationSecs = 0;
|
||||
int version = 1; // Default version == 1.
|
||||
boolean hasEndTag = false;
|
||||
Segment initializationSegment = null;
|
||||
|
|
@ -219,6 +213,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
|
||||
long segmentDurationUs = 0;
|
||||
int discontinuitySequenceNumber = 0;
|
||||
long playlistStartTimeUs = 0;
|
||||
long segmentStartTimeUs = 0;
|
||||
long segmentByteRangeOffset = 0;
|
||||
long segmentByteRangeLength = C.LENGTH_UNSET;
|
||||
|
|
@ -244,8 +239,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength);
|
||||
segmentByteRangeOffset = 0;
|
||||
segmentByteRangeLength = C.LENGTH_UNSET;
|
||||
} else if (line.startsWith(TAG_TARGET_DURATION)) {
|
||||
targetDurationSecs = parseIntAttr(line, REGEX_TARGET_DURATION);
|
||||
} else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
|
||||
mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE);
|
||||
segmentMediaSequence = mediaSequence;
|
||||
|
|
@ -275,6 +268,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1));
|
||||
} else if (line.equals(TAG_DISCONTINUITY)) {
|
||||
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("#")) {
|
||||
String segmentEncryptionIV;
|
||||
if (!isEncrypted) {
|
||||
|
|
@ -301,8 +300,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
hasEndTag = true;
|
||||
}
|
||||
}
|
||||
return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag,
|
||||
initializationSegment, segments);
|
||||
return new HlsMediaPlaylist(baseUri, playlistStartTimeUs, mediaSequence, version, hasEndTag,
|
||||
playlistStartTimeUs != 0, initializationSegment, segments);
|
||||
}
|
||||
|
||||
private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
|
||||
|
|
|
|||
|
|
@ -292,46 +292,39 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
|||
*/
|
||||
private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist,
|
||||
HlsMediaPlaylist newPlaylist) {
|
||||
if (newPlaylist.hasProgramDateTime) {
|
||||
if (newPlaylist.isNewerThan(oldPlaylist)) {
|
||||
return newPlaylist;
|
||||
} else {
|
||||
return oldPlaylist;
|
||||
}
|
||||
}
|
||||
HlsMediaPlaylist primaryPlaylistSnapshot =
|
||||
playlistBundles.get(primaryHlsUrl).latestPlaylistSnapshot;
|
||||
if (oldPlaylist == null) {
|
||||
if (primaryPlaylistSnapshot == null) {
|
||||
// Playback has just started so no adjustment is needed.
|
||||
if (primaryPlaylistSnapshot == null
|
||||
|| primaryPlaylistSnapshot.startTimeUs == newPlaylist.startTimeUs) {
|
||||
// Playback has just started or is VOD so no adjustment is needed.
|
||||
return newPlaylist;
|
||||
} 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 newPlaylistSize = newPlaylist.segments.size();
|
||||
int mediaSequenceOffset = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
|
||||
if (newPlaylistSize == oldPlaylistSize && mediaSequenceOffset == 0
|
||||
&& oldPlaylist.hasEndTag == newPlaylist.hasEndTag) {
|
||||
if (!newPlaylist.isNewerThan(oldPlaylist)) {
|
||||
// Playlist has not changed.
|
||||
return oldPlaylist;
|
||||
}
|
||||
if (mediaSequenceOffset < 0) {
|
||||
// Playlist has changed but media sequence has regressed.
|
||||
return oldPlaylist;
|
||||
}
|
||||
int mediaSequenceOffset = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
|
||||
if (mediaSequenceOffset <= oldPlaylistSize) {
|
||||
// We can extrapolate the start time of new segments from the segments of the old snapshot.
|
||||
ArrayList<HlsMediaPlaylist.Segment> newSegments = new ArrayList<>(newPlaylistSize);
|
||||
for (int i = mediaSequenceOffset; i < oldPlaylistSize; i++) {
|
||||
newSegments.add(oldSegments.get(i));
|
||||
}
|
||||
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());
|
||||
long adjustedNewPlaylistStartTimeUs = mediaSequenceOffset == oldPlaylistSize
|
||||
? oldPlaylist.getEndTimeUs()
|
||||
: oldPlaylist.startTimeUs + oldSegments.get(mediaSequenceOffset).relativeStartTimeUs;
|
||||
return newPlaylist.copyWithStartTimeUs(adjustedNewPlaylistStartTimeUs);
|
||||
}
|
||||
// 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
|
|||
}
|
||||
|
||||
public void adjustTimestampsOfPlaylist(int chunkMediaSequence, long adjustedStartTimeUs) {
|
||||
ArrayList<Segment> segments = new ArrayList<>(latestPlaylistSnapshot.segments);
|
||||
int indexOfChunk = chunkMediaSequence - latestPlaylistSnapshot.mediaSequence;
|
||||
if (indexOfChunk < 0) {
|
||||
if (latestPlaylistSnapshot.hasProgramDateTime || indexOfChunk < 0) {
|
||||
return;
|
||||
}
|
||||
Segment actualSegment = segments.get(indexOfChunk);
|
||||
long timestampDriftUs = Math.abs(actualSegment.startTimeUs - adjustedStartTimeUs);
|
||||
Segment actualSegment = latestPlaylistSnapshot.segments.get(indexOfChunk);
|
||||
long segmentAbsoluteStartTimeUs =
|
||||
actualSegment.relativeStartTimeUs + latestPlaylistSnapshot.startTimeUs;
|
||||
long timestampDriftUs = Math.abs(segmentAbsoluteStartTimeUs - 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);
|
||||
latestPlaylistSnapshot = latestPlaylistSnapshot.copyWithStartTimeUs(
|
||||
adjustedStartTimeUs - actualSegment.relativeStartTimeUs);
|
||||
}
|
||||
|
||||
// Loader.Callback implementation.
|
||||
|
|
|
|||
Loading…
Reference in a new issue