summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java100
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java75
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java80
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/Chunk.java137
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java220
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkHolder.java41
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java791
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSource.java111
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java157
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/DataChunk.java119
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/InitializationChunk.java112
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunk.java68
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java104
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java61
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java120
15 files changed, 2296 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java
new file mode 100644
index 0000000000..406cd1617a
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+
+/**
+ * A base implementation of {@link MediaChunk} that outputs to a {@link BaseMediaChunkOutput}.
+ */
+public abstract class BaseMediaChunk extends MediaChunk {
+
+ /**
+ * The time from which output will begin, or {@link C#TIME_UNSET} if output will begin from the
+ * start of the chunk.
+ */
+ public final long clippedStartTimeUs;
+ /**
+ * The time from which output will end, or {@link C#TIME_UNSET} if output will end at the end of
+ * the chunk.
+ */
+ public final long clippedEndTimeUs;
+
+ private BaseMediaChunkOutput output;
+ private int[] firstSampleIndices;
+
+ /**
+ * @param dataSource The source from which the data should be loaded.
+ * @param dataSpec Defines the data to be loaded.
+ * @param trackFormat See {@link #trackFormat}.
+ * @param trackSelectionReason See {@link #trackSelectionReason}.
+ * @param trackSelectionData See {@link #trackSelectionData}.
+ * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
+ * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
+ * @param clippedStartTimeUs The time in the chunk from which output will begin, or {@link
+ * C#TIME_UNSET} to output from the start of the chunk.
+ * @param clippedEndTimeUs The time in the chunk from which output will end, or {@link
+ * C#TIME_UNSET} to output to the end of the chunk.
+ * @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
+ */
+ public BaseMediaChunk(
+ DataSource dataSource,
+ DataSpec dataSpec,
+ Format trackFormat,
+ int trackSelectionReason,
+ @Nullable Object trackSelectionData,
+ long startTimeUs,
+ long endTimeUs,
+ long clippedStartTimeUs,
+ long clippedEndTimeUs,
+ long chunkIndex) {
+ super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
+ endTimeUs, chunkIndex);
+ this.clippedStartTimeUs = clippedStartTimeUs;
+ this.clippedEndTimeUs = clippedEndTimeUs;
+ }
+
+ /**
+ * Initializes the chunk for loading, setting the {@link BaseMediaChunkOutput} that will receive
+ * samples as they are loaded.
+ *
+ * @param output The output that will receive the loaded media samples.
+ */
+ public void init(BaseMediaChunkOutput output) {
+ this.output = output;
+ firstSampleIndices = output.getWriteIndices();
+ }
+
+ /**
+ * Returns the index of the first sample in the specified track of the output that will originate
+ * from this chunk.
+ */
+ public final int getFirstSampleIndex(int trackIndex) {
+ return firstSampleIndices[trackIndex];
+ }
+
+ /**
+ * Returns the output most recently passed to {@link #init(BaseMediaChunkOutput)}.
+ */
+ protected final BaseMediaChunkOutput getOutput() {
+ return output;
+ }
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java
new file mode 100644
index 0000000000..3987260578
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Base class for {@link MediaChunkIterator}s. Handles {@link #next()} and {@link #isEnded()}, and
+ * provides a bounds check for child classes.
+ */
+public abstract class BaseMediaChunkIterator implements MediaChunkIterator {
+
+ private final long fromIndex;
+ private final long toIndex;
+
+ private long currentIndex;
+
+ /**
+ * Creates base iterator.
+ *
+ * @param fromIndex The first available index.
+ * @param toIndex The last available index.
+ */
+ @SuppressWarnings("method.invocation.invalid")
+ public BaseMediaChunkIterator(long fromIndex, long toIndex) {
+ this.fromIndex = fromIndex;
+ this.toIndex = toIndex;
+ reset();
+ }
+
+ @Override
+ public boolean isEnded() {
+ return currentIndex > toIndex;
+ }
+
+ @Override
+ public boolean next() {
+ currentIndex++;
+ return !isEnded();
+ }
+
+ @Override
+ public void reset() {
+ currentIndex = fromIndex - 1;
+ }
+
+ /**
+ * Verifies that the iterator points to a valid element.
+ *
+ * @throws NoSuchElementException If the iterator does not point to a valid element.
+ */
+ protected final void checkInBounds() {
+ if (currentIndex < fromIndex || currentIndex > toIndex) {
+ throw new NoSuchElementException();
+ }
+ }
+
+ /** Returns the current index this iterator is pointing to. */
+ protected final long getCurrentIndex() {
+ return currentIndex;
+ }
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java
new file mode 100644
index 0000000000..5d1f93bf01
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.DummyTrackOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.TrackOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.SampleQueue;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
+
+/**
+ * A {@link TrackOutputProvider} that provides {@link TrackOutput TrackOutputs} based on a
+ * predefined mapping from track type to output.
+ */
+public final class BaseMediaChunkOutput implements TrackOutputProvider {
+
+ private static final String TAG = "BaseMediaChunkOutput";
+
+ private final int[] trackTypes;
+ private final SampleQueue[] sampleQueues;
+
+ /**
+ * @param trackTypes The track types of the individual track outputs.
+ * @param sampleQueues The individual sample queues.
+ */
+ public BaseMediaChunkOutput(int[] trackTypes, SampleQueue[] sampleQueues) {
+ this.trackTypes = trackTypes;
+ this.sampleQueues = sampleQueues;
+ }
+
+ @Override
+ public TrackOutput track(int id, int type) {
+ for (int i = 0; i < trackTypes.length; i++) {
+ if (type == trackTypes[i]) {
+ return sampleQueues[i];
+ }
+ }
+ Log.e(TAG, "Unmatched track of type: " + type);
+ return new DummyTrackOutput();
+ }
+
+ /**
+ * Returns the current absolute write indices of the individual sample queues.
+ */
+ public int[] getWriteIndices() {
+ int[] writeIndices = new int[sampleQueues.length];
+ for (int i = 0; i < sampleQueues.length; i++) {
+ if (sampleQueues[i] != null) {
+ writeIndices[i] = sampleQueues[i].getWriteIndex();
+ }
+ }
+ return writeIndices;
+ }
+
+ /**
+ * Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples
+ * subsequently written to the sample queues.
+ */
+ public void setSampleOffsetUs(long sampleOffsetUs) {
+ for (SampleQueue sampleQueue : sampleQueues) {
+ if (sampleQueue != null) {
+ sampleQueue.setSampleOffsetUs(sampleOffsetUs);
+ }
+ }
+ }
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/Chunk.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/Chunk.java
new file mode 100644
index 0000000000..3f4450eddd
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/Chunk.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import android.net.Uri;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Loader.Loadable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.StatsDataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An abstract base class for {@link Loadable} implementations that load chunks of data required
+ * for the playback of streams.
+ */
+public abstract class Chunk implements Loadable {
+
+ /**
+ * The {@link DataSpec} that defines the data to be loaded.
+ */
+ public final DataSpec dataSpec;
+ /**
+ * The type of the chunk. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For
+ * reporting only.
+ */
+ public final int type;
+ /**
+ * The format of the track to which this chunk belongs, or null if the chunk does not belong to
+ * a track.
+ */
+ public final Format trackFormat;
+ /**
+ * One of the {@link C} {@code SELECTION_REASON_*} constants if the chunk belongs to a track.
+ * {@link C#SELECTION_REASON_UNKNOWN} if the chunk does not belong to a track.
+ */
+ public final int trackSelectionReason;
+ /**
+ * Optional data associated with the selection of the track to which this chunk belongs. Null if
+ * the chunk does not belong to a track.
+ */
+ @Nullable public final Object trackSelectionData;
+ /**
+ * The start time of the media contained by the chunk, or {@link C#TIME_UNSET} if the data
+ * being loaded does not contain media samples.
+ */
+ public final long startTimeUs;
+ /**
+ * The end time of the media contained by the chunk, or {@link C#TIME_UNSET} if the data being
+ * loaded does not contain media samples.
+ */
+ public final long endTimeUs;
+
+ protected final StatsDataSource dataSource;
+
+ /**
+ * @param dataSource The source from which the data should be loaded.
+ * @param dataSpec Defines the data to be loaded.
+ * @param type See {@link #type}.
+ * @param trackFormat See {@link #trackFormat}.
+ * @param trackSelectionReason See {@link #trackSelectionReason}.
+ * @param trackSelectionData See {@link #trackSelectionData}.
+ * @param startTimeUs See {@link #startTimeUs}.
+ * @param endTimeUs See {@link #endTimeUs}.
+ */
+ public Chunk(
+ DataSource dataSource,
+ DataSpec dataSpec,
+ int type,
+ Format trackFormat,
+ int trackSelectionReason,
+ @Nullable Object trackSelectionData,
+ long startTimeUs,
+ long endTimeUs) {
+ this.dataSource = new StatsDataSource(dataSource);
+ this.dataSpec = Assertions.checkNotNull(dataSpec);
+ this.type = type;
+ this.trackFormat = trackFormat;
+ this.trackSelectionReason = trackSelectionReason;
+ this.trackSelectionData = trackSelectionData;
+ this.startTimeUs = startTimeUs;
+ this.endTimeUs = endTimeUs;
+ }
+
+ /**
+ * Returns the duration of the chunk in microseconds.
+ */
+ public final long getDurationUs() {
+ return endTimeUs - startTimeUs;
+ }
+
+ /**
+ * Returns the number of bytes that have been loaded. Must only be called after the load
+ * completed, failed, or was canceled.
+ */
+ public final long bytesLoaded() {
+ return dataSource.getBytesRead();
+ }
+
+ /**
+ * Returns the {@link Uri} associated with the last {@link DataSource#open} call. If redirection
+ * occurred, this is the redirected uri. Must only be called after the load completed, failed, or
+ * was canceled.
+ *
+ * @see DataSource#getUri()
+ */
+ public final Uri getUri() {
+ return dataSource.getLastOpenedUri();
+ }
+
+ /**
+ * Returns the response headers associated with the last {@link DataSource#open} call. Must only
+ * be called after the load completed, failed, or was canceled.
+ *
+ * @see DataSource#getResponseHeaders()
+ */
+ public final Map<String, List<String>> getResponseHeaders() {
+ return dataSource.getLastResponseHeaders();
+ }
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
new file mode 100644
index 0000000000..04cef9198c
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import android.util.SparseArray;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.DummyTrackOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.Extractor;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.SeekMap;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.TrackOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
+import java.io.IOException;
+
+/**
+ * An {@link Extractor} wrapper for loading chunks that contain a single primary track, and possibly
+ * additional embedded tracks.
+ * <p>
+ * The wrapper allows switching of the {@link TrackOutput}s that receive parsed data.
+ */
+public final class ChunkExtractorWrapper implements ExtractorOutput {
+
+ /**
+ * Provides {@link TrackOutput} instances to be written to by the wrapper.
+ */
+ public interface TrackOutputProvider {
+
+ /**
+ * Called to get the {@link TrackOutput} for a specific track.
+ * <p>
+ * The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}.
+ *
+ * @param id A track identifier.
+ * @param type The type of the track. Typically one of the
+ * {@link org.mozilla.thirdparty.com.google.android.exoplayer2C} {@code TRACK_TYPE_*} constants.
+ * @return The {@link TrackOutput} for the given track identifier.
+ */
+ TrackOutput track(int id, int type);
+
+ }
+
+ public final Extractor extractor;
+
+ private final int primaryTrackType;
+ private final Format primaryTrackManifestFormat;
+ private final SparseArray<BindingTrackOutput> bindingTrackOutputs;
+
+ private boolean extractorInitialized;
+ private TrackOutputProvider trackOutputProvider;
+ private long endTimeUs;
+ private SeekMap seekMap;
+ private Format[] sampleFormats;
+
+ /**
+ * @param extractor The extractor to wrap.
+ * @param primaryTrackType The type of the primary track. Typically one of the
+ * {@link org.mozilla.thirdparty.com.google.android.exoplayer2C} {@code TRACK_TYPE_*} constants.
+ * @param primaryTrackManifestFormat A manifest defined {@link Format} whose data should be merged
+ * into any sample {@link Format} output from the {@link Extractor} for the primary track.
+ */
+ public ChunkExtractorWrapper(Extractor extractor, int primaryTrackType,
+ Format primaryTrackManifestFormat) {
+ this.extractor = extractor;
+ this.primaryTrackType = primaryTrackType;
+ this.primaryTrackManifestFormat = primaryTrackManifestFormat;
+ bindingTrackOutputs = new SparseArray<>();
+ }
+
+ /**
+ * Returns the {@link SeekMap} most recently output by the extractor, or null.
+ */
+ public SeekMap getSeekMap() {
+ return seekMap;
+ }
+
+ /**
+ * Returns the sample {@link Format}s most recently output by the extractor, or null.
+ */
+ public Format[] getSampleFormats() {
+ return sampleFormats;
+ }
+
+ /**
+ * Initializes the wrapper to output to {@link TrackOutput}s provided by the specified {@link
+ * TrackOutputProvider}, and configures the extractor to receive data from a new chunk.
+ *
+ * @param trackOutputProvider The provider of {@link TrackOutput}s that will receive sample data.
+ * @param startTimeUs The start position in the new chunk, or {@link C#TIME_UNSET} to output
+ * samples from the start of the chunk.
+ * @param endTimeUs The end position in the new chunk, or {@link C#TIME_UNSET} to output samples
+ * to the end of the chunk.
+ */
+ public void init(
+ @Nullable TrackOutputProvider trackOutputProvider, long startTimeUs, long endTimeUs) {
+ this.trackOutputProvider = trackOutputProvider;
+ this.endTimeUs = endTimeUs;
+ if (!extractorInitialized) {
+ extractor.init(this);
+ if (startTimeUs != C.TIME_UNSET) {
+ extractor.seek(/* position= */ 0, startTimeUs);
+ }
+ extractorInitialized = true;
+ } else {
+ extractor.seek(/* position= */ 0, startTimeUs == C.TIME_UNSET ? 0 : startTimeUs);
+ for (int i = 0; i < bindingTrackOutputs.size(); i++) {
+ bindingTrackOutputs.valueAt(i).bind(trackOutputProvider, endTimeUs);
+ }
+ }
+ }
+
+ // ExtractorOutput implementation.
+
+ @Override
+ public TrackOutput track(int id, int type) {
+ BindingTrackOutput bindingTrackOutput = bindingTrackOutputs.get(id);
+ if (bindingTrackOutput == null) {
+ // Assert that if we're seeing a new track we have not seen endTracks.
+ Assertions.checkState(sampleFormats == null);
+ // TODO: Manifest formats for embedded tracks should also be passed here.
+ bindingTrackOutput = new BindingTrackOutput(id, type,
+ type == primaryTrackType ? primaryTrackManifestFormat : null);
+ bindingTrackOutput.bind(trackOutputProvider, endTimeUs);
+ bindingTrackOutputs.put(id, bindingTrackOutput);
+ }
+ return bindingTrackOutput;
+ }
+
+ @Override
+ public void endTracks() {
+ Format[] sampleFormats = new Format[bindingTrackOutputs.size()];
+ for (int i = 0; i < bindingTrackOutputs.size(); i++) {
+ sampleFormats[i] = bindingTrackOutputs.valueAt(i).sampleFormat;
+ }
+ this.sampleFormats = sampleFormats;
+ }
+
+ @Override
+ public void seekMap(SeekMap seekMap) {
+ this.seekMap = seekMap;
+ }
+
+ // Internal logic.
+
+ private static final class BindingTrackOutput implements TrackOutput {
+
+ private final int id;
+ private final int type;
+ private final Format manifestFormat;
+ private final DummyTrackOutput dummyTrackOutput;
+
+ public Format sampleFormat;
+ private TrackOutput trackOutput;
+ private long endTimeUs;
+
+ public BindingTrackOutput(int id, int type, Format manifestFormat) {
+ this.id = id;
+ this.type = type;
+ this.manifestFormat = manifestFormat;
+ dummyTrackOutput = new DummyTrackOutput();
+ }
+
+ public void bind(TrackOutputProvider trackOutputProvider, long endTimeUs) {
+ if (trackOutputProvider == null) {
+ trackOutput = dummyTrackOutput;
+ return;
+ }
+ this.endTimeUs = endTimeUs;
+ trackOutput = trackOutputProvider.track(id, type);
+ if (sampleFormat != null) {
+ trackOutput.format(sampleFormat);
+ }
+ }
+
+ @Override
+ public void format(Format format) {
+ sampleFormat = manifestFormat != null ? format.copyWithManifestFormatInfo(manifestFormat)
+ : format;
+ trackOutput.format(sampleFormat);
+ }
+
+ @Override
+ public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
+ throws IOException, InterruptedException {
+ return trackOutput.sampleData(input, length, allowEndOfInput);
+ }
+
+ @Override
+ public void sampleData(ParsableByteArray data, int length) {
+ trackOutput.sampleData(data, length);
+ }
+
+ @Override
+ public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
+ CryptoData cryptoData) {
+ if (endTimeUs != C.TIME_UNSET && timeUs >= endTimeUs) {
+ trackOutput = dummyTrackOutput;
+ }
+ trackOutput.sampleMetadata(timeUs, flags, size, offset, cryptoData);
+ }
+
+ }
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkHolder.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkHolder.java
new file mode 100644
index 0000000000..ef9daddd2c
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkHolder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Holds a chunk or an indication that the end of the stream has been reached.
+ */
+public final class ChunkHolder {
+
+ /** The chunk. */
+ @Nullable public Chunk chunk;
+
+ /**
+ * Indicates that the end of the stream has been reached.
+ */
+ public boolean endOfStream;
+
+ /**
+ * Clears the holder.
+ */
+ public void clear() {
+ chunk = null;
+ endOfStream = false;
+ }
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
new file mode 100644
index 0000000000..a789805cd7
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.FormatHolder;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.SeekParameters;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSession;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSessionManager;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.SampleQueue;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.SampleStream;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.SequenceableLoader;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Allocator;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Loader;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link SampleStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}.
+ * May also be configured to expose additional embedded {@link SampleStream}s.
+ */
+public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, SequenceableLoader,
+ Loader.Callback<Chunk>, Loader.ReleaseCallback {
+
+ /** A callback to be notified when a sample stream has finished being released. */
+ public interface ReleaseCallback<T extends ChunkSource> {
+
+ /**
+ * Called when the {@link ChunkSampleStream} has finished being released.
+ *
+ * @param chunkSampleStream The released sample stream.
+ */
+ void onSampleStreamReleased(ChunkSampleStream<T> chunkSampleStream);
+ }
+
+ private static final String TAG = "ChunkSampleStream";
+
+ public final int primaryTrackType;
+
+ @Nullable private final int[] embeddedTrackTypes;
+ @Nullable private final Format[] embeddedTrackFormats;
+ private final boolean[] embeddedTracksSelected;
+ private final T chunkSource;
+ private final SequenceableLoader.Callback<ChunkSampleStream<T>> callback;
+ private final EventDispatcher eventDispatcher;
+ private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
+ private final Loader loader;
+ private final ChunkHolder nextChunkHolder;
+ private final ArrayList<BaseMediaChunk> mediaChunks;
+ private final List<BaseMediaChunk> readOnlyMediaChunks;
+ private final SampleQueue primarySampleQueue;
+ private final SampleQueue[] embeddedSampleQueues;
+ private final BaseMediaChunkOutput chunkOutput;
+
+ private Format primaryDownstreamTrackFormat;
+ @Nullable private ReleaseCallback<T> releaseCallback;
+ private long pendingResetPositionUs;
+ private long lastSeekPositionUs;
+ private int nextNotifyPrimaryFormatMediaChunkIndex;
+
+ /* package */ long decodeOnlyUntilPositionUs;
+ /* package */ boolean loadingFinished;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param primaryTrackType The type of the primary track. One of the {@link C} {@code
+ * TRACK_TYPE_*} constants.
+ * @param embeddedTrackTypes The types of any embedded tracks, or null.
+ * @param embeddedTrackFormats The formats of the embedded tracks, or null.
+ * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
+ * @param callback An {@link Callback} for the stream.
+ * @param allocator An {@link Allocator} from which allocations can be obtained.
+ * @param positionUs The position from which to start loading media.
+ * @param drmSessionManager The {@link DrmSessionManager} to obtain {@link DrmSession DrmSessions}
+ * from.
+ * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
+ * @param eventDispatcher A dispatcher to notify of events.
+ */
+ public ChunkSampleStream(
+ int primaryTrackType,
+ @Nullable int[] embeddedTrackTypes,
+ @Nullable Format[] embeddedTrackFormats,
+ T chunkSource,
+ Callback<ChunkSampleStream<T>> callback,
+ Allocator allocator,
+ long positionUs,
+ DrmSessionManager<?> drmSessionManager,
+ LoadErrorHandlingPolicy loadErrorHandlingPolicy,
+ EventDispatcher eventDispatcher) {
+ this.primaryTrackType = primaryTrackType;
+ this.embeddedTrackTypes = embeddedTrackTypes;
+ this.embeddedTrackFormats = embeddedTrackFormats;
+ this.chunkSource = chunkSource;
+ this.callback = callback;
+ this.eventDispatcher = eventDispatcher;
+ this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
+ loader = new Loader("Loader:ChunkSampleStream");
+ nextChunkHolder = new ChunkHolder();
+ mediaChunks = new ArrayList<>();
+ readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
+
+ int embeddedTrackCount = embeddedTrackTypes == null ? 0 : embeddedTrackTypes.length;
+ embeddedSampleQueues = new SampleQueue[embeddedTrackCount];
+ embeddedTracksSelected = new boolean[embeddedTrackCount];
+ int[] trackTypes = new int[1 + embeddedTrackCount];
+ SampleQueue[] sampleQueues = new SampleQueue[1 + embeddedTrackCount];
+
+ primarySampleQueue = new SampleQueue(allocator, drmSessionManager);
+ trackTypes[0] = primaryTrackType;
+ sampleQueues[0] = primarySampleQueue;
+
+ for (int i = 0; i < embeddedTrackCount; i++) {
+ SampleQueue sampleQueue =
+ new SampleQueue(allocator, DrmSessionManager.getDummyDrmSessionManager());
+ embeddedSampleQueues[i] = sampleQueue;
+ sampleQueues[i + 1] = sampleQueue;
+ trackTypes[i + 1] = embeddedTrackTypes[i];
+ }
+
+ chunkOutput = new BaseMediaChunkOutput(trackTypes, sampleQueues);
+ pendingResetPositionUs = positionUs;
+ lastSeekPositionUs = positionUs;
+ }
+
+ /**
+ * Discards buffered media up to the specified position.
+ *
+ * @param positionUs The position to discard up to, in microseconds.
+ * @param toKeyframe If true then for each track discards samples up to the keyframe before or at
+ * the specified position, rather than any sample before or at that position.
+ */
+ public void discardBuffer(long positionUs, boolean toKeyframe) {
+ if (isPendingReset()) {
+ return;
+ }
+ int oldFirstSampleIndex = primarySampleQueue.getFirstIndex();
+ primarySampleQueue.discardTo(positionUs, toKeyframe, true);
+ int newFirstSampleIndex = primarySampleQueue.getFirstIndex();
+ if (newFirstSampleIndex > oldFirstSampleIndex) {
+ long discardToUs = primarySampleQueue.getFirstTimestampUs();
+ for (int i = 0; i < embeddedSampleQueues.length; i++) {
+ embeddedSampleQueues[i].discardTo(discardToUs, toKeyframe, embeddedTracksSelected[i]);
+ }
+ }
+ discardDownstreamMediaChunks(newFirstSampleIndex);
+ }
+
+ /**
+ * Selects the embedded track, returning a new {@link EmbeddedSampleStream} from which the track's
+ * samples can be consumed. {@link EmbeddedSampleStream#release()} must be called on the returned
+ * stream when the track is no longer required, and before calling this method again to obtain
+ * another stream for the same track.
+ *
+ * @param positionUs The current playback position in microseconds.
+ * @param trackType The type of the embedded track to enable.
+ * @return The {@link EmbeddedSampleStream} for the embedded track.
+ */
+ public EmbeddedSampleStream selectEmbeddedTrack(long positionUs, int trackType) {
+ for (int i = 0; i < embeddedSampleQueues.length; i++) {
+ if (embeddedTrackTypes[i] == trackType) {
+ Assertions.checkState(!embeddedTracksSelected[i]);
+ embeddedTracksSelected[i] = true;
+ embeddedSampleQueues[i].seekTo(positionUs, /* allowTimeBeyondBuffer= */ true);
+ return new EmbeddedSampleStream(this, embeddedSampleQueues[i], i);
+ }
+ }
+ // Should never happen.
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Returns the {@link ChunkSource} used by this stream.
+ */
+ public T getChunkSource() {
+ return chunkSource;
+ }
+
+ /**
+ * Returns an estimate of the position up to which data is buffered.
+ *
+ * @return An estimate of the absolute position in microseconds up to which data is buffered, or
+ * {@link C#TIME_END_OF_SOURCE} if the track is fully buffered.
+ */
+ @Override
+ public long getBufferedPositionUs() {
+ if (loadingFinished) {
+ return C.TIME_END_OF_SOURCE;
+ } else if (isPendingReset()) {
+ return pendingResetPositionUs;
+ } else {
+ long bufferedPositionUs = lastSeekPositionUs;
+ BaseMediaChunk lastMediaChunk = getLastMediaChunk();
+ BaseMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk
+ : mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null;
+ if (lastCompletedMediaChunk != null) {
+ bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs);
+ }
+ return Math.max(bufferedPositionUs, primarySampleQueue.getLargestQueuedTimestampUs());
+ }
+ }
+
+ /**
+ * Adjusts a seek position given the specified {@link SeekParameters}. Chunk boundaries are used
+ * as sync points.
+ *
+ * @param positionUs The seek position in microseconds.
+ * @param seekParameters Parameters that control how the seek is performed.
+ * @return The adjusted seek position, in microseconds.
+ */
+ public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
+ return chunkSource.getAdjustedSeekPositionUs(positionUs, seekParameters);
+ }
+
+ /**
+ * 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.
+ 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 the sample timestamps are slightly offset from the chunk start times.
+ seekInsideBuffer = primarySampleQueue.seekTo(seekToMediaChunk.getFirstSampleIndex(0));
+ decodeOnlyUntilPositionUs = 0;
+ } else {
+ seekInsideBuffer =
+ primarySampleQueue.seekTo(
+ positionUs, /* allowTimeBeyondBuffer= */ positionUs < getNextLoadPositionUs());
+ decodeOnlyUntilPositionUs = lastSeekPositionUs;
+ }
+
+ 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()) {
+ loader.cancelLoading();
+ } else {
+ loader.clearFatalError();
+ primarySampleQueue.reset();
+ for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
+ embeddedSampleQueue.reset();
+ }
+ }
+ }
+ }
+
+ /**
+ * Releases the stream.
+ *
+ * <p>This method should be called when the stream is no longer required. Either this method or
+ * {@link #release(ReleaseCallback)} can be used to release this stream.
+ */
+ public void release() {
+ release(null);
+ }
+
+ /**
+ * Releases the stream.
+ *
+ * <p>This method should be called when the stream is no longer required. Either this method or
+ * {@link #release()} can be used to release this stream.
+ *
+ * @param callback An optional callback to be called on the loading thread once the loader has
+ * been released.
+ */
+ public void release(@Nullable ReleaseCallback<T> callback) {
+ this.releaseCallback = callback;
+ // Discard as much as we can synchronously.
+ primarySampleQueue.preRelease();
+ for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
+ embeddedSampleQueue.preRelease();
+ }
+ loader.release(this);
+ }
+
+ @Override
+ public void onLoaderReleased() {
+ primarySampleQueue.release();
+ for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
+ embeddedSampleQueue.release();
+ }
+ if (releaseCallback != null) {
+ releaseCallback.onSampleStreamReleased(this);
+ }
+ }
+
+ // SampleStream implementation.
+
+ @Override
+ public boolean isReady() {
+ return !isPendingReset() && primarySampleQueue.isReady(loadingFinished);
+ }
+
+ @Override
+ public void maybeThrowError() throws IOException {
+ loader.maybeThrowError();
+ primarySampleQueue.maybeThrowError();
+ if (!loader.isLoading()) {
+ chunkSource.maybeThrowError();
+ }
+ }
+
+ @Override
+ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
+ boolean formatRequired) {
+ if (isPendingReset()) {
+ return C.RESULT_NOTHING_READ;
+ }
+ maybeNotifyPrimaryTrackFormatChanged();
+
+ return primarySampleQueue.read(
+ formatHolder, buffer, formatRequired, loadingFinished, decodeOnlyUntilPositionUs);
+ }
+
+ @Override
+ public int skipData(long positionUs) {
+ if (isPendingReset()) {
+ return 0;
+ }
+ int skipCount;
+ if (loadingFinished && positionUs > primarySampleQueue.getLargestQueuedTimestampUs()) {
+ skipCount = primarySampleQueue.advanceToEnd();
+ } else {
+ skipCount = primarySampleQueue.advanceTo(positionUs);
+ }
+ maybeNotifyPrimaryTrackFormatChanged();
+ return skipCount;
+ }
+
+ // Loader.Callback implementation.
+
+ @Override
+ public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
+ chunkSource.onChunkLoadCompleted(loadable);
+ eventDispatcher.loadCompleted(
+ loadable.dataSpec,
+ loadable.getUri(),
+ loadable.getResponseHeaders(),
+ loadable.type,
+ primaryTrackType,
+ loadable.trackFormat,
+ loadable.trackSelectionReason,
+ loadable.trackSelectionData,
+ loadable.startTimeUs,
+ loadable.endTimeUs,
+ elapsedRealtimeMs,
+ loadDurationMs,
+ loadable.bytesLoaded());
+ callback.onContinueLoadingRequested(this);
+ }
+
+ @Override
+ public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
+ boolean released) {
+ eventDispatcher.loadCanceled(
+ loadable.dataSpec,
+ loadable.getUri(),
+ loadable.getResponseHeaders(),
+ loadable.type,
+ primaryTrackType,
+ loadable.trackFormat,
+ loadable.trackSelectionReason,
+ loadable.trackSelectionData,
+ loadable.startTimeUs,
+ loadable.endTimeUs,
+ elapsedRealtimeMs,
+ loadDurationMs,
+ loadable.bytesLoaded());
+ if (!released) {
+ primarySampleQueue.reset();
+ for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
+ embeddedSampleQueue.reset();
+ }
+ callback.onContinueLoadingRequested(this);
+ }
+ }
+
+ @Override
+ public LoadErrorAction onLoadError(
+ Chunk loadable,
+ long elapsedRealtimeMs,
+ long loadDurationMs,
+ IOException error,
+ int errorCount) {
+ long bytesLoaded = loadable.bytesLoaded();
+ boolean isMediaChunk = isMediaChunk(loadable);
+ int lastChunkIndex = mediaChunks.size() - 1;
+ boolean cancelable =
+ bytesLoaded == 0 || !isMediaChunk || !haveReadFromMediaChunk(lastChunkIndex);
+ long blacklistDurationMs =
+ cancelable
+ ? loadErrorHandlingPolicy.getBlacklistDurationMsFor(
+ loadable.type, loadDurationMs, error, errorCount)
+ : C.TIME_UNSET;
+ LoadErrorAction loadErrorAction = null;
+ if (chunkSource.onChunkLoadError(loadable, cancelable, error, blacklistDurationMs)) {
+ if (cancelable) {
+ loadErrorAction = Loader.DONT_RETRY;
+ if (isMediaChunk) {
+ BaseMediaChunk removed = discardUpstreamMediaChunksFromIndex(lastChunkIndex);
+ Assertions.checkState(removed == loadable);
+ if (mediaChunks.isEmpty()) {
+ pendingResetPositionUs = lastSeekPositionUs;
+ }
+ }
+ } else {
+ Log.w(TAG, "Ignoring attempt to cancel non-cancelable load.");
+ }
+ }
+
+ if (loadErrorAction == null) {
+ // The load was not cancelled. Either the load must be retried or the error propagated.
+ long retryDelayMs =
+ loadErrorHandlingPolicy.getRetryDelayMsFor(
+ loadable.type, loadDurationMs, error, errorCount);
+ loadErrorAction =
+ retryDelayMs != C.TIME_UNSET
+ ? Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs)
+ : Loader.DONT_RETRY_FATAL;
+ }
+
+ boolean canceled = !loadErrorAction.isRetry();
+ eventDispatcher.loadError(
+ loadable.dataSpec,
+ loadable.getUri(),
+ loadable.getResponseHeaders(),
+ loadable.type,
+ primaryTrackType,
+ loadable.trackFormat,
+ loadable.trackSelectionReason,
+ loadable.trackSelectionData,
+ loadable.startTimeUs,
+ loadable.endTimeUs,
+ elapsedRealtimeMs,
+ loadDurationMs,
+ bytesLoaded,
+ error,
+ canceled);
+ if (canceled) {
+ callback.onContinueLoadingRequested(this);
+ }
+ return loadErrorAction;
+ }
+
+ // SequenceableLoader implementation
+
+ @Override
+ public boolean continueLoading(long positionUs) {
+ if (loadingFinished || loader.isLoading() || loader.hasFatalError()) {
+ return false;
+ }
+
+ boolean pendingReset = isPendingReset();
+ List<BaseMediaChunk> chunkQueue;
+ long loadPositionUs;
+ if (pendingReset) {
+ chunkQueue = Collections.emptyList();
+ loadPositionUs = pendingResetPositionUs;
+ } else {
+ chunkQueue = readOnlyMediaChunks;
+ loadPositionUs = getLastMediaChunk().endTimeUs;
+ }
+ chunkSource.getNextChunk(positionUs, loadPositionUs, chunkQueue, nextChunkHolder);
+ boolean endOfStream = nextChunkHolder.endOfStream;
+ Chunk loadable = nextChunkHolder.chunk;
+ nextChunkHolder.clear();
+
+ if (endOfStream) {
+ pendingResetPositionUs = C.TIME_UNSET;
+ loadingFinished = true;
+ return true;
+ }
+
+ if (loadable == null) {
+ return false;
+ }
+
+ if (isMediaChunk(loadable)) {
+ BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
+ if (pendingReset) {
+ boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs;
+ // Only enable setting of the decode only flag if we're not resetting to a chunk boundary.
+ decodeOnlyUntilPositionUs = resetToMediaChunk ? 0 : pendingResetPositionUs;
+ pendingResetPositionUs = C.TIME_UNSET;
+ }
+ mediaChunk.init(chunkOutput);
+ mediaChunks.add(mediaChunk);
+ } else if (loadable instanceof InitializationChunk) {
+ ((InitializationChunk) loadable).init(chunkOutput);
+ }
+ long elapsedRealtimeMs =
+ loader.startLoading(
+ loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type));
+ eventDispatcher.loadStarted(
+ loadable.dataSpec,
+ loadable.type,
+ primaryTrackType,
+ loadable.trackFormat,
+ loadable.trackSelectionReason,
+ loadable.trackSelectionData,
+ loadable.startTimeUs,
+ loadable.endTimeUs,
+ elapsedRealtimeMs);
+ return true;
+ }
+
+ @Override
+ public boolean isLoading() {
+ return loader.isLoading();
+ }
+
+ @Override
+ public long getNextLoadPositionUs() {
+ if (isPendingReset()) {
+ return pendingResetPositionUs;
+ } else {
+ return loadingFinished ? C.TIME_END_OF_SOURCE : getLastMediaChunk().endTimeUs;
+ }
+ }
+
+ @Override
+ public void reevaluateBuffer(long positionUs) {
+ if (loader.isLoading() || loader.hasFatalError() || isPendingReset()) {
+ return;
+ }
+
+ int currentQueueSize = mediaChunks.size();
+ int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
+ if (currentQueueSize <= preferredQueueSize) {
+ return;
+ }
+
+ int newQueueSize = currentQueueSize;
+ for (int i = preferredQueueSize; i < currentQueueSize; i++) {
+ if (!haveReadFromMediaChunk(i)) {
+ newQueueSize = i;
+ break;
+ }
+ }
+ if (newQueueSize == currentQueueSize) {
+ return;
+ }
+
+ long endTimeUs = getLastMediaChunk().endTimeUs;
+ BaseMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(newQueueSize);
+ if (mediaChunks.isEmpty()) {
+ pendingResetPositionUs = lastSeekPositionUs;
+ }
+ loadingFinished = false;
+ eventDispatcher.upstreamDiscarded(primaryTrackType, firstRemovedChunk.startTimeUs, endTimeUs);
+ }
+
+ // Internal methods
+
+ private boolean isMediaChunk(Chunk chunk) {
+ return chunk instanceof BaseMediaChunk;
+ }
+
+ /** Returns whether samples have been read from media chunk at given index. */
+ private boolean haveReadFromMediaChunk(int mediaChunkIndex) {
+ BaseMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
+ if (primarySampleQueue.getReadIndex() > mediaChunk.getFirstSampleIndex(0)) {
+ return true;
+ }
+ for (int i = 0; i < embeddedSampleQueues.length; i++) {
+ if (embeddedSampleQueues[i].getReadIndex() > mediaChunk.getFirstSampleIndex(i + 1)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /* package */ boolean isPendingReset() {
+ return pendingResetPositionUs != C.TIME_UNSET;
+ }
+
+ private void discardDownstreamMediaChunks(int discardToSampleIndex) {
+ int discardToMediaChunkIndex =
+ primarySampleIndexToMediaChunkIndex(discardToSampleIndex, /* minChunkIndex= */ 0);
+ // Don't discard any chunks that we haven't reported the primary format change for yet.
+ discardToMediaChunkIndex =
+ Math.min(discardToMediaChunkIndex, nextNotifyPrimaryFormatMediaChunkIndex);
+ if (discardToMediaChunkIndex > 0) {
+ Util.removeRange(mediaChunks, /* fromIndex= */ 0, /* toIndex= */ discardToMediaChunkIndex);
+ nextNotifyPrimaryFormatMediaChunkIndex -= discardToMediaChunkIndex;
+ }
+ }
+
+ private void maybeNotifyPrimaryTrackFormatChanged() {
+ int readSampleIndex = primarySampleQueue.getReadIndex();
+ int notifyToMediaChunkIndex =
+ primarySampleIndexToMediaChunkIndex(
+ readSampleIndex, /* minChunkIndex= */ nextNotifyPrimaryFormatMediaChunkIndex - 1);
+ while (nextNotifyPrimaryFormatMediaChunkIndex <= notifyToMediaChunkIndex) {
+ maybeNotifyPrimaryTrackFormatChanged(nextNotifyPrimaryFormatMediaChunkIndex++);
+ }
+ }
+
+ private void maybeNotifyPrimaryTrackFormatChanged(int mediaChunkReadIndex) {
+ BaseMediaChunk currentChunk = mediaChunks.get(mediaChunkReadIndex);
+ Format trackFormat = currentChunk.trackFormat;
+ if (!trackFormat.equals(primaryDownstreamTrackFormat)) {
+ eventDispatcher.downstreamFormatChanged(primaryTrackType, trackFormat,
+ currentChunk.trackSelectionReason, currentChunk.trackSelectionData,
+ currentChunk.startTimeUs);
+ }
+ primaryDownstreamTrackFormat = trackFormat;
+ }
+
+ /**
+ * Returns the media chunk index corresponding to a given primary sample index.
+ *
+ * @param primarySampleIndex The primary sample index for which the corresponding media chunk
+ * index is required.
+ * @param minChunkIndex A minimum chunk index from which to start searching, or -1 if no hint can
+ * be provided.
+ * @return The index of the media chunk corresponding to the sample index, or -1 if the list of
+ * media chunks is empty, or {@code minChunkIndex} if the sample precedes the first chunk in
+ * the search (i.e. the chunk at {@code minChunkIndex}, or at index 0 if {@code minChunkIndex}
+ * is -1.
+ */
+ private int primarySampleIndexToMediaChunkIndex(int primarySampleIndex, int minChunkIndex) {
+ for (int i = minChunkIndex + 1; i < mediaChunks.size(); i++) {
+ if (mediaChunks.get(i).getFirstSampleIndex(0) > primarySampleIndex) {
+ return i - 1;
+ }
+ }
+ return mediaChunks.size() - 1;
+ }
+
+ private BaseMediaChunk getLastMediaChunk() {
+ return mediaChunks.get(mediaChunks.size() - 1);
+ }
+
+ /**
+ * Discard upstream media chunks from {@code chunkIndex} and corresponding samples from sample
+ * queues.
+ *
+ * @param chunkIndex The index of the first chunk to discard.
+ * @return The chunk at given index.
+ */
+ private BaseMediaChunk discardUpstreamMediaChunksFromIndex(int chunkIndex) {
+ BaseMediaChunk firstRemovedChunk = mediaChunks.get(chunkIndex);
+ Util.removeRange(mediaChunks, /* fromIndex= */ chunkIndex, /* toIndex= */ mediaChunks.size());
+ nextNotifyPrimaryFormatMediaChunkIndex =
+ Math.max(nextNotifyPrimaryFormatMediaChunkIndex, mediaChunks.size());
+ primarySampleQueue.discardUpstreamSamples(firstRemovedChunk.getFirstSampleIndex(0));
+ for (int i = 0; i < embeddedSampleQueues.length; i++) {
+ embeddedSampleQueues[i].discardUpstreamSamples(firstRemovedChunk.getFirstSampleIndex(i + 1));
+ }
+ return firstRemovedChunk;
+ }
+
+ /**
+ * A {@link SampleStream} embedded in a {@link ChunkSampleStream}.
+ */
+ public final class EmbeddedSampleStream implements SampleStream {
+
+ public final ChunkSampleStream<T> parent;
+
+ private final SampleQueue sampleQueue;
+ private final int index;
+
+ private boolean notifiedDownstreamFormat;
+
+ public EmbeddedSampleStream(ChunkSampleStream<T> parent, SampleQueue sampleQueue, int index) {
+ this.parent = parent;
+ this.sampleQueue = sampleQueue;
+ this.index = index;
+ }
+
+ @Override
+ public boolean isReady() {
+ return !isPendingReset() && sampleQueue.isReady(loadingFinished);
+ }
+
+ @Override
+ public int skipData(long positionUs) {
+ if (isPendingReset()) {
+ return 0;
+ }
+ maybeNotifyDownstreamFormat();
+ int skipCount;
+ if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
+ skipCount = sampleQueue.advanceToEnd();
+ } else {
+ skipCount = sampleQueue.advanceTo(positionUs);
+ }
+ return skipCount;
+ }
+
+ @Override
+ public void maybeThrowError() throws IOException {
+ // Do nothing. Errors will be thrown from the primary stream.
+ }
+
+ @Override
+ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
+ boolean formatRequired) {
+ if (isPendingReset()) {
+ return C.RESULT_NOTHING_READ;
+ }
+ maybeNotifyDownstreamFormat();
+ return sampleQueue.read(
+ formatHolder,
+ buffer,
+ formatRequired,
+ loadingFinished,
+ decodeOnlyUntilPositionUs);
+ }
+
+ public void release() {
+ Assertions.checkState(embeddedTracksSelected[index]);
+ embeddedTracksSelected[index] = false;
+ }
+
+ private void maybeNotifyDownstreamFormat() {
+ if (!notifiedDownstreamFormat) {
+ eventDispatcher.downstreamFormatChanged(
+ embeddedTrackTypes[index],
+ embeddedTrackFormats[index],
+ C.SELECTION_REASON_UNKNOWN,
+ /* trackSelectionData= */ null,
+ lastSeekPositionUs);
+ notifiedDownstreamFormat = true;
+ }
+ }
+ }
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSource.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSource.java
new file mode 100644
index 0000000000..33cee8e20e
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ChunkSource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.SeekParameters;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * A provider of {@link Chunk}s for a {@link ChunkSampleStream} to load.
+ */
+public interface ChunkSource {
+
+ /**
+ * Adjusts a seek position given the specified {@link SeekParameters}. Chunk boundaries are used
+ * as sync points.
+ *
+ * @param positionUs The seek position in microseconds.
+ * @param seekParameters Parameters that control how the seek is performed.
+ * @return The adjusted seek position, in microseconds.
+ */
+ long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters);
+
+ /**
+ * If the source is currently having difficulty providing chunks, then this method throws the
+ * underlying error. Otherwise does nothing.
+ * <p>
+ * This method should only be called after the source has been prepared.
+ *
+ * @throws IOException The underlying error.
+ */
+ void maybeThrowError() throws IOException;
+
+ /**
+ * Evaluates whether {@link MediaChunk}s should be removed from the back of the queue.
+ * <p>
+ * Removing {@link MediaChunk}s from the back of the queue can be useful if they could be replaced
+ * with chunks of a significantly higher quality (e.g. because the available bandwidth has
+ * substantially increased).
+ *
+ * @param playbackPositionUs The current playback position.
+ * @param queue The queue of buffered {@link MediaChunk}s.
+ * @return The preferred queue size.
+ */
+ int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue);
+
+ /**
+ * Returns the next chunk to load.
+ *
+ * <p>If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has
+ * been reached then {@link ChunkHolder#endOfStream} is set. If a chunk is not available but the
+ * end of the stream has not been reached, the {@link ChunkHolder} is not modified.
+ *
+ * @param playbackPositionUs The current playback position in microseconds. If playback of the
+ * period to which this chunk source belongs has not yet started, the value will be the
+ * starting position in the period minus the duration of any media in previous periods still
+ * to be played.
+ * @param loadPositionUs The current load position in microseconds. If {@code queue} is empty,
+ * this is the starting position from which chunks should be provided. Else it's equal to
+ * {@link MediaChunk#endTimeUs} of the last chunk in the {@code queue}.
+ * @param queue The queue of buffered {@link MediaChunk}s.
+ * @param out A holder to populate.
+ */
+ void getNextChunk(
+ long playbackPositionUs,
+ long loadPositionUs,
+ List<? extends MediaChunk> queue,
+ ChunkHolder out);
+
+ /**
+ * Called when the {@link ChunkSampleStream} has finished loading a chunk obtained from this
+ * source.
+ *
+ * <p>This method should only be called when the source is enabled.
+ *
+ * @param chunk The chunk whose load has been completed.
+ */
+ void onChunkLoadCompleted(Chunk chunk);
+
+ /**
+ * Called when the {@link ChunkSampleStream} encounters an error loading a chunk obtained from
+ * this source.
+ *
+ * <p>This method should only be called when the source is enabled.
+ *
+ * @param chunk The chunk whose load encountered the error.
+ * @param cancelable Whether the load can be canceled.
+ * @param e The error.
+ * @param blacklistDurationMs The duration for which the associated track may be blacklisted, or
+ * {@link C#TIME_UNSET} if the track may not be blacklisted.
+ * @return Whether the load should be canceled so that a replacement chunk can be loaded instead.
+ * Must be {@code false} if {@code cancelable} is {@code false}. If {@code true}, {@link
+ * #getNextChunk(long, long, List, ChunkHolder)} will be called to obtain the replacement
+ * chunk.
+ */
+ boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e, long blacklistDurationMs);
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
new file mode 100644
index 0000000000..98865e8b0e
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.DefaultExtractorInput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.Extractor;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.PositionHolder;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+
+/**
+ * A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data.
+ */
+public class ContainerMediaChunk extends BaseMediaChunk {
+
+ private static final PositionHolder DUMMY_POSITION_HOLDER = new PositionHolder();
+
+ private final int chunkCount;
+ private final long sampleOffsetUs;
+ private final ChunkExtractorWrapper extractorWrapper;
+
+ private long nextLoadPosition;
+ private volatile boolean loadCanceled;
+ private boolean loadCompleted;
+
+ /**
+ * @param dataSource The source from which the data should be loaded.
+ * @param dataSpec Defines the data to be loaded.
+ * @param trackFormat See {@link #trackFormat}.
+ * @param trackSelectionReason See {@link #trackSelectionReason}.
+ * @param trackSelectionData See {@link #trackSelectionData}.
+ * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
+ * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
+ * @param clippedStartTimeUs The time in the chunk from which output will begin, or {@link
+ * C#TIME_UNSET} to output from the start of the chunk.
+ * @param clippedEndTimeUs The time in the chunk from which output will end, or {@link
+ * C#TIME_UNSET} to output to the end of the chunk.
+ * @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
+ * @param chunkCount The number of chunks in the underlying media that are spanned by this
+ * instance. Normally equal to one, but may be larger if multiple chunks as defined by the
+ * underlying media are being merged into a single load.
+ * @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
+ * @param extractorWrapper A wrapped extractor to use for parsing the data.
+ */
+ public ContainerMediaChunk(
+ DataSource dataSource,
+ DataSpec dataSpec,
+ Format trackFormat,
+ int trackSelectionReason,
+ Object trackSelectionData,
+ long startTimeUs,
+ long endTimeUs,
+ long clippedStartTimeUs,
+ long clippedEndTimeUs,
+ long chunkIndex,
+ int chunkCount,
+ long sampleOffsetUs,
+ ChunkExtractorWrapper extractorWrapper) {
+ super(
+ dataSource,
+ dataSpec,
+ trackFormat,
+ trackSelectionReason,
+ trackSelectionData,
+ startTimeUs,
+ endTimeUs,
+ clippedStartTimeUs,
+ clippedEndTimeUs,
+ chunkIndex);
+ this.chunkCount = chunkCount;
+ this.sampleOffsetUs = sampleOffsetUs;
+ this.extractorWrapper = extractorWrapper;
+ }
+
+ @Override
+ public long getNextChunkIndex() {
+ return chunkIndex + chunkCount;
+ }
+
+ @Override
+ public boolean isLoadCompleted() {
+ return loadCompleted;
+ }
+
+ // Loadable implementation.
+
+ @Override
+ public final void cancelLoad() {
+ loadCanceled = true;
+ }
+
+ @SuppressWarnings("NonAtomicVolatileUpdate")
+ @Override
+ public final void load() throws IOException, InterruptedException {
+ if (nextLoadPosition == 0) {
+ // Configure the output and set it as the target for the extractor wrapper.
+ BaseMediaChunkOutput output = getOutput();
+ output.setSampleOffsetUs(sampleOffsetUs);
+ extractorWrapper.init(
+ getTrackOutputProvider(output),
+ clippedStartTimeUs == C.TIME_UNSET ? C.TIME_UNSET : (clippedStartTimeUs - sampleOffsetUs),
+ clippedEndTimeUs == C.TIME_UNSET ? C.TIME_UNSET : (clippedEndTimeUs - sampleOffsetUs));
+ }
+ try {
+ // Create and open the input.
+ DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
+ ExtractorInput input =
+ new DefaultExtractorInput(
+ dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
+ // Load and decode the sample data.
+ try {
+ Extractor extractor = extractorWrapper.extractor;
+ int result = Extractor.RESULT_CONTINUE;
+ while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
+ result = extractor.read(input, DUMMY_POSITION_HOLDER);
+ }
+ Assertions.checkState(result != Extractor.RESULT_SEEK);
+ } finally {
+ nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
+ }
+ } finally {
+ Util.closeQuietly(dataSource);
+ }
+ loadCompleted = true;
+ }
+
+ /**
+ * Returns the {@link TrackOutputProvider} to be used by the wrapped extractor.
+ *
+ * @param baseMediaChunkOutput The {@link BaseMediaChunkOutput} most recently passed to {@link
+ * #init(BaseMediaChunkOutput)}.
+ * @return A {@link TrackOutputProvider} to be used by the wrapped extractor.
+ */
+ protected TrackOutputProvider getTrackOutputProvider(BaseMediaChunkOutput baseMediaChunkOutput) {
+ return baseMediaChunkOutput;
+ }
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/DataChunk.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/DataChunk.java
new file mode 100644
index 0000000000..583f8ceeee
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/DataChunk.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * A base class for {@link Chunk} implementations where the data should be loaded into a
+ * {@code byte[]} before being consumed.
+ */
+public abstract class DataChunk extends Chunk {
+
+ private static final int READ_GRANULARITY = 16 * 1024;
+
+ private byte[] data;
+
+ private volatile boolean loadCanceled;
+
+ /**
+ * @param dataSource The source from which the data should be loaded.
+ * @param dataSpec Defines the data to be loaded.
+ * @param type See {@link #type}.
+ * @param trackFormat See {@link #trackFormat}.
+ * @param trackSelectionReason See {@link #trackSelectionReason}.
+ * @param trackSelectionData See {@link #trackSelectionData}.
+ * @param data An optional recycled array that can be used as a holder for the data.
+ */
+ public DataChunk(
+ DataSource dataSource,
+ DataSpec dataSpec,
+ int type,
+ Format trackFormat,
+ int trackSelectionReason,
+ @Nullable Object trackSelectionData,
+ byte[] data) {
+ super(dataSource, dataSpec, type, trackFormat, trackSelectionReason, trackSelectionData,
+ C.TIME_UNSET, C.TIME_UNSET);
+ this.data = data;
+ }
+
+ /**
+ * Returns the array in which the data is held.
+ * <p>
+ * This method should be used for recycling the holder only, and not for reading the data.
+ *
+ * @return The array in which the data is held.
+ */
+ public byte[] getDataHolder() {
+ return data;
+ }
+
+ // Loadable implementation
+
+ @Override
+ public final void cancelLoad() {
+ loadCanceled = true;
+ }
+
+ @Override
+ public final void load() throws IOException, InterruptedException {
+ try {
+ dataSource.open(dataSpec);
+ int limit = 0;
+ int bytesRead = 0;
+ while (bytesRead != C.RESULT_END_OF_INPUT && !loadCanceled) {
+ maybeExpandData(limit);
+ bytesRead = dataSource.read(data, limit, READ_GRANULARITY);
+ if (bytesRead != -1) {
+ limit += bytesRead;
+ }
+ }
+ if (!loadCanceled) {
+ consume(data, limit);
+ }
+ } finally {
+ Util.closeQuietly(dataSource);
+ }
+ }
+
+ /**
+ * Called by {@link #load()}. Implementations should override this method to consume the loaded
+ * data.
+ *
+ * @param data An array containing the data.
+ * @param limit The limit of the data.
+ * @throws IOException If an error occurs consuming the loaded data.
+ */
+ protected abstract void consume(byte[] data, int limit) throws IOException;
+
+ private void maybeExpandData(int limit) {
+ if (data == null) {
+ data = new byte[READ_GRANULARITY];
+ } else if (data.length < limit + READ_GRANULARITY) {
+ // The new length is calculated as (data.length + READ_GRANULARITY) rather than
+ // (limit + READ_GRANULARITY) in order to avoid small increments in the length.
+ data = Arrays.copyOf(data, data.length + READ_GRANULARITY);
+ }
+ }
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/InitializationChunk.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
new file mode 100644
index 0000000000..db6e82c2c7
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.DefaultExtractorInput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.Extractor;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.PositionHolder;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+/**
+ * A {@link Chunk} that uses an {@link Extractor} to decode initialization data for single track.
+ */
+public final class InitializationChunk extends Chunk {
+
+ private static final PositionHolder DUMMY_POSITION_HOLDER = new PositionHolder();
+
+ private final ChunkExtractorWrapper extractorWrapper;
+
+ @MonotonicNonNull private TrackOutputProvider trackOutputProvider;
+ private long nextLoadPosition;
+ private volatile boolean loadCanceled;
+
+ /**
+ * @param dataSource The source from which the data should be loaded.
+ * @param dataSpec Defines the data to be loaded.
+ * @param trackFormat See {@link #trackFormat}.
+ * @param trackSelectionReason See {@link #trackSelectionReason}.
+ * @param trackSelectionData See {@link #trackSelectionData}.
+ * @param extractorWrapper A wrapped extractor to use for parsing the initialization data.
+ */
+ public InitializationChunk(
+ DataSource dataSource,
+ DataSpec dataSpec,
+ Format trackFormat,
+ int trackSelectionReason,
+ @Nullable Object trackSelectionData,
+ ChunkExtractorWrapper extractorWrapper) {
+ super(dataSource, dataSpec, C.DATA_TYPE_MEDIA_INITIALIZATION, trackFormat, trackSelectionReason,
+ trackSelectionData, C.TIME_UNSET, C.TIME_UNSET);
+ this.extractorWrapper = extractorWrapper;
+ }
+
+ /**
+ * Initializes the chunk for loading, setting a {@link TrackOutputProvider} for track outputs to
+ * which formats will be written as they are loaded.
+ *
+ * @param trackOutputProvider The {@link TrackOutputProvider} for track outputs to which formats
+ * will be written as they are loaded.
+ */
+ public void init(TrackOutputProvider trackOutputProvider) {
+ this.trackOutputProvider = trackOutputProvider;
+ }
+
+ // Loadable implementation.
+
+ @Override
+ public void cancelLoad() {
+ loadCanceled = true;
+ }
+
+ @SuppressWarnings("NonAtomicVolatileUpdate")
+ @Override
+ public void load() throws IOException, InterruptedException {
+ if (nextLoadPosition == 0) {
+ extractorWrapper.init(
+ trackOutputProvider, /* startTimeUs= */ C.TIME_UNSET, /* endTimeUs= */ C.TIME_UNSET);
+ }
+ try {
+ // Create and open the input.
+ DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
+ ExtractorInput input =
+ new DefaultExtractorInput(
+ dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
+ // Load and decode the initialization data.
+ try {
+ Extractor extractor = extractorWrapper.extractor;
+ int result = Extractor.RESULT_CONTINUE;
+ while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
+ result = extractor.read(input, DUMMY_POSITION_HOLDER);
+ }
+ Assertions.checkState(result != Extractor.RESULT_SEEK);
+ } finally {
+ nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
+ }
+ } finally {
+ Util.closeQuietly(dataSource);
+ }
+ }
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunk.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunk.java
new file mode 100644
index 0000000000..81c9d216b9
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunk.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+
+/**
+ * An abstract base class for {@link Chunk}s that contain media samples.
+ */
+public abstract class MediaChunk extends Chunk {
+
+ /** The chunk index, or {@link C#INDEX_UNSET} if it is not known. */
+ public final long chunkIndex;
+
+ /**
+ * @param dataSource The source from which the data should be loaded.
+ * @param dataSpec Defines the data to be loaded.
+ * @param trackFormat See {@link #trackFormat}.
+ * @param trackSelectionReason See {@link #trackSelectionReason}.
+ * @param trackSelectionData See {@link #trackSelectionData}.
+ * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
+ * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
+ * @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
+ */
+ public MediaChunk(
+ DataSource dataSource,
+ DataSpec dataSpec,
+ Format trackFormat,
+ int trackSelectionReason,
+ @Nullable Object trackSelectionData,
+ long startTimeUs,
+ long endTimeUs,
+ long chunkIndex) {
+ super(dataSource, dataSpec, C.DATA_TYPE_MEDIA, trackFormat, trackSelectionReason,
+ trackSelectionData, startTimeUs, endTimeUs);
+ Assertions.checkNotNull(trackFormat);
+ this.chunkIndex = chunkIndex;
+ }
+
+ /** Returns the next chunk index or {@link C#INDEX_UNSET} if it is not known. */
+ public long getNextChunkIndex() {
+ return chunkIndex != C.INDEX_UNSET ? chunkIndex + 1 : C.INDEX_UNSET;
+ }
+
+ /**
+ * Returns whether the chunk has been fully loaded.
+ */
+ public abstract boolean isLoadCompleted();
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java
new file mode 100644
index 0000000000..c6f5b1d41e
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator for media chunk sequences.
+ *
+ * <p>The iterator initially points in front of the first available element. The first call to
+ * {@link #next()} moves the iterator to the first element. Check the return value of {@link
+ * #next()} or {@link #isEnded()} to determine whether the iterator reached the end of the available
+ * data.
+ */
+public interface MediaChunkIterator {
+
+ /** An empty media chunk iterator without available data. */
+ MediaChunkIterator EMPTY =
+ new MediaChunkIterator() {
+ @Override
+ public boolean isEnded() {
+ return true;
+ }
+
+ @Override
+ public boolean next() {
+ return false;
+ }
+
+ @Override
+ public DataSpec getDataSpec() {
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public long getChunkStartTimeUs() {
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public long getChunkEndTimeUs() {
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void reset() {
+ // Do nothing.
+ }
+ };
+
+ /** Returns whether the iteration has reached the end of the available data. */
+ boolean isEnded();
+
+ /**
+ * Moves the iterator to the next media chunk.
+ *
+ * <p>Check the return value or {@link #isEnded()} to determine whether the iterator reached the
+ * end of the available data.
+ *
+ * @return Whether the iterator points to a media chunk with available data.
+ */
+ boolean next();
+
+ /**
+ * Returns the {@link DataSpec} used to load the media chunk.
+ *
+ * @throws java.util.NoSuchElementException If the method is called before the first call to
+ * {@link #next()} or when {@link #isEnded()} is true.
+ */
+ DataSpec getDataSpec();
+
+ /**
+ * Returns the media start time of the chunk, in microseconds.
+ *
+ * @throws java.util.NoSuchElementException If the method is called before the first call to
+ * {@link #next()} or when {@link #isEnded()} is true.
+ */
+ long getChunkStartTimeUs();
+
+ /**
+ * Returns the media end time of the chunk, in microseconds.
+ *
+ * @throws java.util.NoSuchElementException If the method is called before the first call to
+ * {@link #next()} or when {@link #isEnded()} is true.
+ */
+ long getChunkEndTimeUs();
+
+ /** Resets the iterator to the initial position. */
+ void reset();
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java
new file mode 100644
index 0000000000..1b3004418e
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import java.util.List;
+
+/** A {@link MediaChunkIterator} which iterates over a {@link List} of {@link MediaChunk}s. */
+public final class MediaChunkListIterator extends BaseMediaChunkIterator {
+
+ private final List<? extends MediaChunk> chunks;
+ private final boolean reverseOrder;
+
+ /**
+ * Creates iterator.
+ *
+ * @param chunks The list of chunks to iterate over.
+ * @param reverseOrder Whether to iterate in reverse order.
+ */
+ public MediaChunkListIterator(List<? extends MediaChunk> chunks, boolean reverseOrder) {
+ super(0, chunks.size() - 1);
+ this.chunks = chunks;
+ this.reverseOrder = reverseOrder;
+ }
+
+ @Override
+ public DataSpec getDataSpec() {
+ return getCurrentChunk().dataSpec;
+ }
+
+ @Override
+ public long getChunkStartTimeUs() {
+ return getCurrentChunk().startTimeUs;
+ }
+
+ @Override
+ public long getChunkEndTimeUs() {
+ return getCurrentChunk().endTimeUs;
+ }
+
+ private MediaChunk getCurrentChunk() {
+ int index = (int) super.getCurrentIndex();
+ if (reverseOrder) {
+ index = chunks.size() - 1 - index;
+ }
+ return chunks.get(index);
+ }
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java
new file mode 100644
index 0000000000..b3d30408ee
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.DefaultExtractorInput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.TrackOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+
+/**
+ * A {@link BaseMediaChunk} for chunks consisting of a single raw sample.
+ */
+public final class SingleSampleMediaChunk extends BaseMediaChunk {
+
+ private final int trackType;
+ private final Format sampleFormat;
+
+ private long nextLoadPosition;
+ private boolean loadCompleted;
+
+ /**
+ * @param dataSource The source from which the data should be loaded.
+ * @param dataSpec Defines the data to be loaded.
+ * @param trackFormat See {@link #trackFormat}.
+ * @param trackSelectionReason See {@link #trackSelectionReason}.
+ * @param trackSelectionData See {@link #trackSelectionData}.
+ * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
+ * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
+ * @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
+ * @param trackType The type of the chunk. Typically one of the {@link C} {@code TRACK_TYPE_*}
+ * constants.
+ * @param sampleFormat The {@link Format} of the sample in the chunk.
+ */
+ public SingleSampleMediaChunk(
+ DataSource dataSource,
+ DataSpec dataSpec,
+ Format trackFormat,
+ int trackSelectionReason,
+ Object trackSelectionData,
+ long startTimeUs,
+ long endTimeUs,
+ long chunkIndex,
+ int trackType,
+ Format sampleFormat) {
+ super(
+ dataSource,
+ dataSpec,
+ trackFormat,
+ trackSelectionReason,
+ trackSelectionData,
+ startTimeUs,
+ endTimeUs,
+ /* clippedStartTimeUs= */ C.TIME_UNSET,
+ /* clippedEndTimeUs= */ C.TIME_UNSET,
+ chunkIndex);
+ this.trackType = trackType;
+ this.sampleFormat = sampleFormat;
+ }
+
+
+ @Override
+ public boolean isLoadCompleted() {
+ return loadCompleted;
+ }
+
+ // Loadable implementation.
+
+ @Override
+ public void cancelLoad() {
+ // Do nothing.
+ }
+
+ @SuppressWarnings("NonAtomicVolatileUpdate")
+ @Override
+ public void load() throws IOException, InterruptedException {
+ BaseMediaChunkOutput output = getOutput();
+ output.setSampleOffsetUs(0);
+ TrackOutput trackOutput = output.track(0, trackType);
+ trackOutput.format(sampleFormat);
+ try {
+ // Create and open the input.
+ DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
+ long length = dataSource.open(loadDataSpec);
+ if (length != C.LENGTH_UNSET) {
+ length += nextLoadPosition;
+ }
+ ExtractorInput extractorInput =
+ new DefaultExtractorInput(dataSource, nextLoadPosition, length);
+ // Load the sample data.
+ int result = 0;
+ while (result != C.RESULT_END_OF_INPUT) {
+ nextLoadPosition += result;
+ result = trackOutput.sampleData(extractorInput, Integer.MAX_VALUE, true);
+ }
+ int sampleSize = (int) nextLoadPosition;
+ trackOutput.sampleMetadata(startTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null);
+ } finally {
+ Util.closeQuietly(dataSource);
+ }
+ loadCompleted = true;
+ }
+}