diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java index ec28a47458..a8b79e6c45 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java @@ -31,7 +31,7 @@ import com.google.android.exoplayer.SingleSampleSource; import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioTrack; -import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener; +import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener; import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.metadata.MetadataTrackRenderer; @@ -61,7 +61,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * A wrapper around {@link ExoPlayer} that provides a higher level interface. */ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener, - ChunkSampleSourceEventListener, ExtractorSampleSource.EventListener, + ChunkTrackStreamEventListener, ExtractorSampleSource.EventListener, SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer>, diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java index 72202b58aa..a38e901ff1 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -104,7 +104,7 @@ public interface SampleSource { long getBufferedPositionUs(); /** - * Seeks to the specified time in microseconds. + * Seeks to the specified position in microseconds. *

* This method should only be called when at least one track is selected. * diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java index 4195241804..05177644a0 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java @@ -15,13 +15,11 @@ */ package com.google.android.exoplayer.chunk; -import com.google.android.exoplayer.TrackGroup; - import java.io.IOException; import java.util.List; /** - * A provider of {@link Chunk}s for a {@link ChunkSampleSource} to load. + * A provider of {@link Chunk}s for a {@link ChunkTrackStream} to load. */ /* * TODO: Share more state between this interface and {@link ChunkSampleSource}. In particular @@ -40,25 +38,6 @@ public interface ChunkSource { */ void maybeThrowError() throws IOException; - /** - * Gets the group of tracks provided by the source. - *

- * This method should only be called after the source has been prepared. - * - * @return The track group. - */ - TrackGroup getTracks(); - - /** - * Enable the source for the specified tracks. - *

- * This method should only be called after the source has been prepared and when the source is - * disabled. - * - * @param tracks The track indices. - */ - void enable(int[] tracks); - /** * Evaluates whether {@link MediaChunk}s should be removed from the back of the queue. *

@@ -88,7 +67,7 @@ public interface ChunkSource { void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out); /** - * Invoked when the {@link ChunkSampleSource} has finished loading a chunk obtained from this + * Invoked when the {@link ChunkTrackStream} has finished loading a chunk obtained from this * source. *

* This method should only be called when the source is enabled. @@ -98,7 +77,7 @@ public interface ChunkSource { void onChunkLoadCompleted(Chunk chunk); /** - * Invoked when the {@link ChunkSampleSource} encounters an error loading a chunk obtained from + * Invoked when the {@link ChunkTrackStream} encounters an error loading a chunk obtained from * this source. *

* This method should only be called when the source is enabled. @@ -110,11 +89,4 @@ public interface ChunkSource { */ boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e); - /** - * Disables the source. - *

- * This method should only be called when the source is enabled. - */ - void disable(); - } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java similarity index 76% rename from library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java rename to library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java index fe61b310f9..0c7c351ee6 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java @@ -20,12 +20,8 @@ import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.FormatHolder; import com.google.android.exoplayer.LoadControl; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.TrackGroup; -import com.google.android.exoplayer.TrackGroupArray; -import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; -import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener.EventDispatcher; +import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher; import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; @@ -40,10 +36,9 @@ import java.util.LinkedList; import java.util.List; /** - * A {@link SampleSource} that loads media in {@link Chunk}s, which are themselves obtained from a - * {@link ChunkSource}. + * A {@link TrackStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}. */ -public class ChunkSampleSource implements TrackStream, Loader.Callback { +public class ChunkTrackStream implements TrackStream, Loader.Callback { /** * The default minimum number of times to retry loading data prior to failing. @@ -55,18 +50,14 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { private final LinkedList mediaChunks; private final List readOnlyMediaChunks; private final DefaultTrackOutput sampleQueue; - private final int bufferSizeContribution; private final ChunkHolder nextChunkHolder; private final EventDispatcher eventDispatcher; private final LoadControl loadControl; - private boolean notifyReset; + private boolean readingEnabled; private long lastPreferredQueueSizeEvaluationTimeMs; private Format downstreamFormat; - private TrackGroupArray trackGroups; - private boolean trackEnabled; - private long downstreamPositionUs; private long lastSeekPositionUs; private long pendingResetPositionUs; @@ -74,30 +65,22 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { private Chunk currentLoadable; private long currentLoadStartTimeMs; private boolean loadingFinished; + private boolean released; /** * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. * @param loadControl Controls when the source is permitted to load data. * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. - */ - public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution) { - this(chunkSource, loadControl, bufferSizeContribution, null, null, 0); - } - - /** - * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. - * @param loadControl Controls when the source is permitted to load data. - * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. + * @param positionUs The position from which to start loading media. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. */ - public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Handler eventHandler, - ChunkSampleSourceEventListener eventListener, int eventSourceId) { - this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, + public ChunkTrackStream(ChunkSource chunkSource, LoadControl loadControl, + int bufferSizeContribution, long positionUs, Handler eventHandler, + ChunkTrackStreamEventListener eventListener, int eventSourceId) { + this(chunkSource, loadControl, bufferSizeContribution, positionUs, eventHandler, eventListener, eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); } @@ -105,6 +88,7 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. * @param loadControl Controls when the source is permitted to load data. * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. + * @param positionUs The position from which to start loading media. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. @@ -112,80 +96,35 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { * @param minLoadableRetryCount The minimum number of times that the source should retry a load * before propagating an error. */ - public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Handler eventHandler, - ChunkSampleSourceEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { + public ChunkTrackStream(ChunkSource chunkSource, LoadControl loadControl, + int bufferSizeContribution, long positionUs, Handler eventHandler, + ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { this.chunkSource = chunkSource; this.loadControl = loadControl; - this.bufferSizeContribution = bufferSizeContribution; - loader = new Loader("Loader:ChunkSampleSource", minLoadableRetryCount); + loader = new Loader("Loader:ChunkTrackStream", minLoadableRetryCount); eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId); nextChunkHolder = new ChunkHolder(); mediaChunks = new LinkedList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); sampleQueue = new DefaultTrackOutput(loadControl.getAllocator()); pendingResetPositionUs = C.UNSET_TIME_US; + readingEnabled = true; + downstreamPositionUs = positionUs; + lastSeekPositionUs = positionUs; + loadControl.register(this, bufferSizeContribution); + restartFrom(positionUs); } - // SampleSource implementation. - - public void prepare() { - TrackGroup tracks = chunkSource.getTracks(); - if (tracks != null) { - trackGroups = new TrackGroupArray(tracks); - } else { - trackGroups = new TrackGroupArray(); - } - } - - public TrackGroupArray getTrackGroups() { - return trackGroups; - } - - public TrackStream[] selectTracks(List oldStreams, - List newSelections, long positionUs) { - Assertions.checkState(oldStreams.size() <= 1); - Assertions.checkState(newSelections.size() <= 1); - boolean trackWasEnabled = trackEnabled; - // Unselect old tracks. - if (!oldStreams.isEmpty()) { - Assertions.checkState(trackEnabled); - trackEnabled = false; - chunkSource.disable(); - } - // Select new tracks. - TrackStream[] newStreams = new TrackStream[newSelections.size()]; - if (!newSelections.isEmpty()) { - Assertions.checkState(!trackEnabled); - trackEnabled = true; - chunkSource.enable(newSelections.get(0).getTracks()); - newStreams[0] = this; - } - // Cancel or start requests as necessary. - if (!trackEnabled) { - if (trackWasEnabled) { - loadControl.unregister(this); - } - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - clearState(); - loadControl.trimAllocator(); - } - } else { - if (!trackWasEnabled) { - loadControl.register(this, bufferSizeContribution); - } - downstreamFormat = null; - sampleQueue.needDownstreamFormat(); - downstreamPositionUs = positionUs; - lastSeekPositionUs = positionUs; - notifyReset = false; - restartFrom(positionUs); - } - return newStreams; + /** + * Enables or disables reading of data from {@link #readData(FormatHolder, DecoderInputBuffer)}. + * + * @param readingEnabled Whether reading should be enabled. + */ + public void setReadingEnabled(boolean readingEnabled) { + this.readingEnabled = readingEnabled; } + // TODO[REFACTOR]: Find a way to get rid of this. public void continueBuffering(long positionUs) { downstreamPositionUs = positionUs; if (!loader.isLoading()) { @@ -193,14 +132,12 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { } } - public long readReset() { - if (notifyReset) { - notifyReset = false; - return lastSeekPositionUs; - } - return C.UNSET_TIME_US; - } - + /** + * Returns an estimate of the position up to which data is buffered. + * + * @return An estimate of the absolute position in microseconds up to which data is buffered, or + * {@link C#END_OF_SOURCE_US} if the track is fully buffered. + */ public long getBufferedPositionUs() { if (loadingFinished) { return C.END_OF_SOURCE_US; @@ -218,6 +155,11 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { } } + /** + * Seeks to the specified position in microseconds. + * + * @param positionUs The seek position in microseconds. + */ public void seekToUs(long positionUs) { downstreamPositionUs = positionUs; lastSeekPositionUs = positionUs; @@ -233,16 +175,23 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { // We failed, and need to restart. restartFrom(positionUs); } - // Either way, we need to send a discontinuity to the downstream components. - notifyReset = true; } + /** + * Releases the stream. + *

+ * This method should be called when the stream is no longer required. + */ public void release() { - if (trackEnabled) { - loadControl.unregister(this); - trackEnabled = false; + loadControl.unregister(this); + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + clearState(); + loadControl.trimAllocator(); } loader.release(); + released = true; } // TrackStream implementation. @@ -260,7 +209,7 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { @Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { - if (notifyReset || isPendingReset()) { + if (!readingEnabled || isPendingReset()) { return NOTHING_READ; } @@ -306,7 +255,7 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback { @Override public void onLoadCanceled(Loadable loadable) { eventDispatcher.loadCanceled(currentLoadable.bytesLoaded()); - if (trackEnabled) { + if (!released) { restartFrom(pendingResetPositionUs); } else { clearState(); diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSourceEventListener.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStreamEventListener.java similarity index 90% rename from library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSourceEventListener.java rename to library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStreamEventListener.java index f961ce2f58..db9d51153c 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSourceEventListener.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStreamEventListener.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer.chunk; import com.google.android.exoplayer.C; import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.util.Util; @@ -26,14 +25,14 @@ import android.os.Handler; import java.io.IOException; /** - * Interface for callbacks to be notified of chunk based {@link SampleSource} events. + * Interface for callbacks to be notified of chunk based {@link ChunkTrackStream} events. */ -public interface ChunkSampleSourceEventListener { +public interface ChunkTrackStreamEventListener { /** * Invoked when an upstream load is started. * - * @param sourceId The id of the reporting {@link SampleSource}. + * @param sourceId The id of the reporting {@link ChunkTrackStream}. * @param length The length of the data being loaded in bytes, or {@link C#LENGTH_UNBOUNDED} if * the length of the data is not known in advance. * @param type The type of the data being loaded. @@ -51,7 +50,7 @@ public interface ChunkSampleSourceEventListener { /** * Invoked when the current load operation completes. * - * @param sourceId The id of the reporting {@link SampleSource}. + * @param sourceId The id of the reporting {@link ChunkTrackStream}. * @param bytesLoaded The number of bytes that were loaded. * @param type The type of the loaded data. * @param trigger The reason for the data being loaded. @@ -70,7 +69,7 @@ public interface ChunkSampleSourceEventListener { /** * Invoked when the current upstream load operation is canceled. * - * @param sourceId The id of the reporting {@link SampleSource}. + * @param sourceId The id of the reporting {@link ChunkTrackStream}. * @param bytesLoaded The number of bytes that were loaded prior to the cancellation. */ void onLoadCanceled(int sourceId, long bytesLoaded); @@ -78,7 +77,7 @@ public interface ChunkSampleSourceEventListener { /** * Invoked when an error occurs loading media data. * - * @param sourceId The id of the reporting {@link SampleSource}. + * @param sourceId The id of the reporting {@link ChunkTrackStream}. * @param e The cause of the failure. */ void onLoadError(int sourceId, IOException e); @@ -87,7 +86,7 @@ public interface ChunkSampleSourceEventListener { * Invoked when data is removed from the back of the buffer, typically so that it can be * re-buffered using a different representation. * - * @param sourceId The id of the reporting {@link SampleSource}. + * @param sourceId The id of the reporting {@link ChunkTrackStream}. * @param mediaStartTimeMs The media time of the start of the discarded data. * @param mediaEndTimeMs The media time of the end of the discarded data. */ @@ -97,7 +96,7 @@ public interface ChunkSampleSourceEventListener { * Invoked when the downstream format changes (i.e. when the format being supplied to the * caller of {@link TrackStream#readData} changes). * - * @param sourceId The id of the reporting {@link SampleSource}. + * @param sourceId The id of the reporting {@link ChunkTrackStream}. * @param format The format. * @param trigger The trigger specified in the corresponding upstream load, as specified by the * {@link ChunkSource}. @@ -106,15 +105,15 @@ public interface ChunkSampleSourceEventListener { void onDownstreamFormatChanged(int sourceId, Format format, int trigger, long mediaTimeMs); /** - * Dispatches events to a {@link ChunkSampleSourceEventListener}. + * Dispatches events to a {@link ChunkTrackStreamEventListener}. */ final class EventDispatcher { private final Handler handler; - private final ChunkSampleSourceEventListener listener; + private final ChunkTrackStreamEventListener listener; private final int sourceId; - public EventDispatcher(Handler handler, ChunkSampleSourceEventListener listener, int sourceId) { + public EventDispatcher(Handler handler, ChunkTrackStreamEventListener listener, int sourceId) { this.handler = listener != null ? handler : null; this.listener = listener; this.sourceId = sourceId; diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index d7cb4a8ee2..3130c0a3cf 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -52,52 +52,85 @@ import java.util.List; /** * An {@link ChunkSource} for DASH streams. - *

- * This implementation currently supports fMP4, webm, webvtt and ttml. - *

- * This implementation makes the following assumptions about multi-period manifests: - *

    - *
  1. that new periods will contain the same representations as previous periods (i.e. no new or - * missing representations) and
  2. - *
  3. that representations are contiguous across multiple periods
  4. - *
*/ -// TODO: handle cases where the above assumption are false -// TODO[REFACTOR]: Handle multiple adaptation sets of the same type (at a higher level). public class DashChunkSource implements ChunkSource { - private final int adaptationSetType; + private final int adaptationSetIndex; + private final TrackGroup trackGroup; + private final RepresentationHolder[] representationHolders; + private final Format[] enabledFormats; + private final boolean[] adaptiveFormatBlacklistFlags; private final DataSource dataSource; private final FormatEvaluator adaptiveFormatEvaluator; private final Evaluation evaluation; - private MediaPresentationDescription currentManifest; + private MediaPresentationDescription manifest; private DrmInitData drmInitData; private boolean lastChunkWasInitialization; private IOException fatalError; - // Properties of exposed tracks. - private int adaptationSetIndex; - private TrackGroup trackGroup; - private RepresentationHolder[] representationHolders; - - // Properties of enabled tracks. - private Format[] enabledFormats; - private boolean[] adaptiveFormatBlacklistFlags; - /** - * @param adaptationSetType The type of the adaptation set exposed by this source. One of - * {@link C#TRACK_TYPE_AUDIO}, {@link C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_TEXT}. + * @param manifest The initial manifest. + * @param adaptationSetIndex The index of the adaptation set in the manifest. + * @param trackGroup The track group corresponding to the adaptation set. + * @param tracks The indices of the selected tracks within the adaptation set. * @param dataSource A {@link DataSource} suitable for loading the media data. * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. */ - public DashChunkSource(int adaptationSetType, DataSource dataSource, + public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetIndex, + TrackGroup trackGroup, int[] tracks, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) { - this.adaptationSetType = adaptationSetType; + this.manifest = manifest; + this.adaptationSetIndex = adaptationSetIndex; + this.trackGroup = trackGroup; this.dataSource = dataSource; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; this.evaluation = new Evaluation(); + + Period period = manifest.getPeriod(0); + long periodDurationUs = getPeriodDurationUs(manifest, 0); + AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); + drmInitData = getDrmInitData(adaptationSet); + + List representations = adaptationSet.representations; + representationHolders = new RepresentationHolder[representations.size()]; + for (int i = 0; i < representations.size(); i++) { + Representation representation = representations.get(i); + representationHolders[i] = new RepresentationHolder(periodDurationUs, representation); + } + enabledFormats = new Format[tracks.length]; + for (int i = 0; i < tracks.length; i++) { + enabledFormats[i] = trackGroup.getFormat(tracks[i]); + } + Arrays.sort(enabledFormats, new DecreasingBandwidthComparator()); + if (adaptiveFormatEvaluator != null) { + adaptiveFormatEvaluator.enable(enabledFormats); + adaptiveFormatBlacklistFlags = new boolean[tracks.length]; + } else { + adaptiveFormatBlacklistFlags = null; + } + } + + public void updateManifest(MediaPresentationDescription newManifest) { + try { + manifest = newManifest; + long periodDurationUs = getPeriodDurationUs(manifest, 0); + List representations = manifest.getPeriod(0).adaptationSets + .get(adaptationSetIndex).representations; + for (int i = 0; i < representationHolders.length; i++) { + Representation representation = representations.get(i); + representationHolders[i].updateRepresentation(periodDurationUs, representation); + } + } catch (BehindLiveWindowException e) { + fatalError = e; + } + } + + public void release() { + if (adaptiveFormatEvaluator != null) { + adaptiveFormatEvaluator.disable(); + } } // ChunkSource implementation. @@ -109,33 +142,6 @@ public class DashChunkSource implements ChunkSource { } } - public void init(MediaPresentationDescription initialManifest) { - currentManifest = initialManifest; - initForManifest(currentManifest); - } - - @Override - public final TrackGroup getTracks() { - return trackGroup; - } - - @Override - public void enable(int[] tracks) { - enabledFormats = new Format[tracks.length]; - for (int i = 0; i < tracks.length; i++) { - enabledFormats[i] = trackGroup.getFormat(tracks[i]); - } - Arrays.sort(enabledFormats, new DecreasingBandwidthComparator()); - if (enabledFormats.length > 1) { - adaptiveFormatEvaluator.enable(enabledFormats); - adaptiveFormatBlacklistFlags = new boolean[tracks.length]; - } - } - - public void updateManifest(MediaPresentationDescription newManifest) { - processManifest(newManifest); - } - @Override public int getPreferredQueueSize(long playbackPositionUs, List queue) { if (fatalError != null || enabledFormats.length < 2) { @@ -200,9 +206,9 @@ public class DashChunkSource implements ChunkSource { if (indexUnbounded) { // The index is itself unbounded. We need to use the current time to calculate the range of // available segments. - long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000; - if (currentManifest.timeShiftBufferDepth != -1) { - long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000; + long liveEdgeTimestampUs = nowUs - manifest.availabilityStartTime * 1000; + if (manifest.timeShiftBufferDepth != -1) { + long bufferDepthUs = manifest.timeShiftBufferDepth * 1000; firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum, representationHolder.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs)); } @@ -226,7 +232,7 @@ public class DashChunkSource implements ChunkSource { if (segmentNum > lastAvailableSegmentNum) { // This is beyond the last chunk in the current manifest. - out.endOfStream = !currentManifest.dynamic; + out.endOfStream = !manifest.dynamic; return; } @@ -270,59 +276,8 @@ public class DashChunkSource implements ChunkSource { return false; } - @Override - public void disable() { - if (enabledFormats.length > 1) { - adaptiveFormatEvaluator.disable(); - } - evaluation.clear(); - fatalError = null; - enabledFormats = null; - } - // Private methods. - private void initForManifest(MediaPresentationDescription manifest) { - Period period = manifest.getPeriod(0); - for (int i = 0; i < period.adaptationSets.size(); i++) { - AdaptationSet adaptationSet = period.adaptationSets.get(i); - if (adaptationSet.type == adaptationSetType) { - adaptationSetIndex = i; - List representations = adaptationSet.representations; - if (!representations.isEmpty()) { - // We've found a non-empty adaptation set of the exposed type. - long periodDurationUs = getPeriodDurationUs(manifest, 0); - representationHolders = new RepresentationHolder[representations.size()]; - Format[] trackFormats = new Format[representations.size()]; - for (int j = 0; j < trackFormats.length; j++) { - Representation representation = representations.get(j); - representationHolders[j] = new RepresentationHolder(periodDurationUs, representation); - trackFormats[j] = representation.format; - } - trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats); - drmInitData = getDrmInitData(adaptationSet); - return; - } - } - } - trackGroup = null; - } - - private void processManifest(MediaPresentationDescription newManifest) { - try { - currentManifest = newManifest; - long periodDurationUs = getPeriodDurationUs(currentManifest, 0); - List representations = currentManifest.getPeriod(0).adaptationSets - .get(adaptationSetIndex).representations; - for (int i = 0; i < representationHolders.length; i++) { - Representation representation = representations.get(i); - representationHolders[i].updateRepresentation(periodDurationUs, representation); - } - } catch (BehindLiveWindowException e) { - fatalError = e; - } - } - private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, int trigger) { diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java index 2526edfa02..f16bc35659 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java @@ -17,23 +17,29 @@ package com.google.android.exoplayer.dash; import com.google.android.exoplayer.C; import com.google.android.exoplayer.DefaultLoadControl; +import com.google.android.exoplayer.Format; import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; -import com.google.android.exoplayer.chunk.ChunkSampleSource; -import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener; +import com.google.android.exoplayer.chunk.ChunkTrackStream; +import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener; +import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; +import com.google.android.exoplayer.dash.mpd.AdaptationSet; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser; +import com.google.android.exoplayer.dash.mpd.Period; +import com.google.android.exoplayer.dash.mpd.Representation; import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.ManifestFetcher; +import com.google.android.exoplayer.util.Util; import android.net.Uri; import android.os.Handler; @@ -41,62 +47,47 @@ import android.os.SystemClock; import android.util.Pair; import java.io.IOException; -import java.util.ArrayList; -import java.util.IdentityHashMap; +import java.util.Arrays; import java.util.List; /** - * Combines multiple {@link SampleSource} instances. + * A {@link SampleSource} for DASH media. */ public final class DashSampleSource implements SampleSource { private final ManifestFetcher manifestFetcher; - private final DashChunkSource[] chunkSources; - private final ChunkSampleSource[] sources; - private final IdentityHashMap trackStreamSources; - private final int[] selectedTrackCounts; + private final DataSourceFactory dataSourceFactory; + private final BandwidthMeter bandwidthMeter; + private final Handler eventHandler; + private final ChunkTrackStreamEventListener eventListener; + private final LoadControl loadControl; - private MediaPresentationDescription currentManifest; private boolean prepared; - private boolean seenFirstTrackSelection; private long durationUs; + private MediaPresentationDescription currentManifest; private TrackGroupArray trackGroups; - private ChunkSampleSource[] enabledSources; + private int[] trackGroupAdaptationSetIndices; + private boolean pendingReset; + private long lastSeekPositionUs; + + private DashChunkSource[] chunkSources; + private ChunkTrackStream[] trackStreams; public DashSampleSource(Uri uri, DataSourceFactory dataSourceFactory, BandwidthMeter bandwidthMeter, Handler eventHandler, - ChunkSampleSourceEventListener eventListener) { + ChunkTrackStreamEventListener eventListener) { + this.dataSourceFactory = dataSourceFactory; + this.bandwidthMeter = bandwidthMeter; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + + loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); + chunkSources = new DashChunkSource[0]; + trackStreams = new ChunkTrackStream[0]; + MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); DataSource manifestDataSource = dataSourceFactory.createDataSource(); manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser); - - LoadControl loadControl = new DefaultLoadControl( - new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); - - // Build the video renderer. - DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - DashChunkSource videoChunkSource = new DashChunkSource(C.TRACK_TYPE_VIDEO, videoDataSource, - new AdaptiveEvaluator(bandwidthMeter)); - ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, - C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO); - - // Build the audio renderer. - DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - DashChunkSource audioChunkSource = new DashChunkSource(C.TRACK_TYPE_AUDIO, audioDataSource, - null); - ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, - C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO); - - // Build the text renderer. - DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - DashChunkSource textChunkSource = new DashChunkSource(C.TRACK_TYPE_TEXT, textDataSource, null); - ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, - C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT); - - chunkSources = new DashChunkSource[] {videoChunkSource, audioChunkSource, textChunkSource}; - sources = new ChunkSampleSource[] {videoSampleSource, audioSampleSource, textSampleSource}; - trackStreamSources = new IdentityHashMap<>(); - selectedTrackCounts = new int[sources.length]; } @Override @@ -111,30 +102,12 @@ public final class DashSampleSource implements SampleSource { manifestFetcher.maybeThrowError(); manifestFetcher.requestRefresh(); return false; - } else { - durationUs = currentManifest.dynamic ? C.UNSET_TIME_US : currentManifest.duration * 1000; - for (DashChunkSource chunkSource : chunkSources) { - chunkSource.init(currentManifest); - } } } - for (ChunkSampleSource source : sources) { - source.prepare(); - } - int totalTrackGroupCount = 0; - for (ChunkSampleSource source : sources) { - totalTrackGroupCount += source.getTrackGroups().length; - } - TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; - int trackGroupIndex = 0; - for (ChunkSampleSource source : sources) { - int sourceTrackGroupCount = source.getTrackGroups().length; - for (int j = 0; j < sourceTrackGroupCount; j++) { - trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j); - } - } - trackGroups = new TrackGroupArray(trackGroupArray); + durationUs = currentManifest.dynamic ? C.UNSET_TIME_US : currentManifest.duration * 1000; + buildTrackGroups(currentManifest); + prepared = true; return true; } @@ -153,26 +126,37 @@ public final class DashSampleSource implements SampleSource { public TrackStream[] selectTracks(List oldStreams, List newSelections, long positionUs) { Assertions.checkState(prepared); - TrackStream[] newStreams = new TrackStream[newSelections.size()]; - // Select tracks for each source. - int enabledSourceCount = 0; - for (int i = 0; i < sources.length; i++) { - selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs, - newStreams); - if (selectedTrackCounts[i] > 0) { - enabledSourceCount++; + + int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size(); + DashChunkSource[] newChunkSources = new DashChunkSource[newEnabledSourceCount]; + ChunkTrackStream[] newTrackStreams = new ChunkTrackStream[newEnabledSourceCount]; + int newEnabledSourceIndex = 0; + + // Iterate over currently enabled streams, either releasing them or adding them to the new list. + for (int i = 0; i < trackStreams.length; i++) { + ChunkTrackStream trackStream = trackStreams[i]; + if (oldStreams.contains(trackStream)) { + chunkSources[i].release(); + trackStream.release(); + } else { + newChunkSources[newEnabledSourceIndex] = chunkSources[i]; + newTrackStreams[newEnabledSourceIndex++] = trackStream; } } - // Update the enabled sources. - enabledSources = new ChunkSampleSource[enabledSourceCount]; - enabledSourceCount = 0; - for (int i = 0; i < sources.length; i++) { - if (selectedTrackCounts[i] > 0) { - enabledSources[enabledSourceCount++] = sources[i]; - } + + // Instantiate and return new streams. + TrackStream[] streamsToReturn = new TrackStream[newSelections.size()]; + for (int i = 0; i < newSelections.size(); i++) { + Pair trackComponents = + buildTrackStream(newSelections.get(i), positionUs); + newChunkSources[newEnabledSourceIndex] = trackComponents.first; + newTrackStreams[newEnabledSourceIndex++] = trackComponents.second; + streamsToReturn[i] = trackComponents.second; } - seenFirstTrackSelection = true; - return newStreams; + + chunkSources = newChunkSources; + trackStreams = newTrackStreams; + return streamsToReturn; } @Override @@ -201,103 +185,102 @@ public final class DashSampleSource implements SampleSource { } } - for (ChunkSampleSource source : enabledSources) { - source.continueBuffering(positionUs); + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.continueBuffering(positionUs); } } @Override public long readReset() { - long resetPositionUs = C.UNSET_TIME_US; - for (ChunkSampleSource source : enabledSources) { - long childResetPositionUs = source.readReset(); - if (resetPositionUs == C.UNSET_TIME_US) { - resetPositionUs = childResetPositionUs; - } else if (childResetPositionUs != C.UNSET_TIME_US) { - resetPositionUs = Math.min(resetPositionUs, childResetPositionUs); + if (pendingReset) { + pendingReset = false; + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.setReadingEnabled(true); } + return lastSeekPositionUs; } - return resetPositionUs; + return C.UNSET_TIME_US; } @Override public long getBufferedPositionUs() { - long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE; - for (ChunkSampleSource source : enabledSources) { - long rendererBufferedPositionUs = source.getBufferedPositionUs(); - if (rendererBufferedPositionUs == C.UNSET_TIME_US) { - return C.UNSET_TIME_US; - } else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) { - // This source is fully buffered. - } else { + long bufferedPositionUs = Long.MAX_VALUE; + for (ChunkTrackStream trackStream : trackStreams) { + long rendererBufferedPositionUs = trackStream.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.END_OF_SOURCE_US) { bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); } } - return bufferedPositionUs == Long.MAX_VALUE ? C.UNSET_TIME_US : bufferedPositionUs; + return bufferedPositionUs == Long.MAX_VALUE ? C.END_OF_SOURCE_US : bufferedPositionUs; } @Override public void seekToUs(long positionUs) { - for (ChunkSampleSource source : enabledSources) { - source.seekToUs(positionUs); + lastSeekPositionUs = positionUs; + pendingReset = true; + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.setReadingEnabled(false); + trackStream.seekToUs(positionUs); } } @Override public void release() { manifestFetcher.release(); - for (ChunkSampleSource source : sources) { - source.release(); + for (DashChunkSource chunkSource : chunkSources) { + chunkSource.release(); + } + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.release(); } } // Internal methods. - private int selectTracks(ChunkSampleSource source, List allOldStreams, - List allNewSelections, long positionUs, TrackStream[] allNewStreams) { - // Get the subset of the old streams for the source. - ArrayList oldStreams = new ArrayList<>(); - for (int i = 0; i < allOldStreams.size(); i++) { - TrackStream stream = allOldStreams.get(i); - if (trackStreamSources.get(stream) == source) { - trackStreamSources.remove(stream); - oldStreams.add(stream); + private void buildTrackGroups(MediaPresentationDescription manifest) { + Period period = manifest.getPeriod(0); + int trackGroupCount = 0; + trackGroupAdaptationSetIndices = new int[period.adaptationSets.size()]; + TrackGroup[] trackGroupArray = new TrackGroup[period.adaptationSets.size()]; + for (int i = 0; i < period.adaptationSets.size(); i++) { + AdaptationSet adaptationSet = period.adaptationSets.get(i); + int adaptationSetType = adaptationSet.type; + List representations = adaptationSet.representations; + if (!representations.isEmpty() && (adaptationSetType == C.TRACK_TYPE_AUDIO + || adaptationSetType == C.TRACK_TYPE_VIDEO || adaptationSetType == C.TRACK_TYPE_TEXT)) { + Format[] formats = new Format[representations.size()]; + for (int j = 0; j < formats.length; j++) { + formats[j] = representations.get(j).format; + } + trackGroupAdaptationSetIndices[trackGroupCount] = i; + boolean adaptive = adaptationSetType == C.TRACK_TYPE_VIDEO; + trackGroupArray[trackGroupCount++] = new TrackGroup(adaptive, formats); } } - // Get the subset of the new selections for the source. - ArrayList newSelections = new ArrayList<>(); - int[] newSelectionOriginalIndices = new int[allNewSelections.size()]; - for (int i = 0; i < allNewSelections.size(); i++) { - TrackSelection selection = allNewSelections.get(i); - Pair sourceAndGroup = getSourceAndGroup(selection.group); - if (sourceAndGroup.first == source) { - newSelectionOriginalIndices[newSelections.size()] = i; - newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks())); - } + if (trackGroupCount < trackGroupArray.length) { + trackGroupAdaptationSetIndices = Arrays.copyOf(trackGroupAdaptationSetIndices, + trackGroupCount); + trackGroupArray = Arrays.copyOf(trackGroupArray, trackGroupCount); } - // Do nothing if nothing has changed, except during the first selection. - if (seenFirstTrackSelection && oldStreams.isEmpty() && newSelections.isEmpty()) { - return 0; - } - // Perform the selection. - TrackStream[] newStreams = source.selectTracks(oldStreams, newSelections, positionUs); - for (int j = 0; j < newStreams.length; j++) { - allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j]; - trackStreamSources.put(newStreams[j], source); - } - return newSelections.size() - oldStreams.size(); + trackGroups = new TrackGroupArray(trackGroupArray); } - private Pair getSourceAndGroup(int group) { - int totalTrackGroupCount = 0; - for (ChunkSampleSource source : sources) { - int sourceTrackGroupCount = source.getTrackGroups().length; - if (group < totalTrackGroupCount + sourceTrackGroupCount) { - return Pair.create(source, group - totalTrackGroupCount); - } - totalTrackGroupCount += sourceTrackGroupCount; - } - throw new IndexOutOfBoundsException(); + private Pair buildTrackStream(TrackSelection selection, + long positionUs) { + int[] selectedTracks = selection.getTracks(); + FormatEvaluator adaptiveEvaluator = selectedTracks.length > 1 + ? new AdaptiveEvaluator(bandwidthMeter) : null; + int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group]; + AdaptationSet adaptationSet = currentManifest.getPeriod(0).adaptationSets.get( + adaptationSetIndex); + int adaptationSetType = adaptationSet.type; + int bufferSize = Util.getDefaultBufferSize(adaptationSetType); + DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter); + DashChunkSource chunkSource = new DashChunkSource(currentManifest, adaptationSetIndex, + trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator); + ChunkTrackStream trackStream = new ChunkTrackStream(chunkSource, loadControl, bufferSize, + positionUs, eventHandler, eventListener, adaptationSetType); + return Pair.create(chunkSource, trackStream); } } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index a35236fa50..75c978a3d7 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -27,8 +27,8 @@ import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.ChunkHolder; -import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener; -import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener.EventDispatcher; +import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener; +import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher; import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; @@ -109,7 +109,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { */ public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, int bufferSizeContribution, Handler eventHandler, - ChunkSampleSourceEventListener eventListener, int eventSourceId) { + ChunkTrackStreamEventListener eventListener, int eventSourceId) { this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); } @@ -127,7 +127,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { */ public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, int bufferSizeContribution, Handler eventHandler, - ChunkSampleSourceEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { + ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { this.chunkSource = chunkSource; this.loadControl = loadControl; this.bufferSizeContribution = bufferSizeContribution; diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index f5427d8950..bc4ada7ca4 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -46,86 +46,75 @@ import java.util.List; /** * An {@link ChunkSource} for SmoothStreaming. */ -// TODO[REFACTOR]: Handle multiple stream elements of the same type (at a higher level). public class SmoothStreamingChunkSource implements ChunkSource { - private final int streamElementType; + private final int elementIndex; + private final TrackGroup trackGroup; + private final ChunkExtractorWrapper[] extractorWrappers; + private final Format[] enabledFormats; + private final boolean[] adaptiveFormatBlacklistFlags; private final DataSource dataSource; private final Evaluation evaluation; private final FormatEvaluator adaptiveFormatEvaluator; - private SmoothStreamingManifest currentManifest; - private TrackEncryptionBox[] trackEncryptionBoxes; + private SmoothStreamingManifest manifest; private DrmInitData drmInitData; private int currentManifestChunkOffset; private boolean needManifestRefresh; - // Properties of exposed tracks. - private int elementIndex; - private TrackGroup trackGroup; - private ChunkExtractorWrapper[] extractorWrappers; - - // Properties of enabled tracks. - private Format[] enabledFormats; - private boolean[] adaptiveFormatBlacklistFlags; - private IOException fatalError; /** - * @param streamElementType The type of stream element exposed by this source. One of - * {@link C#TRACK_TYPE_VIDEO}, {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_TEXT}. + * @param manifest The initial manifest. + * @param elementIndex The index of the stream element in the manifest. + * @param trackGroup The track group corresponding to the stream element. + * @param tracks The indices of the selected tracks within the stream element. * @param dataSource A {@link DataSource} suitable for loading the media data. * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. + * @param trackEncryptionBoxes Track encryption boxes for the stream. + * @param drmInitData Drm initialization data for the stream. */ - public SmoothStreamingChunkSource(int streamElementType, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator) { - this.streamElementType = streamElementType; + public SmoothStreamingChunkSource(SmoothStreamingManifest manifest, int elementIndex, + TrackGroup trackGroup, int[] tracks, DataSource dataSource, + FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes, + DrmInitData drmInitData) { + this.manifest = manifest; + this.elementIndex = elementIndex; + this.trackGroup = trackGroup; this.dataSource = dataSource; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; - evaluation = new Evaluation(); - } - - public boolean needManifestRefresh() { - return needManifestRefresh; - } - - // ChunkSource implementation. - - @Override - public void maybeThrowError() throws IOException { - if (fatalError != null) { - throw fatalError; - } - } - - public void init(SmoothStreamingManifest initialManifest, - TrackEncryptionBox[] trackEncryptionBoxes, DrmInitData drmInitData) { - this.currentManifest = initialManifest; - this.trackEncryptionBoxes = trackEncryptionBoxes; this.drmInitData = drmInitData; - initForManifest(currentManifest); - } + this.evaluation = new Evaluation(); - @Override - public final TrackGroup getTracks() { - return trackGroup; - } + StreamElement streamElement = manifest.streamElements[elementIndex]; + Format[] formats = streamElement.formats; + extractorWrappers = new ChunkExtractorWrapper[formats.length]; + for (int j = 0; j < formats.length; j++) { + int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : -1; + Track track = new Track(j, streamElement.type, streamElement.timescale, C.UNSET_TIME_US, + manifest.durationUs, formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, + null, null); + FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( + FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME + | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track); + extractorWrappers[j] = new ChunkExtractorWrapper(extractor); + } - @Override - public void enable(int[] tracks) { enabledFormats = new Format[tracks.length]; for (int i = 0; i < tracks.length; i++) { enabledFormats[i] = trackGroup.getFormat(tracks[i]); } Arrays.sort(enabledFormats, new DecreasingBandwidthComparator()); - if (enabledFormats.length > 1) { + if (adaptiveFormatEvaluator != null) { adaptiveFormatEvaluator.enable(enabledFormats); adaptiveFormatBlacklistFlags = new boolean[tracks.length]; + } else { + adaptiveFormatBlacklistFlags = null; } } public void updateManifest(SmoothStreamingManifest newManifest) { - StreamElement currentElement = currentManifest.streamElements[elementIndex]; + StreamElement currentElement = manifest.streamElements[elementIndex]; int currentElementChunkCount = currentElement.chunkCount; StreamElement newElement = newManifest.streamElements[elementIndex]; if (currentElementChunkCount == 0 || newElement.chunkCount == 0) { @@ -143,10 +132,29 @@ public class SmoothStreamingChunkSource implements ChunkSource { currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs); } } - currentManifest = newManifest; + manifest = newManifest; needManifestRefresh = false; } + public boolean needManifestRefresh() { + return needManifestRefresh; + } + + public void release() { + if (adaptiveFormatEvaluator != null) { + adaptiveFormatEvaluator.disable(); + } + } + + // ChunkSource implementation. + + @Override + public void maybeThrowError() throws IOException { + if (fatalError != null) { + throw fatalError; + } + } + @Override public int getPreferredQueueSize(long playbackPositionUs, List queue) { if (fatalError != null || enabledFormats.length < 2) { @@ -176,9 +184,9 @@ public class SmoothStreamingChunkSource implements ChunkSource { return; } - StreamElement streamElement = currentManifest.streamElements[elementIndex]; + StreamElement streamElement = manifest.streamElements[elementIndex]; if (streamElement.chunkCount == 0) { - if (currentManifest.isLive) { + if (manifest.isLive) { needManifestRefresh = true; } else { out.endOfStream = true; @@ -198,10 +206,10 @@ public class SmoothStreamingChunkSource implements ChunkSource { } } - needManifestRefresh = currentManifest.isLive && chunkIndex >= streamElement.chunkCount - 1; + needManifestRefresh = manifest.isLive && chunkIndex >= streamElement.chunkCount - 1; if (chunkIndex >= streamElement.chunkCount) { // This is beyond the last chunk in the current manifest. - out.endOfStream = !currentManifest.isLive; + out.endOfStream = !manifest.isLive; return; } @@ -231,45 +239,8 @@ public class SmoothStreamingChunkSource implements ChunkSource { return false; } - @Override - public void disable() { - if (enabledFormats.length > 1) { - adaptiveFormatEvaluator.disable(); - } - evaluation.clear(); - fatalError = null; - } - // Private methods. - private void initForManifest(SmoothStreamingManifest manifest) { - for (int i = 0; i < manifest.streamElements.length; i++) { - if (manifest.streamElements[i].type == streamElementType) { - Format[] formats = manifest.streamElements[i].formats; - if (formats.length > 0) { - // We've found an element of the desired type. - long timescale = manifest.streamElements[i].timescale; - extractorWrappers = new ChunkExtractorWrapper[formats.length]; - for (int j = 0; j < formats.length; j++) { - int nalUnitLengthFieldLength = streamElementType == C.TRACK_TYPE_VIDEO ? 4 : -1; - Track track = new Track(j, streamElementType, timescale, C.UNSET_TIME_US, - manifest.durationUs, formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, - null, null); - FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( - FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME - | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track); - extractorWrappers[j] = new ChunkExtractorWrapper(extractor); - } - elementIndex = i; - trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, formats); - return; - } - } - } - extractorWrappers = null; - trackGroup = null; - } - /** * Gets the index of a format in a track group, using referential equality. */ diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java index f95cc398e2..cfe4e4d50e 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java @@ -17,19 +17,22 @@ package com.google.android.exoplayer.smoothstreaming; import com.google.android.exoplayer.C; import com.google.android.exoplayer.DefaultLoadControl; +import com.google.android.exoplayer.Format; import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; -import com.google.android.exoplayer.chunk.ChunkSampleSource; -import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener; +import com.google.android.exoplayer.chunk.ChunkTrackStream; +import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener; +import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement; +import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; @@ -46,69 +49,55 @@ import android.util.Base64; import android.util.Pair; import java.io.IOException; -import java.util.ArrayList; -import java.util.IdentityHashMap; +import java.util.Arrays; import java.util.List; /** - * Combines multiple {@link SampleSource} instances. + * A {@link SampleSource} for SmoothStreaming media. */ public final class SmoothStreamingSampleSource implements SampleSource { private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000; private static final int INITIALIZATION_VECTOR_SIZE = 8; + private final DataSourceFactory dataSourceFactory; + private final BandwidthMeter bandwidthMeter; + private final Handler eventHandler; + private final ChunkTrackStreamEventListener eventListener; + private final LoadControl loadControl; private final ManifestFetcher manifestFetcher; - private final SmoothStreamingChunkSource[] chunkSources; - private final ChunkSampleSource[] sources; - private final IdentityHashMap trackStreamSources; - private final int[] selectedTrackCounts; - private SmoothStreamingManifest currentManifest; private boolean prepared; - private boolean seenFirstTrackSelection; private long durationUs; + private SmoothStreamingManifest currentManifest; + private TrackEncryptionBox[] trackEncryptionBoxes; + private DrmInitData.Mapped drmInitData; private TrackGroupArray trackGroups; - private ChunkSampleSource[] enabledSources; + private int[] trackGroupElementIndices; + private boolean pendingReset; + private long lastSeekPositionUs; + + private SmoothStreamingChunkSource[] chunkSources; + private ChunkTrackStream[] trackStreams; public SmoothStreamingSampleSource(Uri uri, DataSourceFactory dataSourceFactory, BandwidthMeter bandwidthMeter, Handler eventHandler, - ChunkSampleSourceEventListener eventListener) { + ChunkTrackStreamEventListener eventListener) { + this.dataSourceFactory = dataSourceFactory; + this.bandwidthMeter = bandwidthMeter; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + + loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); + chunkSources = new SmoothStreamingChunkSource[0]; + trackStreams = new ChunkTrackStream[0]; + if (!Util.toLowerInvariant(uri.getLastPathSegment()).equals("manifest")) { uri = Uri.withAppendedPath(uri, "Manifest"); } SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser(); DataSource manifestDataSource = dataSourceFactory.createDataSource(); manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser); - LoadControl loadControl = new DefaultLoadControl( - new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); - - // Build the video renderer. - DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - SmoothStreamingChunkSource videoChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_VIDEO, - videoDataSource, new AdaptiveEvaluator(bandwidthMeter)); - ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, - C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO); - - // Build the audio renderer. - DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - SmoothStreamingChunkSource audioChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_AUDIO, - audioDataSource, null); - ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, - C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO); - - // Build the text renderer. - DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - SmoothStreamingChunkSource textChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_TEXT, - textDataSource, null); - ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, - C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT); - - chunkSources = new SmoothStreamingChunkSource[] {videoChunkSource, audioChunkSource, - textChunkSource}; - sources = new ChunkSampleSource[] {videoSampleSource, audioSampleSource, textSampleSource}; - trackStreamSources = new IdentityHashMap<>(); - selectedTrackCounts = new int[sources.length]; } @Override @@ -123,46 +112,22 @@ public final class SmoothStreamingSampleSource implements SampleSource { manifestFetcher.maybeThrowError(); manifestFetcher.requestRefresh(); return false; - } else { - durationUs = currentManifest.durationUs; - - TrackEncryptionBox[] trackEncryptionBoxes; - DrmInitData.Mapped drmInitData; - ProtectionElement protectionElement = currentManifest.protectionElement; - if (protectionElement != null) { - byte[] keyId = getProtectionElementKeyId(protectionElement.data); - trackEncryptionBoxes = new TrackEncryptionBox[1]; - trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); - drmInitData = new DrmInitData.Mapped(); - drmInitData.put(protectionElement.uuid, - new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data)); - } else { - trackEncryptionBoxes = null; - drmInitData = null; - } - - for (SmoothStreamingChunkSource chunkSource : chunkSources) { - chunkSource.init(currentManifest, trackEncryptionBoxes, drmInitData); - } } } - for (ChunkSampleSource source : sources) { - source.prepare(); + durationUs = currentManifest.durationUs; + buildTrackGroups(currentManifest); + + ProtectionElement protectionElement = currentManifest.protectionElement; + if (protectionElement != null) { + byte[] keyId = getProtectionElementKeyId(protectionElement.data); + trackEncryptionBoxes = new TrackEncryptionBox[1]; + trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); + drmInitData = new DrmInitData.Mapped(); + drmInitData.put(protectionElement.uuid, + new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data)); } - int totalTrackGroupCount = 0; - for (ChunkSampleSource source : sources) { - totalTrackGroupCount += source.getTrackGroups().length; - } - TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; - int trackGroupIndex = 0; - for (ChunkSampleSource source : sources) { - int sourceTrackGroupCount = source.getTrackGroups().length; - for (int j = 0; j < sourceTrackGroupCount; j++) { - trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j); - } - } - trackGroups = new TrackGroupArray(trackGroupArray); + prepared = true; return true; } @@ -181,26 +146,38 @@ public final class SmoothStreamingSampleSource implements SampleSource { public TrackStream[] selectTracks(List oldStreams, List newSelections, long positionUs) { Assertions.checkState(prepared); - TrackStream[] newStreams = new TrackStream[newSelections.size()]; - // Select tracks for each source. - int enabledSourceCount = 0; - for (int i = 0; i < sources.length; i++) { - selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs, - newStreams); - if (selectedTrackCounts[i] > 0) { - enabledSourceCount++; + + int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size(); + SmoothStreamingChunkSource[] newChunkSources = + new SmoothStreamingChunkSource[newEnabledSourceCount]; + ChunkTrackStream[] newTrackStreams = new ChunkTrackStream[newEnabledSourceCount]; + int newEnabledSourceIndex = 0; + + // Iterate over currently enabled streams, either releasing them or adding them to the new list. + for (int i = 0; i < trackStreams.length; i++) { + ChunkTrackStream trackStream = trackStreams[i]; + if (oldStreams.contains(trackStream)) { + chunkSources[i].release(); + trackStream.release(); + } else { + newChunkSources[newEnabledSourceIndex] = chunkSources[i]; + newTrackStreams[newEnabledSourceIndex++] = trackStream; } } - // Update the enabled sources. - enabledSources = new ChunkSampleSource[enabledSourceCount]; - enabledSourceCount = 0; - for (int i = 0; i < sources.length; i++) { - if (selectedTrackCounts[i] > 0) { - enabledSources[enabledSourceCount++] = sources[i]; - } + + // Instantiate and return new streams. + TrackStream[] streamsToReturn = new TrackStream[newSelections.size()]; + for (int i = 0; i < newSelections.size(); i++) { + Pair trackComponents = + buildTrackStream(newSelections.get(i), positionUs); + newChunkSources[newEnabledSourceIndex] = trackComponents.first; + newTrackStreams[newEnabledSourceIndex++] = trackComponents.second; + streamsToReturn[i] = trackComponents.second; } - seenFirstTrackSelection = true; - return newStreams; + + chunkSources = newChunkSources; + trackStreams = newTrackStreams; + return streamsToReturn; } @Override @@ -225,103 +202,96 @@ public final class SmoothStreamingSampleSource implements SampleSource { } } - for (ChunkSampleSource source : enabledSources) { - source.continueBuffering(positionUs); + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.continueBuffering(positionUs); } } @Override public long readReset() { - long resetPositionUs = C.UNSET_TIME_US; - for (ChunkSampleSource source : enabledSources) { - long childResetPositionUs = source.readReset(); - if (resetPositionUs == C.UNSET_TIME_US) { - resetPositionUs = childResetPositionUs; - } else if (childResetPositionUs != C.UNSET_TIME_US) { - resetPositionUs = Math.min(resetPositionUs, childResetPositionUs); + if (pendingReset) { + pendingReset = false; + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.setReadingEnabled(true); } + return lastSeekPositionUs; } - return resetPositionUs; + return C.UNSET_TIME_US; } @Override public long getBufferedPositionUs() { - long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE; - for (ChunkSampleSource source : enabledSources) { - long rendererBufferedPositionUs = source.getBufferedPositionUs(); - if (rendererBufferedPositionUs == C.UNSET_TIME_US) { - return C.UNSET_TIME_US; - } else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) { - // This source is fully buffered. - } else { + long bufferedPositionUs = Long.MAX_VALUE; + for (ChunkTrackStream trackStream : trackStreams) { + long rendererBufferedPositionUs = trackStream.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.END_OF_SOURCE_US) { bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); } } - return bufferedPositionUs == Long.MAX_VALUE ? C.UNSET_TIME_US : bufferedPositionUs; + return bufferedPositionUs == Long.MAX_VALUE ? C.END_OF_SOURCE_US : bufferedPositionUs; } @Override public void seekToUs(long positionUs) { - for (ChunkSampleSource source : enabledSources) { - source.seekToUs(positionUs); + lastSeekPositionUs = positionUs; + pendingReset = true; + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.setReadingEnabled(false); + trackStream.seekToUs(positionUs); } } @Override public void release() { manifestFetcher.release(); - for (ChunkSampleSource source : sources) { - source.release(); + for (SmoothStreamingChunkSource chunkSource : chunkSources) { + chunkSource.release(); + } + for (ChunkTrackStream trackStream : trackStreams) { + trackStream.release(); } } // Internal methods. - private int selectTracks(ChunkSampleSource source, List allOldStreams, - List allNewSelections, long positionUs, TrackStream[] allNewStreams) { - // Get the subset of the old streams for the source. - ArrayList oldStreams = new ArrayList<>(); - for (int i = 0; i < allOldStreams.size(); i++) { - TrackStream stream = allOldStreams.get(i); - if (trackStreamSources.get(stream) == source) { - trackStreamSources.remove(stream); - oldStreams.add(stream); + private void buildTrackGroups(SmoothStreamingManifest manifest) { + int trackGroupCount = 0; + trackGroupElementIndices = new int[manifest.streamElements.length]; + TrackGroup[] trackGroupArray = new TrackGroup[manifest.streamElements.length]; + for (int i = 0; i < manifest.streamElements.length; i++) { + StreamElement streamElement = manifest.streamElements[i]; + int streamElementType = streamElement.type; + Format[] formats = streamElement.formats; + if (formats.length > 0 && (streamElementType == C.TRACK_TYPE_AUDIO + || streamElementType == C.TRACK_TYPE_VIDEO || streamElementType == C.TRACK_TYPE_TEXT)) { + trackGroupElementIndices[trackGroupCount] = i; + boolean adaptive = streamElementType == C.TRACK_TYPE_VIDEO; + trackGroupArray[trackGroupCount++] = new TrackGroup(adaptive, formats); } } - // Get the subset of the new selections for the source. - ArrayList newSelections = new ArrayList<>(); - int[] newSelectionOriginalIndices = new int[allNewSelections.size()]; - for (int i = 0; i < allNewSelections.size(); i++) { - TrackSelection selection = allNewSelections.get(i); - Pair sourceAndGroup = getSourceAndGroup(selection.group); - if (sourceAndGroup.first == source) { - newSelectionOriginalIndices[newSelections.size()] = i; - newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks())); - } + if (trackGroupCount < trackGroupArray.length) { + trackGroupElementIndices = Arrays.copyOf(trackGroupElementIndices, trackGroupCount); + trackGroupArray = Arrays.copyOf(trackGroupArray, trackGroupCount); } - // Do nothing if nothing has changed, except during the first selection. - if (seenFirstTrackSelection && oldStreams.isEmpty() && newSelections.isEmpty()) { - return 0; - } - // Perform the selection. - TrackStream[] newStreams = source.selectTracks(oldStreams, newSelections, positionUs); - for (int j = 0; j < newStreams.length; j++) { - allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j]; - trackStreamSources.put(newStreams[j], source); - } - return newSelections.size() - oldStreams.size(); + trackGroups = new TrackGroupArray(trackGroupArray); } - private Pair getSourceAndGroup(int group) { - int totalTrackGroupCount = 0; - for (ChunkSampleSource source : sources) { - int sourceTrackGroupCount = source.getTrackGroups().length; - if (group < totalTrackGroupCount + sourceTrackGroupCount) { - return Pair.create(source, group - totalTrackGroupCount); - } - totalTrackGroupCount += sourceTrackGroupCount; - } - throw new IndexOutOfBoundsException(); + private Pair buildTrackStream( + TrackSelection selection, long positionUs) { + int[] selectedTracks = selection.getTracks(); + FormatEvaluator adaptiveEvaluator = selectedTracks.length > 1 + ? new AdaptiveEvaluator(bandwidthMeter) : null; + int streamElementIndex = trackGroupElementIndices[selection.group]; + StreamElement streamElement = currentManifest.streamElements[streamElementIndex]; + int streamElementType = streamElement.type; + int bufferSize = Util.getDefaultBufferSize(streamElementType); + DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter); + SmoothStreamingChunkSource chunkSource = new SmoothStreamingChunkSource(currentManifest, + streamElementIndex, trackGroups.get(selection.group), selectedTracks, dataSource, + adaptiveEvaluator, trackEncryptionBoxes, drmInitData); + ChunkTrackStream trackStream = new ChunkTrackStream(chunkSource, loadControl, bufferSize, + positionUs, eventHandler, eventListener, streamElementType); + return Pair.create(chunkSource, trackStream); } private static byte[] getProtectionElementKeyId(byte[] initData) { diff --git a/library/src/main/java/com/google/android/exoplayer/util/Util.java b/library/src/main/java/com/google/android/exoplayer/util/Util.java index 870c44345b..889d90b06c 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Util.java @@ -797,6 +797,27 @@ public final class Util { } } + /** + * Maps a {@link C} TRACK_TYPE_* constant to its corresponding DEFAULT_*_BUFFER_SIZE value. + * + * @param trackType The track type. + * @return The corresponding default buffer size in bytes. + */ + public static int getDefaultBufferSize(int trackType) { + switch (trackType) { + case C.TRACK_TYPE_DEFAULT: + return C.DEFAULT_MUXED_BUFFER_SIZE; + case C.TRACK_TYPE_AUDIO: + return C.DEFAULT_AUDIO_BUFFER_SIZE; + case C.TRACK_TYPE_VIDEO: + return C.DEFAULT_VIDEO_BUFFER_SIZE; + case C.TRACK_TYPE_TEXT: + return C.DEFAULT_TEXT_BUFFER_SIZE; + default: + throw new IllegalStateException(); + } + } + /** * Escapes a string so that it's safe for use as a file or directory name on at least FAT32 * filesystems. FAT32 is the most restrictive of all filesystems still commonly used today.