use of com.google.android.exoplayer2.metadata.mp4.SlowMotionData.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#playlistUrl} is set to
* contain the {@link Uri} that refers to the playlist that needs refreshing.
*
* @param playbackPositionUs The current playback position relative to the period start in
* microseconds. If playback of the period to which this chunk source belongs has not yet
* started, the value will be the starting position in the period minus the duration of any
* media in previous periods still to be played.
* @param loadPositionUs The current load position relative to the period start in microseconds.
* @param queue The queue of buffered {@link HlsMediaChunk}s.
* @param allowEndOfStream Whether {@link HlsChunkHolder#endOfStream} is allowed to be set for
* non-empty media playlists. If {@code false}, the last available chunk is returned instead.
* If the media playlist is empty, {@link HlsChunkHolder#endOfStream} is always set.
* @param out A holder to populate.
*/
public void getNextChunk(long playbackPositionUs, long loadPositionUs, List<HlsMediaChunk> queue, boolean allowEndOfStream, HlsChunkHolder out) {
@Nullable HlsMediaChunk previous = queue.isEmpty() ? null : Iterables.getLast(queue);
int oldTrackIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
long bufferedDurationUs = loadPositionUs - playbackPositionUs;
long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs);
if (previous != null && !independentSegments) {
// Unless segments are known to be independent, switching tracks requires downloading
// overlapping segments. Hence we subtract the previous segment's duration from the buffered
// duration.
// This may affect the live-streaming adaptive track selection logic, when we compare the
// buffered duration to time-to-live-edge to decide whether to switch. Therefore, we subtract
// the duration of the last loaded segment from timeToLiveEdgeUs as well.
long subtractedDurationUs = previous.getDurationUs();
bufferedDurationUs = max(0, bufferedDurationUs - subtractedDurationUs);
if (timeToLiveEdgeUs != C.TIME_UNSET) {
timeToLiveEdgeUs = max(0, timeToLiveEdgeUs - subtractedDurationUs);
}
}
// Select the track.
MediaChunkIterator[] mediaChunkIterators = createMediaChunkIterators(previous, loadPositionUs);
trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators);
int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingTrack = oldTrackIndex != selectedTrackIndex;
Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) {
out.playlistUrl = selectedPlaylistUrl;
seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
expectedPlaylistUrl = selectedPlaylistUrl;
// Retry when playlist is refreshed.
return;
}
@Nullable HlsMediaPlaylist playlist = playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */
true);
// playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
checkNotNull(playlist);
independentSegments = playlist.hasIndependentSegments;
updateLiveEdgeTimeUs(playlist);
// Select the chunk.
long startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
Pair<Long, Integer> nextMediaSequenceAndPartIndex = getNextMediaSequenceAndPartIndex(previous, switchingTrack, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
long chunkMediaSequence = nextMediaSequenceAndPartIndex.first;
int partIndex = nextMediaSequenceAndPartIndex.second;
if (chunkMediaSequence < playlist.mediaSequence && previous != null && switchingTrack) {
// We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window.
selectedTrackIndex = oldTrackIndex;
selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
playlist = playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */
true);
// playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
checkNotNull(playlist);
startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
// Get the next segment/part without switching tracks.
Pair<Long, Integer> nextMediaSequenceAndPartIndexWithoutAdapting = getNextMediaSequenceAndPartIndex(previous, /* switchingTrack= */
false, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
chunkMediaSequence = nextMediaSequenceAndPartIndexWithoutAdapting.first;
partIndex = nextMediaSequenceAndPartIndexWithoutAdapting.second;
}
if (chunkMediaSequence < playlist.mediaSequence) {
fatalError = new BehindLiveWindowException();
return;
}
@Nullable SegmentBaseHolder segmentBaseHolder = getNextSegmentHolder(playlist, chunkMediaSequence, partIndex);
if (segmentBaseHolder == null) {
if (!playlist.hasEndTag) {
// Reload the playlist in case of a live stream.
out.playlistUrl = selectedPlaylistUrl;
seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
expectedPlaylistUrl = selectedPlaylistUrl;
return;
} else if (allowEndOfStream || playlist.segments.isEmpty()) {
out.endOfStream = true;
return;
}
// Use the last segment available in case of a VOD stream.
segmentBaseHolder = new SegmentBaseHolder(Iterables.getLast(playlist.segments), playlist.mediaSequence + playlist.segments.size() - 1, /* partIndex= */
C.INDEX_UNSET);
}
// We have a valid media segment, we can discard any playlist errors at this point.
seenExpectedPlaylistError = false;
expectedPlaylistUrl = null;
// Check if the media segment or its initialization segment are fully encrypted.
@Nullable Uri initSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase.initializationSegment);
out.chunk = maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex);
if (out.chunk != null) {
return;
}
@Nullable Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase);
out.chunk = maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex);
if (out.chunk != null) {
return;
}
boolean shouldSpliceIn = HlsMediaChunk.shouldSpliceIn(previous, selectedPlaylistUrl, playlist, segmentBaseHolder, startOfPlaylistInPeriodUs);
if (shouldSpliceIn && segmentBaseHolder.isPreload) {
// becomes fully available (or the track selection selects another track).
return;
}
out.chunk = HlsMediaChunk.createInstance(extractorFactory, mediaDataSource, playlistFormats[selectedTrackIndex], startOfPlaylistInPeriodUs, playlist, segmentBaseHolder, selectedPlaylistUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), isTimestampMaster, timestampAdjusterProvider, previous, /* mediaSegmentKey= */
keyCache.get(mediaSegmentKeyUri), /* initSegmentKey= */
keyCache.get(initSegmentKeyUri), shouldSpliceIn, playerId);
}
use of com.google.android.exoplayer2.metadata.mp4.SlowMotionData.Segment in project ExoPlayer by google.
the class HlsMediaChunk method createInstance.
/**
* Creates a new instance.
*
* @param extractorFactory A {@link HlsExtractorFactory} from which the {@link
* HlsMediaChunkExtractor} is obtained.
* @param dataSource The source from which the data should be loaded.
* @param format The chunk format.
* @param startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds.
* @param mediaPlaylist The media playlist from which this chunk was obtained.
* @param segmentBaseHolder The segment holder.
* @param playlistUrl The url of the playlist from which this chunk was obtained.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the multivariant playlist.
* @param trackSelectionReason See {@link #trackSelectionReason}.
* @param trackSelectionData See {@link #trackSelectionData}.
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjusterProvider The provider from which to obtain the {@link
* TimestampAdjuster}.
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
* @param mediaSegmentKey The media segment decryption key, if fully encrypted. Null otherwise.
* @param initSegmentKey The initialization segment decryption key, if fully encrypted. Null
* otherwise.
* @param shouldSpliceIn Whether samples for this chunk should be spliced into existing samples.
*/
public static HlsMediaChunk createInstance(HlsExtractorFactory extractorFactory, DataSource dataSource, Format format, long startOfPlaylistInPeriodUs, HlsMediaPlaylist mediaPlaylist, HlsChunkSource.SegmentBaseHolder segmentBaseHolder, Uri playlistUrl, @Nullable List<Format> muxedCaptionFormats, @C.SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, boolean isMasterTimestampSource, TimestampAdjusterProvider timestampAdjusterProvider, @Nullable HlsMediaChunk previousChunk, @Nullable byte[] mediaSegmentKey, @Nullable byte[] initSegmentKey, boolean shouldSpliceIn, PlayerId playerId) {
// Media segment.
HlsMediaPlaylist.SegmentBase mediaSegment = segmentBaseHolder.segmentBase;
DataSpec dataSpec = new DataSpec.Builder().setUri(UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url)).setPosition(mediaSegment.byteRangeOffset).setLength(mediaSegment.byteRangeLength).setFlags(segmentBaseHolder.isPreload ? FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED : 0).build();
boolean mediaSegmentEncrypted = mediaSegmentKey != null;
@Nullable byte[] mediaSegmentIv = mediaSegmentEncrypted ? getEncryptionIvArray(Assertions.checkNotNull(mediaSegment.encryptionIV)) : null;
DataSource mediaDataSource = buildDataSource(dataSource, mediaSegmentKey, mediaSegmentIv);
// Init segment.
HlsMediaPlaylist.Segment initSegment = mediaSegment.initializationSegment;
DataSpec initDataSpec = null;
boolean initSegmentEncrypted = false;
@Nullable DataSource initDataSource = null;
if (initSegment != null) {
initSegmentEncrypted = initSegmentKey != null;
@Nullable byte[] initSegmentIv = initSegmentEncrypted ? getEncryptionIvArray(Assertions.checkNotNull(initSegment.encryptionIV)) : null;
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
initDataSpec = new DataSpec(initSegmentUri, initSegment.byteRangeOffset, initSegment.byteRangeLength);
initDataSource = buildDataSource(dataSource, initSegmentKey, initSegmentIv);
}
long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + mediaSegment.relativeStartTimeUs;
long segmentEndTimeInPeriodUs = segmentStartTimeInPeriodUs + mediaSegment.durationUs;
int discontinuitySequenceNumber = mediaPlaylist.discontinuitySequence + mediaSegment.relativeDiscontinuitySequence;
@Nullable HlsMediaChunkExtractor previousExtractor = null;
Id3Decoder id3Decoder;
ParsableByteArray scratchId3Data;
if (previousChunk != null) {
boolean isSameInitData = initDataSpec == previousChunk.initDataSpec || (initDataSpec != null && previousChunk.initDataSpec != null && initDataSpec.uri.equals(previousChunk.initDataSpec.uri) && initDataSpec.position == previousChunk.initDataSpec.position);
boolean isFollowingChunk = playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted;
id3Decoder = previousChunk.id3Decoder;
scratchId3Data = previousChunk.scratchId3Data;
previousExtractor = isSameInitData && isFollowingChunk && !previousChunk.extractorInvalidated && previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber ? previousChunk.extractor : null;
} else {
id3Decoder = new Id3Decoder();
scratchId3Data = new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH);
}
return new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, format, mediaSegmentEncrypted, initDataSource, initDataSpec, initSegmentEncrypted, playlistUrl, muxedCaptionFormats, trackSelectionReason, trackSelectionData, segmentStartTimeInPeriodUs, segmentEndTimeInPeriodUs, segmentBaseHolder.mediaSequence, segmentBaseHolder.partIndex, /* isPublished= */
!segmentBaseHolder.isPreload, discontinuitySequenceNumber, mediaSegment.hasGapTag, isMasterTimestampSource, /* timestampAdjuster= */
timestampAdjusterProvider.getAdjuster(discontinuitySequenceNumber), mediaSegment.drmInitData, previousExtractor, id3Decoder, scratchId3Data, shouldSpliceIn, playerId);
}
use of com.google.android.exoplayer2.metadata.mp4.SlowMotionData.Segment in project ExoPlayer by google.
the class HlsMediaPlaylistParserTest method encryptedMapTagWithNoIvFails.
@Test
public void encryptedMapTagWithNoIvFails() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-TARGETDURATION:5\n" + "#EXT-X-MEDIA-SEQUENCE:10\n" + "#EXT-X-KEY:METHOD=AES-128," + "URI=\"https://priv.example.com/key.php?r=2680\"\n" + "#EXT-X-MAP:URI=\"init1.ts\"" + "#EXTINF:5.005,\n" + "02/00/32.ts\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
try {
new HlsPlaylistParser().parse(playlistUri, inputStream);
fail();
} catch (ParserException e) {
// Expected because the initialization segment does not have a defined initialization vector,
// although it is affected by an EXT-X-KEY tag.
}
}
use of com.google.android.exoplayer2.metadata.mp4.SlowMotionData.Segment in project ExoPlayer by google.
the class HlsMediaPlaylistParserTest method parseMediaPlaylist_withByteRanges.
@Test
public void parseMediaPlaylist_withByteRanges() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-TARGETDURATION:5\n" + "\n" + "#EXT-X-BYTERANGE:200@100\n" + "#EXT-X-MAP:URI=\"stream.mp4\"\n" + "#EXTINF:5,\n" + "#EXT-X-BYTERANGE:400\n" + "stream.mp4\n" + "#EXTINF:5,\n" + "#EXT-X-BYTERANGE:500\n" + "stream.mp4\n" + "#EXT-X-DISCONTINUITY\n" + "#EXT-X-MAP:URI=\"init.mp4\"\n" + "#EXTINF:5,\n" + "segment.mp4\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
List<Segment> segments = mediaPlaylist.segments;
assertThat(segments).isNotNull();
assertThat(segments).hasSize(3);
Segment segment = segments.get(0);
assertThat(segment.initializationSegment.byteRangeOffset).isEqualTo(100);
assertThat(segment.initializationSegment.byteRangeLength).isEqualTo(200);
assertThat(segment.byteRangeOffset).isEqualTo(300);
assertThat(segment.byteRangeLength).isEqualTo(400);
segment = segments.get(1);
assertThat(segment.byteRangeOffset).isEqualTo(700);
assertThat(segment.byteRangeLength).isEqualTo(500);
segment = segments.get(2);
assertThat(segment.initializationSegment.byteRangeOffset).isEqualTo(0);
assertThat(segment.initializationSegment.byteRangeLength).isEqualTo(C.LENGTH_UNSET);
assertThat(segment.byteRangeOffset).isEqualTo(0);
assertThat(segment.byteRangeLength).isEqualTo(C.LENGTH_UNSET);
}
use of com.google.android.exoplayer2.metadata.mp4.SlowMotionData.Segment in project ExoPlayer by google.
the class HlsMediaPlaylistParserTest method iframeOnly_withExplicitInitSegment_hasCorrectByteRange.
@Test
public void iframeOnly_withExplicitInitSegment_hasCorrectByteRange() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "#EXT-X-MEDIA-SEQUENCE:1616630672\n" + "#EXT-X-TARGETDURATION:7\n" + "#EXT-X-DISCONTINUITY-SEQUENCE:491 \n" + "#EXT-X-MAP:URI=\"iframe0.tsv\",BYTERANGE=\"564@0\"\n" + "\n" + "#EXT-X-I-FRAMES-ONLY\n" + "#EXT-X-PROGRAM-DATE-TIME:2021-04-12T17:08:22.000Z\n" + "#EXTINF:1.001000,\n" + "#EXT-X-BYTERANGE:121260@1128\n" + "iframe0.tsv";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist standalonePlaylist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
@Nullable Segment initSegment = standalonePlaylist.segments.get(0).initializationSegment;
assertThat(standalonePlaylist.segments).hasSize(1);
assertThat(initSegment.byteRangeLength).isEqualTo(564);
assertThat(initSegment.byteRangeOffset).isEqualTo(0);
}
Aggregations