Expose new multi-track APIs in the ExoPlayer interface + plumb

multi-track support upstream to the ChunkSource interface.

This change does not yet make use of the newly exposed APIs. This
will come in a subsequent CL.

Issue #514.
This commit is contained in:
Oliver Woodman 2015-08-17 17:08:09 +01:00
parent bf77f3b289
commit 6e527c550f
10 changed files with 136 additions and 45 deletions

View File

@ -245,6 +245,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
pushSurface(true); pushSurface(true);
} }
@SuppressWarnings("deprecation")
public int getTrackCount(int type) { public int getTrackCount(int type) {
return !player.getRendererHasMedia(type) ? 0 : trackNames[type].length; return !player.getRendererHasMedia(type) ? 0 : trackNames[type].length;
} }
@ -330,7 +331,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
if (trackNames[rendererIndex] == null) { if (trackNames[rendererIndex] == null) {
// Convert a null trackNames to an array of suitable length. // Convert a null trackNames to an array of suitable length.
int trackCount = multiTrackSources[rendererIndex] != null int trackCount = multiTrackSources[rendererIndex] != null
? multiTrackSources[rendererIndex].getTrackCount() : 1; ? multiTrackSources[rendererIndex].getMultiTrackCount() : 1;
trackNames[rendererIndex] = new String[trackCount]; trackNames[rendererIndex] = new String[trackCount];
} }
} }
@ -619,6 +620,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
} }
@SuppressWarnings("deprecation")
private void pushTrackSelection(int type, boolean allowRendererEnable) { private void pushTrackSelection(int type, boolean allowRendererEnable) {
if (multiTrackSources == null) { if (multiTrackSources == null) {
return; return;

View File

@ -101,7 +101,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
public void testGetSeekRangeOnVod() { public void testGetSeekRangeOnVod() {
DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO, DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO,
null, null, mock(FormatEvaluator.class)); null, null, mock(FormatEvaluator.class));
chunkSource.enable(); chunkSource.enable(0);
TimeRange seekRange = chunkSource.getSeekRange(); TimeRange seekRange = chunkSource.getSeekRange();
checkSeekRange(seekRange, 0, VOD_DURATION_MS * 1000); checkSeekRange(seekRange, 0, VOD_DURATION_MS * 1000);
@ -394,7 +394,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
AdaptationSet.TYPE_VIDEO, null, mockDataSource, EVALUATOR, AdaptationSet.TYPE_VIDEO, null, mockDataSource, EVALUATOR,
new FakeClock(AVAILABILITY_CURRENT_TIME_MS + periodStartMs), liveEdgeLatencyMs * 1000, new FakeClock(AVAILABILITY_CURRENT_TIME_MS + periodStartMs), liveEdgeLatencyMs * 1000,
AVAILABILITY_REALTIME_OFFSET_MS * 1000, false, null, null); AVAILABILITY_REALTIME_OFFSET_MS * 1000, false, null, null);
chunkSource.enable(); chunkSource.enable(0);
return chunkSource; return chunkSource;
} }

View File

