use of com.google.android.exoplayer2.extractor.ChunkIndex 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.extractor.ChunkIndex in project ExoPlayer by google.
the class DefaultSsChunkSource method newMediaChunk.
// Private methods.
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, long chunkSeekTimeUs, @C.SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, ChunkExtractor chunkExtractor) {
DataSpec dataSpec = new DataSpec(uri);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
long sampleOffsetUs = chunkStartTimeUs;
return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason, trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkSeekTimeUs, /* clippedEndTimeUs= */
C.TIME_UNSET, chunkIndex, /* chunkCount= */
1, sampleOffsetUs, chunkExtractor);
}
use of com.google.android.exoplayer2.extractor.ChunkIndex in project ExoPlayer by google.
the class DefaultSsChunkSource method getAdjustedSeekPositionUs.
@Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
StreamElement streamElement = manifest.streamElements[streamElementIndex];
int chunkIndex = streamElement.getChunkIndex(positionUs);
long firstSyncUs = streamElement.getStartTimeUs(chunkIndex);
long secondSyncUs = firstSyncUs < positionUs && chunkIndex < streamElement.chunkCount - 1 ? streamElement.getStartTimeUs(chunkIndex + 1) : firstSyncUs;
return seekParameters.resolveSeekPositionUs(positionUs, firstSyncUs, secondSyncUs);
}
use of com.google.android.exoplayer2.extractor.ChunkIndex in project ExoPlayer by google.
the class HlsSampleStreamWrapper method readData.
public int readData(int sampleQueueIndex, FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) {
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
// TODO: Split into discard (in discardBuffer) and format change (here and in skipData) steps.
if (!mediaChunks.isEmpty()) {
int discardToMediaChunkIndex = 0;
while (discardToMediaChunkIndex < mediaChunks.size() - 1 && finishedReadingChunk(mediaChunks.get(discardToMediaChunkIndex))) {
discardToMediaChunkIndex++;
}
Util.removeRange(mediaChunks, 0, discardToMediaChunkIndex);
HlsMediaChunk currentChunk = mediaChunks.get(0);
Format trackFormat = currentChunk.trackFormat;
if (!trackFormat.equals(downstreamTrackFormat)) {
mediaSourceEventDispatcher.downstreamFormatChanged(trackType, trackFormat, currentChunk.trackSelectionReason, currentChunk.trackSelectionData, currentChunk.startTimeUs);
}
downstreamTrackFormat = trackFormat;
}
if (!mediaChunks.isEmpty() && !mediaChunks.get(0).isPublished()) {
// Don't read into preload chunks until we can be sure they are permanently published.
return C.RESULT_NOTHING_READ;
}
int result = sampleQueues[sampleQueueIndex].read(formatHolder, buffer, readFlags, loadingFinished);
if (result == C.RESULT_FORMAT_READ) {
Format format = Assertions.checkNotNull(formatHolder.format);
if (sampleQueueIndex == primarySampleQueueIndex) {
// Fill in primary sample format with information from the track format.
int chunkUid = sampleQueues[sampleQueueIndex].peekSourceId();
int chunkIndex = 0;
while (chunkIndex < mediaChunks.size() && mediaChunks.get(chunkIndex).uid != chunkUid) {
chunkIndex++;
}
Format trackFormat = chunkIndex < mediaChunks.size() ? mediaChunks.get(chunkIndex).trackFormat : Assertions.checkNotNull(upstreamTrackFormat);
format = format.withManifestFormatInfo(trackFormat);
}
formatHolder.format = format;
}
return result;
}
use of com.google.android.exoplayer2.extractor.ChunkIndex in project ExoPlayer by google.
the class HlsSampleStreamWrapper method selectTracks.
/**
* Called by the parent {@link HlsMediaPeriod} when a track selection occurs.
*
* @param selections The renderer track selections.
* @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained
* for each selection. A {@code true} value indicates that the selection is unchanged, and
* that the caller does not require that the sample stream be recreated.
* @param streams The existing sample streams, which will be updated to reflect the provided
* selections.
* @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that
* have been retained but with the requirement that the consuming renderer be reset.
* @param positionUs The current playback position in microseconds.
* @param forceReset If true then a reset is forced (i.e. a seek will be performed with in-buffer
* seeking disabled).
* @return Whether this wrapper requires the parent {@link HlsMediaPeriod} to perform a seek as
* part of the track selection.
*/
public boolean selectTracks(@NullableType ExoTrackSelection[] selections, boolean[] mayRetainStreamFlags, @NullableType SampleStream[] streams, boolean[] streamResetFlags, long positionUs, boolean forceReset) {
assertIsPrepared();
int oldEnabledTrackGroupCount = enabledTrackGroupCount;
// Deselect old tracks.
for (int i = 0; i < selections.length; i++) {
HlsSampleStream stream = (HlsSampleStream) streams[i];
if (stream != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
enabledTrackGroupCount--;
stream.unbindSampleQueue();
streams[i] = null;
}
}
// We'll always need to seek if we're being forced to reset, or if this is a first selection to
// a position other than the one we started preparing with, or if we're making a selection
// having previously disabled all tracks.
boolean seekRequired = forceReset || (seenFirstTrackSelection ? oldEnabledTrackGroupCount == 0 : positionUs != lastSeekPositionUs);
// Get the old (i.e. current before the loop below executes) primary track selection. The new
// primary selection will equal the old one unless it's changed in the loop.
ExoTrackSelection oldPrimaryTrackSelection = chunkSource.getTrackSelection();
ExoTrackSelection primaryTrackSelection = oldPrimaryTrackSelection;
// Select new tracks.
for (int i = 0; i < selections.length; i++) {
ExoTrackSelection selection = selections[i];
if (selection == null) {
continue;
}
int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup());
if (trackGroupIndex == primaryTrackGroupIndex) {
primaryTrackSelection = selection;
chunkSource.setTrackSelection(selection);
}
if (streams[i] == null) {
enabledTrackGroupCount++;
streams[i] = new HlsSampleStream(this, trackGroupIndex);
streamResetFlags[i] = true;
if (trackGroupToSampleQueueIndex != null) {
((HlsSampleStream) streams[i]).bindSampleQueue();
// If there's still a chance of avoiding a seek, try and seek within the sample queue.
if (!seekRequired) {
SampleQueue sampleQueue = sampleQueues[trackGroupToSampleQueueIndex[trackGroupIndex]];
// A seek can be avoided if we're able to seek to the current playback position in
// the sample queue, or if we haven't read anything from the queue since the previous
// seek (this case is common for sparse tracks such as metadata tracks). In all other
// cases a seek is required.
seekRequired = !sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */
true) && sampleQueue.getReadIndex() != 0;
}
}
}
}
if (enabledTrackGroupCount == 0) {
chunkSource.reset();
downstreamTrackFormat = null;
pendingResetUpstreamFormats = true;
mediaChunks.clear();
if (loader.isLoading()) {
if (sampleQueuesBuilt) {
// Discard as much as we can synchronously.
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.discardToEnd();
}
}
loader.cancelLoading();
} else {
resetSampleQueues();
}
} else {
if (!mediaChunks.isEmpty() && !Util.areEqual(primaryTrackSelection, oldPrimaryTrackSelection)) {
// The primary track selection has changed and we have buffered media. The buffered media
// may need to be discarded.
boolean primarySampleQueueDirty = false;
if (!seenFirstTrackSelection) {
long bufferedDurationUs = positionUs < 0 ? -positionUs : 0;
HlsMediaChunk lastMediaChunk = getLastMediaChunk();
MediaChunkIterator[] mediaChunkIterators = chunkSource.createMediaChunkIterators(lastMediaChunk, positionUs);
primaryTrackSelection.updateSelectedTrack(positionUs, bufferedDurationUs, C.TIME_UNSET, readOnlyMediaChunks, mediaChunkIterators);
int chunkIndex = chunkSource.getTrackGroup().indexOf(lastMediaChunk.trackFormat);
if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
// This is the first selection and the chunk loaded during preparation does not match
// the initially selected format.
primarySampleQueueDirty = true;
}
} else {
// The primary sample queue contains media buffered for the old primary track selection.
primarySampleQueueDirty = true;
}
if (primarySampleQueueDirty) {
forceReset = true;
seekRequired = true;
pendingResetUpstreamFormats = true;
}
}
if (seekRequired) {
seekToUs(positionUs, forceReset);
// We'll need to reset renderers consuming from all streams due to the seek.
for (int i = 0; i < streams.length; i++) {
if (streams[i] != null) {
streamResetFlags[i] = true;
}
}
}
}
updateSampleStreams(streams);
seenFirstTrackSelection = true;
return seekRequired;
}
Aggregations