1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.source.chunk; 17 18 import static com.google.android.exoplayer2.util.Util.castNonNull; 19 20 import android.util.SparseArray; 21 import androidx.annotation.Nullable; 22 import com.google.android.exoplayer2.C; 23 import com.google.android.exoplayer2.Format; 24 import com.google.android.exoplayer2.extractor.DummyTrackOutput; 25 import com.google.android.exoplayer2.extractor.Extractor; 26 import com.google.android.exoplayer2.extractor.ExtractorOutput; 27 import com.google.android.exoplayer2.extractor.SeekMap; 28 import com.google.android.exoplayer2.extractor.TrackOutput; 29 import com.google.android.exoplayer2.upstream.DataReader; 30 import com.google.android.exoplayer2.util.Assertions; 31 import com.google.android.exoplayer2.util.ParsableByteArray; 32 import java.io.IOException; 33 import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 34 35 /** 36 * An {@link Extractor} wrapper for loading chunks that contain a single primary track, and possibly 37 * additional embedded tracks. 38 * <p> 39 * The wrapper allows switching of the {@link TrackOutput}s that receive parsed data. 40 */ 41 public final class ChunkExtractorWrapper implements ExtractorOutput { 42 43 /** 44 * Provides {@link TrackOutput} instances to be written to by the wrapper. 45 */ 46 public interface TrackOutputProvider { 47 48 /** 49 * Called to get the {@link TrackOutput} for a specific track. 50 * <p> 51 * The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}. 52 * 53 * @param id A track identifier. 54 * @param type The type of the track. Typically one of the 55 * {@link com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. 56 * @return The {@link TrackOutput} for the given track identifier. 57 */ track(int id, int type)58 TrackOutput track(int id, int type); 59 60 } 61 62 public final Extractor extractor; 63 64 private final int primaryTrackType; 65 private final Format primaryTrackManifestFormat; 66 private final SparseArray<BindingTrackOutput> bindingTrackOutputs; 67 68 private boolean extractorInitialized; 69 @Nullable private TrackOutputProvider trackOutputProvider; 70 private long endTimeUs; 71 private @MonotonicNonNull SeekMap seekMap; 72 private Format @MonotonicNonNull [] sampleFormats; 73 74 /** 75 * @param extractor The extractor to wrap. 76 * @param primaryTrackType The type of the primary track. Typically one of the 77 * {@link com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. 78 * @param primaryTrackManifestFormat A manifest defined {@link Format} whose data should be merged 79 * into any sample {@link Format} output from the {@link Extractor} for the primary track. 80 */ ChunkExtractorWrapper(Extractor extractor, int primaryTrackType, Format primaryTrackManifestFormat)81 public ChunkExtractorWrapper(Extractor extractor, int primaryTrackType, 82 Format primaryTrackManifestFormat) { 83 this.extractor = extractor; 84 this.primaryTrackType = primaryTrackType; 85 this.primaryTrackManifestFormat = primaryTrackManifestFormat; 86 bindingTrackOutputs = new SparseArray<>(); 87 } 88 89 /** 90 * Returns the {@link SeekMap} most recently output by the extractor, or null if the extractor has 91 * not output a {@link SeekMap}. 92 */ 93 @Nullable getSeekMap()94 public SeekMap getSeekMap() { 95 return seekMap; 96 } 97 98 /** 99 * Returns the sample {@link Format}s for the tracks identified by the extractor, or null if the 100 * extractor has not finished identifying tracks. 101 */ 102 @Nullable getSampleFormats()103 public Format[] getSampleFormats() { 104 return sampleFormats; 105 } 106 107 /** 108 * Initializes the wrapper to output to {@link TrackOutput}s provided by the specified {@link 109 * TrackOutputProvider}, and configures the extractor to receive data from a new chunk. 110 * 111 * @param trackOutputProvider The provider of {@link TrackOutput}s that will receive sample data. 112 * @param startTimeUs The start position in the new chunk, or {@link C#TIME_UNSET} to output 113 * samples from the start of the chunk. 114 * @param endTimeUs The end position in the new chunk, or {@link C#TIME_UNSET} to output samples 115 * to the end of the chunk. 116 */ init( @ullable TrackOutputProvider trackOutputProvider, long startTimeUs, long endTimeUs)117 public void init( 118 @Nullable TrackOutputProvider trackOutputProvider, long startTimeUs, long endTimeUs) { 119 this.trackOutputProvider = trackOutputProvider; 120 this.endTimeUs = endTimeUs; 121 if (!extractorInitialized) { 122 extractor.init(this); 123 if (startTimeUs != C.TIME_UNSET) { 124 extractor.seek(/* position= */ 0, startTimeUs); 125 } 126 extractorInitialized = true; 127 } else { 128 extractor.seek(/* position= */ 0, startTimeUs == C.TIME_UNSET ? 0 : startTimeUs); 129 for (int i = 0; i < bindingTrackOutputs.size(); i++) { 130 bindingTrackOutputs.valueAt(i).bind(trackOutputProvider, endTimeUs); 131 } 132 } 133 } 134 135 // ExtractorOutput implementation. 136 137 @Override track(int id, int type)138 public TrackOutput track(int id, int type) { 139 BindingTrackOutput bindingTrackOutput = bindingTrackOutputs.get(id); 140 if (bindingTrackOutput == null) { 141 // Assert that if we're seeing a new track we have not seen endTracks. 142 Assertions.checkState(sampleFormats == null); 143 // TODO: Manifest formats for embedded tracks should also be passed here. 144 bindingTrackOutput = new BindingTrackOutput(id, type, 145 type == primaryTrackType ? primaryTrackManifestFormat : null); 146 bindingTrackOutput.bind(trackOutputProvider, endTimeUs); 147 bindingTrackOutputs.put(id, bindingTrackOutput); 148 } 149 return bindingTrackOutput; 150 } 151 152 @Override endTracks()153 public void endTracks() { 154 Format[] sampleFormats = new Format[bindingTrackOutputs.size()]; 155 for (int i = 0; i < bindingTrackOutputs.size(); i++) { 156 sampleFormats[i] = Assertions.checkStateNotNull(bindingTrackOutputs.valueAt(i).sampleFormat); 157 } 158 this.sampleFormats = sampleFormats; 159 } 160 161 @Override seekMap(SeekMap seekMap)162 public void seekMap(SeekMap seekMap) { 163 this.seekMap = seekMap; 164 } 165 166 // Internal logic. 167 168 private static final class BindingTrackOutput implements TrackOutput { 169 170 private final int id; 171 private final int type; 172 @Nullable private final Format manifestFormat; 173 private final DummyTrackOutput dummyTrackOutput; 174 175 public @MonotonicNonNull Format sampleFormat; 176 private @MonotonicNonNull TrackOutput trackOutput; 177 private long endTimeUs; 178 BindingTrackOutput(int id, int type, @Nullable Format manifestFormat)179 public BindingTrackOutput(int id, int type, @Nullable Format manifestFormat) { 180 this.id = id; 181 this.type = type; 182 this.manifestFormat = manifestFormat; 183 dummyTrackOutput = new DummyTrackOutput(); 184 } 185 bind(@ullable TrackOutputProvider trackOutputProvider, long endTimeUs)186 public void bind(@Nullable TrackOutputProvider trackOutputProvider, long endTimeUs) { 187 if (trackOutputProvider == null) { 188 trackOutput = dummyTrackOutput; 189 return; 190 } 191 this.endTimeUs = endTimeUs; 192 trackOutput = trackOutputProvider.track(id, type); 193 if (sampleFormat != null) { 194 trackOutput.format(sampleFormat); 195 } 196 } 197 198 @Override format(Format format)199 public void format(Format format) { 200 sampleFormat = 201 manifestFormat != null ? format.withManifestFormatInfo(manifestFormat) : format; 202 castNonNull(trackOutput).format(sampleFormat); 203 } 204 205 @Override sampleData( DataReader input, int length, boolean allowEndOfInput, @SampleDataPart int sampleDataPart)206 public int sampleData( 207 DataReader input, int length, boolean allowEndOfInput, @SampleDataPart int sampleDataPart) 208 throws IOException { 209 return castNonNull(trackOutput).sampleData(input, length, allowEndOfInput); 210 } 211 212 @Override sampleData(ParsableByteArray data, int length, @SampleDataPart int sampleDataPart)213 public void sampleData(ParsableByteArray data, int length, @SampleDataPart int sampleDataPart) { 214 castNonNull(trackOutput).sampleData(data, length); 215 } 216 217 @Override sampleMetadata( long timeUs, @C.BufferFlags int flags, int size, int offset, @Nullable CryptoData cryptoData)218 public void sampleMetadata( 219 long timeUs, 220 @C.BufferFlags int flags, 221 int size, 222 int offset, 223 @Nullable CryptoData cryptoData) { 224 if (endTimeUs != C.TIME_UNSET && timeUs >= endTimeUs) { 225 trackOutput = dummyTrackOutput; 226 } 227 castNonNull(trackOutput).sampleMetadata(timeUs, flags, size, offset, cryptoData); 228 } 229 } 230 } 231