use of androidx.media3.exoplayer.source.chunk.MediaChunk in project media by androidx.
the class AdaptiveTrackSelection method updateSelectedTrack.
@Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs, long availableDurationUs, List<? extends MediaChunk> queue, MediaChunkIterator[] mediaChunkIterators) {
long nowMs = clock.elapsedRealtime();
long chunkDurationUs = getNextChunkDurationUs(mediaChunkIterators, queue);
// Make initial selection
if (reason == C.SELECTION_REASON_UNKNOWN) {
reason = C.SELECTION_REASON_INITIAL;
selectedIndex = determineIdealSelectedIndex(nowMs, chunkDurationUs);
return;
}
int previousSelectedIndex = selectedIndex;
@C.SelectionReason int previousReason = reason;
int formatIndexOfPreviousChunk = queue.isEmpty() ? C.INDEX_UNSET : indexOf(Iterables.getLast(queue).trackFormat);
if (formatIndexOfPreviousChunk != C.INDEX_UNSET) {
previousSelectedIndex = formatIndexOfPreviousChunk;
previousReason = Iterables.getLast(queue).trackSelectionReason;
}
int newSelectedIndex = determineIdealSelectedIndex(nowMs, chunkDurationUs);
if (!isBlacklisted(previousSelectedIndex, nowMs)) {
// Revert back to the previous selection if conditions are not suitable for switching.
Format currentFormat = getFormat(previousSelectedIndex);
Format selectedFormat = getFormat(newSelectedIndex);
long minDurationForQualityIncreaseUs = minDurationForQualityIncreaseUs(availableDurationUs, chunkDurationUs);
if (selectedFormat.bitrate > currentFormat.bitrate && bufferedDurationUs < minDurationForQualityIncreaseUs) {
// The selected track is a higher quality, but we have insufficient buffer to safely switch
// up. Defer switching up for now.
newSelectedIndex = previousSelectedIndex;
} else if (selectedFormat.bitrate < currentFormat.bitrate && bufferedDurationUs >= maxDurationForQualityDecreaseUs) {
// The selected track is a lower quality, but we have sufficient buffer to defer switching
// down for now.
newSelectedIndex = previousSelectedIndex;
}
}
// If we adapted, update the trigger.
reason = newSelectedIndex == previousSelectedIndex ? previousReason : C.SELECTION_REASON_ADAPTIVE;
selectedIndex = newSelectedIndex;
}
use of androidx.media3.exoplayer.source.chunk.MediaChunk in project media by androidx.
the class AdaptiveTrackSelection method evaluateQueueSize.
@Override
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
long nowMs = clock.elapsedRealtime();
if (!shouldEvaluateQueueSize(nowMs, queue)) {
return queue.size();
}
lastBufferEvaluationMs = nowMs;
lastBufferEvaluationMediaChunk = queue.isEmpty() ? null : Iterables.getLast(queue);
if (queue.isEmpty()) {
return 0;
}
int queueSize = queue.size();
MediaChunk lastChunk = queue.get(queueSize - 1);
long playoutBufferedDurationBeforeLastChunkUs = Util.getPlayoutDurationForMediaDuration(lastChunk.startTimeUs - playbackPositionUs, playbackSpeed);
long minDurationToRetainAfterDiscardUs = getMinDurationToRetainAfterDiscardUs();
if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) {
return queueSize;
}
int idealSelectedIndex = determineIdealSelectedIndex(nowMs, getLastChunkDurationUs(queue));
Format idealFormat = getFormat(idealSelectedIndex);
// are less than or equal to maxWidthToDiscard and maxHeightToDiscard respectively.
for (int i = 0; i < queueSize; i++) {
MediaChunk chunk = queue.get(i);
Format format = chunk.trackFormat;
long mediaDurationBeforeThisChunkUs = chunk.startTimeUs - playbackPositionUs;
long playoutDurationBeforeThisChunkUs = Util.getPlayoutDurationForMediaDuration(mediaDurationBeforeThisChunkUs, playbackSpeed);
if (playoutDurationBeforeThisChunkUs >= minDurationToRetainAfterDiscardUs && format.bitrate < idealFormat.bitrate && format.height != Format.NO_VALUE && format.height <= maxHeightToDiscard && format.width != Format.NO_VALUE && format.width <= maxWidthToDiscard && format.height < idealFormat.height) {
return i;
}
}
return queueSize;
}
use of androidx.media3.exoplayer.source.chunk.MediaChunk in project media by androidx.
the class ChunkSampleStream method seekToUs.
/**
* Seeks to the specified position in microseconds.
*
* @param positionUs The seek position in microseconds.
*/
public void seekToUs(long positionUs) {
lastSeekPositionUs = positionUs;
if (isPendingReset()) {
// A reset is already pending. We only need to update its position.
pendingResetPositionUs = positionUs;
return;
}
// Detect whether the seek is to the start of a chunk that's at least partially buffered.
@Nullable BaseMediaChunk seekToMediaChunk = null;
for (int i = 0; i < mediaChunks.size(); i++) {
BaseMediaChunk mediaChunk = mediaChunks.get(i);
long mediaChunkStartTimeUs = mediaChunk.startTimeUs;
if (mediaChunkStartTimeUs == positionUs && mediaChunk.clippedStartTimeUs == C.TIME_UNSET) {
seekToMediaChunk = mediaChunk;
break;
} else if (mediaChunkStartTimeUs > positionUs) {
// We're not going to find a chunk with a matching start time.
break;
}
}
// See if we can seek inside the primary sample queue.
boolean seekInsideBuffer;
if (seekToMediaChunk != null) {
// When seeking to the start of a chunk we use the index of the first sample in the chunk
// rather than the seek position. This ensures we seek to the keyframe at the start of the
// chunk even if its timestamp is slightly earlier than the advertised chunk start time.
seekInsideBuffer = primarySampleQueue.seekTo(seekToMediaChunk.getFirstSampleIndex(0));
} else {
seekInsideBuffer = primarySampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */
positionUs < getNextLoadPositionUs());
}
if (seekInsideBuffer) {
// We can seek inside the buffer.
nextNotifyPrimaryFormatMediaChunkIndex = primarySampleIndexToMediaChunkIndex(primarySampleQueue.getReadIndex(), /* minChunkIndex= */
0);
// Seek the embedded sample queues.
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */
true);
}
} else {
// We can't seek inside the buffer, and so need to reset.
pendingResetPositionUs = positionUs;
loadingFinished = false;
mediaChunks.clear();
nextNotifyPrimaryFormatMediaChunkIndex = 0;
if (loader.isLoading()) {
// Discard as much as we can synchronously.
primarySampleQueue.discardToEnd();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.discardToEnd();
}
loader.cancelLoading();
} else {
loader.clearFatalError();
resetSampleQueues();
}
}
}
use of androidx.media3.exoplayer.source.chunk.MediaChunk in project media by androidx.
the class DefaultDashChunkSource method getNextChunk.
@Override
public void getNextChunk(long playbackPositionUs, long loadPositionUs, List<? extends MediaChunk> queue, ChunkHolder out) {
if (fatalError != null) {
return;
}
long bufferedDurationUs = loadPositionUs - playbackPositionUs;
long presentationPositionUs = Util.msToUs(manifest.availabilityStartTimeMs) + Util.msToUs(manifest.getPeriod(periodIndex).startMs) + loadPositionUs;
if (playerTrackEmsgHandler != null && playerTrackEmsgHandler.maybeRefreshManifestBeforeLoadingNextChunk(presentationPositionUs)) {
return;
}
long nowUnixTimeUs = Util.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs));
long nowPeriodTimeUs = getNowPeriodTimeUs(nowUnixTimeUs);
MediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1);
MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()];
for (int i = 0; i < chunkIterators.length; i++) {
RepresentationHolder representationHolder = representationHolders[i];
if (representationHolder.segmentIndex == null) {
chunkIterators[i] = MediaChunkIterator.EMPTY;
} else {
long firstAvailableSegmentNum = representationHolder.getFirstAvailableSegmentNum(nowUnixTimeUs);
long lastAvailableSegmentNum = representationHolder.getLastAvailableSegmentNum(nowUnixTimeUs);
long segmentNum = getSegmentNum(representationHolder, previous, loadPositionUs, firstAvailableSegmentNum, lastAvailableSegmentNum);
if (segmentNum < firstAvailableSegmentNum) {
chunkIterators[i] = MediaChunkIterator.EMPTY;
} else {
representationHolder = updateSelectedBaseUrl(/* trackIndex= */
i);
chunkIterators[i] = new RepresentationSegmentIterator(representationHolder, segmentNum, lastAvailableSegmentNum, nowPeriodTimeUs);
}
}
}
long availableLiveDurationUs = getAvailableLiveDurationUs(nowUnixTimeUs, playbackPositionUs);
trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, availableLiveDurationUs, queue, chunkIterators);
RepresentationHolder representationHolder = updateSelectedBaseUrl(trackSelection.getSelectedIndex());
if (representationHolder.chunkExtractor != null) {
Representation selectedRepresentation = representationHolder.representation;
@Nullable RangedUri pendingInitializationUri = null;
@Nullable RangedUri pendingIndexUri = null;
if (representationHolder.chunkExtractor.getSampleFormats() == null) {
pendingInitializationUri = selectedRepresentation.getInitializationUri();
}
if (representationHolder.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 periodDurationUs = representationHolder.periodDurationUs;
boolean periodEnded = periodDurationUs != C.TIME_UNSET;
if (representationHolder.getSegmentCount() == 0) {
// The index doesn't define any segments.
out.endOfStream = periodEnded;
return;
}
long firstAvailableSegmentNum = representationHolder.getFirstAvailableSegmentNum(nowUnixTimeUs);
long lastAvailableSegmentNum = representationHolder.getLastAvailableSegmentNum(nowUnixTimeUs);
long segmentNum = getSegmentNum(representationHolder, previous, loadPositionUs, firstAvailableSegmentNum, lastAvailableSegmentNum);
if (segmentNum < firstAvailableSegmentNum) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
}
if (segmentNum > lastAvailableSegmentNum || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
// The segment is beyond the end of the period.
out.endOfStream = periodEnded;
return;
}
if (periodEnded && representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) {
// The period duration clips the period to a position before the segment.
out.endOfStream = true;
return;
}
int maxSegmentCount = (int) min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
if (periodDurationUs != C.TIME_UNSET) {
while (maxSegmentCount > 1 && representationHolder.getSegmentStartTimeUs(segmentNum + maxSegmentCount - 1) >= periodDurationUs) {
// The period duration clips the period to a position before the last segment in the range
// [segmentNum, segmentNum + maxSegmentCount - 1]. Reduce maxSegmentCount.
maxSegmentCount--;
}
}
long seekTimeUs = queue.isEmpty() ? loadPositionUs : C.TIME_UNSET;
out.chunk = newMediaChunk(representationHolder, dataSource, trackType, trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segmentNum, maxSegmentCount, seekTimeUs, nowPeriodTimeUs);
}
use of androidx.media3.exoplayer.source.chunk.MediaChunk in project media by androidx.
the class DefaultDashChunkSource method onChunkLoadError.
@Override
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
if (!cancelable) {
return false;
}
if (playerTrackEmsgHandler != null && playerTrackEmsgHandler.onChunkLoadError(chunk)) {
return true;
}
// Workaround for missing segment at the end of the period
if (!manifest.dynamic && chunk instanceof MediaChunk && loadErrorInfo.exception instanceof InvalidResponseCodeException && ((InvalidResponseCodeException) loadErrorInfo.exception).responseCode == 404) {
RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(chunk.trackFormat)];
long segmentCount = representationHolder.getSegmentCount();
if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED && segmentCount != 0) {
long lastAvailableSegmentNum = representationHolder.getFirstSegmentNum() + segmentCount - 1;
if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
missingLastSegment = true;
return true;
}
}
}
int trackIndex = trackSelection.indexOf(chunk.trackFormat);
RepresentationHolder representationHolder = representationHolders[trackIndex];
@Nullable BaseUrl newBaseUrl = baseUrlExclusionList.selectBaseUrl(representationHolder.representation.baseUrls);
if (newBaseUrl != null && !representationHolder.selectedBaseUrl.equals(newBaseUrl)) {
// which will use the new base URL.
return true;
}
LoadErrorHandlingPolicy.FallbackOptions fallbackOptions = createFallbackOptions(trackSelection, representationHolder.representation.baseUrls);
if (!fallbackOptions.isFallbackAvailable(LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK) && !fallbackOptions.isFallbackAvailable(LoadErrorHandlingPolicy.FALLBACK_TYPE_LOCATION)) {
return false;
}
@Nullable LoadErrorHandlingPolicy.FallbackSelection fallbackSelection = loadErrorHandlingPolicy.getFallbackSelectionFor(fallbackOptions, loadErrorInfo);
if (fallbackSelection == null || !fallbackOptions.isFallbackAvailable(fallbackSelection.type)) {
// Policy indicated to not use any fallback or a fallback type that is not available.
return false;
}
boolean cancelLoad = false;
if (fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK) {
cancelLoad = trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), fallbackSelection.exclusionDurationMs);
} else if (fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_LOCATION) {
baseUrlExclusionList.exclude(representationHolder.selectedBaseUrl, fallbackSelection.exclusionDurationMs);
cancelLoad = true;
}
return cancelLoad;
}
Aggregations