blob: 76a4665d77948112901af2c73f6ece69c64c2ac9 [file] [log] [blame]
/*
* 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 com.google.android.exoplayer2.source.chunk;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* 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 com.google.android.exoplayer2.C} {@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;
@Nullable private TrackOutputProvider trackOutputProvider;
private long endTimeUs;
private @MonotonicNonNull SeekMap seekMap;
private Format @MonotonicNonNull [] sampleFormats;
/**
* @param extractor The extractor to wrap.
* @param primaryTrackType The type of the primary track. Typically one of the
* {@link com.google.android.exoplayer2.C} {@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 if the extractor has
* not output a {@link SeekMap}.
*/
@Nullable
public SeekMap getSeekMap() {
return seekMap;
}
/**
* Returns the sample {@link Format}s for the tracks identified by the extractor, or null if the
* extractor has not finished identifying tracks.
*/
@Nullable
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] = Assertions.checkStateNotNull(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;
@Nullable private final Format manifestFormat;
private final DummyTrackOutput dummyTrackOutput;
public @MonotonicNonNull Format sampleFormat;
private @MonotonicNonNull TrackOutput trackOutput;
private long endTimeUs;
public BindingTrackOutput(int id, int type, @Nullable Format manifestFormat) {
this.id = id;
this.type = type;
this.manifestFormat = manifestFormat;
dummyTrackOutput = new DummyTrackOutput();
}
public void bind(@Nullable 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.withManifestFormatInfo(manifestFormat) : format;
castNonNull(trackOutput).format(sampleFormat);
}
@Override
public int sampleData(DataReader input, int length, boolean allowEndOfInput)
throws IOException {
return castNonNull(trackOutput).sampleData(input, length, allowEndOfInput);
}
@Override
public void sampleData(ParsableByteArray data, int length) {
castNonNull(trackOutput).sampleData(data, length);
}
@Override
public void sampleMetadata(
long timeUs,
@C.BufferFlags int flags,
int size,
int offset,
@Nullable CryptoData cryptoData) {
if (endTimeUs != C.TIME_UNSET && timeUs >= endTimeUs) {
trackOutput = dummyTrackOutput;
}
castNonNull(trackOutput).sampleMetadata(timeUs, flags, size, offset, cryptoData);
}
}
}