Skip to content

Commit

Permalink
Refactor #6.4: Create child source instances on demand.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ojw28 committed Jun 15, 2016
1 parent d82bb3f commit 0841d04
Show file tree
Hide file tree
Showing 11 changed files with 468 additions and 648 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
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;
Expand Down Expand Up @@ -61,7 +61,7 @@
* 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<List<Id3Frame>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ TrackStream[] selectTracks(List<TrackStream> oldStreams, List<TrackSelection> ne
long getBufferedPositionUs();

/**
* Seeks to the specified time in microseconds.
* Seeks to the specified position in microseconds.
* <p>
* This method should only be called when at least one track is selected.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,25 +38,6 @@ public interface ChunkSource {
*/
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.
* <p>
Expand Down Expand Up @@ -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.
* <p>
* This method should only be called when the source is enabled.
Expand All @@ -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.
* <p>
* This method should only be called when the source is enabled.
Expand All @@ -110,11 +89,4 @@ public interface ChunkSource {
*/
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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@
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;
Expand All @@ -40,10 +36,9 @@
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.
Expand All @@ -55,152 +50,94 @@ public class ChunkSampleSource implements TrackStream, Loader.Callback {
private final LinkedList<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> 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;

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);
}

/**
* @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.
* @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<TrackStream> oldStreams,
List<TrackSelection> 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()) {
maybeStartLoading();
}
}

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;
Expand All @@ -218,6 +155,11 @@ public long getBufferedPositionUs() {
}
}

/**
* Seeks to the specified position in microseconds.
*
* @param positionUs The seek position in microseconds.
*/
public void seekToUs(long positionUs) {
downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs;
Expand All @@ -233,16 +175,23 @@ public void seekToUs(long positionUs) {
// 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.
* <p>
* 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.
Expand All @@ -260,7 +209,7 @@ public void maybeThrowError() throws IOException {

@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
if (notifyReset || isPendingReset()) {
if (!readingEnabled || isPendingReset()) {
return NOTHING_READ;
}

Expand Down Expand Up @@ -306,7 +255,7 @@ public void onLoadCompleted(Loadable loadable) {
@Override
public void onLoadCanceled(Loadable loadable) {
eventDispatcher.loadCanceled(currentLoadable.bytesLoaded());
if (trackEnabled) {
if (!released) {
restartFrom(pendingResetPositionUs);
} else {
clearState();
Expand Down
Loading

0 comments on commit 0841d04

Please sign in to comment.