@Override public TrackOutput track(int id) { DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator); sampleQueues.put(id, sampleQueue); return sampleQueue; }
/** * True if at least one sample can be read from the queue. False otherwise. */ public boolean isEmpty() { return !advanceToEligibleSample(); }
/** * Clears queues for all tracks, returning all allocations to the allocator. */ public void clear() { for (int i = 0; i < sampleQueues.size(); i++) { sampleQueues.valueAt(i).clear(); } }
@Override public void seekToUs(long positionUs) { Assertions.checkState(state == STATE_ENABLED); long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs; downstreamPositionUs = positionUs; lastSeekPositionUs = positionUs; if (currentPositionUs == positionUs) { return; } // If we're not pending a reset, see if we can seek within the sample queue. boolean seekInsideBuffer = !isPendingReset() && sampleQueue.skipToKeyframeBefore(positionUs); if (seekInsideBuffer) { // We succeeded. All we need to do is discard any chunks that we've moved past. boolean haveSamples = !sampleQueue.isEmpty(); while (haveSamples && mediaChunks.size() > 1 && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { mediaChunks.removeFirst(); } } else { // We failed, and need to restart. restartFrom(positionUs); } // Either way, we need to send a discontinuity to the downstream components. pendingDiscontinuity = true; }
boolean haveSamples = !sampleQueue.isEmpty(); BaseMediaChunk currentChunk = mediaChunks.getFirst(); while (haveSamples && mediaChunks.size() > 1 && mediaChunks.get(1).getFirstSampleIndex() == sampleQueue.getReadIndex()) { mediaChunks.removeFirst(); currentChunk = mediaChunks.getFirst(); if (sampleQueue.getSample(sampleHolder)) { boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs; sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0;
/** * Whether the extractor is prepared. * * @return True if the extractor is prepared. False otherwise. */ public boolean isPrepared() { if (!prepared && tracksBuilt) { for (int i = 0; i < sampleQueues.size(); i++) { if (!sampleQueues.valueAt(i).hasFormat()) { return false; } } prepared = true; sampleQueueFormats = new MediaFormat[sampleQueues.size()]; for (int i = 0; i < sampleQueueFormats.length; i++) { MediaFormat format = sampleQueues.valueAt(i).getFormat(); if (MimeTypes.isVideo(format.mimeType) && (adaptiveMaxWidth != MediaFormat.NO_VALUE || adaptiveMaxHeight != MediaFormat.NO_VALUE)) { format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight); } sampleQueueFormats[i] = format; } } return prepared; }
/** * Gets the largest timestamp of any sample parsed by the extractor. * * @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed. */ public long getLargestParsedTimestampUs() { long largestParsedTimestampUs = Long.MIN_VALUE; for (int i = 0; i < sampleQueues.size(); i++) { largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sampleQueues.valueAt(i).getLargestParsedTimestampUs()); } return largestParsedTimestampUs; }
/** * Discards samples for the specified track up to the specified time. * <p> * This method must only be called after the extractor has been prepared. * * @param track The track from which samples should be discarded. * @param timeUs The time up to which samples should be discarded, in microseconds. */ public void discardUntil(int track, long timeUs) { Assertions.checkState(isPrepared()); sampleQueues.valueAt(track).discardUntil(timeUs); }
/** * Gets the next sample for the specified track. * <p> * This method must only be called after the extractor has been prepared. * * @param track The track from which to read. * @param holder A {@link SampleHolder} into which the sample should be read. * @return True if a sample was read. False otherwise. */ public boolean getSample(int track, SampleHolder holder) { Assertions.checkState(isPrepared()); return sampleQueues.valueAt(track).getSample(holder); }
/** * Discard upstream media chunks until the queue length is equal to the length specified. * * @param queueLength The desired length of the queue. * @return True if chunks were discarded. False otherwise. */ private boolean discardUpstreamMediaChunks(int queueLength) { if (mediaChunks.size() <= queueLength) { return false; } long startTimeUs = 0; long endTimeUs = mediaChunks.getLast().endTimeUs; BaseMediaChunk removed = null; while (mediaChunks.size() > queueLength) { removed = mediaChunks.removeLast(); startTimeUs = removed.startTimeUs; } sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex()); notifyUpstreamDiscarded(startTimeUs, endTimeUs); return true; }
DefaultTrackOutput currentSampleQueue = sampleQueues.valueAt(i); DefaultTrackOutput nextSampleQueue = nextExtractor.sampleQueues.valueAt(i); spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue);
@Override public long getBufferedPositionUs() { Assertions.checkState(state == STATE_ENABLED); if (isPendingReset()) { return pendingResetPositionUs; } else if (loadingFinished) { return TrackRenderer.END_OF_TRACK_US; } else { long largestParsedTimestampUs = sampleQueue.getLargestParsedTimestampUs(); return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs : largestParsedTimestampUs; } }
private void restartFrom(long positionUs) { pendingResetPositionUs = positionUs; loadingFinished = false; if (loader.isLoading()) { loader.cancelLoading(); } else { sampleQueue.clear(); mediaChunks.clear(); clearCurrentLoadable(); updateLoadControl(); } }
public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, int bufferSizeContribution, Handler eventHandler, EventListener eventListener, int eventSourceId, int minLoadableRetryCount) { this.chunkSource = chunkSource; this.loadControl = loadControl; this.bufferSizeContribution = bufferSizeContribution; this.eventHandler = eventHandler; this.eventListener = eventListener; this.eventSourceId = eventSourceId; this.minLoadableRetryCount = minLoadableRetryCount; currentLoadableHolder = new ChunkOperationHolder(); mediaChunks = new LinkedList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); sampleQueue = new DefaultTrackOutput(loadControl.getAllocator()); state = STATE_IDLE; pendingResetPositionUs = NO_RESET_PENDING; }
/** * Removes the next sample from the head of the queue, writing it into the provided holder. * <p> * The first sample returned is guaranteed to be a keyframe, since any non-keyframe samples * queued prior to the first keyframe are discarded. * * @param holder A {@link SampleHolder} into which the sample should be read. * @return True if a sample was read. False otherwise. */ public boolean getSample(SampleHolder holder) { boolean foundEligibleSample = advanceToEligibleSample(); if (!foundEligibleSample) { return false; } // Write the sample into the holder. rollingBuffer.readSample(holder); needKeyframe = false; lastReadTimeUs = holder.timeUs; return true; }
@Override public void onLoadCanceled(Loadable loadable) { Chunk currentLoadable = currentLoadableHolder.chunk; notifyLoadCanceled(currentLoadable.bytesLoaded()); clearCurrentLoadable(); if (state == STATE_ENABLED) { restartFrom(pendingResetPositionUs); } else { sampleQueue.clear(); mediaChunks.clear(); clearCurrentLoadable(); loadControl.trimAllocator(); } }
@Override public void disable(int track) { Assertions.checkState(state == STATE_ENABLED); Assertions.checkState(--enabledTrackCount == 0); state = STATE_PREPARED; try { chunkSource.disable(mediaChunks); } finally { loadControl.unregister(this); if (loader.isLoading()) { loader.cancelLoading(); } else { sampleQueue.clear(); mediaChunks.clear(); clearCurrentLoadable(); loadControl.trimAllocator(); } } }