• 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.dash;
17 
18 import android.util.Pair;
19 import android.util.SparseArray;
20 import android.util.SparseIntArray;
21 import androidx.annotation.IntDef;
22 import androidx.annotation.Nullable;
23 import com.google.android.exoplayer2.C;
24 import com.google.android.exoplayer2.Format;
25 import com.google.android.exoplayer2.SeekParameters;
26 import com.google.android.exoplayer2.drm.DrmInitData;
27 import com.google.android.exoplayer2.drm.DrmSessionManager;
28 import com.google.android.exoplayer2.offline.StreamKey;
29 import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
30 import com.google.android.exoplayer2.source.EmptySampleStream;
31 import com.google.android.exoplayer2.source.MediaPeriod;
32 import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
33 import com.google.android.exoplayer2.source.SampleStream;
34 import com.google.android.exoplayer2.source.SequenceableLoader;
35 import com.google.android.exoplayer2.source.TrackGroup;
36 import com.google.android.exoplayer2.source.TrackGroupArray;
37 import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
38 import com.google.android.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream;
39 import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCallback;
40 import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler;
41 import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
42 import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
43 import com.google.android.exoplayer2.source.dash.manifest.Descriptor;
44 import com.google.android.exoplayer2.source.dash.manifest.EventStream;
45 import com.google.android.exoplayer2.source.dash.manifest.Period;
46 import com.google.android.exoplayer2.source.dash.manifest.Representation;
47 import com.google.android.exoplayer2.trackselection.TrackSelection;
48 import com.google.android.exoplayer2.upstream.Allocator;
49 import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
50 import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
51 import com.google.android.exoplayer2.upstream.TransferListener;
52 import com.google.android.exoplayer2.util.MimeTypes;
53 import com.google.android.exoplayer2.util.Util;
54 import java.io.IOException;
55 import java.lang.annotation.Documented;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.IdentityHashMap;
61 import java.util.List;
62 import java.util.regex.Matcher;
63 import java.util.regex.Pattern;
64 import org.checkerframework.checker.nullness.compatqual.NullableType;
65 
66 /** A DASH {@link MediaPeriod}. */
67 /* package */ final class DashMediaPeriod
68     implements MediaPeriod,
69         SequenceableLoader.Callback<ChunkSampleStream<DashChunkSource>>,
70         ChunkSampleStream.ReleaseCallback<DashChunkSource> {
71 
72   private static final Pattern CEA608_SERVICE_DESCRIPTOR_REGEX = Pattern.compile("CC([1-4])=(.+)");
73 
74   /* package */ final int id;
75   private final DashChunkSource.Factory chunkSourceFactory;
76   @Nullable private final TransferListener transferListener;
77   private final DrmSessionManager drmSessionManager;
78   private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
79   private final long elapsedRealtimeOffsetMs;
80   private final LoaderErrorThrower manifestLoaderErrorThrower;
81   private final Allocator allocator;
82   private final TrackGroupArray trackGroups;
83   private final TrackGroupInfo[] trackGroupInfos;
84   private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
85   private final PlayerEmsgHandler playerEmsgHandler;
86   private final IdentityHashMap<ChunkSampleStream<DashChunkSource>, PlayerTrackEmsgHandler>
87       trackEmsgHandlerBySampleStream;
88   private final EventDispatcher eventDispatcher;
89 
90   @Nullable private Callback callback;
91   private ChunkSampleStream<DashChunkSource>[] sampleStreams;
92   private EventSampleStream[] eventSampleStreams;
93   private SequenceableLoader compositeSequenceableLoader;
94   private DashManifest manifest;
95   private int periodIndex;
96   private List<EventStream> eventStreams;
97   private boolean notifiedReadingStarted;
98 
DashMediaPeriod( int id, DashManifest manifest, int periodIndex, DashChunkSource.Factory chunkSourceFactory, @Nullable TransferListener transferListener, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, EventDispatcher eventDispatcher, long elapsedRealtimeOffsetMs, LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, PlayerEmsgCallback playerEmsgCallback)99   public DashMediaPeriod(
100       int id,
101       DashManifest manifest,
102       int periodIndex,
103       DashChunkSource.Factory chunkSourceFactory,
104       @Nullable TransferListener transferListener,
105       DrmSessionManager drmSessionManager,
106       LoadErrorHandlingPolicy loadErrorHandlingPolicy,
107       EventDispatcher eventDispatcher,
108       long elapsedRealtimeOffsetMs,
109       LoaderErrorThrower manifestLoaderErrorThrower,
110       Allocator allocator,
111       CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
112       PlayerEmsgCallback playerEmsgCallback) {
113     this.id = id;
114     this.manifest = manifest;
115     this.periodIndex = periodIndex;
116     this.chunkSourceFactory = chunkSourceFactory;
117     this.transferListener = transferListener;
118     this.drmSessionManager = drmSessionManager;
119     this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
120     this.eventDispatcher = eventDispatcher;
121     this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
122     this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
123     this.allocator = allocator;
124     this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
125     playerEmsgHandler = new PlayerEmsgHandler(manifest, playerEmsgCallback, allocator);
126     sampleStreams = newSampleStreamArray(0);
127     eventSampleStreams = new EventSampleStream[0];
128     trackEmsgHandlerBySampleStream = new IdentityHashMap<>();
129     compositeSequenceableLoader =
130         compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams);
131     Period period = manifest.getPeriod(periodIndex);
132     eventStreams = period.eventStreams;
133     Pair<TrackGroupArray, TrackGroupInfo[]> result =
134         buildTrackGroups(drmSessionManager, period.adaptationSets, eventStreams);
135     trackGroups = result.first;
136     trackGroupInfos = result.second;
137     eventDispatcher.mediaPeriodCreated();
138   }
139 
140   /**
141    * Updates the {@link DashManifest} and the index of this period in the manifest.
142    *
143    * @param manifest The updated manifest.
144    * @param periodIndex the new index of this period in the updated manifest.
145    */
updateManifest(DashManifest manifest, int periodIndex)146   public void updateManifest(DashManifest manifest, int periodIndex) {
147     this.manifest = manifest;
148     this.periodIndex = periodIndex;
149     playerEmsgHandler.updateManifest(manifest);
150     if (sampleStreams != null) {
151       for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
152         sampleStream.getChunkSource().updateManifest(manifest, periodIndex);
153       }
154       callback.onContinueLoadingRequested(this);
155     }
156     eventStreams = manifest.getPeriod(periodIndex).eventStreams;
157     for (EventSampleStream eventSampleStream : eventSampleStreams) {
158       for (EventStream eventStream : eventStreams) {
159         if (eventStream.id().equals(eventSampleStream.eventStreamId())) {
160           int lastPeriodIndex = manifest.getPeriodCount() - 1;
161           eventSampleStream.updateEventStream(
162               eventStream,
163               /* eventStreamAppendable= */ manifest.dynamic && periodIndex == lastPeriodIndex);
164           break;
165         }
166       }
167     }
168   }
169 
release()170   public void release() {
171     playerEmsgHandler.release();
172     for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
173       sampleStream.release(this);
174     }
175     callback = null;
176     eventDispatcher.mediaPeriodReleased();
177   }
178 
179   // ChunkSampleStream.ReleaseCallback implementation.
180 
181   @Override
onSampleStreamReleased(ChunkSampleStream<DashChunkSource> stream)182   public synchronized void onSampleStreamReleased(ChunkSampleStream<DashChunkSource> stream) {
183     PlayerTrackEmsgHandler trackEmsgHandler = trackEmsgHandlerBySampleStream.remove(stream);
184     if (trackEmsgHandler != null) {
185       trackEmsgHandler.release();
186     }
187   }
188 
189   // MediaPeriod implementation.
190 
191   @Override
prepare(Callback callback, long positionUs)192   public void prepare(Callback callback, long positionUs) {
193     this.callback = callback;
194     callback.onPrepared(this);
195   }
196 
197   @Override
maybeThrowPrepareError()198   public void maybeThrowPrepareError() throws IOException {
199     manifestLoaderErrorThrower.maybeThrowError();
200   }
201 
202   @Override
getTrackGroups()203   public TrackGroupArray getTrackGroups() {
204     return trackGroups;
205   }
206 
207   @Override
getStreamKeys(List<TrackSelection> trackSelections)208   public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
209     List<AdaptationSet> manifestAdaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
210     List<StreamKey> streamKeys = new ArrayList<>();
211     for (TrackSelection trackSelection : trackSelections) {
212       int trackGroupIndex = trackGroups.indexOf(trackSelection.getTrackGroup());
213       TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex];
214       if (trackGroupInfo.trackGroupCategory != TrackGroupInfo.CATEGORY_PRIMARY) {
215         // Ignore non-primary tracks.
216         continue;
217       }
218       int[] adaptationSetIndices = trackGroupInfo.adaptationSetIndices;
219       int[] trackIndices = new int[trackSelection.length()];
220       for (int i = 0; i < trackSelection.length(); i++) {
221         trackIndices[i] = trackSelection.getIndexInTrackGroup(i);
222       }
223       Arrays.sort(trackIndices);
224 
225       int currentAdaptationSetIndex = 0;
226       int totalTracksInPreviousAdaptationSets = 0;
227       int tracksInCurrentAdaptationSet =
228           manifestAdaptationSets.get(adaptationSetIndices[0]).representations.size();
229       for (int trackIndex : trackIndices) {
230         while (trackIndex >= totalTracksInPreviousAdaptationSets + tracksInCurrentAdaptationSet) {
231           currentAdaptationSetIndex++;
232           totalTracksInPreviousAdaptationSets += tracksInCurrentAdaptationSet;
233           tracksInCurrentAdaptationSet =
234               manifestAdaptationSets
235                   .get(adaptationSetIndices[currentAdaptationSetIndex])
236                   .representations
237                   .size();
238         }
239         streamKeys.add(
240             new StreamKey(
241                 periodIndex,
242                 adaptationSetIndices[currentAdaptationSetIndex],
243                 trackIndex - totalTracksInPreviousAdaptationSets));
244       }
245     }
246     return streamKeys;
247   }
248 
249   @Override
selectTracks( @ullableType TrackSelection[] selections, boolean[] mayRetainStreamFlags, @NullableType SampleStream[] streams, boolean[] streamResetFlags, long positionUs)250   public long selectTracks(
251       @NullableType TrackSelection[] selections,
252       boolean[] mayRetainStreamFlags,
253       @NullableType SampleStream[] streams,
254       boolean[] streamResetFlags,
255       long positionUs) {
256     int[] streamIndexToTrackGroupIndex = getStreamIndexToTrackGroupIndex(selections);
257     releaseDisabledStreams(selections, mayRetainStreamFlags, streams);
258     releaseOrphanEmbeddedStreams(selections, streams, streamIndexToTrackGroupIndex);
259     selectNewStreams(
260         selections, streams, streamResetFlags, positionUs, streamIndexToTrackGroupIndex);
261 
262     ArrayList<ChunkSampleStream<DashChunkSource>> sampleStreamList = new ArrayList<>();
263     ArrayList<EventSampleStream> eventSampleStreamList = new ArrayList<>();
264     for (SampleStream sampleStream : streams) {
265       if (sampleStream instanceof ChunkSampleStream) {
266         @SuppressWarnings("unchecked")
267         ChunkSampleStream<DashChunkSource> stream =
268             (ChunkSampleStream<DashChunkSource>) sampleStream;
269         sampleStreamList.add(stream);
270       } else if (sampleStream instanceof EventSampleStream) {
271         eventSampleStreamList.add((EventSampleStream) sampleStream);
272       }
273     }
274     sampleStreams = newSampleStreamArray(sampleStreamList.size());
275     sampleStreamList.toArray(sampleStreams);
276     eventSampleStreams = new EventSampleStream[eventSampleStreamList.size()];
277     eventSampleStreamList.toArray(eventSampleStreams);
278 
279     compositeSequenceableLoader =
280         compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams);
281     return positionUs;
282   }
283 
284   @Override
discardBuffer(long positionUs, boolean toKeyframe)285   public void discardBuffer(long positionUs, boolean toKeyframe) {
286     for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
287       sampleStream.discardBuffer(positionUs, toKeyframe);
288     }
289   }
290 
291   @Override
reevaluateBuffer(long positionUs)292   public void reevaluateBuffer(long positionUs) {
293     compositeSequenceableLoader.reevaluateBuffer(positionUs);
294   }
295 
296   @Override
continueLoading(long positionUs)297   public boolean continueLoading(long positionUs) {
298     return compositeSequenceableLoader.continueLoading(positionUs);
299   }
300 
301   @Override
isLoading()302   public boolean isLoading() {
303     return compositeSequenceableLoader.isLoading();
304   }
305 
306   @Override
getNextLoadPositionUs()307   public long getNextLoadPositionUs() {
308     return compositeSequenceableLoader.getNextLoadPositionUs();
309   }
310 
311   @Override
readDiscontinuity()312   public long readDiscontinuity() {
313     if (!notifiedReadingStarted) {
314       eventDispatcher.readingStarted();
315       notifiedReadingStarted = true;
316     }
317     return C.TIME_UNSET;
318   }
319 
320   @Override
getBufferedPositionUs()321   public long getBufferedPositionUs() {
322     return compositeSequenceableLoader.getBufferedPositionUs();
323   }
324 
325   @Override
seekToUs(long positionUs)326   public long seekToUs(long positionUs) {
327     for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
328       sampleStream.seekToUs(positionUs);
329     }
330     for (EventSampleStream sampleStream : eventSampleStreams) {
331       sampleStream.seekToUs(positionUs);
332     }
333     return positionUs;
334   }
335 
336   @Override
getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters)337   public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
338     for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
339       if (sampleStream.primaryTrackType == C.TRACK_TYPE_VIDEO) {
340         return sampleStream.getAdjustedSeekPositionUs(positionUs, seekParameters);
341       }
342     }
343     return positionUs;
344   }
345 
346   // SequenceableLoader.Callback implementation.
347 
348   @Override
onContinueLoadingRequested(ChunkSampleStream<DashChunkSource> sampleStream)349   public void onContinueLoadingRequested(ChunkSampleStream<DashChunkSource> sampleStream) {
350     callback.onContinueLoadingRequested(this);
351   }
352 
353   // Internal methods.
354 
getStreamIndexToTrackGroupIndex(TrackSelection[] selections)355   private int[] getStreamIndexToTrackGroupIndex(TrackSelection[] selections) {
356     int[] streamIndexToTrackGroupIndex = new int[selections.length];
357     for (int i = 0; i < selections.length; i++) {
358       if (selections[i] != null) {
359         streamIndexToTrackGroupIndex[i] = trackGroups.indexOf(selections[i].getTrackGroup());
360       } else {
361         streamIndexToTrackGroupIndex[i] = C.INDEX_UNSET;
362       }
363     }
364     return streamIndexToTrackGroupIndex;
365   }
366 
releaseDisabledStreams( TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams)367   private void releaseDisabledStreams(
368       TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams) {
369     for (int i = 0; i < selections.length; i++) {
370       if (selections[i] == null || !mayRetainStreamFlags[i]) {
371         if (streams[i] instanceof ChunkSampleStream) {
372           @SuppressWarnings("unchecked")
373           ChunkSampleStream<DashChunkSource> stream =
374               (ChunkSampleStream<DashChunkSource>) streams[i];
375           stream.release(this);
376         } else if (streams[i] instanceof EmbeddedSampleStream) {
377           ((EmbeddedSampleStream) streams[i]).release();
378         }
379         streams[i] = null;
380       }
381     }
382   }
383 
releaseOrphanEmbeddedStreams( TrackSelection[] selections, SampleStream[] streams, int[] streamIndexToTrackGroupIndex)384   private void releaseOrphanEmbeddedStreams(
385       TrackSelection[] selections, SampleStream[] streams, int[] streamIndexToTrackGroupIndex) {
386     for (int i = 0; i < selections.length; i++) {
387       if (streams[i] instanceof EmptySampleStream || streams[i] instanceof EmbeddedSampleStream) {
388         // We need to release an embedded stream if the corresponding primary stream is released.
389         int primaryStreamIndex = getPrimaryStreamIndex(i, streamIndexToTrackGroupIndex);
390         boolean mayRetainStream;
391         if (primaryStreamIndex == C.INDEX_UNSET) {
392           // If the corresponding primary stream is not selected, we may retain an existing
393           // EmptySampleStream.
394           mayRetainStream = streams[i] instanceof EmptySampleStream;
395         } else {
396           // If the corresponding primary stream is selected, we may retain the embedded stream if
397           // the stream's parent still matches.
398           mayRetainStream =
399               (streams[i] instanceof EmbeddedSampleStream)
400                   && ((EmbeddedSampleStream) streams[i]).parent == streams[primaryStreamIndex];
401         }
402         if (!mayRetainStream) {
403           if (streams[i] instanceof EmbeddedSampleStream) {
404             ((EmbeddedSampleStream) streams[i]).release();
405           }
406           streams[i] = null;
407         }
408       }
409     }
410   }
411 
selectNewStreams( TrackSelection[] selections, SampleStream[] streams, boolean[] streamResetFlags, long positionUs, int[] streamIndexToTrackGroupIndex)412   private void selectNewStreams(
413       TrackSelection[] selections,
414       SampleStream[] streams,
415       boolean[] streamResetFlags,
416       long positionUs,
417       int[] streamIndexToTrackGroupIndex) {
418     // Create newly selected primary and event streams.
419     for (int i = 0; i < selections.length; i++) {
420       TrackSelection selection = selections[i];
421       if (selection == null) {
422         continue;
423       }
424       if (streams[i] == null) {
425         // Create new stream for selection.
426         streamResetFlags[i] = true;
427         int trackGroupIndex = streamIndexToTrackGroupIndex[i];
428         TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex];
429         if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_PRIMARY) {
430           streams[i] = buildSampleStream(trackGroupInfo, selection, positionUs);
431         } else if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_MANIFEST_EVENTS) {
432           EventStream eventStream = eventStreams.get(trackGroupInfo.eventStreamGroupIndex);
433           Format format = selection.getTrackGroup().getFormat(0);
434           streams[i] = new EventSampleStream(eventStream, format, manifest.dynamic);
435         }
436       } else if (streams[i] instanceof ChunkSampleStream) {
437         // Update selection in existing stream.
438         @SuppressWarnings("unchecked")
439         ChunkSampleStream<DashChunkSource> stream = (ChunkSampleStream<DashChunkSource>) streams[i];
440         stream.getChunkSource().updateTrackSelection(selection);
441       }
442     }
443     // Create newly selected embedded streams from the corresponding primary stream. Note that this
444     // second pass is needed because the primary stream may not have been created yet in a first
445     // pass if the index of the primary stream is greater than the index of the embedded stream.
446     for (int i = 0; i < selections.length; i++) {
447       if (streams[i] == null && selections[i] != null) {
448         int trackGroupIndex = streamIndexToTrackGroupIndex[i];
449         TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex];
450         if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_EMBEDDED) {
451           int primaryStreamIndex = getPrimaryStreamIndex(i, streamIndexToTrackGroupIndex);
452           if (primaryStreamIndex == C.INDEX_UNSET) {
453             // If an embedded track is selected without the corresponding primary track, create an
454             // empty sample stream instead.
455             streams[i] = new EmptySampleStream();
456           } else {
457             streams[i] =
458                 ((ChunkSampleStream) streams[primaryStreamIndex])
459                     .selectEmbeddedTrack(positionUs, trackGroupInfo.trackType);
460           }
461         }
462       }
463     }
464   }
465 
getPrimaryStreamIndex(int embeddedStreamIndex, int[] streamIndexToTrackGroupIndex)466   private int getPrimaryStreamIndex(int embeddedStreamIndex, int[] streamIndexToTrackGroupIndex) {
467     int embeddedTrackGroupIndex = streamIndexToTrackGroupIndex[embeddedStreamIndex];
468     if (embeddedTrackGroupIndex == C.INDEX_UNSET) {
469       return C.INDEX_UNSET;
470     }
471     int primaryTrackGroupIndex = trackGroupInfos[embeddedTrackGroupIndex].primaryTrackGroupIndex;
472     for (int i = 0; i < streamIndexToTrackGroupIndex.length; i++) {
473       int trackGroupIndex = streamIndexToTrackGroupIndex[i];
474       if (trackGroupIndex == primaryTrackGroupIndex
475           && trackGroupInfos[trackGroupIndex].trackGroupCategory
476               == TrackGroupInfo.CATEGORY_PRIMARY) {
477         return i;
478       }
479     }
480     return C.INDEX_UNSET;
481   }
482 
buildTrackGroups( DrmSessionManager drmSessionManager, List<AdaptationSet> adaptationSets, List<EventStream> eventStreams)483   private static Pair<TrackGroupArray, TrackGroupInfo[]> buildTrackGroups(
484       DrmSessionManager drmSessionManager,
485       List<AdaptationSet> adaptationSets,
486       List<EventStream> eventStreams) {
487     int[][] groupedAdaptationSetIndices = getGroupedAdaptationSetIndices(adaptationSets);
488 
489     int primaryGroupCount = groupedAdaptationSetIndices.length;
490     boolean[] primaryGroupHasEventMessageTrackFlags = new boolean[primaryGroupCount];
491     Format[][] primaryGroupCea608TrackFormats = new Format[primaryGroupCount][];
492     int totalEmbeddedTrackGroupCount =
493         identifyEmbeddedTracks(
494             primaryGroupCount,
495             adaptationSets,
496             groupedAdaptationSetIndices,
497             primaryGroupHasEventMessageTrackFlags,
498             primaryGroupCea608TrackFormats);
499 
500     int totalGroupCount = primaryGroupCount + totalEmbeddedTrackGroupCount + eventStreams.size();
501     TrackGroup[] trackGroups = new TrackGroup[totalGroupCount];
502     TrackGroupInfo[] trackGroupInfos = new TrackGroupInfo[totalGroupCount];
503 
504     int trackGroupCount =
505         buildPrimaryAndEmbeddedTrackGroupInfos(
506             drmSessionManager,
507             adaptationSets,
508             groupedAdaptationSetIndices,
509             primaryGroupCount,
510             primaryGroupHasEventMessageTrackFlags,
511             primaryGroupCea608TrackFormats,
512             trackGroups,
513             trackGroupInfos);
514 
515     buildManifestEventTrackGroupInfos(eventStreams, trackGroups, trackGroupInfos, trackGroupCount);
516 
517     return Pair.create(new TrackGroupArray(trackGroups), trackGroupInfos);
518   }
519 
520   /**
521    * Groups adaptation sets. Two adaptations sets belong to the same group if either:
522    *
523    * <ul>
524    *   <li>One is a trick-play adaptation set and uses a {@code
525    *       http://dashif.org/guidelines/trickmode} essential or supplemental property to indicate
526    *       that the other is the main adaptation set to which it corresponds.
527    *   <li>The two adaptation sets are marked as safe for switching using {@code
528    *       urn:mpeg:dash:adaptation-set-switching:2016} supplemental properties.
529    * </ul>
530    *
531    * @param adaptationSets The adaptation sets to merge.
532    * @return An array of groups, where each group is an array of adaptation set indices.
533    */
getGroupedAdaptationSetIndices(List<AdaptationSet> adaptationSets)534   private static int[][] getGroupedAdaptationSetIndices(List<AdaptationSet> adaptationSets) {
535     int adaptationSetCount = adaptationSets.size();
536     SparseIntArray adaptationSetIdToIndex = new SparseIntArray(adaptationSetCount);
537     List<List<Integer>> adaptationSetGroupedIndices = new ArrayList<>(adaptationSetCount);
538     SparseArray<List<Integer>> adaptationSetIndexToGroupedIndices =
539         new SparseArray<>(adaptationSetCount);
540 
541     // Initially make each adaptation set belong to its own group. Also build the
542     // adaptationSetIdToIndex map.
543     for (int i = 0; i < adaptationSetCount; i++) {
544       adaptationSetIdToIndex.put(adaptationSets.get(i).id, i);
545       List<Integer> initialGroup = new ArrayList<>();
546       initialGroup.add(i);
547       adaptationSetGroupedIndices.add(initialGroup);
548       adaptationSetIndexToGroupedIndices.put(i, initialGroup);
549     }
550 
551     // Merge adaptation set groups.
552     for (int i = 0; i < adaptationSetCount; i++) {
553       int mergedGroupIndex = i;
554       AdaptationSet adaptationSet = adaptationSets.get(i);
555 
556       // Trick-play adaptation sets are merged with their corresponding main adaptation sets.
557       @Nullable
558       Descriptor trickPlayProperty = findTrickPlayProperty(adaptationSet.essentialProperties);
559       if (trickPlayProperty == null) {
560         // Trick-play can also be specified using a supplemental property.
561         trickPlayProperty = findTrickPlayProperty(adaptationSet.supplementalProperties);
562       }
563       if (trickPlayProperty != null) {
564         int mainAdaptationSetId = Integer.parseInt(trickPlayProperty.value);
565         int mainAdaptationSetIndex =
566             adaptationSetIdToIndex.get(mainAdaptationSetId, /* valueIfKeyNotFound= */ -1);
567         if (mainAdaptationSetIndex != -1) {
568           mergedGroupIndex = mainAdaptationSetIndex;
569         }
570       }
571 
572       // Adaptation sets that are safe for switching are merged, using the smallest index for the
573       // merged group.
574       if (mergedGroupIndex == i) {
575         @Nullable
576         Descriptor adaptationSetSwitchingProperty =
577             findAdaptationSetSwitchingProperty(adaptationSet.supplementalProperties);
578         if (adaptationSetSwitchingProperty != null) {
579           String[] otherAdaptationSetIds = Util.split(adaptationSetSwitchingProperty.value, ",");
580           for (String adaptationSetId : otherAdaptationSetIds) {
581             int otherAdaptationSetId =
582                 adaptationSetIdToIndex.get(
583                     Integer.parseInt(adaptationSetId), /* valueIfKeyNotFound= */ -1);
584             if (otherAdaptationSetId != -1) {
585               mergedGroupIndex = Math.min(mergedGroupIndex, otherAdaptationSetId);
586             }
587           }
588         }
589       }
590 
591       // Merge the groups if necessary.
592       if (mergedGroupIndex != i) {
593         List<Integer> thisGroup = adaptationSetIndexToGroupedIndices.get(i);
594         List<Integer> mergedGroup = adaptationSetIndexToGroupedIndices.get(mergedGroupIndex);
595         mergedGroup.addAll(thisGroup);
596         adaptationSetIndexToGroupedIndices.put(i, mergedGroup);
597         adaptationSetGroupedIndices.remove(thisGroup);
598       }
599     }
600 
601     int[][] groupedAdaptationSetIndices = new int[adaptationSetGroupedIndices.size()][];
602     for (int i = 0; i < groupedAdaptationSetIndices.length; i++) {
603       groupedAdaptationSetIndices[i] = Util.toArray(adaptationSetGroupedIndices.get(i));
604       // Restore the original adaptation set order within each group.
605       Arrays.sort(groupedAdaptationSetIndices[i]);
606     }
607     return groupedAdaptationSetIndices;
608   }
609 
610   /**
611    * Iterates through list of primary track groups and identifies embedded tracks.
612    *
613    * @param primaryGroupCount The number of primary track groups.
614    * @param adaptationSets The list of {@link AdaptationSet} of the current DASH period.
615    * @param groupedAdaptationSetIndices The indices of {@link AdaptationSet} that belongs to the
616    *     same primary group, grouped in primary track groups order.
617    * @param primaryGroupHasEventMessageTrackFlags An output array to be filled with flags indicating
618    *     whether each of the primary track groups contains an embedded event message track.
619    * @param primaryGroupCea608TrackFormats An output array to be filled with track formats for
620    *     CEA-608 tracks embedded in each of the primary track groups.
621    * @return Total number of embedded track groups.
622    */
identifyEmbeddedTracks( int primaryGroupCount, List<AdaptationSet> adaptationSets, int[][] groupedAdaptationSetIndices, boolean[] primaryGroupHasEventMessageTrackFlags, Format[][] primaryGroupCea608TrackFormats)623   private static int identifyEmbeddedTracks(
624       int primaryGroupCount,
625       List<AdaptationSet> adaptationSets,
626       int[][] groupedAdaptationSetIndices,
627       boolean[] primaryGroupHasEventMessageTrackFlags,
628       Format[][] primaryGroupCea608TrackFormats) {
629     int numEmbeddedTrackGroups = 0;
630     for (int i = 0; i < primaryGroupCount; i++) {
631       if (hasEventMessageTrack(adaptationSets, groupedAdaptationSetIndices[i])) {
632         primaryGroupHasEventMessageTrackFlags[i] = true;
633         numEmbeddedTrackGroups++;
634       }
635       primaryGroupCea608TrackFormats[i] =
636           getCea608TrackFormats(adaptationSets, groupedAdaptationSetIndices[i]);
637       if (primaryGroupCea608TrackFormats[i].length != 0) {
638         numEmbeddedTrackGroups++;
639       }
640     }
641     return numEmbeddedTrackGroups;
642   }
643 
buildPrimaryAndEmbeddedTrackGroupInfos( DrmSessionManager drmSessionManager, List<AdaptationSet> adaptationSets, int[][] groupedAdaptationSetIndices, int primaryGroupCount, boolean[] primaryGroupHasEventMessageTrackFlags, Format[][] primaryGroupCea608TrackFormats, TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos)644   private static int buildPrimaryAndEmbeddedTrackGroupInfos(
645       DrmSessionManager drmSessionManager,
646       List<AdaptationSet> adaptationSets,
647       int[][] groupedAdaptationSetIndices,
648       int primaryGroupCount,
649       boolean[] primaryGroupHasEventMessageTrackFlags,
650       Format[][] primaryGroupCea608TrackFormats,
651       TrackGroup[] trackGroups,
652       TrackGroupInfo[] trackGroupInfos) {
653     int trackGroupCount = 0;
654     for (int i = 0; i < primaryGroupCount; i++) {
655       int[] adaptationSetIndices = groupedAdaptationSetIndices[i];
656       List<Representation> representations = new ArrayList<>();
657       for (int adaptationSetIndex : adaptationSetIndices) {
658         representations.addAll(adaptationSets.get(adaptationSetIndex).representations);
659       }
660       Format[] formats = new Format[representations.size()];
661       for (int j = 0; j < formats.length; j++) {
662         Format format = representations.get(j).format;
663         DrmInitData drmInitData = format.drmInitData;
664         if (drmInitData != null) {
665           format =
666               format.copyWithExoMediaCryptoType(
667                   drmSessionManager.getExoMediaCryptoType(drmInitData));
668         }
669         formats[j] = format;
670       }
671 
672       AdaptationSet firstAdaptationSet = adaptationSets.get(adaptationSetIndices[0]);
673       int primaryTrackGroupIndex = trackGroupCount++;
674       int eventMessageTrackGroupIndex =
675           primaryGroupHasEventMessageTrackFlags[i] ? trackGroupCount++ : C.INDEX_UNSET;
676       int cea608TrackGroupIndex =
677           primaryGroupCea608TrackFormats[i].length != 0 ? trackGroupCount++ : C.INDEX_UNSET;
678 
679       trackGroups[primaryTrackGroupIndex] = new TrackGroup(formats);
680       trackGroupInfos[primaryTrackGroupIndex] =
681           TrackGroupInfo.primaryTrack(
682               firstAdaptationSet.type,
683               adaptationSetIndices,
684               primaryTrackGroupIndex,
685               eventMessageTrackGroupIndex,
686               cea608TrackGroupIndex);
687       if (eventMessageTrackGroupIndex != C.INDEX_UNSET) {
688         Format format =
689             new Format.Builder()
690                 .setId(firstAdaptationSet.id + ":emsg")
691                 .setSampleMimeType(MimeTypes.APPLICATION_EMSG)
692                 .build();
693         trackGroups[eventMessageTrackGroupIndex] = new TrackGroup(format);
694         trackGroupInfos[eventMessageTrackGroupIndex] =
695             TrackGroupInfo.embeddedEmsgTrack(adaptationSetIndices, primaryTrackGroupIndex);
696       }
697       if (cea608TrackGroupIndex != C.INDEX_UNSET) {
698         trackGroups[cea608TrackGroupIndex] = new TrackGroup(primaryGroupCea608TrackFormats[i]);
699         trackGroupInfos[cea608TrackGroupIndex] =
700             TrackGroupInfo.embeddedCea608Track(adaptationSetIndices, primaryTrackGroupIndex);
701       }
702     }
703     return trackGroupCount;
704   }
705 
buildManifestEventTrackGroupInfos(List<EventStream> eventStreams, TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos, int existingTrackGroupCount)706   private static void buildManifestEventTrackGroupInfos(List<EventStream> eventStreams,
707       TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos, int existingTrackGroupCount) {
708     for (int i = 0; i < eventStreams.size(); i++) {
709       EventStream eventStream = eventStreams.get(i);
710       Format format =
711           new Format.Builder()
712               .setId(eventStream.id())
713               .setSampleMimeType(MimeTypes.APPLICATION_EMSG)
714               .build();
715       trackGroups[existingTrackGroupCount] = new TrackGroup(format);
716       trackGroupInfos[existingTrackGroupCount++] = TrackGroupInfo.mpdEventTrack(i);
717     }
718   }
719 
buildSampleStream(TrackGroupInfo trackGroupInfo, TrackSelection selection, long positionUs)720   private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackGroupInfo trackGroupInfo,
721       TrackSelection selection, long positionUs) {
722     int embeddedTrackCount = 0;
723     boolean enableEventMessageTrack =
724         trackGroupInfo.embeddedEventMessageTrackGroupIndex != C.INDEX_UNSET;
725     TrackGroup embeddedEventMessageTrackGroup = null;
726     if (enableEventMessageTrack) {
727       embeddedEventMessageTrackGroup =
728           trackGroups.get(trackGroupInfo.embeddedEventMessageTrackGroupIndex);
729       embeddedTrackCount++;
730     }
731     boolean enableCea608Tracks = trackGroupInfo.embeddedCea608TrackGroupIndex != C.INDEX_UNSET;
732     TrackGroup embeddedCea608TrackGroup = null;
733     if (enableCea608Tracks) {
734       embeddedCea608TrackGroup = trackGroups.get(trackGroupInfo.embeddedCea608TrackGroupIndex);
735       embeddedTrackCount += embeddedCea608TrackGroup.length;
736     }
737 
738     Format[] embeddedTrackFormats = new Format[embeddedTrackCount];
739     int[] embeddedTrackTypes = new int[embeddedTrackCount];
740     embeddedTrackCount = 0;
741     if (enableEventMessageTrack) {
742       embeddedTrackFormats[embeddedTrackCount] = embeddedEventMessageTrackGroup.getFormat(0);
743       embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_METADATA;
744       embeddedTrackCount++;
745     }
746     List<Format> embeddedCea608TrackFormats = new ArrayList<>();
747     if (enableCea608Tracks) {
748       for (int i = 0; i < embeddedCea608TrackGroup.length; i++) {
749         embeddedTrackFormats[embeddedTrackCount] = embeddedCea608TrackGroup.getFormat(i);
750         embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_TEXT;
751         embeddedCea608TrackFormats.add(embeddedTrackFormats[embeddedTrackCount]);
752         embeddedTrackCount++;
753       }
754     }
755 
756     PlayerTrackEmsgHandler trackPlayerEmsgHandler =
757         manifest.dynamic && enableEventMessageTrack
758             ? playerEmsgHandler.newPlayerTrackEmsgHandler()
759             : null;
760     DashChunkSource chunkSource =
761         chunkSourceFactory.createDashChunkSource(
762             manifestLoaderErrorThrower,
763             manifest,
764             periodIndex,
765             trackGroupInfo.adaptationSetIndices,
766             selection,
767             trackGroupInfo.trackType,
768             elapsedRealtimeOffsetMs,
769             enableEventMessageTrack,
770             embeddedCea608TrackFormats,
771             trackPlayerEmsgHandler,
772             transferListener);
773     ChunkSampleStream<DashChunkSource> stream =
774         new ChunkSampleStream<>(
775             trackGroupInfo.trackType,
776             embeddedTrackTypes,
777             embeddedTrackFormats,
778             chunkSource,
779             this,
780             allocator,
781             positionUs,
782             drmSessionManager,
783             loadErrorHandlingPolicy,
784             eventDispatcher);
785     synchronized (this) {
786       // The map is also accessed on the loading thread so synchronize access.
787       trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler);
788     }
789     return stream;
790   }
791 
792   @Nullable
findAdaptationSetSwitchingProperty(List<Descriptor> descriptors)793   private static Descriptor findAdaptationSetSwitchingProperty(List<Descriptor> descriptors) {
794     return findDescriptor(descriptors, "urn:mpeg:dash:adaptation-set-switching:2016");
795   }
796 
797   @Nullable
findTrickPlayProperty(List<Descriptor> descriptors)798   private static Descriptor findTrickPlayProperty(List<Descriptor> descriptors) {
799     return findDescriptor(descriptors, "http://dashif.org/guidelines/trickmode");
800   }
801 
802   @Nullable
findDescriptor(List<Descriptor> descriptors, String schemeIdUri)803   private static Descriptor findDescriptor(List<Descriptor> descriptors, String schemeIdUri) {
804     for (int i = 0; i < descriptors.size(); i++) {
805       Descriptor descriptor = descriptors.get(i);
806       if (schemeIdUri.equals(descriptor.schemeIdUri)) {
807         return descriptor;
808       }
809     }
810     return null;
811   }
812 
hasEventMessageTrack(List<AdaptationSet> adaptationSets, int[] adaptationSetIndices)813   private static boolean hasEventMessageTrack(List<AdaptationSet> adaptationSets,
814       int[] adaptationSetIndices) {
815     for (int i : adaptationSetIndices) {
816       List<Representation> representations = adaptationSets.get(i).representations;
817       for (int j = 0; j < representations.size(); j++) {
818         Representation representation = representations.get(j);
819         if (!representation.inbandEventStreams.isEmpty()) {
820           return true;
821         }
822       }
823     }
824     return false;
825   }
826 
getCea608TrackFormats( List<AdaptationSet> adaptationSets, int[] adaptationSetIndices)827   private static Format[] getCea608TrackFormats(
828       List<AdaptationSet> adaptationSets, int[] adaptationSetIndices) {
829     for (int i : adaptationSetIndices) {
830       AdaptationSet adaptationSet = adaptationSets.get(i);
831       List<Descriptor> descriptors = adaptationSets.get(i).accessibilityDescriptors;
832       for (int j = 0; j < descriptors.size(); j++) {
833         Descriptor descriptor = descriptors.get(j);
834         if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) {
835           @Nullable String value = descriptor.value;
836           if (value == null) {
837             // There are embedded CEA-608 tracks, but service information is not declared.
838             return new Format[] {buildCea608TrackFormat(adaptationSet.id)};
839           }
840           String[] services = Util.split(value, ";");
841           Format[] formats = new Format[services.length];
842           for (int k = 0; k < services.length; k++) {
843             Matcher matcher = CEA608_SERVICE_DESCRIPTOR_REGEX.matcher(services[k]);
844             if (!matcher.matches()) {
845               // If we can't parse service information for all services, assume a single track.
846               return new Format[] {buildCea608TrackFormat(adaptationSet.id)};
847             }
848             formats[k] =
849                 buildCea608TrackFormat(
850                     adaptationSet.id,
851                     /* language= */ matcher.group(2),
852                     /* accessibilityChannel= */ Integer.parseInt(matcher.group(1)));
853           }
854           return formats;
855         }
856       }
857     }
858     return new Format[0];
859   }
860 
buildCea608TrackFormat(int adaptationSetId)861   private static Format buildCea608TrackFormat(int adaptationSetId) {
862     return buildCea608TrackFormat(
863         adaptationSetId, /* language= */ null, /* accessibilityChannel= */ Format.NO_VALUE);
864   }
865 
buildCea608TrackFormat( int adaptationSetId, @Nullable String language, int accessibilityChannel)866   private static Format buildCea608TrackFormat(
867       int adaptationSetId, @Nullable String language, int accessibilityChannel) {
868     String id =
869         adaptationSetId
870             + ":cea608"
871             + (accessibilityChannel != Format.NO_VALUE ? ":" + accessibilityChannel : "");
872     return new Format.Builder()
873         .setId(id)
874         .setSampleMimeType(MimeTypes.APPLICATION_CEA608)
875         .setLanguage(language)
876         .setAccessibilityChannel(accessibilityChannel)
877         .build();
878   }
879 
880   // We won't assign the array to a variable that erases the generic type, and then write into it.
881   @SuppressWarnings({"unchecked", "rawtypes"})
newSampleStreamArray(int length)882   private static ChunkSampleStream<DashChunkSource>[] newSampleStreamArray(int length) {
883     return new ChunkSampleStream[length];
884   }
885 
886   private static final class TrackGroupInfo {
887 
888     @Documented
889     @Retention(RetentionPolicy.SOURCE)
890     @IntDef({CATEGORY_PRIMARY, CATEGORY_EMBEDDED, CATEGORY_MANIFEST_EVENTS})
891     public @interface TrackGroupCategory {}
892 
893     /**
894      * A normal track group that has its samples drawn from the stream.
895      * For example: a video Track Group or an audio Track Group.
896      */
897     private static final int CATEGORY_PRIMARY = 0;
898 
899     /**
900      * A track group whose samples are embedded within one of the primary streams. For example: an
901      * EMSG track has its sample embedded in emsg atoms in one of the primary streams.
902      */
903     private static final int CATEGORY_EMBEDDED = 1;
904 
905     /**
906      * A track group that has its samples listed explicitly in the DASH manifest file.
907      * For example: an EventStream track has its sample (Events) included directly in the DASH
908      * manifest file.
909      */
910     private static final int CATEGORY_MANIFEST_EVENTS = 2;
911 
912     public final int[] adaptationSetIndices;
913     public final int trackType;
914     @TrackGroupCategory public final int trackGroupCategory;
915 
916     public final int eventStreamGroupIndex;
917     public final int primaryTrackGroupIndex;
918     public final int embeddedEventMessageTrackGroupIndex;
919     public final int embeddedCea608TrackGroupIndex;
920 
primaryTrack( int trackType, int[] adaptationSetIndices, int primaryTrackGroupIndex, int embeddedEventMessageTrackGroupIndex, int embeddedCea608TrackGroupIndex)921     public static TrackGroupInfo primaryTrack(
922         int trackType,
923         int[] adaptationSetIndices,
924         int primaryTrackGroupIndex,
925         int embeddedEventMessageTrackGroupIndex,
926         int embeddedCea608TrackGroupIndex) {
927       return new TrackGroupInfo(
928           trackType,
929           CATEGORY_PRIMARY,
930           adaptationSetIndices,
931           primaryTrackGroupIndex,
932           embeddedEventMessageTrackGroupIndex,
933           embeddedCea608TrackGroupIndex,
934           /* eventStreamGroupIndex= */ -1);
935     }
936 
embeddedEmsgTrack(int[] adaptationSetIndices, int primaryTrackGroupIndex)937     public static TrackGroupInfo embeddedEmsgTrack(int[] adaptationSetIndices,
938         int primaryTrackGroupIndex) {
939       return new TrackGroupInfo(
940           C.TRACK_TYPE_METADATA,
941           CATEGORY_EMBEDDED,
942           adaptationSetIndices,
943           primaryTrackGroupIndex,
944           C.INDEX_UNSET,
945           C.INDEX_UNSET,
946           /* eventStreamGroupIndex= */ -1);
947     }
948 
embeddedCea608Track(int[] adaptationSetIndices, int primaryTrackGroupIndex)949     public static TrackGroupInfo embeddedCea608Track(int[] adaptationSetIndices,
950         int primaryTrackGroupIndex) {
951       return new TrackGroupInfo(
952           C.TRACK_TYPE_TEXT,
953           CATEGORY_EMBEDDED,
954           adaptationSetIndices,
955           primaryTrackGroupIndex,
956           C.INDEX_UNSET,
957           C.INDEX_UNSET,
958           /* eventStreamGroupIndex= */ -1);
959     }
960 
mpdEventTrack(int eventStreamIndex)961     public static TrackGroupInfo mpdEventTrack(int eventStreamIndex) {
962       return new TrackGroupInfo(
963           C.TRACK_TYPE_METADATA,
964           CATEGORY_MANIFEST_EVENTS,
965           new int[0],
966           /* primaryTrackGroupIndex= */ -1,
967           C.INDEX_UNSET,
968           C.INDEX_UNSET,
969           eventStreamIndex);
970     }
971 
TrackGroupInfo( int trackType, @TrackGroupCategory int trackGroupCategory, int[] adaptationSetIndices, int primaryTrackGroupIndex, int embeddedEventMessageTrackGroupIndex, int embeddedCea608TrackGroupIndex, int eventStreamGroupIndex)972     private TrackGroupInfo(
973         int trackType,
974         @TrackGroupCategory int trackGroupCategory,
975         int[] adaptationSetIndices,
976         int primaryTrackGroupIndex,
977         int embeddedEventMessageTrackGroupIndex,
978         int embeddedCea608TrackGroupIndex,
979         int eventStreamGroupIndex) {
980       this.trackType = trackType;
981       this.adaptationSetIndices = adaptationSetIndices;
982       this.trackGroupCategory = trackGroupCategory;
983       this.primaryTrackGroupIndex = primaryTrackGroupIndex;
984       this.embeddedEventMessageTrackGroupIndex = embeddedEventMessageTrackGroupIndex;
985       this.embeddedCea608TrackGroupIndex = embeddedCea608TrackGroupIndex;
986       this.eventStreamGroupIndex = eventStreamGroupIndex;
987     }
988   }
989 
990 }
991