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
This commit is contained in:
olly 2017-03-08 04:09:05 -08:00 committed by Oliver Woodman
parent 99e19a92af
commit 09471defd7
4 changed files with 283 additions and 113 deletions

View file

@ -35,7 +35,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOut
* @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;
}

View file

@ -33,31 +33,34 @@ import java.util.List;
/**
* 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<T extends ChunkSource> implements SampleStream, SequenceableLoader,
Loader.Callback<Chunk> {
private final int trackType;
private final int primaryTrackType;
private final int[] embeddedTrackTypes;
private final T chunkSource;
private final SequenceableLoader.Callback<ChunkSampleStream<T>> callback;
private final EventDispatcher eventDispatcher;
private final int minLoadableRetryCount;
private final Loader loader;
private final ChunkHolder nextChunkHolder;
private final LinkedList<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> 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<T extends ChunkSource> implements SampleStream, S
* before propagating an error.
* @param eventDispatcher A dispatcher to notify of events.
*/
public ChunkSampleStream(int trackType, T chunkSource,
SequenceableLoader.Callback<ChunkSampleStream<T>> callback, Allocator allocator,
long positionUs, int minLoadableRetryCount, EventDispatcher eventDispatcher) {
this.trackType = trackType;
public ChunkSampleStream(int primaryTrackType, int[] embeddedTrackTypes, T chunkSource,
Callback<ChunkSampleStream<T>> 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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
*/
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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (loader.isLoading()) {
loader.cancelLoading();
} else {
sampleQueue.reset(true);
primarySampleQueue.reset(true);
for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) {
embeddedSampleStream.reset(true);
}
}
}
}
@ -151,7 +204,10 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
* 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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@Override
public boolean isReady() {
return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty());
return loadingFinished || (!isPendingReset() && !primarySampleQueue.isEmpty());
}
@Override
@ -176,27 +232,15 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
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 class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
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 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
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 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
}
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 <T extends ChunkSource> ChunkSampleStream<T>.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);
}
}
}

View file

@ -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.Allocator;
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 @@ import java.util.List;
private final LoaderErrorThrower manifestLoaderErrorThrower;
private final Allocator allocator;
private final TrackGroupArray trackGroups;
private final EmbeddedTrackInfo[] embeddedTrackInfos;
private Callback callback;
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
@ -76,7 +79,9 @@ import java.util.List;
sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
trackGroups = buildTrackGroups(adaptationSets);
Pair<TrackGroupArray, EmbeddedTrackInfo[]> result = buildTrackGroups(adaptationSets);
trackGroups = result.first;
embeddedTrackInfos = result.second;
}
public void updateManifest(DashManifest manifest, int periodIndex) {
@ -116,37 +121,59 @@ import java.util.List;
@Override
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
ArrayList<ChunkSampleStream<DashChunkSource>> sampleStreamsList = new ArrayList<>();
int adaptationSetCount = adaptationSets.size();
HashMap<Integer, ChunkSampleStream<DashChunkSource>> 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<DashChunkSource> stream = (ChunkSampleStream<DashChunkSource>) 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.
}
if (streams[i] == null && selections[i] != null) {
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
if (trackGroupIndex < adaptationSetCount) {
ChunkSampleStream<DashChunkSource> stream = buildSampleStream(trackGroupIndex,
selections[i], positionUs);
primarySampleStreams.put(trackGroupIndex, stream);
streams[i] = stream;
streamResetFlags[i] = true;
}
}
}
// 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 adaptationSetIndex = trackGroups.indexOf(selections[i].getTrackGroup());
if (adaptationSetIndex < adaptationSets.size()) {
ChunkSampleStream<DashChunkSource> stream = buildSampleStream(adaptationSetIndex,
selections[i], positionUs);
sampleStreamsList.add(stream);
streams[i] = stream;
} else {
// TODO: Output streams for cea-608 and emsg tracks.
streams[i] = new EmptySampleStream();
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
if (trackGroupIndex >= adaptationSetCount) {
EmbeddedTrackInfo embeddedTrackInfo =
embeddedTrackInfos[trackGroupIndex - adaptationSetCount];
int adaptationSetIndex = embeddedTrackInfo.adaptationSetIndex;
ChunkSampleStream<DashChunkSource> 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;
}
streamResetFlags[i] = true;
}
}
sampleStreams = newSampleStreamArray(sampleStreamsList.size());
sampleStreamsList.toArray(sampleStreams);
sampleStreams = newSampleStreamArray(primarySampleStreams.size());
primarySampleStreams.values().toArray(sampleStreams);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
return positionUs;
}
@ -195,14 +222,14 @@ import java.util.List;
// Internal methods.
private static TrackGroupArray buildTrackGroups(List<AdaptationSet> adaptationSets) {
private static Pair<TrackGroupArray, EmbeddedTrackInfo[]> buildTrackGroups(
List<AdaptationSet> 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<Representation> representations = adaptationSet.representations;
@ -214,38 +241,57 @@ import java.util.List;
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<DashChunkSource> 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<DashChunkSource> stream = new ChunkSampleStream<>(adaptationSet.type,
embeddedTrackTypes, chunkSource, this, allocator, positionUs, minLoadableRetryCount,
eventDispatcher);
return stream;
}
private static int getEventMessageTrackCount(List<AdaptationSet> adaptationSets) {
int inbandEventStreamTrackCount = 0;
private static int getEmbeddedTrackCount(List<AdaptationSet> 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 @@ import java.util.List;
return false;
}
private static int getCea608TrackCount(List<AdaptationSet> 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<SchemeValuePair> descriptors = adaptationSet.accessibilityDescriptors;
for (int i = 0; i < descriptors.size(); i++) {
@ -285,4 +321,16 @@ import java.util.List;
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;
}
}
}

View file

@ -185,8 +185,8 @@ import java.util.ArrayList;
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) {