@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; String formatId = initializationChunk.format.id; RepresentationHolder representationHolder = representationHolders.get(formatId); if (initializationChunk.hasFormat()) { representationHolder.format = initializationChunk.getFormat(); } if (initializationChunk.hasSeekMap()) { representationHolder.segmentIndex = new DashWrappingSegmentIndex( (ChunkIndex) initializationChunk.getSeekMap(), initializationChunk.dataSpec.uri.toString(), representationHolder.representation.periodStartMs * 1000); } // The null check avoids overwriting drmInitData obtained from the manifest with drmInitData // obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3. if (drmInitData == null && initializationChunk.hasDrmInitData()) { drmInitData = initializationChunk.getDrmInitData(); } } }
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, int trigger) { RangedUri requestUri; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. requestUri = initializationUri.attemptMerge(indexUri); if (requestUri == null) { requestUri = initializationUri; } } else { requestUri = indexUri; } DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, representation.getCacheKey()); return new InitializationChunk(dataSource, dataSpec, trigger, representation.format, extractor); }
/** * Invoked when the {@link HlsSampleSource} has finished loading a chunk obtained from this * source. * * @param chunk The chunk whose load has been completed. */ public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof MediaPlaylistChunk) { MediaPlaylistChunk mediaPlaylistChunk = (MediaPlaylistChunk) chunk; scratchSpace = mediaPlaylistChunk.getDataHolder(); setMediaPlaylist(mediaPlaylistChunk.variantIndex, mediaPlaylistChunk.getResult()); if (eventHandler != null && eventListener != null) { final byte[] rawResponse = mediaPlaylistChunk.getRawResponse(); eventHandler.post(new Runnable() { @Override public void run() { eventListener.onMediaPlaylistLoadCompleted(rawResponse); } }); } } else if (chunk instanceof EncryptionKeyChunk) { EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk; scratchSpace = encryptionKeyChunk.getDataHolder(); setEncryptionData(encryptionKeyChunk.dataSpec.uri, encryptionKeyChunk.iv, encryptionKeyChunk.getResult()); } }
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, int manifestIndex, int trigger) { RangedUri requestUri; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. requestUri = initializationUri.attemptMerge(indexUri); if (requestUri == null) { requestUri = initializationUri; } } else { requestUri = indexUri; } DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, representation.getCacheKey()); return new InitializationChunk(dataSource, dataSpec, trigger, representation.format, extractor, manifestIndex); }
protected Chunk newMediaChunk( PeriodHolder periodHolder, RepresentationHolder representationHolder, DataSource dataSource, MediaFormat mediaFormat, ExposedTrack enabledTrack, int segmentNum, int trigger) { Representation representation = representationHolder.representation; Format format = representation.format; long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum); long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum); RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs; if (mimeTypeIsRawText(format.mimeType)) { return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format, startTimeUs, endTimeUs, segmentNum, enabledTrack.trackFormat, null, periodHolder.localIndex); } else { boolean isMediaFormatFinal = (mediaFormat != null); return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, mediaFormat, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight, periodHolder.drmInitData, isMediaFormatFinal, periodHolder.localIndex); } }
/** * Invoked when the {@link HlsSampleSource} has finished loading a chunk obtained from this * source. * * @param chunk The chunk whose load has been completed. */ public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof MediaPlaylistChunk) { MediaPlaylistChunk mediaPlaylistChunk = (MediaPlaylistChunk) chunk; scratchSpace = mediaPlaylistChunk.getDataHolder(); setMediaPlaylist(mediaPlaylistChunk.variantIndex, mediaPlaylistChunk.getResult()); } else if (chunk instanceof EncryptionKeyChunk) { EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk; scratchSpace = encryptionKeyChunk.getDataHolder(); setEncryptionData(encryptionKeyChunk.dataSpec.uri, encryptionKeyChunk.iv, encryptionKeyChunk.getResult()); } }
public MediaPlaylistChunk(DataSource dataSource, DataSpec dataSpec, byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex, String playlistUrl) { super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, null, scratchSpace); this.variantIndex = variantIndex; this.playlistParser = playlistParser; this.playlistUrl = playlistUrl; }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, int segmentNum, int trigger) { Representation representation = representationHolder.representation; DashSegmentIndex segmentIndex = representationHolder.segmentIndex; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = startTimeUs + segmentIndex.getDurationUs(segmentNum); int absoluteSegmentNum = segmentNum + representationHolder.segmentNumShift; boolean isLastSegment = !currentManifest.dynamic && segmentNum == segmentIndex.getLastSegmentNum(); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long sampleOffsetUs = representation.periodStartMs * 1000 - representation.presentationTimeOffsetUs; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { if (representationHolder.vttHeaderOffsetUs != sampleOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(C.WEBVTT_EXO_HEADER).append("=") .append(C.WEBVTT_EXO_HEADER_OFFSET).append(sampleOffsetUs) .append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); representationHolder.vttHeaderOffsetUs = sampleOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader); } else { return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs, representationHolder.extractorWrapper, representationHolder.format, drmInitData, true); } }
public MediaPlaylistChunk(DataSource dataSource, DataSpec dataSpec, byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex, String playlistUrl) { super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, null, Chunk.NO_PARENT_ID, scratchSpace); this.variantIndex = variantIndex; this.playlistParser = playlistParser; this.playlistUrl = playlistUrl; }
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, byte[] scratchSpace, String iv, int variantIndex) { super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, null, Chunk.NO_PARENT_ID, scratchSpace); this.iv = iv; this.variantIndex = variantIndex; }
@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; String formatId = initializationChunk.format.id; PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId); if (periodHolder == null) { // period for this initialization chunk may no longer be on the manifest return; } RepresentationHolder representationHolder = periodHolder.representationHolders.get(formatId); if (initializationChunk.hasFormat()) { representationHolder.mediaFormat = initializationChunk.getFormat(); } // The null check avoids overwriting an index obtained from the manifest with one obtained // from the stream. If the manifest defines an index then the stream shouldn't, but in cases // where it does we should ignore it. if (representationHolder.segmentIndex == null && initializationChunk.hasSeekMap()) { representationHolder.segmentIndex = new DashWrappingSegmentIndex( (ChunkIndex) initializationChunk.getSeekMap(), initializationChunk.dataSpec.uri.toString()); } // The null check avoids overwriting drmInitData obtained from the manifest with drmInitData // obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3. if (periodHolder.drmInitData == null && initializationChunk.hasDrmInitData()) { periodHolder.drmInitData = initializationChunk.getDrmInitData(); } } }
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger, MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) { long offset = 0; DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey); // 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. return new ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, chunkStartTimeUs, extractorWrapper, mediaFormat, adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID); }
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, Extractor extractor, DataSource dataSource, int trigger) { int expectedExtractorResult = Extractor.RESULT_END_OF_STREAM; long indexAnchor = 0; RangedUri requestUri; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. expectedExtractorResult |= Extractor.RESULT_READ_INIT; requestUri = initializationUri.attemptMerge(indexUri); if (requestUri != null) { expectedExtractorResult |= Extractor.RESULT_READ_INDEX; if (extractor.hasRelativeIndexOffsets()) { indexAnchor = indexUri.start + indexUri.length; } } else { requestUri = initializationUri; } } else { requestUri = indexUri; if (extractor.hasRelativeIndexOffsets()) { indexAnchor = indexUri.start + indexUri.length; } expectedExtractorResult |= Extractor.RESULT_READ_INDEX; } DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, representation.getCacheKey()); return new InitializationLoadable(dataSource, dataSpec, trigger, representation.format, extractor, expectedExtractorResult, indexAnchor); }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, int segmentNum, int trigger) { Representation representation = representationHolder.representation; DashSegmentIndex segmentIndex = representationHolder.segmentIndex; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = startTimeUs + segmentIndex.getDurationUs(segmentNum); boolean isLastSegment = !currentManifest.dynamic && segmentNum == segmentIndex.getLastSegmentNum(); int nextAbsoluteSegmentNum = isLastSegment ? -1 : (representationHolder.segmentNumShift + segmentNum + 1); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long presentationTimeOffsetUs = representation.presentationTimeOffsetMs * 1000; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { if (representationHolder.vttHeaderOffsetUs != presentationTimeOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(WebvttParser.EXO_HEADER).append("=") .append(WebvttParser.OFFSET).append(presentationTimeOffsetUs).append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); representationHolder.vttHeaderOffsetUs = presentationTimeOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, representation.format, 0, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, null, representationHolder.vttHeader); } else { return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, representationHolder.extractor, psshInfo, false, presentationTimeOffsetUs); } }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, int segmentNum, int trigger) { Representation representation = representationHolder.representation; DashSegmentIndex segmentIndex = representationHolder.segmentIndex; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = startTimeUs + segmentIndex.getDurationUs(segmentNum); boolean isLastSegment = !currentManifest.dynamic && segmentNum == segmentIndex.getLastSegmentNum(); int nextAbsoluteSegmentNum = isLastSegment ? -1 : (representationHolder.segmentNumShift + segmentNum + 1); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long presentationTimeOffsetUs = representation.presentationTimeOffsetUs; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { if (representationHolder.vttHeaderOffsetUs != presentationTimeOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(WebvttParser.EXO_HEADER).append("=") .append(WebvttParser.OFFSET).append(presentationTimeOffsetUs).append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); representationHolder.vttHeaderOffsetUs = presentationTimeOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, representation.format, 0, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, null, representationHolder.vttHeader); } else { return new ContainerMediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, representationHolder.extractor, psshInfo, false, presentationTimeOffsetUs); } }
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex, Extractor extractor, DataSource dataSource, int segmentNum, int trigger) { int lastSegmentNum = segmentIndex.getLastSegmentNum(); int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = segmentNum < lastSegmentNum ? segmentIndex.getTimeUs(segmentNum + 1) : startTimeUs + segmentIndex.getDurationUs(segmentNum); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, endTimeUs, nextSegmentNum, extractor, false, 0); }
@Override public final void getChunkOperation(List<? extends MediaChunk> queue, long seekPositionUs, long playbackPositionUs, ChunkOperationHolder out) { evaluation.queueSize = queue.size(); formatEvaluator.evaluate(queue, playbackPositionUs, formats, evaluation); SmoothStreamingFormat selectedFormat = (SmoothStreamingFormat) evaluation.format; out.queueSize = evaluation.queueSize; if (selectedFormat == null) { out.chunk = null; return; } else if (out.queueSize == queue.size() && out.chunk != null && out.chunk.format.id.equals(evaluation.format.id)) { // We already have a chunk, and the evaluation hasn't changed either the format or the size // of the queue. Do nothing. return; } int nextChunkIndex; if (queue.isEmpty()) { nextChunkIndex = streamElement.getChunkIndex(seekPositionUs); } else { nextChunkIndex = queue.get(out.queueSize - 1).nextChunkIndex; } if (nextChunkIndex == -1) { out.chunk = null; return; } boolean isLastChunk = nextChunkIndex == streamElement.chunkCount - 1; String requestUrl = streamElement.buildRequestUrl(selectedFormat.trackIndex, nextChunkIndex); Uri uri = Uri.parse(baseUrl + '/' + requestUrl); Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, extractors.get(Integer.parseInt(selectedFormat.id)), dataSource, nextChunkIndex, isLastChunk, streamElement.getStartTimeUs(nextChunkIndex), isLastChunk ? -1 : streamElement.getStartTimeUs(nextChunkIndex + 1), 0); out.chunk = mediaChunk; }
private void maybeStartLoading() { long now = SystemClock.elapsedRealtime(); long nextLoadPositionUs = getNextLoadPositionUs(); boolean isBackedOff = currentLoadableException != null; boolean loadingOrBackedOff = loader.isLoading() || isBackedOff; // Update the control with our current state, and determine whether we're the next loader. boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, loadingOrBackedOff); if (isBackedOff) { long elapsedMillis = now - currentLoadableExceptionTimestamp; if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { currentLoadableException = null; loader.startLoading(currentLoadable, this); } return; } if (loader.isLoading() || !nextLoader) { return; } Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs, downstreamPositionUs); if (nextLoadable == null) { return; } currentLoadStartTimeMs = now; currentLoadable = nextLoadable; if (isTsChunk(currentLoadable)) { TsChunk tsChunk = (TsChunk) currentLoadable; if (isPendingReset()) { pendingResetPositionUs = NO_RESET_PENDING; } HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper; if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) { extractorWrapper.init(loadControl.getAllocator()); extractors.addLast(extractorWrapper); } notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format, tsChunk.startTimeUs, tsChunk.endTimeUs); currentTsLoadable = tsChunk; } else { notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type, currentLoadable.trigger, currentLoadable.format, -1, -1); } loader.startLoading(currentLoadable, this); }
private boolean isTsChunk(Chunk chunk) { return chunk instanceof TsChunk; }
/** * Invoked when the {@link HlsSampleSource} encounters an error loading a chunk obtained from * this source. * * @param chunk The chunk whose load encountered the error. * @param e The error. * @return True if the error was handled by the source. False otherwise. */ public boolean onChunkLoadError(Chunk chunk, IOException e) { if (chunk.bytesLoaded() == 0 && (chunk instanceof TsChunk || chunk instanceof MediaPlaylistChunk || chunk instanceof EncryptionKeyChunk) && (e instanceof InvalidResponseCodeException)) { InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e; int responseCode = responseCodeException.responseCode; if (responseCode == 404 || responseCode == 410) { int variantIndex; if (chunk instanceof TsChunk) { TsChunk tsChunk = (TsChunk) chunk; variantIndex = getVariantIndex(tsChunk.format); } else if (chunk instanceof MediaPlaylistChunk) { MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk; variantIndex = playlistChunk.variantIndex; } else { EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk; variantIndex = encryptionChunk.variantIndex; } boolean alreadyBlacklisted = variantBlacklistTimes[variantIndex] != 0; variantBlacklistTimes[variantIndex] = SystemClock.elapsedRealtime(); if (alreadyBlacklisted) { // The playlist was already blacklisted. Log.w(TAG, "Already blacklisted variant (" + responseCode + "): " + chunk.dataSpec.uri); return false; } else if (!allVariantsBlacklisted()) { // We've handled the 404/410 by blacklisting the variant. Log.w(TAG, "Blacklisted variant (" + responseCode + "): " + chunk.dataSpec.uri); return true; } else { // This was the last non-blacklisted playlist. Don't blacklist it. Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): " + chunk.dataSpec.uri); variantBlacklistTimes[variantIndex] = 0; return false; } } } return false; }
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, byte[] scratchSpace, String iv, int variantIndex) { super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, null, scratchSpace); this.iv = iv; this.variantIndex = variantIndex; }
@Override public void onChunkLoadError(Chunk chunk, Exception e) { // Do nothing. }
@Override public void onChunkLoadCompleted(Chunk chunk) { // Do nothing. }
private void maybeStartLoading() { long now = SystemClock.elapsedRealtime(); long nextLoadPositionUs = getNextLoadPositionUs(); boolean isBackedOff = currentLoadableException != null; boolean loadingOrBackedOff = loader.isLoading() || isBackedOff; // Update the control with our current state, and determine whether we're the next loader. boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, loadingOrBackedOff); if (isBackedOff) { long elapsedMillis = now - currentLoadableExceptionTimestamp; if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { currentLoadableException = null; loader.startLoading(currentLoadable, this); } return; } if (loader.isLoading() || !nextLoader || (prepared && enabledTrackCount == 0)) { return; } chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs : downstreamPositionUs, chunkOperationHolder); boolean endOfStream = chunkOperationHolder.endOfStream; Chunk nextLoadable = chunkOperationHolder.chunk; chunkOperationHolder.clear(); if (endOfStream) { loadingFinished = true; loadControl.update(this, downstreamPositionUs, -1, false); return; } if (nextLoadable == null) { return; } currentLoadStartTimeMs = now; currentLoadable = nextLoadable; if (isTsChunk(currentLoadable)) { TsChunk tsChunk = (TsChunk) currentLoadable; if (isPendingReset()) { pendingResetPositionUs = NO_RESET_PENDING; } HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper; if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) { extractorWrapper.init(loadControl.getAllocator()); extractors.addLast(extractorWrapper); } notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format, tsChunk.startTimeUs, tsChunk.endTimeUs); currentTsLoadable = tsChunk; } else { notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type, currentLoadable.trigger, currentLoadable.format, -1, -1); } loader.startLoading(currentLoadable, this); }