mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Track HLS discontinuities when playlist does not declare sequence
This is an initial version that does not handle cross-playlists adjustment in an ideal way. Issue:#1789 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=144692969
This commit is contained in:
parent
7d7a159195
commit
6e481178ea
5 changed files with 136 additions and 96 deletions
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.hls.playlist;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -73,59 +74,64 @@ public class HlsMediaPlaylistParserTest extends TestCase {
|
||||||
|
|
||||||
assertEquals(2679, mediaPlaylist.mediaSequence);
|
assertEquals(2679, mediaPlaylist.mediaSequence);
|
||||||
assertEquals(3, mediaPlaylist.version);
|
assertEquals(3, mediaPlaylist.version);
|
||||||
assertEquals(true, mediaPlaylist.hasEndTag);
|
assertTrue(mediaPlaylist.hasEndTag);
|
||||||
List<HlsMediaPlaylist.Segment> segments = mediaPlaylist.segments;
|
List<Segment> segments = mediaPlaylist.segments;
|
||||||
assertNotNull(segments);
|
assertNotNull(segments);
|
||||||
assertEquals(5, segments.size());
|
assertEquals(5, segments.size());
|
||||||
|
|
||||||
assertEquals(4, segments.get(0).discontinuitySequenceNumber);
|
Segment segment = segments.get(0);
|
||||||
assertEquals(7975000, segments.get(0).durationUs);
|
assertEquals(4, mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence);
|
||||||
assertEquals(false, segments.get(0).isEncrypted);
|
assertEquals(7975000, segment.durationUs);
|
||||||
assertEquals(null, segments.get(0).encryptionKeyUri);
|
assertFalse(segment.isEncrypted);
|
||||||
assertEquals(null, segments.get(0).encryptionIV);
|
assertEquals(null, segment.encryptionKeyUri);
|
||||||
assertEquals(51370, segments.get(0).byterangeLength);
|
assertEquals(null, segment.encryptionIV);
|
||||||
assertEquals(0, segments.get(0).byterangeOffset);
|
assertEquals(51370, segment.byterangeLength);
|
||||||
assertEquals("https://priv.example.com/fileSequence2679.ts", segments.get(0).url);
|
assertEquals(0, segment.byterangeOffset);
|
||||||
|
assertEquals("https://priv.example.com/fileSequence2679.ts", segment.url);
|
||||||
|
|
||||||
assertEquals(4, segments.get(1).discontinuitySequenceNumber);
|
segment = segments.get(1);
|
||||||
assertEquals(7975000, segments.get(1).durationUs);
|
assertEquals(0, segment.relativeDiscontinuitySequence);
|
||||||
assertEquals(true, segments.get(1).isEncrypted);
|
assertEquals(7975000, segment.durationUs);
|
||||||
assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri);
|
assertTrue(segment.isEncrypted);
|
||||||
assertEquals("0x1566B", segments.get(1).encryptionIV);
|
assertEquals("https://priv.example.com/key.php?r=2680", segment.encryptionKeyUri);
|
||||||
assertEquals(51501, segments.get(1).byterangeLength);
|
assertEquals("0x1566B", segment.encryptionIV);
|
||||||
assertEquals(2147483648L, segments.get(1).byterangeOffset);
|
assertEquals(51501, segment.byterangeLength);
|
||||||
assertEquals("https://priv.example.com/fileSequence2680.ts", segments.get(1).url);
|
assertEquals(2147483648L, segment.byterangeOffset);
|
||||||
|
assertEquals("https://priv.example.com/fileSequence2680.ts", segment.url);
|
||||||
|
|
||||||
assertEquals(4, segments.get(2).discontinuitySequenceNumber);
|
segment = segments.get(2);
|
||||||
assertEquals(7941000, segments.get(2).durationUs);
|
assertEquals(0, segment.relativeDiscontinuitySequence);
|
||||||
assertEquals(false, segments.get(2).isEncrypted);
|
assertEquals(7941000, segment.durationUs);
|
||||||
assertEquals(null, segments.get(2).encryptionKeyUri);
|
assertFalse(segment.isEncrypted);
|
||||||
assertEquals(null, segments.get(2).encryptionIV);
|
assertEquals(null, segment.encryptionKeyUri);
|
||||||
assertEquals(51501, segments.get(2).byterangeLength);
|
assertEquals(null, segment.encryptionIV);
|
||||||
assertEquals(2147535149L, segments.get(2).byterangeOffset);
|
assertEquals(51501, segment.byterangeLength);
|
||||||
assertEquals("https://priv.example.com/fileSequence2681.ts", segments.get(2).url);
|
assertEquals(2147535149L, segment.byterangeOffset);
|
||||||
|
assertEquals("https://priv.example.com/fileSequence2681.ts", segment.url);
|
||||||
|
|
||||||
assertEquals(5, segments.get(3).discontinuitySequenceNumber);
|
segment = segments.get(3);
|
||||||
assertEquals(7975000, segments.get(3).durationUs);
|
assertEquals(1, segment.relativeDiscontinuitySequence);
|
||||||
assertEquals(true, segments.get(3).isEncrypted);
|
assertEquals(7975000, segment.durationUs);
|
||||||
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri);
|
assertTrue(segment.isEncrypted);
|
||||||
|
assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
|
||||||
// 0xA7A == 2682.
|
// 0xA7A == 2682.
|
||||||
assertNotNull(segments.get(3).encryptionIV);
|
assertNotNull(segment.encryptionIV);
|
||||||
assertEquals("A7A", segments.get(3).encryptionIV.toUpperCase(Locale.getDefault()));
|
assertEquals("A7A", segment.encryptionIV.toUpperCase(Locale.getDefault()));
|
||||||
assertEquals(51740, segments.get(3).byterangeLength);
|
assertEquals(51740, segment.byterangeLength);
|
||||||
assertEquals(2147586650L, segments.get(3).byterangeOffset);
|
assertEquals(2147586650L, segment.byterangeOffset);
|
||||||
assertEquals("https://priv.example.com/fileSequence2682.ts", segments.get(3).url);
|
assertEquals("https://priv.example.com/fileSequence2682.ts", segment.url);
|
||||||
|
|
||||||
assertEquals(5, segments.get(4).discontinuitySequenceNumber);
|
segment = segments.get(4);
|
||||||
assertEquals(7975000, segments.get(4).durationUs);
|
assertEquals(1, segment.relativeDiscontinuitySequence);
|
||||||
assertEquals(true, segments.get(4).isEncrypted);
|
assertEquals(7975000, segment.durationUs);
|
||||||
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri);
|
assertTrue(segment.isEncrypted);
|
||||||
|
assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
|
||||||
// 0xA7B == 2683.
|
// 0xA7B == 2683.
|
||||||
assertNotNull(segments.get(4).encryptionIV);
|
assertNotNull(segment.encryptionIV);
|
||||||
assertEquals("A7B", segments.get(4).encryptionIV.toUpperCase(Locale.getDefault()));
|
assertEquals("A7B", segment.encryptionIV.toUpperCase(Locale.getDefault()));
|
||||||
assertEquals(C.LENGTH_UNSET, segments.get(4).byterangeLength);
|
assertEquals(C.LENGTH_UNSET, segment.byterangeLength);
|
||||||
assertEquals(0, segments.get(4).byterangeOffset);
|
assertEquals(0, segment.byterangeOffset);
|
||||||
assertEquals("https://priv.example.com/fileSequence2683.ts", segments.get(4).url);
|
assertEquals("https://priv.example.com/fileSequence2683.ts", segment.url);
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
fail(exception.getMessage());
|
fail(exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,8 +270,10 @@ import java.util.Locale;
|
||||||
|
|
||||||
// Compute start time of the next chunk.
|
// Compute start time of the next chunk.
|
||||||
long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
|
long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
|
||||||
|
int discontinuitySequence = mediaPlaylist.discontinuitySequence
|
||||||
|
+ segment.relativeDiscontinuitySequence;
|
||||||
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
|
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
|
||||||
segment.discontinuitySequenceNumber, startTimeUs);
|
discontinuitySequence, 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);
|
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
|
||||||
|
|
@ -279,9 +281,8 @@ import java.util.Locale;
|
||||||
null);
|
null);
|
||||||
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
|
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
|
||||||
trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
|
trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
|
||||||
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence,
|
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence,
|
||||||
segment.discontinuitySequenceNumber, isTimestampMaster, timestampAdjuster, previous,
|
isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv);
|
||||||
encryptionKey, encryptionIv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,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 relativeDiscontinuitySequence;
|
||||||
public final long relativeStartTimeUs;
|
public final long relativeStartTimeUs;
|
||||||
public final boolean isEncrypted;
|
public final boolean isEncrypted;
|
||||||
public final String encryptionKeyUri;
|
public final String encryptionKeyUri;
|
||||||
|
|
@ -43,12 +43,12 @@ 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, long durationUs, int discontinuitySequenceNumber,
|
public Segment(String uri, long durationUs, int relativeDiscontinuitySequence,
|
||||||
long relativeStartTimeUs, 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.relativeDiscontinuitySequence = relativeDiscontinuitySequence;
|
||||||
this.relativeStartTimeUs = relativeStartTimeUs;
|
this.relativeStartTimeUs = relativeStartTimeUs;
|
||||||
this.isEncrypted = isEncrypted;
|
this.isEncrypted = isEncrypted;
|
||||||
this.encryptionKeyUri = encryptionKeyUri;
|
this.encryptionKeyUri = encryptionKeyUri;
|
||||||
|
|
@ -67,6 +67,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||||
|
|
||||||
public final long startOffsetUs;
|
public final long startOffsetUs;
|
||||||
public final long startTimeUs;
|
public final long startTimeUs;
|
||||||
|
public final boolean hasDiscontinuitySequence;
|
||||||
|
public final int discontinuitySequence;
|
||||||
public final int mediaSequence;
|
public final int mediaSequence;
|
||||||
public final int version;
|
public final int version;
|
||||||
public final long targetDurationUs;
|
public final long targetDurationUs;
|
||||||
|
|
@ -76,11 +78,14 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||||
public final List<Segment> segments;
|
public final List<Segment> segments;
|
||||||
public final long durationUs;
|
public final long durationUs;
|
||||||
|
|
||||||
public HlsMediaPlaylist(String baseUri, long startOffsetUs, long startTimeUs, int mediaSequence,
|
public HlsMediaPlaylist(String baseUri, long startOffsetUs, long startTimeUs,
|
||||||
int version, long targetDurationUs, boolean hasEndTag, boolean hasProgramDateTime,
|
boolean hasDiscontinuitySequence, int discontinuitySequence, int mediaSequence, int version,
|
||||||
|
long targetDurationUs, boolean hasEndTag, boolean hasProgramDateTime,
|
||||||
Segment initializationSegment, List<Segment> segments) {
|
Segment initializationSegment, List<Segment> segments) {
|
||||||
super(baseUri, HlsPlaylist.TYPE_MEDIA);
|
super(baseUri, HlsPlaylist.TYPE_MEDIA);
|
||||||
this.startTimeUs = startTimeUs;
|
this.startTimeUs = startTimeUs;
|
||||||
|
this.hasDiscontinuitySequence = hasDiscontinuitySequence;
|
||||||
|
this.discontinuitySequence = discontinuitySequence;
|
||||||
this.mediaSequence = mediaSequence;
|
this.mediaSequence = mediaSequence;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.targetDurationUs = targetDurationUs;
|
this.targetDurationUs = targetDurationUs;
|
||||||
|
|
@ -123,19 +128,18 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a playlist identical to this one except for the start time, which is set to the
|
* Returns a playlist identical to this one except for the start time, the discontinuity sequence
|
||||||
* specified value. If the start time already equals the specified value then the playlist will
|
* and {@code hasDiscontinuitySequence} values. The first two are set to the specified values,
|
||||||
* return itself.
|
* {@code hasDiscontinuitySequence} is set to true.
|
||||||
*
|
*
|
||||||
* @param startTimeUs The start time for the returned playlist.
|
* @param startTimeUs The start time for the returned playlist.
|
||||||
|
* @param discontinuitySequence The discontinuity sequence for the returned playlist.
|
||||||
* @return The playlist.
|
* @return The playlist.
|
||||||
*/
|
*/
|
||||||
public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) {
|
public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) {
|
||||||
if (this.startTimeUs == startTimeUs) {
|
return new HlsMediaPlaylist(baseUri, startOffsetUs, startTimeUs, true, discontinuitySequence,
|
||||||
return this;
|
mediaSequence, version, targetDurationUs, hasEndTag, hasProgramDateTime,
|
||||||
}
|
initializationSegment, segments);
|
||||||
return new HlsMediaPlaylist(baseUri, startOffsetUs, startTimeUs, mediaSequence, version,
|
|
||||||
targetDurationUs, hasEndTag, hasProgramDateTime, initializationSegment, segments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -148,8 +152,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||||
if (this.hasEndTag) {
|
if (this.hasEndTag) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new HlsMediaPlaylist(baseUri, startOffsetUs, startTimeUs, mediaSequence, version,
|
return new HlsMediaPlaylist(baseUri, startOffsetUs, startTimeUs, hasDiscontinuitySequence,
|
||||||
targetDurationUs, true, hasProgramDateTime, initializationSegment, segments);
|
discontinuitySequence, mediaSequence, version, targetDurationUs, true, hasProgramDateTime,
|
||||||
|
initializationSegment, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||||
List<Segment> segments = new ArrayList<>();
|
List<Segment> segments = new ArrayList<>();
|
||||||
|
|
||||||
long segmentDurationUs = 0;
|
long segmentDurationUs = 0;
|
||||||
int discontinuitySequenceNumber = 0;
|
boolean hasDiscontinuitySequence = false;
|
||||||
|
int playlistDiscontinuitySequence = 0;
|
||||||
|
int relativeDiscontinuitySequence = 0;
|
||||||
long playlistStartTimeUs = 0;
|
long playlistStartTimeUs = 0;
|
||||||
long segmentStartTimeUs = 0;
|
long segmentStartTimeUs = 0;
|
||||||
long segmentByteRangeOffset = 0;
|
long segmentByteRangeOffset = 0;
|
||||||
|
|
@ -323,9 +325,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||||
segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
|
segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
|
||||||
}
|
}
|
||||||
} else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) {
|
} else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) {
|
||||||
discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1));
|
hasDiscontinuitySequence = true;
|
||||||
|
playlistDiscontinuitySequence = Integer.parseInt(line.substring(line.indexOf(':') + 1));
|
||||||
} else if (line.equals(TAG_DISCONTINUITY)) {
|
} else if (line.equals(TAG_DISCONTINUITY)) {
|
||||||
discontinuitySequenceNumber++;
|
relativeDiscontinuitySequence++;
|
||||||
} else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) {
|
} else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) {
|
||||||
if (playlistStartTimeUs == 0) {
|
if (playlistStartTimeUs == 0) {
|
||||||
long programDatetimeUs =
|
long programDatetimeUs =
|
||||||
|
|
@ -345,7 +348,7 @@ 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, segmentDurationUs, discontinuitySequenceNumber,
|
segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence,
|
||||||
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
|
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
|
||||||
segmentByteRangeOffset, segmentByteRangeLength));
|
segmentByteRangeOffset, segmentByteRangeLength));
|
||||||
segmentStartTimeUs += segmentDurationUs;
|
segmentStartTimeUs += segmentDurationUs;
|
||||||
|
|
@ -358,7 +361,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||||
hasEndTag = true;
|
hasEndTag = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new HlsMediaPlaylist(baseUri, startOffsetUs, playlistStartTimeUs, mediaSequence, version,
|
return new HlsMediaPlaylist(baseUri, startOffsetUs, playlistStartTimeUs,
|
||||||
|
hasDiscontinuitySequence, playlistDiscontinuitySequence, mediaSequence, version,
|
||||||
targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments);
|
targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -334,47 +334,71 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Track discontinuities for media playlists that don't include the discontinuity number.
|
|
||||||
private HlsMediaPlaylist getLatestPlaylistSnapshot(HlsMediaPlaylist oldPlaylist,
|
private HlsMediaPlaylist getLatestPlaylistSnapshot(HlsMediaPlaylist oldPlaylist,
|
||||||
HlsMediaPlaylist loadedPlaylist) {
|
HlsMediaPlaylist loadedPlaylist) {
|
||||||
if (loadedPlaylist.hasProgramDateTime) {
|
if (!loadedPlaylist.isNewerThan(oldPlaylist)) {
|
||||||
if (loadedPlaylist.isNewerThan(oldPlaylist)) {
|
if (loadedPlaylist.hasEndTag) {
|
||||||
return loadedPlaylist;
|
|
||||||
} else {
|
|
||||||
// If the loaded playlist has an end tag but is not newer than the old playlist then we have
|
// If the loaded playlist has an end tag but is not newer than the old playlist then we have
|
||||||
// an inconsistent state. This is typically caused by the server incorrectly resetting the
|
// an inconsistent state. This is typically caused by the server incorrectly resetting the
|
||||||
// media sequence when appending the end tag. We resolve this case as best we can by
|
// media sequence when appending the end tag. We resolve this case as best we can by
|
||||||
// returning the old playlist with the end tag appended.
|
// returning the old playlist with the end tag appended.
|
||||||
return loadedPlaylist.hasEndTag ? oldPlaylist.copyWithEndTag() : oldPlaylist;
|
return oldPlaylist.copyWithEndTag();
|
||||||
|
} else {
|
||||||
|
return oldPlaylist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Once playlist type support is added, the snapshot's age can be added by using the
|
long startTimeUs = getLoadedPlaylistStartTimeUs(oldPlaylist, loadedPlaylist);
|
||||||
// target duration.
|
int discontinuitySequence = getLoadedPlaylistDiscontinuitySequence(oldPlaylist, loadedPlaylist);
|
||||||
|
return loadedPlaylist.copyWith(startTimeUs, discontinuitySequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getLoadedPlaylistStartTimeUs(HlsMediaPlaylist oldPlaylist,
|
||||||
|
HlsMediaPlaylist loadedPlaylist) {
|
||||||
|
if (loadedPlaylist.hasProgramDateTime) {
|
||||||
|
return loadedPlaylist.startTimeUs;
|
||||||
|
}
|
||||||
long primarySnapshotStartTimeUs = primaryUrlSnapshot != null
|
long primarySnapshotStartTimeUs = primaryUrlSnapshot != null
|
||||||
? primaryUrlSnapshot.startTimeUs : 0;
|
? primaryUrlSnapshot.startTimeUs : 0;
|
||||||
if (oldPlaylist == null) {
|
if (oldPlaylist == null) {
|
||||||
if (loadedPlaylist.startTimeUs == primarySnapshotStartTimeUs) {
|
return primarySnapshotStartTimeUs;
|
||||||
// Playback has just started or is VOD so no adjustment is needed.
|
|
||||||
return loadedPlaylist;
|
|
||||||
} else {
|
|
||||||
return loadedPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!loadedPlaylist.isNewerThan(oldPlaylist)) {
|
int oldPlaylistSize = oldPlaylist.segments.size();
|
||||||
// See comment above.
|
Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist);
|
||||||
return loadedPlaylist.hasEndTag ? oldPlaylist.copyWithEndTag() : oldPlaylist;
|
if (firstOldOverlappingSegment != null) {
|
||||||
|
return oldPlaylist.startTimeUs + firstOldOverlappingSegment.relativeStartTimeUs;
|
||||||
|
} else if (oldPlaylistSize == loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence) {
|
||||||
|
return oldPlaylist.getEndTimeUs();
|
||||||
|
} else {
|
||||||
|
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
|
||||||
|
return primarySnapshotStartTimeUs;
|
||||||
}
|
}
|
||||||
List<Segment> oldSegments = oldPlaylist.segments;
|
}
|
||||||
int oldPlaylistSize = oldSegments.size();
|
|
||||||
|
private int getLoadedPlaylistDiscontinuitySequence(HlsMediaPlaylist oldPlaylist,
|
||||||
|
HlsMediaPlaylist loadedPlaylist) {
|
||||||
|
if (loadedPlaylist.hasDiscontinuitySequence) {
|
||||||
|
return loadedPlaylist.discontinuitySequence;
|
||||||
|
}
|
||||||
|
// TODO: Improve cross-playlist discontinuity adjustment.
|
||||||
|
int primaryUrlDiscontinuitySequence = primaryUrlSnapshot != null
|
||||||
|
? primaryUrlSnapshot.discontinuitySequence : 0;
|
||||||
|
if (oldPlaylist == null) {
|
||||||
|
return primaryUrlDiscontinuitySequence;
|
||||||
|
}
|
||||||
|
Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist);
|
||||||
|
if (firstOldOverlappingSegment != null) {
|
||||||
|
return oldPlaylist.discontinuitySequence
|
||||||
|
+ firstOldOverlappingSegment.relativeDiscontinuitySequence
|
||||||
|
- loadedPlaylist.segments.get(0).relativeDiscontinuitySequence;
|
||||||
|
}
|
||||||
|
return primaryUrlDiscontinuitySequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Segment getFirstOldOverlappingSegment(HlsMediaPlaylist oldPlaylist,
|
||||||
|
HlsMediaPlaylist loadedPlaylist) {
|
||||||
int mediaSequenceOffset = loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence;
|
int mediaSequenceOffset = loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence;
|
||||||
if (mediaSequenceOffset <= oldPlaylistSize) {
|
List<Segment> oldSegments = oldPlaylist.segments;
|
||||||
long adjustedNewPlaylistStartTimeUs = mediaSequenceOffset == oldPlaylistSize
|
return mediaSequenceOffset < oldSegments.size() ? oldSegments.get(mediaSequenceOffset) : null;
|
||||||
? oldPlaylist.getEndTimeUs()
|
|
||||||
: oldPlaylist.startTimeUs + oldSegments.get(mediaSequenceOffset).relativeStartTimeUs;
|
|
||||||
return loadedPlaylist.copyWithStartTimeUs(adjustedNewPlaylistStartTimeUs);
|
|
||||||
}
|
|
||||||
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
|
|
||||||
return loadedPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue