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 { ...@@ -37,16 +37,23 @@ android {
abortOnError false abortOnError false
} }
flavorDimensions "extensions"
productFlavors { productFlavors {
noExtensions noExtns {
withExtensions dimension "extensions"
}
extns {
dimension "extensions"
}
} }
} }
dependencies { dependencies {
compile project(':library') compile project(':library')
withExtensionsCompile project(path: ':extension-ffmpeg') extnsCompile project(path: ':extension-ffmpeg')
withExtensionsCompile project(path: ':extension-flac') extnsCompile project(path: ':extension-flac')
withExtensionsCompile project(path: ':extension-opus') extnsCompile project(path: ':extension-opus')
withExtensionsCompile project(path: ':extension-vp9') extnsCompile project(path: ':extension-vp9')
} }
...@@ -36,13 +36,19 @@ public class DemoApplication extends Application { ...@@ -36,13 +36,19 @@ public class DemoApplication extends Application {
userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
} }
DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) { public DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultDataSourceFactory(this, bandwidthMeter, return new DefaultDataSourceFactory(this, bandwidthMeter,
buildHttpDataSourceFactory(bandwidthMeter)); buildHttpDataSourceFactory(bandwidthMeter));
} }
HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) { public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultHttpDataSourceFactory(userAgent, 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 ...@@ -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 = TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER); new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER);
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory); trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(), player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
drmSessionManager, preferExtensionDecoders); drmSessionManager, extensionRendererMode);
player.addListener(this); player.addListener(this);
eventLogger = new EventLogger(trackSelector); eventLogger = new EventLogger(trackSelector);
......
...@@ -49,7 +49,7 @@ public final class ExoPlayerFactory { ...@@ -49,7 +49,7 @@ public final class ExoPlayerFactory {
/** /**
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated * 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 context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance.
...@@ -59,7 +59,8 @@ public final class ExoPlayerFactory { ...@@ -59,7 +59,8 @@ public final class ExoPlayerFactory {
*/ */
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) { 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 { ...@@ -71,15 +72,15 @@ public final class ExoPlayerFactory {
* @param loadControl The {@link LoadControl} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks. * will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link Renderer} instances defined in * @param extensionRendererMode The extension renderer mode, which determines if and how available
* available extensions over those defined in the core library. Note that extensions must be * extension renderers are used. Note that extensions must be included in the application
* included in the application build for setting this flag to have any effect. * build for them to be considered available.
*/ */
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders) { @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode) {
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, 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 { ...@@ -91,17 +92,18 @@ public final class ExoPlayerFactory {
* @param loadControl The {@link LoadControl} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks. * will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link Renderer} instances defined in * @param extensionRendererMode The extension renderer mode, which determines if and how available
* available extensions over those defined in the core library. Note that extensions must be * extension renderers are used. Note that extensions must be included in the application
* included in the application build for setting this flag to have any effect. * build for them to be considered available.
* @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to * @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to
* seamlessly join an ongoing playback. * seamlessly join an ongoing playback.
*/ */
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager, return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager,
preferExtensionDecoders, allowedVideoJoiningTimeMs); extensionRendererMode, allowedVideoJoiningTimeMs);
} }
/** /**
......
...@@ -21,6 +21,7 @@ import android.graphics.SurfaceTexture; ...@@ -21,6 +21,7 @@ import android.graphics.SurfaceTexture;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.PlaybackParams; import android.media.PlaybackParams;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.IntDef;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
...@@ -45,6 +46,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; ...@@ -45,6 +46,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -54,7 +57,7 @@ import java.util.List; ...@@ -54,7 +57,7 @@ import java.util.List;
* be obtained from {@link ExoPlayerFactory}. * be obtained from {@link ExoPlayerFactory}.
*/ */
@TargetApi(16) @TargetApi(16)
public final class SimpleExoPlayer implements ExoPlayer { public class SimpleExoPlayer implements ExoPlayer {
/** /**
* A listener for video rendering information from a {@link SimpleExoPlayer}. * A listener for video rendering information from a {@link SimpleExoPlayer}.
...@@ -88,8 +91,33 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -88,8 +91,33 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
/**
* Modes for using extension renderers.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER})
public @interface ExtensionRendererMode {}
/**
* Do not allow use of extension renderers.
*/
public static final int EXTENSION_RENDERER_MODE_OFF = 0;
/**
* Allow use of extension renderers. Extension renderers are indexed after core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use a core renderer to an extension renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_ON = 1;
/**
* Allow use of extension renderers. Extension renderers are indexed before core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use an extension renderer to a core renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_PREFER = 2;
private static final String TAG = "SimpleExoPlayer"; private static final String TAG = "SimpleExoPlayer";
private static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
private final ExoPlayer player; private final ExoPlayer player;
private final Renderer[] renderers; private final Renderer[] renderers;
...@@ -120,21 +148,16 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -120,21 +148,16 @@ public final class SimpleExoPlayer implements ExoPlayer {
private float audioVolume; private float audioVolume;
private PlaybackParamsHolder playbackParamsHolder; private PlaybackParamsHolder playbackParamsHolder;
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) {
mainHandler = new Handler(); mainHandler = new Handler();
componentListener = new ComponentListener(); componentListener = new ComponentListener();
// Build the renderers. // Build the renderers.
ArrayList<Renderer> renderersList = new ArrayList<>(); ArrayList<Renderer> renderersList = new ArrayList<>();
if (preferExtensionDecoders) { buildRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs); allowedVideoJoiningTimeMs, renderersList);
buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs);
} else {
buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs);
buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs);
}
renderers = renderersList.toArray(new Renderer[renderersList.size()]); renderers = renderersList.toArray(new Renderer[renderersList.size()]);
// Obtain counts of video and audio renderers. // Obtain counts of video and audio renderers.
...@@ -593,54 +616,99 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -593,54 +616,99 @@ public final class SimpleExoPlayer implements ExoPlayer {
return player.getCurrentManifest(); return player.getCurrentManifest();
} }
// Internal methods. // Renderer building.
private void buildRenderers(Context context, private void buildRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, ArrayList<Renderer> renderersList, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
long allowedVideoJoiningTimeMs) { @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs,
MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context, ArrayList<Renderer> out) {
MediaCodecSelector.DEFAULT, allowedVideoJoiningTimeMs, drmSessionManager, false, buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); componentListener, allowedVideoJoiningTimeMs, out);
renderersList.add(videoRenderer); buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, out);
Renderer audioRenderer = new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
drmSessionManager, true, mainHandler, componentListener, buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
AudioCapabilities.getCapabilities(context)); buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out);
renderersList.add(audioRenderer);
Renderer textRenderer = new TextRenderer(componentListener, mainHandler.getLooper());
renderersList.add(textRenderer);
MetadataRenderer metadataRenderer = new MetadataRenderer(componentListener,
mainHandler.getLooper(), new Id3Decoder());
renderersList.add(metadataRenderer);
} }
private void buildExtensionRenderers(ArrayList<Renderer> renderersList, /**
long allowedVideoJoiningTimeMs) { * Builds video renderers for use by the player.
// Load extension renderers using reflection so that demo app doesn't depend on them. *
// Class.forName(<class name>) appears for each renderer so that automated tools like proguard * @param context The {@link Context} associated with the player.
// can detect the use of reflection (see http://proguard.sourceforge.net/FAQ.html#forname). * @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers
* can attempt to seamlessly join an ongoing playback.
* @param out An array to which the built renderers should be appended.
*/
protected void buildVideoRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs, ArrayList<Renderer> out) {
out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT,
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try { try {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer"); Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer");
Constructor<?> constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, Constructor<?> constructor = clazz.getConstructor(boolean.class, long.class, Handler.class,
VideoRendererEventListener.class, int.class); VideoRendererEventListener.class, int.class);
renderersList.add((Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs,
mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibvpxVideoRenderer."); Log.i(TAG, "Loaded LibvpxVideoRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Expected if the app was built without the extension. // Expected if the app was built without the extension.
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}
/**
* Builds audio renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param out An array to which the built renderers should be appended.
*/
protected void buildAudioRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener,
ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true,
mainHandler, eventListener, AudioCapabilities.getCapabilities(context)));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try { try {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class);
renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener)); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer."); Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Expected if the app was built without the extension. // Expected if the app was built without the extension.
...@@ -653,7 +721,8 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -653,7 +721,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class);
renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener)); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer."); Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Expected if the app was built without the extension. // Expected if the app was built without the extension.
...@@ -666,7 +735,8 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -666,7 +735,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class);
renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener)); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer."); Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Expected if the app was built without the extension. // Expected if the app was built without the extension.
...@@ -675,6 +745,51 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -675,6 +745,51 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
} }
/**
* Builds text renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param output An output for the renderers.
* @param out An array to which the built renderers should be appended.
*/
protected void buildTextRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, TextRenderer.Output output,
ArrayList<Renderer> out) {
out.add(new TextRenderer(output, mainHandler.getLooper()));
}
/**
* Builds metadata renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param output An output for the renderers.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMetadataRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output,
ArrayList<Renderer> out) {
out.add(new MetadataRenderer(output, mainHandler.getLooper(), new Id3Decoder()));
}
/**
* Builds any miscellaneous renderers used by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMiscellaneousRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) {
// Do nothing.
}
// Internal methods.
private void removeSurfaceCallbacks() { private void removeSurfaceCallbacks() {
if (textureView != null) { if (textureView != null) {
if (textureView.getSurfaceTextureListener() != componentListener) { if (textureView.getSurfaceTextureListener() != componentListener) {
......
...@@ -21,11 +21,15 @@ import android.media.UnsupportedSchemeException; ...@@ -21,11 +21,15 @@ import android.media.UnsupportedSchemeException;
import android.net.Uri; import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2; import android.test.ActivityInstrumentationTestCase2;
import android.util.Log; import android.util.Log;
import android.view.Surface;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
...@@ -34,6 +38,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; ...@@ -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;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.playbacktests.util.ActionSchedule; 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.DecoderCountersUtil;
import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest; import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest;
import com.google.android.exoplayer2.playbacktests.util.HostActivity; import com.google.android.exoplayer2.playbacktests.util.HostActivity;
...@@ -727,7 +732,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -727,7 +732,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
} }
@Override @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) { TransferListener<? super DataSource> mediaTransferListener) {
DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent); DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent);
DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent, DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent,
......
...@@ -19,93 +19,124 @@ import android.annotation.TargetApi; ...@@ -19,93 +19,124 @@ import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.ExoPlaybackException; 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.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.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.util.ArrayList;
/** /**
* Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp * A debug extension of {@link SimpleExoPlayer}. Provides video buffer timestamp assertions.
* assertions.
*/ */
@TargetApi(16) @TargetApi(16)
public class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer { public class DebugSimpleExoPlayer extends SimpleExoPlayer {
private static final int ARRAY_SIZE = 1000; public DebugSimpleExoPlayer(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
private final long[] timestampsList = new long[ARRAY_SIZE]; super(context, trackSelector, loadControl, drmSessionManager,
SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF, 0);
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;
} }
@Override @Override
protected void releaseCodec() { protected void buildVideoRenderers(Context context, Handler mainHandler,
super.releaseCodec(); DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
clearTimestamps(); @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 { * Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp
super.flushCodec(); * assertions.
clearTimestamps(); */
} private static class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
@Override private static final int ARRAY_SIZE = 1000;
protected void onQueueInputBuffer(DecoderInputBuffer buffer) {
insertTimestamp(buffer.timeUs);
maybeShiftTimestampsList();
}
@Override private final long[] timestampsList = new long[ARRAY_SIZE];
protected void onProcessedOutputBuffer(long presentationTimeUs) {
bufferCount++; private int startIndex;
long expectedTimestampUs = dequeueTimestamp(); private int queueSize;
if (expectedTimestampUs != presentationTimeUs) { private int bufferCount;
throw new IllegalStateException("Expected to dequeue video buffer with presentation "
+ "timestamp: " + expectedTimestampUs + ". Instead got: " + presentationTimeUs public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector,
+ " (Processed buffers since last flush: " + bufferCount + ")."); long allowedJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) {
super(context, mediaCodecSelector, allowedJoiningTimeMs, null, false, eventHandler,
eventListener, maxDroppedFrameCountToNotify);
startIndex = 0;
queueSize = 0;
} }
}
private void clearTimestamps() { @Override
startIndex = 0; protected void releaseCodec() {
queueSize = 0; super.releaseCodec();
bufferCount = 0; clearTimestamps();
} }
private void insertTimestamp(long presentationTimeUs) { @Override
for (int i = startIndex + queueSize - 1; i >= startIndex; i--) { protected void flushCodec() throws ExoPlaybackException {
if (presentationTimeUs >= timestampsList[i]) { super.flushCodec();
timestampsList[i + 1] = presentationTimeUs; clearTimestamps();
queueSize++; }
return;
@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() { private void clearTimestamps() {
if (startIndex + queueSize == ARRAY_SIZE) {
System.arraycopy(timestampsList, startIndex, timestampsList, 0, queueSize);
startIndex = 0; 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 ...@@ -320,7 +320,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
MappingTrackSelector trackSelector, MappingTrackSelector trackSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) { DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector, SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector,
new DefaultLoadControl(), drmSessionManager, false, 0); new DefaultLoadControl(), drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF,
0);
player.setVideoSurface(surface); player.setVideoSurface(surface);
return player; 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