@Override public int readData(int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder) { Assertions.checkState(mPrepared); Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED); if (mPendingDiscontinuities.get(track)) { return NOTHING_READ; } if (mTrackStates.get(track) != TRACK_STATE_FORMAT_SENT) { mSampleExtractor.getTrackMediaFormat(track, formatHolder); mTrackStates.set(track, TRACK_STATE_FORMAT_SENT); return FORMAT_READ; } mPendingSeekPositionUs = C.UNKNOWN_TIME_US; return mSampleExtractor.readSample(track, sampleHolder); }
@Override public long open(DataSpec dataSpec) throws IOException { Assertions.checkState(dataSource == null); // Choose the correct source for the scheme. String scheme = dataSpec.uri.getScheme(); if (SCHEME_FILE.equals(scheme) || TextUtils.isEmpty(scheme)) { if (dataSpec.uri.getPath().startsWith("/android_asset/")) { dataSource = assetDataSource; } else { dataSource = fileDataSource; } } else if (SCHEME_ASSET.equals(scheme)) { dataSource = assetDataSource; } else if (SCHEME_CONTENT.equals(scheme)) { dataSource = contentDataSource; } else { dataSource = httpDataSource; } // Open the source and return. return dataSource.open(dataSpec); }
@Override public boolean continueBuffering(int track, long playbackPositionUs) { Assertions.checkState(prepared); Assertions.checkState(trackEnabledStates[track]); downstreamPositionUs = playbackPositionUs; if (!extractors.isEmpty()) { discardSamplesForDisabledTracks(getCurrentExtractor(), downstreamPositionUs); } if (loadingFinished) { return true; } maybeStartLoading(); if (isPendingReset() || extractors.isEmpty()) { return false; } for (int extractorIndex = 0; extractorIndex < extractors.size(); extractorIndex++) { HlsExtractorWrapper extractor = extractors.get(extractorIndex); if (!extractor.isPrepared()) { break; } if (extractor.hasSamples(track)) { return true; } } return false; }
/** * Should be invoked after processing each child Representation element, in order to apply * consistency checks. */ public void endRepresentation() { if (!representationProtectionsSet) { if (currentRepresentationProtections != null) { Collections.sort(currentRepresentationProtections, this); } representationProtections = currentRepresentationProtections; representationProtectionsSet = true; } else { // Assert that each Representation element defines the same ContentProtection elements. if (currentRepresentationProtections == null) { Assertions.checkState(representationProtections == null); } else { Collections.sort(currentRepresentationProtections, this); Assertions.checkState(currentRepresentationProtections.equals(representationProtections)); } } currentRepresentationProtections = null; }
/** * Returns a position converging to the {@code targetGranule} to which the {@link ExtractorInput} * has to seek and then be passed for another call until -1 is return. If -1 is returned the * input is at a position which is before the start of the page before the target page and at * which it is sensible to just skip pages to the target granule and pre-roll instead of doing * another seek request. * * @param targetGranule the target granule position to seek to. * @param input the {@link ExtractorInput} to read from. * @return the position to seek the {@link ExtractorInput} to for a next call or -1 if it's close * enough to skip to the target page. * @throws IOException thrown if reading from the input fails. * @throws InterruptedException thrown if interrupted while reading from the input. */ public long getNextSeekPosition(long targetGranule, ExtractorInput input) throws IOException, InterruptedException { Assertions.checkState(audioDataLength != C.LENGTH_UNBOUNDED && totalSamples != 0); OggUtil.populatePageHeader(input, pageHeader, headerArray, false); long granuleDistance = targetGranule - pageHeader.granulePosition; if (granuleDistance <= 0 || granuleDistance > MATCH_RANGE) { // estimated position too high or too low long offset = (pageHeader.bodySize + pageHeader.headerSize) * (granuleDistance <= 0 ? 2 : 1); return input.getPosition() - offset + (granuleDistance * audioDataLength / totalSamples); } // position accepted (below target granule and within MATCH_RANGE) input.resetPeekPosition(); return -1; }
@Override public void seekToUs(long positionUs) { Assertions.checkState(prepared); Assertions.checkState(enabledTrackCount > 0); // Treat all seeks into live streams as being to t=0. positionUs = chunkSource.isLive() ? 0 : positionUs; // Ignore seeks to the current position. long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs; downstreamPositionUs = positionUs; lastSeekPositionUs = positionUs; if (currentPositionUs == positionUs) { return; } seekToInternal(positionUs); }
@Override public void disable(int track) { Assertions.checkState(prepared); Assertions.checkState(trackEnabledStates[track]); enabledTrackCount--; trackEnabledStates[track] = false; if (enabledTrackCount == 0) { downstreamPositionUs = Long.MIN_VALUE; if (loader.isLoading()) { loader.cancelLoading(); } else { clearState(); allocator.trim(0); } } }
@Override public void onLoadCompleted(Loadable loadable) { Assertions.checkState(loadable == currentLoadable); long now = SystemClock.elapsedRealtime(); long loadDurationMs = now - currentLoadStartTimeMs; chunkSource.onChunkLoadCompleted(currentLoadable); if (isTsChunk(currentLoadable)) { Assertions.checkState(currentLoadable == currentTsLoadable); previousTsLoadable = currentTsLoadable; notifyLoadCompleted(currentLoadable.bytesLoaded(), currentTsLoadable.type, currentTsLoadable.trigger, currentTsLoadable.format, currentTsLoadable.startTimeUs, currentTsLoadable.endTimeUs, now, loadDurationMs); } else { notifyLoadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type, currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs); } clearCurrentLoadable(); maybeStartLoading(); }
@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(); } } }
/** * @param userAgent The User-Agent string that should be used. * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * predicate then a {@link HttpDataSource.InvalidContentTypeException} * is thrown from {@link #open(DataSpec)}. * @param listener An optional listener. * @param cacheControl An optional {@link CacheControl} which sets all requests' * Cache-Control header. For example, you could force the network * response for all requests. */ private OkHttpDataSource(String userAgent, Predicate<String> contentTypePredicate, TransferListener listener, CacheControl cacheControl) { DmlApplication.getInstance().getBackendComponent().inject(this); Assertions.checkNotNull(callFactory); this.userAgent = Assertions.checkNotEmpty(userAgent); this.contentTypePredicate = contentTypePredicate; this.listener = listener; this.cacheControl = cacheControl; this.requestProperties = new HashMap<>(); }
public ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, boolean chunkOffsetsAreLongs) { this.stsc = stsc; this.chunkOffsets = chunkOffsets; this.chunkOffsetsAreLongs = chunkOffsetsAreLongs; chunkOffsets.setPosition(Atom.FULL_HEADER_SIZE); length = chunkOffsets.readUnsignedIntToInt(); stsc.setPosition(Atom.FULL_HEADER_SIZE); remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt(); Assertions.checkState(stsc.readInt() == 1, "first_chunk must be 1"); index = -1; }
@Override public void enable(int track, long positionUs) { Assertions.checkState(prepared); setTrackEnabledState(track, true); downstreamMediaFormats[track] = null; pendingDiscontinuities[track] = false; downstreamFormat = null; boolean wasLoadControlRegistered = loadControlRegistered; if (!loadControlRegistered) { loadControl.register(this, bufferSizeContribution); loadControlRegistered = true; } // Treat enabling of a live stream as occurring at t=0 in both of the blocks below. positionUs = chunkSource.isLive() ? 0 : positionUs; int chunkSourceTrack = chunkSourceTrackIndices[track]; if (chunkSourceTrack != -1 && chunkSourceTrack != chunkSource.getSelectedTrackIndex()) { // This is a primary track whose corresponding chunk source track is different to the one // currently selected. We need to change the selection and restart. Since other exposed tracks // may be enabled too, we need to implement the restart as a seek so that all downstream // renderers receive a discontinuity event. chunkSource.selectTrack(chunkSourceTrack); seekToInternal(positionUs); return; } if (enabledTrackCount == 1) { lastSeekPositionUs = positionUs; if (wasLoadControlRegistered && downstreamPositionUs == positionUs) { // TODO: Address [Internal: b/21743989] to remove the need for this kind of hack. // This is the first track to be enabled after preparation and the position is the same as // was passed to prepare. In this case we can avoid restarting, which would reload the same // chunks as were loaded during preparation. maybeStartLoading(); } else { downstreamPositionUs = positionUs; restartFrom(positionUs); } } }
@Override public void enable(int track, long positionUs) { Assertions.checkState(state == STATE_PREPARED); Assertions.checkState(enabledTrackCount++ == 0); state = STATE_ENABLED; chunkSource.enable(track); loadControl.register(this, bufferSizeContribution); downstreamFormat = null; downstreamMediaFormat = null; downstreamPositionUs = positionUs; lastSeekPositionUs = positionUs; pendingDiscontinuity = false; restartFrom(positionUs); }
@Override public void release() { Assertions.checkState(remainingReleaseCount > 0); if (--remainingReleaseCount == 0 && extractor != null) { extractor.release(); extractor = null; } }
@Override public long getBufferedPositionUs() { Assertions.checkState(prepared); long bufferedDurationUs = extractor.getCachedDuration(); if (bufferedDurationUs == -1) { return TrackRenderer.UNKNOWN_TIME_US; } else { long sampleTime = extractor.getSampleTime(); return sampleTime == -1 ? TrackRenderer.END_OF_TRACK_US : sampleTime + bufferedDurationUs; } }
@Override public void close() throws IOException { Assertions.checkState(opened); opened = false; if (currentSegmentIndex < segments.size()) { Segment current = segments.get(currentSegmentIndex); if (current.isErrorSegment() && current.exceptionThrown) { current.exceptionCleared = true; } } }
@Override public int readSample(int track, SampleHolder outSample) { Assertions.checkState(mTrackSelected[track]); maybeReadSample(mReadSampleQueues.get(track), track); int result = mReadSampleQueues.get(track).dequeueSample(outSample); if ((result != SampleSource.SAMPLE_READ && mEos) || mError) { return SampleSource.END_OF_STREAM; } return result; }
@Override public void clearRequestProperty(String name) { Assertions.checkNotNull(name); synchronized (requestProperties) { requestProperties.remove(name); } }
/** * Invoked to indicate that a NAL unit has started. * * @param type The type of the NAL unit. */ public void startNalUnit(int type) { Assertions.checkState(!isFilling); isFilling = type == targetType; if (isFilling) { // Skip the three byte start code when writing data. nalLength = 3; isCompleted = false; } }
@Override public void enable(int track, long positionUs) { Assertions.checkState(prepared); Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED); trackStates[track] = TRACK_STATE_ENABLED; extractor.selectTrack(track); seekToUsInternal(positionUs, positionUs != 0); }
@Override public DataSink open(DataSpec dataSpec) throws IOException { if (dataSpec.length == C.LENGTH_UNBOUNDED) { stream = new ByteArrayOutputStream(); } else { Assertions.checkArgument(dataSpec.length <= Integer.MAX_VALUE); stream = new ByteArrayOutputStream((int) dataSpec.length); } return this; }
@Override public void disable(int track) { Assertions.checkState(prepared); Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); extractor.unselectTrack(track); pendingDiscontinuities[track] = false; trackStates[track] = TRACK_STATE_DISABLED; }
/** * Start a parsing operation. * <p> * The holder returned by {@link #getSampleHolder()} should be populated with the data to be * parsed prior to calling this method. */ public synchronized void startParseOperation() { Assertions.checkState(!parsing); parsing = true; result = null; error = null; runtimeError = null; handler.obtainMessage(MSG_SAMPLE, Util.getTopInt(sampleHolder.timeUs), Util.getBottomInt(sampleHolder.timeUs), sampleHolder).sendToTarget(); }
/** * @param id The format identifier. * @param mimeType The format mime type. * @param width The width of the video in pixels, or -1 if unknown or not applicable. * @param height The height of the video in pixels, or -1 if unknown or not applicable. * @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not * applicable. * @param audioChannels The number of audio channels, or -1 if unknown or not applicable. * @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable. * @param bitrate The average bandwidth of the format in bits per second. * @param language The language of the format. * @param codecs The codecs used to decode the format. */ public Format(String id, String mimeType, int width, int height, float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language, String codecs) { this.id = Assertions.checkNotNull(id); this.mimeType = mimeType; this.width = width; this.height = height; this.frameRate = frameRate; this.audioChannels = audioChannels; this.audioSamplingRate = audioSamplingRate; this.bitrate = bitrate; this.language = language; this.codecs = codecs; }
@Override public long open(DataSpec dataSpec) throws IOException { Assertions.checkState(!opened); // DataSpec requires a matching close call even if open fails. opened = true; // If the source knows that the request is unsatisfiable then fail. if (dataSpec.position >= totalLength) { throw new IOException("Unsatisfiable position"); } else if (dataSpec.length != C.LENGTH_UNBOUNDED && dataSpec.position + dataSpec.length >= totalLength) { throw new IOException("Unsatisfiable range"); } // Scan through the segments, configuring them for the current read. boolean findingCurrentSegmentIndex = true; currentSegmentIndex = 0; int scannedLength = 0; for (Segment segment : segments) { segment.bytesRead = (int) Math.min(Math.max(0, dataSpec.position - scannedLength), segment.length); scannedLength += segment.length; findingCurrentSegmentIndex &= segment.isErrorSegment() ? segment.exceptionCleared : segment.bytesRead == segment.length; if (findingCurrentSegmentIndex) { currentSegmentIndex++; } } // Configure bytesRemaining, and return. if (dataSpec.length == C.LENGTH_UNBOUNDED) { bytesRemaining = totalLength - dataSpec.position; return simulateUnknownLength ? C.LENGTH_UNBOUNDED : bytesRemaining; } else { bytesRemaining = dataSpec.length; return bytesRemaining; } }
@Override public void release() { Assertions.checkState(remainingReleaseCount > 0); if (--remainingReleaseCount == 0 && loader != null) { loader.release(); loader = null; } }
/** * Skips to the last Ogg page in the stream and reads the header's granule field which is the * total number of samples per channel. * * @param input The {@link ExtractorInput} to read from. * @return the total number of samples of this input. * @throws IOException thrown if reading from the input fails. * @throws InterruptedException thrown if interrupted while reading from the input. */ public long readGranuleOfLastPage(ExtractorInput input) throws IOException, InterruptedException { Assertions.checkArgument(input.getLength() != C.LENGTH_UNBOUNDED); // never read forever! OggUtil.skipToNextPage(input); pageHeader.reset(); while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < input.getLength()) { OggUtil.populatePageHeader(input, pageHeader, headerArray, false); input.skipFully(pageHeader.headerSize + pageHeader.bodySize); } return pageHeader.granulePosition; }
@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 TtmlNode(String tag, String text, long startTimeUs, long endTimeUs, TtmlStyle style, String[] styleIds, String regionId) { this.tag = tag; this.text = text; this.style = style; this.styleIds = styleIds; this.isTextNode = text != null; this.startTimeUs = startTimeUs; this.endTimeUs = endTimeUs; this.regionId = Assertions.checkNotNull(regionId); nodeStartsByRegion = new HashMap<>(); nodeEndsByRegion = new HashMap<>(); }
@Override public void setRequestProperty(String name, String value) { Assertions.checkNotNull(name); Assertions.checkNotNull(value); synchronized (requestProperties) { requestProperties.put(name, value); } }
@Override public boolean continueBuffering(int track, long playbackPositionUs) { Assertions.checkState(prepared); Assertions.checkState(trackEnabledStates[track]); downstreamPositionUs = playbackPositionUs; discardSamplesForDisabledTracks(downstreamPositionUs); if (loadingFinished) { return true; } maybeStartLoading(); if (isPendingReset()) { return false; } return !sampleQueues.valueAt(track).isEmpty(); }
TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) { Assertions.checkArgument(sizes.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length); this.offsets = offsets; this.sizes = sizes; this.maximumSize = maximumSize; this.timestampsUs = timestampsUs; this.flags = flags; sampleCount = offsets.length; }
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, Allocator allocator, int requestedBufferSize, long position) { this.uri = Assertions.checkNotNull(uri); this.dataSource = Assertions.checkNotNull(dataSource); this.extractorHolder = Assertions.checkNotNull(extractorHolder); this.allocator = Assertions.checkNotNull(allocator); this.requestedBufferSize = requestedBufferSize; positionHolder = new PositionHolder(); positionHolder.position = position; pendingExtractorSeek = true; }
private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs) { stsd.setPosition(Atom.FULL_HEADER_SIZE); int numberOfEntries = stsd.readInt(); StsdDataHolder holder = new StsdDataHolder(numberOfEntries); for (int i = 0; i < numberOfEntries; i++) { int childStartPosition = stsd.getPosition(); int childAtomSize = stsd.readInt(); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = stsd.readInt(); if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 || childAtomType == Atom.TYPE_s263) { parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, durationUs, holder, i); } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca || childAtomType == Atom.TYPE_ac_3) { parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs, holder, i); } else if (childAtomType == Atom.TYPE_TTML) { holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, durationUs); } else if (childAtomType == Atom.TYPE_tx3g) { holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, durationUs); } stsd.setPosition(childStartPosition + childAtomSize); } return holder; }
@Override public int read(byte[] buffer, int offset, int readLength) throws IOException { Assertions.checkState(cipherInputStream != null); int bytesRead = cipherInputStream.read(buffer, offset, readLength); if (bytesRead < 0) { return -1; } return bytesRead; }
private UtcTimingElementResolver(UriDataSource uriDataSource, UtcTimingElement timingElement, long timingElementElapsedRealtime, UtcTimingCallback callback) { this.uriDataSource = uriDataSource; this.timingElement = Assertions.checkNotNull(timingElement); this.timingElementElapsedRealtime = timingElementElapsedRealtime; this.callback = Assertions.checkNotNull(callback); }