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