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);
}
@SuppressWarnings("deprecation")
public int getTrackCount(int type) {
return !player.getRendererHasMedia(type) ? 0 : trackNames[type].length;
}
@ -330,7 +331,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
if (trackNames[rendererIndex] == null) {
// Convert a null trackNames to an array of suitable length.
int trackCount = multiTrackSources[rendererIndex] != null
? multiTrackSources[rendererIndex].getTrackCount() : 1;
? multiTrackSources[rendererIndex].getMultiTrackCount() : 1;
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) {
if (multiTrackSources == null) {
return;

View File

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

View File

@ -74,7 +74,7 @@ import android.os.Looper;
* <h3>Player state</h3>
*
* <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
* 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
@ -219,6 +219,18 @@ public interface ExoPlayer {
* The player has finished playing the media.
*/
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.
*/
@ -266,27 +278,73 @@ public interface ExoPlayer {
* <p>
* 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.
* @return True if the renderer has media to play, false otherwise.
*/
@Deprecated
public boolean getRendererHasMedia(int rendererIndex);
/**
* 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 enabled Whether the renderer at the given index should be enabled.
*/
@Deprecated
public void setRendererEnabled(int rendererIndex, boolean 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.
* @return Whether the renderer is enabled.
*/
@Deprecated
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}.
* 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);
}
@Deprecated
@Override
// TODO: Deprecate in ExoPlayer.
public boolean getRendererHasMedia(int rendererIndex) {
return getRendererTrackCount(rendererIndex) > 0;
return getTrackCount(rendererIndex) > 0;
}
@Deprecated
@Override
// TODO: Deprecate in ExoPlayer.
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
// TODO: Deprecate in ExoPlayer.
public boolean getRendererEnabled(int rendererIndex) {
return getRendererSelectedTrack(rendererIndex) == 0;
}
// TODO: Expose in ExoPlayer.
public int getRendererTrackCount(int rendererIndex) {
public int getTrackCount(int rendererIndex) {
return trackFormats[rendererIndex] != null ? trackFormats[rendererIndex].length : 0;
}
// TODO: Expose in ExoPlayer.
public MediaFormat getRendererTrackInfo(int rendererIndex, int trackIndex) {
@Override
public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) {
return trackFormats[rendererIndex][trackIndex];
}
// TODO: Expose in ExoPlayer.
public void setRendererSelectedTrack(int rendererIndex, int trackIndex) {
@Override
public void setSelectedTrack(int rendererIndex, int trackIndex) {
if (selectedTrackIndices[rendererIndex] != trackIndex) {
selectedTrackIndices[rendererIndex] = trackIndex;
internalPlayer.setRendererSelectedTrack(rendererIndex, trackIndex);
}
}
// TODO: Expose in ExoPlayer.
public int getRendererSelectedTrack(int rendererIndex) {
@Override
public int getSelectedTrack(int rendererIndex) {
return selectedTrackIndices[rendererIndex];
}

View File

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

View File

@ -31,13 +31,21 @@ import java.util.List;
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>
* 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
@ -52,8 +60,10 @@ public interface ChunkSource {
/**
* Called when the source is enabled.
*
* @param track The track index.
*/
void enable();
void enable(int track);
/**
* 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}
* instances.
*/
// TODO: Expose multiple tracks directly in DashChunkSource and SmoothStreamingChunkSource, and
// delete this class.
public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
/**
@ -55,18 +57,23 @@ public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerCompon
*
* @return The number of tracks.
*/
public int getTrackCount() {
public int getMultiTrackCount() {
return allSources.length;
}
@Override
public MediaFormat getFormat() {
return selectedSource.getFormat();
public int getTrackCount() {
return selectedSource.getTrackCount();
}
@Override
public void enable() {
selectedSource.enable();
public MediaFormat getFormat(int track) {
return selectedSource.getFormat(track);
}
@Override
public void enable(int track) {
selectedSource.enable(track);
enabled = true;
}

View File

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

View File

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

View File

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