From 09471defd72e7416f7560a926a335421f6378b40 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 8 Mar 2017 04:09:05 -0800 Subject: [PATCH] Enabled EMSG and CEA-608 embedded streams for DASH Issue: #2362 Issue: #2176 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=149524412 --- .../source/chunk/BaseMediaChunkOutput.java | 2 +- .../source/chunk/ChunkSampleStream.java | 248 +++++++++++++----- .../source/dash/DashMediaPeriod.java | 140 ++++++---- .../source/smoothstreaming/SsMediaPeriod.java | 4 +- 4 files changed, 282 insertions(+), 112 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java index a429a7cab95..3882a330f9c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java @@ -35,7 +35,7 @@ * @param trackTypes The track types of the individual track outputs. * @param trackOutputs The individual track outputs. */ - public BaseMediaChunkOutput(int[] trackTypes, DefaultTrackOutput... trackOutputs) { + public BaseMediaChunkOutput(int[] trackTypes, DefaultTrackOutput[] trackOutputs) { this.trackTypes = trackTypes; this.trackOutputs = trackOutputs; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 909bf317b3a..1ae928045da 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -33,31 +33,34 @@ /** * A {@link SampleStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}. + * May also be configured to expose additional embedded {@link SampleStream}s. */ public class ChunkSampleStream implements SampleStream, SequenceableLoader, Loader.Callback { - private final int trackType; + private final int primaryTrackType; + private final int[] embeddedTrackTypes; private final T chunkSource; private final SequenceableLoader.Callback> callback; private final EventDispatcher eventDispatcher; private final int minLoadableRetryCount; + private final Loader loader; + private final ChunkHolder nextChunkHolder; private final LinkedList mediaChunks; private final List readOnlyMediaChunks; + private final DefaultTrackOutput primarySampleQueue; + private final EmbeddedSampleStream[] embeddedSampleStreams; private final BaseMediaChunkOutput mediaChunkOutput; - private final DefaultTrackOutput sampleQueue; - private final ChunkHolder nextChunkHolder; - private final Loader loader; - - private Format downstreamTrackFormat; - private long lastSeekPositionUs; + private Format primaryDownstreamTrackFormat; private long pendingResetPositionUs; - - private boolean loadingFinished; + /* package */ long lastSeekPositionUs; + /* package */ boolean loadingFinished; /** - * @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param primaryTrackType The type of the primary track. One of the {@link C} + * {@code TRACK_TYPE_*} constants. + * @param embeddedTrackTypes The types of any embedded tracks, or null. * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. * @param callback An {@link Callback} for the stream. * @param allocator An {@link Allocator} from which allocations can be obtained. @@ -66,10 +69,11 @@ public class ChunkSampleStream implements SampleStream, S * before propagating an error. * @param eventDispatcher A dispatcher to notify of events. */ - public ChunkSampleStream(int trackType, T chunkSource, - SequenceableLoader.Callback> callback, Allocator allocator, - long positionUs, int minLoadableRetryCount, EventDispatcher eventDispatcher) { - this.trackType = trackType; + public ChunkSampleStream(int primaryTrackType, int[] embeddedTrackTypes, T chunkSource, + Callback> callback, Allocator allocator, long positionUs, + int minLoadableRetryCount, EventDispatcher eventDispatcher) { + this.primaryTrackType = primaryTrackType; + this.embeddedTrackTypes = embeddedTrackTypes; this.chunkSource = chunkSource; this.callback = callback; this.eventDispatcher = eventDispatcher; @@ -78,16 +82,56 @@ public ChunkSampleStream(int trackType, T chunkSource, nextChunkHolder = new ChunkHolder(); mediaChunks = new LinkedList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); - sampleQueue = new DefaultTrackOutput(allocator); - mediaChunkOutput = new BaseMediaChunkOutput(new int[] {trackType}, sampleQueue); - lastSeekPositionUs = positionUs; + + int embeddedTrackCount = embeddedTrackTypes == null ? 0 : embeddedTrackTypes.length; + embeddedSampleStreams = newEmbeddedSampleStreamArray(embeddedTrackCount); + int[] trackTypes = new int[1 + embeddedTrackCount]; + DefaultTrackOutput[] sampleQueues = new DefaultTrackOutput[1 + embeddedTrackCount]; + + primarySampleQueue = new DefaultTrackOutput(allocator); + trackTypes[0] = primaryTrackType; + sampleQueues[0] = primarySampleQueue; + + for (int i = 0; i < embeddedTrackCount; i++) { + trackTypes[i + 1] = embeddedTrackTypes[i]; + sampleQueues[i + 1] = new DefaultTrackOutput(allocator); + embeddedSampleStreams[i] = new EmbeddedSampleStream(sampleQueues[i + 1]); + } + + mediaChunkOutput = new BaseMediaChunkOutput(trackTypes, sampleQueues); pendingResetPositionUs = positionUs; + lastSeekPositionUs = positionUs; + } + + /** + * Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}. + */ + public static boolean isPrimarySampleStream(SampleStream sampleStream) { + return sampleStream instanceof ChunkSampleStream; + } + + /** + * Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}. + */ + public static boolean isEmbeddedSampleStream(SampleStream sampleStream) { + return sampleStream instanceof ChunkSampleStream.EmbeddedSampleStream; + } + + /** + * Returns the {@link SampleStream} for the embedded track with the specified type. + */ + public SampleStream getEmbeddedSampleStream(int trackType) { + for (int i = 0; i < embeddedTrackTypes.length; i++) { + if (embeddedTrackTypes[i] == trackType) { + return embeddedSampleStreams[i]; + } + } + // Should never happen. + throw new IllegalStateException(); } /** * Returns the {@link ChunkSource} used by this stream. - * - * @return The {@link ChunkSource}. */ public T getChunkSource() { return chunkSource; @@ -112,7 +156,7 @@ public long getBufferedPositionUs() { if (lastCompletedMediaChunk != null) { bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); } - return Math.max(bufferedPositionUs, sampleQueue.getLargestQueuedTimestampUs()); + return Math.max(bufferedPositionUs, primarySampleQueue.getLargestQueuedTimestampUs()); } } @@ -123,15 +167,21 @@ public long getBufferedPositionUs() { */ public void seekToUs(long positionUs) { lastSeekPositionUs = positionUs; - // If we're not pending a reset, see if we can seek within the sample queue. - boolean seekInsideBuffer = !isPendingReset() - && sampleQueue.skipToKeyframeBefore(positionUs, positionUs < getNextLoadPositionUs()); + // If we're not pending a reset, see if we can seek within the primary sample queue. + boolean seekInsideBuffer = !isPendingReset() && primarySampleQueue.skipToKeyframeBefore( + positionUs, positionUs < getNextLoadPositionUs()); if (seekInsideBuffer) { - // We succeeded. All we need to do is discard any chunks that we've moved past. + // We succeeded. We need to discard any chunks that we've moved past and perform the seek for + // any embedded streams as well. while (mediaChunks.size() > 1 - && mediaChunks.get(1).getFirstSampleIndex(0) <= sampleQueue.getReadIndex()) { + && mediaChunks.get(1).getFirstSampleIndex(0) <= primarySampleQueue.getReadIndex()) { mediaChunks.removeFirst(); } + // TODO: For this to work correctly, the embedded streams must not discard anything from their + // sample queues beyond the current read position of the primary stream. + for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) { + embeddedSampleStream.skipToKeyframeBefore(positionUs); + } } else { // We failed, and need to restart. pendingResetPositionUs = positionUs; @@ -140,7 +190,10 @@ public void seekToUs(long positionUs) { if (loader.isLoading()) { loader.cancelLoading(); } else { - sampleQueue.reset(true); + primarySampleQueue.reset(true); + for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) { + embeddedSampleStream.reset(true); + } } } } @@ -151,7 +204,10 @@ public void seekToUs(long positionUs) { * This method should be called when the stream is no longer required. */ public void release() { - sampleQueue.disable(); + primarySampleQueue.disable(); + for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) { + embeddedSampleStream.disable(); + } loader.release(); } @@ -159,7 +215,7 @@ public void release() { @Override public boolean isReady() { - return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty()); + return loadingFinished || (!isPendingReset() && !primarySampleQueue.isEmpty()); } @Override @@ -176,27 +232,15 @@ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, if (isPendingReset()) { return C.RESULT_NOTHING_READ; } - - while (mediaChunks.size() > 1 - && mediaChunks.get(1).getFirstSampleIndex(0) <= sampleQueue.getReadIndex()) { - mediaChunks.removeFirst(); - } - BaseMediaChunk currentChunk = mediaChunks.getFirst(); - - Format trackFormat = currentChunk.trackFormat; - if (!trackFormat.equals(downstreamTrackFormat)) { - eventDispatcher.downstreamFormatChanged(trackType, trackFormat, - currentChunk.trackSelectionReason, currentChunk.trackSelectionData, - currentChunk.startTimeUs); - } - downstreamTrackFormat = trackFormat; - return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, + // TODO: For embedded streams that aren't being used, we need to drain their queues here. + discardDownstreamMediaChunks(primarySampleQueue.getReadIndex()); + return primarySampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs); } @Override public void skipToKeyframeBefore(long timeUs) { - sampleQueue.skipToKeyframeBefore(timeUs); + primarySampleQueue.skipToKeyframeBefore(timeUs); } // Loader.Callback implementation. @@ -204,20 +248,25 @@ public void skipToKeyframeBefore(long timeUs) { @Override public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) { chunkSource.onChunkLoadCompleted(loadable); - eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, - loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, - loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded()); callback.onContinueLoadingRequested(this); } @Override public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) { - eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, - loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, - loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded()); if (!released) { - sampleQueue.reset(true); + primarySampleQueue.reset(true); + for (EmbeddedSampleStream embeddedStream : embeddedSampleStreams) { + embeddedStream.reset(true); + } callback.onContinueLoadingRequested(this); } } @@ -234,16 +283,19 @@ public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDuration if (isMediaChunk) { BaseMediaChunk removed = mediaChunks.removeLast(); Assertions.checkState(removed == loadable); - sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); + primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); + for (int i = 0; i < embeddedSampleStreams.length; i++) { + embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); + } if (mediaChunks.isEmpty()) { pendingResetPositionUs = lastSeekPositionUs; } } } - eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, - loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, - loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded, error, - canceled); + eventDispatcher.loadError(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded, + error, canceled); if (canceled) { callback.onContinueLoadingRequested(this); return Loader.DONT_RETRY; @@ -283,9 +335,9 @@ public boolean continueLoading(long positionUs) { mediaChunks.add(mediaChunk); } long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); - eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, - loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, - loadable.endTimeUs, elapsedRealtimeMs); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs); return true; } @@ -316,10 +368,25 @@ private boolean isMediaChunk(Chunk chunk) { return chunk instanceof BaseMediaChunk; } - private boolean isPendingReset() { + /* package */ boolean isPendingReset() { return pendingResetPositionUs != C.TIME_UNSET; } + private void discardDownstreamMediaChunks(int primaryStreamReadIndex) { + while (mediaChunks.size() > 1 + && mediaChunks.get(1).getFirstSampleIndex(0) <= primaryStreamReadIndex) { + mediaChunks.removeFirst(); + } + BaseMediaChunk currentChunk = mediaChunks.getFirst(); + Format trackFormat = currentChunk.trackFormat; + if (!trackFormat.equals(primaryDownstreamTrackFormat)) { + eventDispatcher.downstreamFormatChanged(primaryTrackType, trackFormat, + currentChunk.trackSelectionReason, currentChunk.trackSelectionData, + currentChunk.startTimeUs); + } + primaryDownstreamTrackFormat = trackFormat; + } + /** * Discard upstream media chunks until the queue length is equal to the length specified. * @@ -332,16 +399,71 @@ private boolean discardUpstreamMediaChunks(int queueLength) { } long startTimeUs = 0; long endTimeUs = mediaChunks.getLast().endTimeUs; - BaseMediaChunk removed = null; while (mediaChunks.size() > queueLength) { removed = mediaChunks.removeLast(); startTimeUs = removed.startTimeUs; loadingFinished = false; } - sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); - eventDispatcher.upstreamDiscarded(trackType, startTimeUs, endTimeUs); + primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); + for (int i = 0; i < embeddedSampleStreams.length; i++) { + embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); + } + eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs); return true; } + @SuppressWarnings("unchecked") + private static ChunkSampleStream.EmbeddedSampleStream[] + newEmbeddedSampleStreamArray(int length) { + return new ChunkSampleStream.EmbeddedSampleStream[length]; + } + + private final class EmbeddedSampleStream implements SampleStream { + + private final DefaultTrackOutput sampleQueue; + + public EmbeddedSampleStream(DefaultTrackOutput sampleQueue) { + this.sampleQueue = sampleQueue; + } + + @Override + public boolean isReady() { + return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty()); + } + + @Override + public void skipToKeyframeBefore(long timeUs) { + sampleQueue.skipToKeyframeBefore(timeUs); + } + + @Override + public void maybeThrowError() throws IOException { + // Do nothing. Errors will be thrown from the primary stream. + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired) { + if (isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, + lastSeekPositionUs); + } + + public void reset(boolean enable) { + sampleQueue.reset(enable); + } + + public void disable() { + sampleQueue.disable(); + } + + public void discardUpstreamSamples(int discardFromIndex) { + sampleQueue.discardUpstreamSamples(discardFromIndex); + } + + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index e89deb53ab5..8905607bc19 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.dash; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; @@ -35,7 +36,8 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; /** @@ -52,6 +54,7 @@ private final LoaderErrorThrower manifestLoaderErrorThrower; private final Allocator allocator; private final TrackGroupArray trackGroups; + private final EmbeddedTrackInfo[] embeddedTrackInfos; private Callback callback; private ChunkSampleStream[] sampleStreams; @@ -76,7 +79,9 @@ public DashMediaPeriod(int id, DashManifest manifest, int periodIndex, sampleStreams = newSampleStreamArray(0); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; - trackGroups = buildTrackGroups(adaptationSets); + Pair result = buildTrackGroups(adaptationSets); + trackGroups = result.first; + embeddedTrackInfos = result.second; } public void updateManifest(DashManifest manifest, int periodIndex) { @@ -116,37 +121,59 @@ public TrackGroupArray getTrackGroups() { @Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { - ArrayList> sampleStreamsList = new ArrayList<>(); + int adaptationSetCount = adaptationSets.size(); + HashMap> primarySampleStreams = new HashMap<>(); + // First pass for primary tracks. for (int i = 0; i < selections.length; i++) { - if (streams[i] instanceof ChunkSampleStream) { + if (ChunkSampleStream.isPrimarySampleStream(streams[i])) { @SuppressWarnings("unchecked") ChunkSampleStream stream = (ChunkSampleStream) streams[i]; if (selections[i] == null || !mayRetainStreamFlags[i]) { stream.release(); streams[i] = null; } else { - sampleStreamsList.add(stream); + int adaptationSetIndex = trackGroups.indexOf(selections[i].getTrackGroup()); + primarySampleStreams.put(adaptationSetIndex, stream); } - } else if (streams[i] instanceof EmptySampleStream && selections[i] == null) { - // TODO: Release streams for cea-608 and emsg tracks. - streams[i] = null; } if (streams[i] == null && selections[i] != null) { - int adaptationSetIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - if (adaptationSetIndex < adaptationSets.size()) { - ChunkSampleStream stream = buildSampleStream(adaptationSetIndex, + int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); + if (trackGroupIndex < adaptationSetCount) { + ChunkSampleStream stream = buildSampleStream(trackGroupIndex, selections[i], positionUs); - sampleStreamsList.add(stream); + primarySampleStreams.put(trackGroupIndex, stream); streams[i] = stream; - } else { - // TODO: Output streams for cea-608 and emsg tracks. - streams[i] = new EmptySampleStream(); + streamResetFlags[i] = true; } - streamResetFlags[i] = true; } } - sampleStreams = newSampleStreamArray(sampleStreamsList.size()); - sampleStreamsList.toArray(sampleStreams); + // Second pass for embedded tracks. + for (int i = 0; i < selections.length; i++) { + if (ChunkSampleStream.isEmbeddedSampleStream(streams[i])) { + // Always clear even if the selection is unchanged, since the parent primary sample stream + // may have been replaced. + streams[i] = null; + } + if (streams[i] == null && selections[i] != null) { + int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); + if (trackGroupIndex >= adaptationSetCount) { + EmbeddedTrackInfo embeddedTrackInfo = + embeddedTrackInfos[trackGroupIndex - adaptationSetCount]; + int adaptationSetIndex = embeddedTrackInfo.adaptationSetIndex; + ChunkSampleStream primarySampleStream = + primarySampleStreams.get(adaptationSetIndex); + if (primarySampleStream != null) { + streams[i] = primarySampleStream.getEmbeddedSampleStream(embeddedTrackInfo.trackType); + } else { + // The primary track in which this one is embedded is not selected. + streams[i] = new EmptySampleStream(); + } + streamResetFlags[i] = true; + } + } + } + sampleStreams = newSampleStreamArray(primarySampleStreams.size()); + primarySampleStreams.values().toArray(sampleStreams); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); return positionUs; } @@ -195,14 +222,14 @@ public void onContinueLoadingRequested(ChunkSampleStream sample // Internal methods. - private static TrackGroupArray buildTrackGroups(List adaptationSets) { + private static Pair buildTrackGroups( + List adaptationSets) { int adaptationSetCount = adaptationSets.size(); - int eventMessageTrackCount = getEventMessageTrackCount(adaptationSets); - int cea608TrackCount = getCea608TrackCount(adaptationSets); - TrackGroup[] trackGroupArray = new TrackGroup[adaptationSetCount + eventMessageTrackCount - + cea608TrackCount]; - int eventMessageTrackIndex = 0; - int cea608TrackIndex = 0; + int embeddedTrackCount = getEmbeddedTrackCount(adaptationSets); + TrackGroup[] trackGroupArray = new TrackGroup[adaptationSetCount + embeddedTrackCount]; + EmbeddedTrackInfo[] embeddedTrackInfos = new EmbeddedTrackInfo[embeddedTrackCount]; + + int embeddedTrackIndex = 0; for (int i = 0; i < adaptationSetCount; i++) { AdaptationSet adaptationSet = adaptationSets.get(i); List representations = adaptationSet.representations; @@ -214,38 +241,57 @@ private static TrackGroupArray buildTrackGroups(List adaptationSe if (hasEventMessageTrack(adaptationSet)) { Format format = Format.createSampleFormat(adaptationSet.id + ":emsg", MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null); - trackGroupArray[adaptationSetCount + eventMessageTrackIndex++] = new TrackGroup(format); + trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format); + embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_METADATA); } if (hasCea608Track(adaptationSet)) { Format format = Format.createTextSampleFormat(adaptationSet.id + ":cea608", MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null); - trackGroupArray[adaptationSetCount + eventMessageTrackCount + cea608TrackIndex++] = - new TrackGroup(format); + trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format); + embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_TEXT); } } - return new TrackGroupArray(trackGroupArray); + + return Pair.create(new TrackGroupArray(trackGroupArray), embeddedTrackInfos); } private ChunkSampleStream buildSampleStream(int adaptationSetIndex, TrackSelection selection, long positionUs) { AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); + int embeddedTrackCount = 0; + int[] embeddedTrackTypes = new int[2]; boolean enableEventMessageTrack = hasEventMessageTrack(adaptationSet); + if (enableEventMessageTrack) { + embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_METADATA; + } boolean enableCea608Track = hasCea608Track(adaptationSet); + if (enableCea608Track) { + embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_TEXT; + } + if (embeddedTrackCount < embeddedTrackTypes.length) { + embeddedTrackTypes = Arrays.copyOf(embeddedTrackTypes, embeddedTrackCount); + } DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( manifestLoaderErrorThrower, manifest, periodIndex, adaptationSetIndex, selection, elapsedRealtimeOffset, enableEventMessageTrack, enableCea608Track); - return new ChunkSampleStream<>(adaptationSet.type, chunkSource, this, allocator, positionUs, - minLoadableRetryCount, eventDispatcher); + ChunkSampleStream stream = new ChunkSampleStream<>(adaptationSet.type, + embeddedTrackTypes, chunkSource, this, allocator, positionUs, minLoadableRetryCount, + eventDispatcher); + return stream; } - private static int getEventMessageTrackCount(List adaptationSets) { - int inbandEventStreamTrackCount = 0; + private static int getEmbeddedTrackCount(List adaptationSets) { + int embeddedTrackCount = 0; for (int i = 0; i < adaptationSets.size(); i++) { - if (hasEventMessageTrack(adaptationSets.get(i))) { - inbandEventStreamTrackCount++; + AdaptationSet adaptationSet = adaptationSets.get(i); + if (hasEventMessageTrack(adaptationSet)) { + embeddedTrackCount++; + } + if (hasCea608Track(adaptationSet)) { + embeddedTrackCount++; } } - return inbandEventStreamTrackCount; + return embeddedTrackCount; } private static boolean hasEventMessageTrack(AdaptationSet adaptationSet) { @@ -259,16 +305,6 @@ private static boolean hasEventMessageTrack(AdaptationSet adaptationSet) { return false; } - private static int getCea608TrackCount(List adaptationSets) { - int cea608TrackCount = 0; - for (int i = 0; i < adaptationSets.size(); i++) { - if (hasCea608Track(adaptationSets.get(i))) { - cea608TrackCount++; - } - } - return cea608TrackCount; - } - private static boolean hasCea608Track(AdaptationSet adaptationSet) { List descriptors = adaptationSet.accessibilityDescriptors; for (int i = 0; i < descriptors.size(); i++) { @@ -285,4 +321,16 @@ private static ChunkSampleStream[] newSampleStreamArray(int len return new ChunkSampleStream[length]; } + private static final class EmbeddedTrackInfo { + + public final int adaptationSetIndex; + public final int trackType; + + public EmbeddedTrackInfo(int adaptationSetIndex, int trackType) { + this.adaptationSetIndex = adaptationSetIndex; + this.trackType = trackType; + } + + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index fef2480fd6f..b9af9930dc6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -185,8 +185,8 @@ private ChunkSampleStream buildSampleStream(TrackSelection select int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup()); SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower, manifest, streamElementIndex, selection, trackEncryptionBoxes); - return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource, - this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); + return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, null, + chunkSource, this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); } private static TrackGroupArray buildTrackGroups(SsManifest manifest) {