mirror of
https://github.com/samsonjs/media.git
synced 2026-04-26 14:57:47 +00:00
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:
parent
99e19a92af
commit
09471defd7
4 changed files with 283 additions and 113 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue