use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class HlsMediaPlaylistParserTest method testParseMediaPlaylist.
public void testParseMediaPlaylist() {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-START:TIME-OFFSET=-25" + "#EXT-X-TARGETDURATION:8\n" + "#EXT-X-MEDIA-SEQUENCE:2679\n" + "#EXT-X-DISCONTINUITY-SEQUENCE:4\n" + "#EXT-X-ALLOW-CACHE:YES\n" + "\n" + "#EXTINF:7.975,\n" + "#EXT-X-BYTERANGE:51370@0\n" + "https://priv.example.com/fileSequence2679.ts\n" + "\n" + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2680\",IV=0x1566B\n" + "#EXTINF:7.975,\n" + "#EXT-X-BYTERANGE:51501@2147483648\n" + "https://priv.example.com/fileSequence2680.ts\n" + "\n" + "#EXT-X-KEY:METHOD=NONE\n" + "#EXTINF:7.941,\n" + // @2147535149
"#EXT-X-BYTERANGE:51501\n" + "https://priv.example.com/fileSequence2681.ts\n" + "\n" + "#EXT-X-DISCONTINUITY\n" + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2682\"\n" + "#EXTINF:7.975,\n" + // @2147586650
"#EXT-X-BYTERANGE:51740\n" + "https://priv.example.com/fileSequence2682.ts\n" + "\n" + "#EXTINF:7.975,\n" + "https://priv.example.com/fileSequence2683.ts\n" + "#EXT-X-ENDLIST";
InputStream inputStream = new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
try {
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
assertNotNull(playlist);
assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType);
assertEquals(mediaPlaylist.durationUs - 25000000, mediaPlaylist.startOffsetUs);
assertEquals(2679, mediaPlaylist.mediaSequence);
assertEquals(3, mediaPlaylist.version);
assertTrue(mediaPlaylist.hasEndTag);
List<Segment> segments = mediaPlaylist.segments;
assertNotNull(segments);
assertEquals(5, segments.size());
Segment segment = segments.get(0);
assertEquals(4, mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertFalse(segment.isEncrypted);
assertEquals(null, segment.encryptionKeyUri);
assertEquals(null, segment.encryptionIV);
assertEquals(51370, segment.byterangeLength);
assertEquals(0, segment.byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2679.ts", segment.url);
segment = segments.get(1);
assertEquals(0, segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertTrue(segment.isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2680", segment.encryptionKeyUri);
assertEquals("0x1566B", segment.encryptionIV);
assertEquals(51501, segment.byterangeLength);
assertEquals(2147483648L, segment.byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2680.ts", segment.url);
segment = segments.get(2);
assertEquals(0, segment.relativeDiscontinuitySequence);
assertEquals(7941000, segment.durationUs);
assertFalse(segment.isEncrypted);
assertEquals(null, segment.encryptionKeyUri);
assertEquals(null, segment.encryptionIV);
assertEquals(51501, segment.byterangeLength);
assertEquals(2147535149L, segment.byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2681.ts", segment.url);
segment = segments.get(3);
assertEquals(1, segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertTrue(segment.isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
// 0xA7A == 2682.
assertNotNull(segment.encryptionIV);
assertEquals("A7A", segment.encryptionIV.toUpperCase(Locale.getDefault()));
assertEquals(51740, segment.byterangeLength);
assertEquals(2147586650L, segment.byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2682.ts", segment.url);
segment = segments.get(4);
assertEquals(1, segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertTrue(segment.isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
// 0xA7B == 2683.
assertNotNull(segment.encryptionIV);
assertEquals("A7B", segment.encryptionIV.toUpperCase(Locale.getDefault()));
assertEquals(C.LENGTH_UNSET, segment.byterangeLength);
assertEquals(0, segment.byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2683.ts", segment.url);
} catch (IOException exception) {
fail(exception.getMessage());
}
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class HlsChunkSource method getNextChunk.
/**
* Returns the next chunk to load.
* <p>
* If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream has
* been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available but
* the end of the stream has not been reached, {@link HlsChunkHolder#playlist} is set to
* contain the {@link HlsUrl} that refers to the playlist that needs refreshing.
*
* @param previous The most recently loaded media chunk.
* @param playbackPositionUs The current playback position. If {@code previous} is null then this
* parameter is the position from which playback is expected to start (or restart) and hence
* should be interpreted as a seek position.
* @param out A holder to populate.
*/
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) {
int oldVariantIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
// Use start time of the previous chunk rather than its end time because switching format will
// require downloading overlapping segments.
long bufferedDurationUs = previous == null ? 0 : Math.max(0, previous.startTimeUs - playbackPositionUs);
// Select the variant.
trackSelection.updateSelectedTrack(bufferedDurationUs);
int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingVariant = oldVariantIndex != selectedVariantIndex;
HlsUrl selectedUrl = variants[selectedVariantIndex];
if (!playlistTracker.isSnapshotValid(selectedUrl)) {
out.playlist = selectedUrl;
// Retry when playlist is refreshed.
return;
}
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
// Select the chunk.
int chunkMediaSequence;
if (previous == null || switchingVariant) {
long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs;
if (!mediaPlaylist.hasEndTag && targetPositionUs > mediaPlaylist.getEndTimeUs()) {
// 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 - 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
// behind the live window.
selectedVariantIndex = oldVariantIndex;
selectedUrl = variants[selectedVariantIndex];
mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
chunkMediaSequence = previous.getNextChunkIndex();
}
}
} else {
chunkMediaSequence = previous.getNextChunkIndex();
}
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
fatalError = new BehindLiveWindowException();
return;
}
int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence;
if (chunkIndex >= mediaPlaylist.segments.size()) {
if (mediaPlaylist.hasEndTag) {
out.endOfStream = true;
} else /* Live */
{
out.playlist = selectedUrl;
}
return;
}
// Handle encryption.
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
// Check if encryption is specified.
if (segment.isEncrypted) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed.
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex, trackSelection.getSelectionReason(), trackSelection.getSelectionData());
return;
}
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
setEncryptionData(keyUri, segment.encryptionIV, encryptionKey);
}
} else {
clearEncryptionData();
}
DataSpec initDataSpec = null;
Segment initSegment = mediaPlaylist.initializationSegment;
if (initSegment != null) {
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset, initSegment.byterangeLength, null);
}
// Compute start time of the next chunk.
long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
int discontinuitySequence = mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence;
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(discontinuitySequence);
// 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(mediaDataSource, dataSpec, initDataSpec, selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv);
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class HlsMediaChunk method loadMedia.
private void loadMedia() throws IOException, InterruptedException {
// If we previously fed part of this chunk to the extractor, we need to skip it this time. For
// encrypted content we need to skip the data by reading it through the source, so as to ensure
// correct decryption of the remainder of the chunk. For clear content, we can request the
// remainder of the chunk directly.
DataSpec loadDataSpec;
boolean skipLoadedBytes;
if (isEncrypted) {
loadDataSpec = dataSpec;
skipLoadedBytes = bytesLoaded != 0;
} else {
loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded);
skipLoadedBytes = false;
}
if (!isMasterTimestampSource) {
timestampAdjuster.waitUntilInitialized();
} else if (timestampAdjuster.getFirstSampleTimestampUs() == TimestampAdjuster.DO_NOT_OFFSET) {
// We're the master and we haven't set the desired first sample timestamp yet.
timestampAdjuster.setFirstSampleTimestampUs(startTimeUs);
}
try {
ExtractorInput input = new DefaultExtractorInput(dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (extractor == null) {
// Media segment format is packed audio.
long id3Timestamp = peekId3PrivTimestamp(input);
extractor = buildPackedAudioExtractor(id3Timestamp != C.TIME_UNSET ? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
}
if (skipLoadedBytes) {
input.skipFully(bytesLoaded);
}
try {
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
result = extractor.read(input, null);
}
} finally {
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
}
} finally {
Util.closeQuietly(dataSource);
}
loadCompleted = true;
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class HlsPlaylistTracker method getLoadedPlaylistStartTimeUs.
private long getLoadedPlaylistStartTimeUs(HlsMediaPlaylist oldPlaylist, HlsMediaPlaylist loadedPlaylist) {
if (loadedPlaylist.hasProgramDateTime) {
return loadedPlaylist.startTimeUs;
}
long primarySnapshotStartTimeUs = primaryUrlSnapshot != null ? primaryUrlSnapshot.startTimeUs : 0;
if (oldPlaylist == null) {
return primarySnapshotStartTimeUs;
}
int oldPlaylistSize = oldPlaylist.segments.size();
Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist);
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;
}
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class DefaultDashChunkSource method getNextChunk.
@Override
public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) {
if (fatalError != null) {
return;
}
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
trackSelection.updateSelectedTrack(bufferedDurationUs);
RepresentationHolder representationHolder = representationHolders[trackSelection.getSelectedIndex()];
Representation selectedRepresentation = representationHolder.representation;
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
RangedUri pendingInitializationUri = null;
RangedUri pendingIndexUri = null;
if (representationHolder.extractorWrapper.getSampleFormats() == null) {
pendingInitializationUri = selectedRepresentation.getInitializationUri();
}
if (segmentIndex == null) {
pendingIndexUri = selectedRepresentation.getIndexUri();
}
if (pendingInitializationUri != null || pendingIndexUri != null) {
// We have initialization and/or index requests to make.
out.chunk = newInitializationChunk(representationHolder, dataSource, trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri);
return;
}
long nowUs = getNowUnixTimeUs();
int availableSegmentCount = representationHolder.getSegmentCount();
if (availableSegmentCount == 0) {
// The index doesn't define any segments.
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
return;
}
int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
int lastAvailableSegmentNum;
if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000;
long periodStartUs = manifest.getPeriod(periodIndex).startMs * 1000;
long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
long bufferDepthUs = manifest.timeShiftBufferDepth * 1000;
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum, representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
} else {
lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
}
int segmentNum;
if (previous == null) {
segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs), firstAvailableSegmentNum, lastAvailableSegmentNum);
} else {
segmentNum = previous.getNextChunkIndex();
if (segmentNum < firstAvailableSegmentNum) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
}
}
if (segmentNum > lastAvailableSegmentNum || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
// This is beyond the last chunk in the current manifest.
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
return;
}
int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
out.chunk = newMediaChunk(representationHolder, dataSource, trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segmentNum, maxSegmentCount);
}
Aggregations