@ -74,7 +74,7 @@ import android.os.Looper;
* <h3>Player state</h3> * <h3>Player state</h3>
* *
* <p>The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. State * <p>The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. State
* accessed by {@link #getRendererEnabled(int)} and {@link #getPlayWhenReady()} are only ever * accessed by {@link #getSelectedTrack(int)} and {@link #getPlayWhenReady()} is only ever
* changed by invoking the player's methods, and are never changed as a result of operations that * changed by invoking the player's methods, and are never changed as a result of operations that
* have been performed asynchronously by the playback thread. In contrast, the playback state * have been performed asynchronously by the playback thread. In contrast, the playback state
* accessed by {@link #getPlaybackState()} is only ever changed as a result of operations * accessed by {@link #getPlaybackState()} is only ever changed as a result of operations
@ -219,6 +219,18 @@ public interface ExoPlayer {
* The player has finished playing the media. * The player has finished playing the media.
*/ */
public static final int STATE_ENDED = 5; public static final int STATE_ENDED = 5;
/**
* A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to
* disable the renderer.
*/
public static final int TRACK_DISABLED = -1;
/**
* A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to
* select the default track.
*/
public static final int TRACK_DEFAULT = 0;
/** /**
* Represents an unknown time or duration. * Represents an unknown time or duration.
*/ */
@ -266,27 +278,73 @@ public interface ExoPlayer {
* <p> * <p>
* Always returns false whilst the player is in the {@link #STATE_PREPARING} state. * Always returns false whilst the player is in the {@link #STATE_PREPARING} state.
* *
* @deprecated Use {@code getTrackCount(rendererIndex) > 0}.
* @param rendererIndex The index of the renderer. * @param rendererIndex The index of the renderer.
* @return True if the renderer has media to play, false otherwise. * @return True if the renderer has media to play, false otherwise.
*/ */
@Deprecated
public boolean getRendererHasMedia(int rendererIndex); public boolean getRendererHasMedia(int rendererIndex);
/** /**
* Sets whether the renderer at the given index is enabled. * Sets whether the renderer at the given index is enabled.
* *
* @deprecated Use {@code setSelectedTrack(rendererIndex, trackIndex)}. Passing
* {@link #TRACK_DEFAULT} as {@code trackIndex} is equivalent to enabling the renderer with
* this method. Passing {@link #TRACK_DISABLED} is equivalent to disabling the renderer.
* @param rendererIndex The index of the renderer. * @param rendererIndex The index of the renderer.
* @param enabled Whether the renderer at the given index should be enabled. * @param enabled Whether the renderer at the given index should be enabled.
*/ */
@Deprecated
public void setRendererEnabled(int rendererIndex, boolean enabled); public void setRendererEnabled(int rendererIndex, boolean enabled);
/** /**
* Whether the renderer at the given index is enabled. * Whether the renderer at the given index is enabled.
* *
* @deprecated Use {@code getSelectedTrack(rendererIndex)}. A return value between 0 (inclusive)
* and {@code getTrackCount(rendererIndex)} (exclusive) indicate the renderer is enabled. A
* value outside of this range (e.g. {@link #TRACK_DISABLED}) indicates that the renderer is
* disabled.
* @param rendererIndex The index of the renderer. * @param rendererIndex The index of the renderer.
* @return Whether the renderer is enabled. * @return Whether the renderer is enabled.
*/ */
@Deprecated
public boolean getRendererEnabled(int rendererIndex); public boolean getRendererEnabled(int rendererIndex);
/**
* Returns the number of tracks exposed by the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @return The number of tracks.
*/
public int getTrackCount(int rendererIndex);
/**
* Returns the format of a track.
*
* @param rendererIndex The index of the renderer.
* @param trackIndex The index of the track.
* @return The format of the track.
*/
public MediaFormat getTrackFormat(int rendererIndex, int trackIndex);
/**
* Selects a track for the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @param trackIndex The index of the track. A negative value or a value greater than or equal to
* the renderer's track count will disable the renderer.
*/
public void setSelectedTrack(int rendererIndex, int trackIndex);
/**
* Returns the index of the currently selected track for the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @return The selected track. A negative value or a value greater than or equal to the renderer's
* track count indicates that the renderer is disabled.
*/
public int getSelectedTrack(int rendererIndex);
/** /**
* Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}.
* If the player is already in this state, then this method can be used to pause and resume * If the player is already in this state, then this method can be used to pause and resume

View File

@ -96,44 +96,45 @@ import java.util.concurrent.CopyOnWriteArraySet;
internalPlayer.prepare(renderers); internalPlayer.prepare(renderers);
} }
@Deprecated
@Override @Override
// TODO: Deprecate in ExoPlayer.
public boolean getRendererHasMedia(int rendererIndex) { public boolean getRendererHasMedia(int rendererIndex) {
return getRendererTrackCount(rendererIndex) > 0; return getTrackCount(rendererIndex) > 0;
} }
@Deprecated
@Override @Override
// TODO: Deprecate in ExoPlayer.
public void setRendererEnabled(int rendererIndex, boolean enabled) { public void setRendererEnabled(int rendererIndex, boolean enabled) {
setRendererSelectedTrack(rendererIndex, enabled ? 0 : -1); setSelectedTrack(rendererIndex, enabled ? ExoPlayer.TRACK_DEFAULT : ExoPlayer.TRACK_DISABLED);
}
@Deprecated
@Override
public boolean getRendererEnabled(int rendererIndex) {
int selectedTrack = getSelectedTrack(rendererIndex);
return 0 <= selectedTrack && selectedTrack < getTrackCount(rendererIndex);
} }
@Override @Override
// TODO: Deprecate in ExoPlayer. public int getTrackCount(int rendererIndex) {
public boolean getRendererEnabled(int rendererIndex) {
return getRendererSelectedTrack(rendererIndex) == 0;
}
// TODO: Expose in ExoPlayer.
public int getRendererTrackCount(int rendererIndex) {
return trackFormats[rendererIndex] != null ? trackFormats[rendererIndex].length : 0; return trackFormats[rendererIndex] != null ? trackFormats[rendererIndex].length : 0;
} }
// TODO: Expose in ExoPlayer. @Override
public MediaFormat getRendererTrackInfo(int rendererIndex, int trackIndex) { public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) {
return trackFormats[rendererIndex][trackIndex]; return trackFormats[rendererIndex][trackIndex];
} }
// TODO: Expose in ExoPlayer. @Override
public void setRendererSelectedTrack(int rendererIndex, int trackIndex) { public void setSelectedTrack(int rendererIndex, int trackIndex) {
if (selectedTrackIndices[rendererIndex] != trackIndex) { if (selectedTrackIndices[rendererIndex] != trackIndex) {
selectedTrackIndices[rendererIndex] = trackIndex; selectedTrackIndices[rendererIndex] = trackIndex;
internalPlayer.setRendererSelectedTrack(rendererIndex, trackIndex); internalPlayer.setRendererSelectedTrack(rendererIndex, trackIndex);
} }
} }
// TODO: Expose in ExoPlayer. @Override
public int getRendererSelectedTrack(int rendererIndex) { public int getSelectedTrack(int rendererIndex) {
return selectedTrackIndices[rendererIndex]; return selectedTrackIndices[rendererIndex];
} }

View File

@ -81,6 +81,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
private Loader loader; private Loader loader;
private boolean loadingFinished; private boolean loadingFinished;
private IOException currentLoadableException; private IOException currentLoadableException;
private int enabledTrackCount;
private int currentLoadableExceptionCount; private int currentLoadableExceptionCount;
private long currentLoadableExceptionTimestamp; private long currentLoadableExceptionTimestamp;
private long currentLoadStartTimeMs; private long currentLoadStartTimeMs;
@ -131,7 +132,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
if (state == STATE_PREPARED) { if (state == STATE_PREPARED) {
return true; return true;
} }
loader = new Loader("Loader:" + chunkSource.getFormat().mimeType); loader = new Loader("Loader:" + chunkSource.getFormat(0).mimeType);
state = STATE_PREPARED; state = STATE_PREPARED;
return true; return true;
} }
@ -139,22 +140,21 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
@Override @Override
public int getTrackCount() { public int getTrackCount() {
Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED); Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED);
return 1; return chunkSource.getTrackCount();
} }
@Override @Override
public MediaFormat getFormat(int track) { public MediaFormat getFormat(int track) {
Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED); Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED);
Assertions.checkState(track == 0); return chunkSource.getFormat(track);
return chunkSource.getFormat();
} }
@Override @Override
public void enable(int track, long positionUs) { public void enable(int track, long positionUs) {
Assertions.checkState(state == STATE_PREPARED); Assertions.checkState(state == STATE_PREPARED);
Assertions.checkState(track == 0); Assertions.checkState(enabledTrackCount++ == 0);
state = STATE_ENABLED; state = STATE_ENABLED;
chunkSource.enable(); chunkSource.enable(track);
loadControl.register(this, bufferSizeContribution); loadControl.register(this, bufferSizeContribution);
downstreamFormat = null; downstreamFormat = null;
downstreamMediaFormat = null; downstreamMediaFormat = null;
@ -167,7 +167,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
@Override @Override
public void disable(int track) { public void disable(int track) {
Assertions.checkState(state == STATE_ENABLED); Assertions.checkState(state == STATE_ENABLED);
Assertions.checkState(track == 0); Assertions.checkState(--enabledTrackCount == 0);
state = STATE_PREPARED; state = STATE_PREPARED;
try { try {
chunkSource.disable(mediaChunks); chunkSource.disable(mediaChunks);
@ -187,7 +187,6 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
@Override @Override
public boolean continueBuffering(int track, long positionUs) { public boolean continueBuffering(int track, long positionUs) {
Assertions.checkState(state == STATE_ENABLED); Assertions.checkState(state == STATE_ENABLED);
Assertions.checkState(track == 0);
downstreamPositionUs = positionUs; downstreamPositionUs = positionUs;
chunkSource.continueBuffering(positionUs); chunkSource.continueBuffering(positionUs);
updateLoadControl(); updateLoadControl();
@ -198,7 +197,6 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
public int readData(int track, long positionUs, MediaFormatHolder formatHolder, public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
Assertions.checkState(state == STATE_ENABLED); Assertions.checkState(state == STATE_ENABLED);
Assertions.checkState(track == 0);
downstreamPositionUs = positionUs; downstreamPositionUs = positionUs;
if (pendingDiscontinuity) { if (pendingDiscontinuity) {

View File

@ -31,13 +31,21 @@ import java.util.List;
public interface ChunkSource { public interface ChunkSource {
/** /**
* Gets the format. * Returns the number of tracks exposed by the source.
*
* @return The number of tracks.
*/
int getTrackCount();
/**
* Gets the format of the specified track.
* <p> * <p>
* May be called when the source is disabled or enabled. * May be called when the source is disabled or enabled.
* *
* @return The format. * @param track The track index.
* @return The format of the track.
*/ */
MediaFormat getFormat(); MediaFormat getFormat(int track);
/** /**
* Adaptive video {@link ChunkSource} implementations must return a copy of the provided * Adaptive video {@link ChunkSource} implementations must return a copy of the provided
@ -52,8 +60,10 @@ public interface ChunkSource {
/** /**
* Called when the source is enabled. * Called when the source is enabled.
*
* @param track The track index.
*/ */
void enable(); void enable(int track);
/** /**
* Called when the source is disabled. * Called when the source is disabled.

View File

@ -27,6 +27,8 @@ import java.util.List;
* A {@link ChunkSource} providing the ability to switch between multiple other {@link ChunkSource} * A {@link ChunkSource} providing the ability to switch between multiple other {@link ChunkSource}
* instances. * instances.
*/ */
// TODO: Expose multiple tracks directly in DashChunkSource and SmoothStreamingChunkSource, and
// delete this class.
public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent { public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
/** /**
@ -55,18 +57,23 @@ public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerCompon
* *
* @return The number of tracks. * @return The number of tracks.
*/ */
public int getTrackCount() { public int getMultiTrackCount() {
return allSources.length; return allSources.length;
} }
@Override @Override
public MediaFormat getFormat() { public int getTrackCount() {
return selectedSource.getFormat(); return selectedSource.getTrackCount();
} }
@Override @Override
public void enable() { public MediaFormat getFormat(int track) {
selectedSource.enable(); return selectedSource.getFormat(track);
}
@Override
public void enable(int track) {
selectedSource.enable(track);
enabled = true; enabled = true;
} }

View File

@ -55,7 +55,12 @@ public final class SingleSampleChunkSource implements ChunkSource {
} }
@Override @Override
public MediaFormat getFormat() { public int getTrackCount() {
return 1;
}
@Override
public MediaFormat getFormat(int track) {
return mediaFormat; return mediaFormat;
} }
@ -65,7 +70,7 @@ public final class SingleSampleChunkSource implements ChunkSource {
} }
@Override @Override
public void enable() { public void enable(int track) {
// Do nothing. // Do nothing.
} }

View File

@ -291,7 +291,12 @@ public class DashChunkSource implements ChunkSource {
} }
@Override @Override
public final MediaFormat getFormat() { public int getTrackCount() {
return 1;
}
@Override
public final MediaFormat getFormat(int track) {
return mediaFormat; return mediaFormat;
} }
@ -301,7 +306,7 @@ public class DashChunkSource implements ChunkSource {
} }
@Override @Override
public void enable() { public void enable(int track) {
fatalError = null; fatalError = null;
formatEvaluator.enable(); formatEvaluator.enable();
if (manifestFetcher != null) { if (manifestFetcher != null) {

View File

@ -187,12 +187,17 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
@Override @Override
public final MediaFormat getFormat() { public int getTrackCount() {
return 1;
}
@Override
public final MediaFormat getFormat(int track) {
return mediaFormat; return mediaFormat;
} }
@Override @Override
public void enable() { public void enable(int track) {
fatalError = null; fatalError = null;
formatEvaluator.enable(); formatEvaluator.enable();
if (manifestFetcher != null) { if (manifestFetcher != null) {