• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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