Commit 894ae1a3 by olly Committed by Oliver Woodman

Improve SimpleExoPlayer flexibility

- Allow extension and overriding of renderer creation.
  Several developers have asked for this, so that they
  can use their own renderers (typically extensions to
  the core ones) without losing the ability to use
  SimpleExoPlayer.
- Add option to not attempt extension renderer creation,
  for efficiency.
- Align build variants for internal and external demo
  apps. This is slightly unfortunate, but convergence
  seems necessary for useExtensionRenderers.
- Fix DASH playback tests to use the debug video
  renderer.

Issue #2102

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=140140915
parent cbf59888
......@@ -37,16 +37,23 @@ android {
abortOnError false
}
flavorDimensions "extensions"
productFlavors {
noExtensions
withExtensions
noExtns {
dimension "extensions"
}
extns {
dimension "extensions"
}
}
}
dependencies {
compile project(':library')
withExtensionsCompile project(path: ':extension-ffmpeg')
withExtensionsCompile project(path: ':extension-flac')
withExtensionsCompile project(path: ':extension-opus')
withExtensionsCompile project(path: ':extension-vp9')
extnsCompile project(path: ':extension-ffmpeg')
extnsCompile project(path: ':extension-flac')
extnsCompile project(path: ':extension-opus')
extnsCompile project(path: ':extension-vp9')
}
......@@ -36,13 +36,19 @@ public class DemoApplication extends Application {
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
}
DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
public DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultDataSourceFactory(this, bandwidthMeter,
buildHttpDataSourceFactory(bandwidthMeter));
}
HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
}
public boolean useExtensionRenderers() {
// We should return BuildConfig.FLAVOR_extensions.equals("extns") here, but this is currently
// incompatible with a Google internal build system.
return true;
}
}
......@@ -251,12 +251,17 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
}
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode =
((DemoApplication) getApplication()).useExtensionRenderers()
? (preferExtensionDecoders ? SimpleExoPlayer.EXTENSION_RENDERER_MODE_PREFER
: SimpleExoPlayer.EXTENSION_RENDERER_MODE_ON)
: SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF;
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER);
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
drmSessionManager, preferExtensionDecoders);
drmSessionManager, extensionRendererMode);
player.addListener(this);
eventLogger = new EventLogger(trackSelector);
......
......@@ -49,7 +49,7 @@ public final class ExoPlayerFactory {
/**
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
* {@link Looper}.
* {@link Looper}. Available extension renderers are not used.
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
......@@ -59,7 +59,8 @@ public final class ExoPlayerFactory {
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, false);
return newSimpleInstance(context, trackSelector, loadControl,
drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF);
}
/**
......@@ -71,15 +72,15 @@ public final class ExoPlayerFactory {
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link Renderer} instances defined in
* available extensions over those defined in the core library. Note that extensions must be
* included in the application build for setting this flag to have any effect.
* @param extensionRendererMode The extension renderer mode, which determines if and how available
* extension renderers are used. Note that extensions must be included in the application
* build for them to be considered available.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders) {
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode) {
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager,
preferExtensionDecoders, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
}
/**
......@@ -91,17 +92,18 @@ public final class ExoPlayerFactory {
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link Renderer} instances defined in
* available extensions over those defined in the core library. Note that extensions must be
* included in the application build for setting this flag to have any effect.
* @param extensionRendererMode The extension renderer mode, which determines if and how available
* extension renderers are used. Note that extensions must be included in the application
* build for them to be considered available.
* @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to
* seamlessly join an ongoing playback.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager,
preferExtensionDecoders, allowedVideoJoiningTimeMs);
extensionRendererMode, allowedVideoJoiningTimeMs);
}
/**
......
......@@ -21,11 +21,15 @@ import android.media.UnsupportedSchemeException;
import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.Surface;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
......@@ -34,6 +38,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.playbacktests.util.ActionSchedule;
import com.google.android.exoplayer2.playbacktests.util.DebugSimpleExoPlayer;
import com.google.android.exoplayer2.playbacktests.util.DecoderCountersUtil;
import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest;
import com.google.android.exoplayer2.playbacktests.util.HostActivity;
......@@ -727,7 +732,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
}
@Override
public MediaSource buildSource(HostActivity host, String userAgent,
protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface,
MappingTrackSelector trackSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
SimpleExoPlayer player = new DebugSimpleExoPlayer(host, trackSelector,
new DefaultLoadControl(), drmSessionManager);
player.setVideoSurface(surface);
return player;
}
@Override
protected MediaSource buildSource(HostActivity host, String userAgent,
TransferListener<? super DataSource> mediaTransferListener) {
DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent);
DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent,
......
......@@ -19,93 +19,124 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.os.Handler;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.util.ArrayList;
/**
* Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp
* assertions.
* A debug extension of {@link SimpleExoPlayer}. Provides video buffer timestamp assertions.
*/
@TargetApi(16)
public class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
public class DebugSimpleExoPlayer extends SimpleExoPlayer {
private static final int ARRAY_SIZE = 1000;
private final long[] timestampsList = new long[ARRAY_SIZE];
private int startIndex;
private int queueSize;
private int bufferCount;
public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) {
super(context, mediaCodecSelector, allowedJoiningTimeMs, null, false, eventHandler,
eventListener, maxDroppedFrameCountToNotify);
startIndex = 0;
queueSize = 0;
public DebugSimpleExoPlayer(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
super(context, trackSelector, loadControl, drmSessionManager,
SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF, 0);
}
@Override
protected void releaseCodec() {
super.releaseCodec();
clearTimestamps();
protected void buildVideoRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs, ArrayList<Renderer> out) {
out.add(new DebugMediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT,
allowedVideoJoiningTimeMs, mainHandler, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
}
@Override
protected void flushCodec() throws ExoPlaybackException {
super.flushCodec();
clearTimestamps();
}
/**
* Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp
* assertions.
*/
private static class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
@Override
protected void onQueueInputBuffer(DecoderInputBuffer buffer) {
insertTimestamp(buffer.timeUs);
maybeShiftTimestampsList();
}
private static final int ARRAY_SIZE = 1000;
@Override
protected void onProcessedOutputBuffer(long presentationTimeUs) {
bufferCount++;
long expectedTimestampUs = dequeueTimestamp();
if (expectedTimestampUs != presentationTimeUs) {
throw new IllegalStateException("Expected to dequeue video buffer with presentation "
+ "timestamp: " + expectedTimestampUs + ". Instead got: " + presentationTimeUs
+ " (Processed buffers since last flush: " + bufferCount + ").");
private final long[] timestampsList = new long[ARRAY_SIZE];
private int startIndex;
private int queueSize;
private int bufferCount;
public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) {
super(context, mediaCodecSelector, allowedJoiningTimeMs, null, false, eventHandler,
eventListener, maxDroppedFrameCountToNotify);
startIndex = 0;
queueSize = 0;
}
}
private void clearTimestamps() {
startIndex = 0;
queueSize = 0;
bufferCount = 0;
}
@Override
protected void releaseCodec() {
super.releaseCodec();
clearTimestamps();
}
private void insertTimestamp(long presentationTimeUs) {
for (int i = startIndex + queueSize - 1; i >= startIndex; i--) {
if (presentationTimeUs >= timestampsList[i]) {
timestampsList[i + 1] = presentationTimeUs;
queueSize++;
return;
@Override
protected void flushCodec() throws ExoPlaybackException {
super.flushCodec();
clearTimestamps();
}
@Override
protected void onQueueInputBuffer(DecoderInputBuffer buffer) {
insertTimestamp(buffer.timeUs);
maybeShiftTimestampsList();
}
@Override
protected void onProcessedOutputBuffer(long presentationTimeUs) {
bufferCount++;
long expectedTimestampUs = dequeueTimestamp();
if (expectedTimestampUs != presentationTimeUs) {
throw new IllegalStateException("Expected to dequeue video buffer with presentation "
+ "timestamp: " + expectedTimestampUs + ". Instead got: " + presentationTimeUs
+ " (Processed buffers since last flush: " + bufferCount + ").");
}
timestampsList[i + 1] = timestampsList[i];
}
timestampsList[startIndex] = presentationTimeUs;
queueSize++;
}
private void maybeShiftTimestampsList() {
if (startIndex + queueSize == ARRAY_SIZE) {
System.arraycopy(timestampsList, startIndex, timestampsList, 0, queueSize);
private void clearTimestamps() {
startIndex = 0;
queueSize = 0;
bufferCount = 0;
}
private void insertTimestamp(long presentationTimeUs) {
for (int i = startIndex + queueSize - 1; i >= startIndex; i--) {
if (presentationTimeUs >= timestampsList[i]) {
timestampsList[i + 1] = presentationTimeUs;
queueSize++;
return;
}
timestampsList[i + 1] = timestampsList[i];
}
timestampsList[startIndex] = presentationTimeUs;
queueSize++;
}
private void maybeShiftTimestampsList() {
if (startIndex + queueSize == ARRAY_SIZE) {
System.arraycopy(timestampsList, startIndex, timestampsList, 0, queueSize);
startIndex = 0;
}
}
private long dequeueTimestamp() {
startIndex++;
queueSize--;
return timestampsList[startIndex - 1];
}
}
private long dequeueTimestamp() {
startIndex++;
queueSize--;
return timestampsList[startIndex - 1];
}
}
......@@ -320,7 +320,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
MappingTrackSelector trackSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector,
new DefaultLoadControl(), drmSessionManager, false, 0);
new DefaultLoadControl(), drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF,
0);
player.setVideoSurface(surface);
return player;
}
......
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