Commit 0841d043 by olly Committed by Oliver Woodman

Refactor #6.4: Create child source instances on demand.

Dash/SS SampleSources now instantiate ChunkSource and
ChunkSampleSource (renamed to ChunkTrackStream) instances
on demand as tracks are enabled. The TrackGroups exposed
by the DASH/SS SampleSources are now constructed at the
top level.

Note that this change resolves the TODOs at the top of the
ChunkSource classes, allowing multiple adaptation sets of
the same type.

Next steps will include:

- Bring back UTC timing element support for DASH, which
  will be an extra request during preparation in  the DASH
  SampleSource.
- Simplification of manifest fetching to use a Loader directly
  in the two top level SampleSource classes. ManifestFetcher
  should eventually go away once HLS no longer needs it.
- Eventually, some consolidation between DASH/SS. There's a
  lot of common code there now.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121001777
parent d82bb3f6
...@@ -31,7 +31,7 @@ import com.google.android.exoplayer.SingleSampleSource; ...@@ -31,7 +31,7 @@ import com.google.android.exoplayer.SingleSampleSource;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioTrack; 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.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
...@@ -61,7 +61,7 @@ import java.util.concurrent.CopyOnWriteArrayList; ...@@ -61,7 +61,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
* A wrapper around {@link ExoPlayer} that provides a higher level interface. * A wrapper around {@link ExoPlayer} that provides a higher level interface.
*/ */
public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener, public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener,
ChunkSampleSourceEventListener, ExtractorSampleSource.EventListener, ChunkTrackStreamEventListener, ExtractorSampleSource.EventListener,
SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener, SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer<List<Id3Frame>>, StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer<List<Id3Frame>>,
......
...@@ -104,7 +104,7 @@ public interface SampleSource { ...@@ -104,7 +104,7 @@ public interface SampleSource {
long getBufferedPositionUs(); long getBufferedPositionUs();
/** /**
* Seeks to the specified time in microseconds. * Seeks to the specified position in microseconds.
* <p> * <p>
* This method should only be called when at least one track is selected. * This method should only be called when at least one track is selected.
* *
......
...@@ -15,13 +15,11 @@ ...@@ -15,13 +15,11 @@
*/ */
package com.google.android.exoplayer.chunk; package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.TrackGroup;
import java.io.IOException; import java.io.IOException;
import java.util.List; 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 * TODO: Share more state between this interface and {@link ChunkSampleSource}. In particular
...@@ -41,25 +39,6 @@ public interface ChunkSource { ...@@ -41,25 +39,6 @@ public interface ChunkSource {
void maybeThrowError() throws IOException; void maybeThrowError() throws IOException;
/** /**
* Gets the group of tracks provided by the source.
* <p>
* 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.
* <p>
* 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. * Evaluates whether {@link MediaChunk}s should be removed from the back of the queue.
* <p> * <p>
* Removing {@link MediaChunk}s from the back of the queue can be useful if they could be replaced * Removing {@link MediaChunk}s from the back of the queue can be useful if they could be replaced
...@@ -88,7 +67,7 @@ public interface ChunkSource { ...@@ -88,7 +67,7 @@ public interface ChunkSource {
void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out); 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. * source.
* <p> * <p>
* This method should only be called when the source is enabled. * This method should only be called when the source is enabled.
...@@ -98,7 +77,7 @@ public interface ChunkSource { ...@@ -98,7 +77,7 @@ public interface ChunkSource {
void onChunkLoadCompleted(Chunk chunk); 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 source.
* <p> * <p>
* This method should only be called when the source is enabled. * This method should only be called when the source is enabled.
...@@ -110,11 +89,4 @@ public interface ChunkSource { ...@@ -110,11 +89,4 @@ public interface ChunkSource {
*/ */
boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e); boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e);
/**
* Disables the source.
* <p>
* This method should only be called when the source is enabled.
*/
void disable();
} }
...@@ -17,7 +17,6 @@ package com.google.android.exoplayer.chunk; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -26,14 +25,14 @@ import android.os.Handler; ...@@ -26,14 +25,14 @@ import android.os.Handler;
import java.io.IOException; 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. * 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 * @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. * the length of the data is not known in advance.
* @param type The type of the data being loaded. * @param type The type of the data being loaded.
...@@ -51,7 +50,7 @@ public interface ChunkSampleSourceEventListener { ...@@ -51,7 +50,7 @@ public interface ChunkSampleSourceEventListener {
/** /**
* Invoked when the current load operation completes. * 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 bytesLoaded The number of bytes that were loaded.
* @param type The type of the loaded data. * @param type The type of the loaded data.
* @param trigger The reason for the data being loaded. * @param trigger The reason for the data being loaded.
...@@ -70,7 +69,7 @@ public interface ChunkSampleSourceEventListener { ...@@ -70,7 +69,7 @@ public interface ChunkSampleSourceEventListener {
/** /**
* Invoked when the current upstream load operation is canceled. * 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. * @param bytesLoaded The number of bytes that were loaded prior to the cancellation.
*/ */
void onLoadCanceled(int sourceId, long bytesLoaded); void onLoadCanceled(int sourceId, long bytesLoaded);
...@@ -78,7 +77,7 @@ public interface ChunkSampleSourceEventListener { ...@@ -78,7 +77,7 @@ public interface ChunkSampleSourceEventListener {
/** /**
* Invoked when an error occurs loading media data. * 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. * @param e The cause of the failure.
*/ */
void onLoadError(int sourceId, IOException e); void onLoadError(int sourceId, IOException e);
...@@ -87,7 +86,7 @@ public interface ChunkSampleSourceEventListener { ...@@ -87,7 +86,7 @@ public interface ChunkSampleSourceEventListener {
* Invoked when data is removed from the back of the buffer, typically so that it can be * Invoked when data is removed from the back of the buffer, typically so that it can be
* re-buffered using a different representation. * 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 mediaStartTimeMs The media time of the start of the discarded data.
* @param mediaEndTimeMs The media time of the end of the discarded data. * @param mediaEndTimeMs The media time of the end of the discarded data.
*/ */
...@@ -97,7 +96,7 @@ public interface ChunkSampleSourceEventListener { ...@@ -97,7 +96,7 @@ public interface ChunkSampleSourceEventListener {
* Invoked when the downstream format changes (i.e. when the format being supplied to the * Invoked when the downstream format changes (i.e. when the format being supplied to the
* caller of {@link TrackStream#readData} changes). * 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 format The format.
* @param trigger The trigger specified in the corresponding upstream load, as specified by the * @param trigger The trigger specified in the corresponding upstream load, as specified by the
* {@link ChunkSource}. * {@link ChunkSource}.
...@@ -106,15 +105,15 @@ public interface ChunkSampleSourceEventListener { ...@@ -106,15 +105,15 @@ public interface ChunkSampleSourceEventListener {
void onDownstreamFormatChanged(int sourceId, Format format, int trigger, long mediaTimeMs); 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 { final class EventDispatcher {
private final Handler handler; private final Handler handler;
private final ChunkSampleSourceEventListener listener; private final ChunkTrackStreamEventListener listener;
private final int sourceId; 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.handler = listener != null ? handler : null;
this.listener = listener; this.listener = listener;
this.sourceId = sourceId; this.sourceId = sourceId;
......
...@@ -52,88 +52,94 @@ import java.util.List; ...@@ -52,88 +52,94 @@ import java.util.List;
/** /**
* An {@link ChunkSource} for DASH streams. * An {@link ChunkSource} for DASH streams.
* <p>
* This implementation currently supports fMP4, webm, webvtt and ttml.
* <p>
* This implementation makes the following assumptions about multi-period manifests:
* <ol>
* <li>that new periods will contain the same representations as previous periods (i.e. no new or
* missing representations) and</li>
* <li>that representations are contiguous across multiple periods</li>
* </ol>
*/ */
// 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 { 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 DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator; private final FormatEvaluator adaptiveFormatEvaluator;
private final Evaluation evaluation; private final Evaluation evaluation;
private MediaPresentationDescription currentManifest; private MediaPresentationDescription manifest;
private DrmInitData drmInitData; private DrmInitData drmInitData;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
private IOException fatalError; 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 * @param manifest The initial manifest.
* {@link C#TRACK_TYPE_AUDIO}, {@link C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_TEXT}. * @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 dataSource A {@link DataSource} suitable for loading the media data.
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. * @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) { FormatEvaluator adaptiveFormatEvaluator) {
this.adaptationSetType = adaptationSetType; this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex;
this.trackGroup = trackGroup;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
}
// ChunkSource implementation. Period period = manifest.getPeriod(0);
long periodDurationUs = getPeriodDurationUs(manifest, 0);
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
drmInitData = getDrmInitData(adaptationSet);
@Override List<Representation> representations = adaptationSet.representations;
public void maybeThrowError() throws IOException { representationHolders = new RepresentationHolder[representations.size()];
if (fatalError != null) { for (int i = 0; i < representations.size(); i++) {
throw fatalError; Representation representation = representations.get(i);
representationHolders[i] = new RepresentationHolder(periodDurationUs, representation);
} }
}
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]; enabledFormats = new Format[tracks.length];
for (int i = 0; i < tracks.length; i++) { for (int i = 0; i < tracks.length; i++) {
enabledFormats[i] = trackGroup.getFormat(tracks[i]); enabledFormats[i] = trackGroup.getFormat(tracks[i]);
} }
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator()); Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
if (enabledFormats.length > 1) { if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.enable(enabledFormats); adaptiveFormatEvaluator.enable(enabledFormats);
adaptiveFormatBlacklistFlags = new boolean[tracks.length]; adaptiveFormatBlacklistFlags = new boolean[tracks.length];
} else {
adaptiveFormatBlacklistFlags = null;
} }
} }
public void updateManifest(MediaPresentationDescription newManifest) { public void updateManifest(MediaPresentationDescription newManifest) {
processManifest(newManifest); try {
manifest = newManifest;
long periodDurationUs = getPeriodDurationUs(manifest, 0);
List<Representation> 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.
@Override
public void maybeThrowError() throws IOException {
if (fatalError != null) {
throw fatalError;
}
} }
@Override @Override
...@@ -200,9 +206,9 @@ public class DashChunkSource implements ChunkSource { ...@@ -200,9 +206,9 @@ public class DashChunkSource implements ChunkSource {
if (indexUnbounded) { if (indexUnbounded) {
// The index is itself unbounded. We need to use the current time to calculate the range of // The index is itself unbounded. We need to use the current time to calculate the range of
// available segments. // available segments.
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000; long liveEdgeTimestampUs = nowUs - manifest.availabilityStartTime * 1000;
if (currentManifest.timeShiftBufferDepth != -1) { if (manifest.timeShiftBufferDepth != -1) {
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000; long bufferDepthUs = manifest.timeShiftBufferDepth * 1000;
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum, firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
representationHolder.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs)); representationHolder.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
} }
...@@ -226,7 +232,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -226,7 +232,7 @@ public class DashChunkSource implements ChunkSource {
if (segmentNum > lastAvailableSegmentNum) { if (segmentNum > lastAvailableSegmentNum) {
// This is beyond the last chunk in the current manifest. // This is beyond the last chunk in the current manifest.
out.endOfStream = !currentManifest.dynamic; out.endOfStream = !manifest.dynamic;
return; return;
} }
...@@ -270,59 +276,8 @@ public class DashChunkSource implements ChunkSource { ...@@ -270,59 +276,8 @@ public class DashChunkSource implements ChunkSource {
return false; return false;
} }
@Override
public void disable() {
if (enabledFormats.length > 1) {
adaptiveFormatEvaluator.disable();
}
evaluation.clear();
fatalError = null;
enabledFormats = null;
}
// Private methods. // 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<Representation> 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<Representation> 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, private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
int trigger) { int trigger) {
......
...@@ -27,8 +27,8 @@ import com.google.android.exoplayer.TrackSelection; ...@@ -27,8 +27,8 @@ import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkHolder; import com.google.android.exoplayer.chunk.ChunkHolder;
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener; import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
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.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
...@@ -109,7 +109,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -109,7 +109,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
*/ */
public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int bufferSizeContribution, Handler eventHandler, int bufferSizeContribution, Handler eventHandler,
ChunkSampleSourceEventListener eventListener, int eventSourceId) { ChunkTrackStreamEventListener eventListener, int eventSourceId) {
this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener,
eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
} }
...@@ -127,7 +127,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -127,7 +127,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
*/ */
public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int bufferSizeContribution, Handler eventHandler, int bufferSizeContribution, Handler eventHandler,
ChunkSampleSourceEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) {
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.loadControl = loadControl; this.loadControl = loadControl;
this.bufferSizeContribution = bufferSizeContribution; this.bufferSizeContribution = bufferSizeContribution;
......
...@@ -46,86 +46,75 @@ import java.util.List; ...@@ -46,86 +46,75 @@ import java.util.List;
/** /**
* An {@link ChunkSource} for SmoothStreaming. * An {@link ChunkSource} for SmoothStreaming.
*/ */
// TODO[REFACTOR]: Handle multiple stream elements of the same type (at a higher level).
public class SmoothStreamingChunkSource implements ChunkSource { 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 DataSource dataSource;
private final Evaluation evaluation; private final Evaluation evaluation;
private final FormatEvaluator adaptiveFormatEvaluator; private final FormatEvaluator adaptiveFormatEvaluator;
private SmoothStreamingManifest currentManifest; private SmoothStreamingManifest manifest;
private TrackEncryptionBox[] trackEncryptionBoxes;
private DrmInitData drmInitData; private DrmInitData drmInitData;
private int currentManifestChunkOffset; private int currentManifestChunkOffset;
private boolean needManifestRefresh; 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; private IOException fatalError;
/** /**
* @param streamElementType The type of stream element exposed by this source. One of * @param manifest The initial manifest.
* {@link C#TRACK_TYPE_VIDEO}, {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_TEXT}. * @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 dataSource A {@link DataSource} suitable for loading the media data.
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. * @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, public SmoothStreamingChunkSource(SmoothStreamingManifest manifest, int elementIndex,
FormatEvaluator adaptiveFormatEvaluator) { TrackGroup trackGroup, int[] tracks, DataSource dataSource,
this.streamElementType = streamElementType; FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes,
DrmInitData drmInitData) {
this.manifest = manifest;
this.elementIndex = elementIndex;
this.trackGroup = trackGroup;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; 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; this.drmInitData = drmInitData;
initForManifest(currentManifest); this.evaluation = new Evaluation();
}
StreamElement streamElement = manifest.streamElements[elementIndex];
@Override Format[] formats = streamElement.formats;
public final TrackGroup getTracks() { extractorWrappers = new ChunkExtractorWrapper[formats.length];
return trackGroup; 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]; enabledFormats = new Format[tracks.length];
for (int i = 0; i < tracks.length; i++) { for (int i = 0; i < tracks.length; i++) {
enabledFormats[i] = trackGroup.getFormat(tracks[i]); enabledFormats[i] = trackGroup.getFormat(tracks[i]);
} }
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator()); Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
if (enabledFormats.length > 1) { if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.enable(enabledFormats); adaptiveFormatEvaluator.enable(enabledFormats);
adaptiveFormatBlacklistFlags = new boolean[tracks.length]; adaptiveFormatBlacklistFlags = new boolean[tracks.length];
} else {
adaptiveFormatBlacklistFlags = null;
} }
} }
public void updateManifest(SmoothStreamingManifest newManifest) { public void updateManifest(SmoothStreamingManifest newManifest) {
StreamElement currentElement = currentManifest.streamElements[elementIndex]; StreamElement currentElement = manifest.streamElements[elementIndex];
int currentElementChunkCount = currentElement.chunkCount; int currentElementChunkCount = currentElement.chunkCount;
StreamElement newElement = newManifest.streamElements[elementIndex]; StreamElement newElement = newManifest.streamElements[elementIndex];
if (currentElementChunkCount == 0 || newElement.chunkCount == 0) { if (currentElementChunkCount == 0 || newElement.chunkCount == 0) {
...@@ -143,10 +132,29 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -143,10 +132,29 @@ public class SmoothStreamingChunkSource implements ChunkSource {
currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs); currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs);
} }
} }
currentManifest = newManifest; manifest = newManifest;
needManifestRefresh = false; 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 @Override
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) { public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
if (fatalError != null || enabledFormats.length < 2) { if (fatalError != null || enabledFormats.length < 2) {
...@@ -176,9 +184,9 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -176,9 +184,9 @@ public class SmoothStreamingChunkSource implements ChunkSource {
return; return;
} }
StreamElement streamElement = currentManifest.streamElements[elementIndex]; StreamElement streamElement = manifest.streamElements[elementIndex];
if (streamElement.chunkCount == 0) { if (streamElement.chunkCount == 0) {
if (currentManifest.isLive) { if (manifest.isLive) {
needManifestRefresh = true; needManifestRefresh = true;
} else { } else {
out.endOfStream = true; out.endOfStream = true;
...@@ -198,10 +206,10 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -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) { if (chunkIndex >= streamElement.chunkCount) {
// This is beyond the last chunk in the current manifest. // This is beyond the last chunk in the current manifest.
out.endOfStream = !currentManifest.isLive; out.endOfStream = !manifest.isLive;
return; return;
} }
...@@ -231,45 +239,8 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -231,45 +239,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
return false; return false;
} }
@Override
public void disable() {
if (enabledFormats.length > 1) {
adaptiveFormatEvaluator.disable();
}
evaluation.clear();
fatalError = null;
}
// Private methods. // 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. * Gets the index of a format in a track group, using referential equality.
*/ */
......
...@@ -798,6 +798,27 @@ public final class Util { ...@@ -798,6 +798,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 * 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. * filesystems. FAT32 is the most restrictive of all filesystems still commonly used today.
* *
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment