From 894ae1a310cc2e1fe5ba59eb56c7fbeb751036d5 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 24 Nov 2016 09:08:10 -0800 Subject: [PATCH] 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 --- demo/build.gradle | 19 +- .../exoplayer2/demo/DemoApplication.java | 10 +- .../exoplayer2/demo/PlayerActivity.java | 7 +- .../android/exoplayer2/ExoPlayerFactory.java | 26 +-- .../android/exoplayer2/SimpleExoPlayer.java | 201 ++++++++++++++---- .../playbacktests/gts/DashTest.java | 17 +- .../util/DebugMediaCodecVideoRenderer.java | 111 ---------- .../util/DebugSimpleExoPlayer.java | 142 +++++++++++++ .../playbacktests/util/ExoHostedTest.java | 3 +- 9 files changed, 359 insertions(+), 177 deletions(-) delete mode 100644 playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugMediaCodecVideoRenderer.java create mode 100644 playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugSimpleExoPlayer.java diff --git a/demo/build.gradle b/demo/build.gradle index bfbcd1aa4cc..2c01cbbe730 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -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') } diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java index 92dc08597fa..a7e67d169a2 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -36,13 +36,19 @@ public void onCreate() { 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; + } + } diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 5351890d6f0..7589d548109 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -251,12 +251,17 @@ private void initializePlayer() { } } + @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); diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java index e43a9c03577..43de6fe7511 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java @@ -49,7 +49,7 @@ public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector t /** * 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 static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector t */ public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager) { - return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, false); + return newSimpleInstance(context, trackSelector, loadControl, + drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF); } /** @@ -71,15 +72,15 @@ public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector t * @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 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 static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector t * @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 drmSessionManager, - boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { + @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode, + long allowedVideoJoiningTimeMs) { return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager, - preferExtensionDecoders, allowedVideoJoiningTimeMs); + extensionRendererMode, allowedVideoJoiningTimeMs); } /** diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index ca193c576bd..36753309e2b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -21,6 +21,7 @@ import android.media.MediaCodec; import android.media.PlaybackParams; import android.os.Handler; +import android.support.annotation.IntDef; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; @@ -45,6 +46,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; @@ -54,7 +57,7 @@ * be obtained from {@link ExoPlayerFactory}. */ @TargetApi(16) -public final class SimpleExoPlayer implements ExoPlayer { +public class SimpleExoPlayer implements ExoPlayer { /** * A listener for video rendering information from a {@link SimpleExoPlayer}. @@ -88,8 +91,33 @@ void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, } + /** + * 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 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 Renderer[] renderers; @@ -120,21 +148,16 @@ void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, private float audioVolume; private PlaybackParamsHolder playbackParamsHolder; - /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, - LoadControl loadControl, DrmSessionManager drmSessionManager, - boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { + protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) { mainHandler = new Handler(); componentListener = new ComponentListener(); // Build the renderers. ArrayList renderersList = new ArrayList<>(); - if (preferExtensionDecoders) { - buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs); - buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs); - } else { - buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs); - buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs); - } + buildRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, + allowedVideoJoiningTimeMs, renderersList); renderers = renderersList.toArray(new Renderer[renderersList.size()]); // Obtain counts of video and audio renderers. @@ -593,54 +616,99 @@ public Object getCurrentManifest() { return player.getCurrentManifest(); } - // Internal methods. - - private void buildRenderers(Context context, - DrmSessionManager drmSessionManager, ArrayList renderersList, - long allowedVideoJoiningTimeMs) { - MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context, - MediaCodecSelector.DEFAULT, allowedVideoJoiningTimeMs, drmSessionManager, false, - mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); - renderersList.add(videoRenderer); - - Renderer audioRenderer = new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, - drmSessionManager, true, mainHandler, componentListener, - AudioCapabilities.getCapabilities(context)); - 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); + // Renderer building. + + private void buildRenderers(Context context, Handler mainHandler, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs, + ArrayList out) { + buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, + componentListener, allowedVideoJoiningTimeMs, out); + buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, + componentListener, out); + buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out); + buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out); + buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out); } - private void buildExtensionRenderers(ArrayList renderersList, - long allowedVideoJoiningTimeMs) { - // Load extension renderers using reflection so that demo app doesn't depend on them. - // Class.forName() appears for each renderer so that automated tools like proguard - // can detect the use of reflection (see http://proguard.sourceforge.net/FAQ.html#forname). + /** + * Builds video 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 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 drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener, + long allowedVideoJoiningTimeMs, ArrayList 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 { Class clazz = Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer"); Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, VideoRendererEventListener.class, int.class); - renderersList.add((Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, - mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); + Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, + mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); + out.add(extensionRendererIndex++, renderer); Log.i(TAG, "Loaded LibvpxVideoRenderer."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. } catch (Exception 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 drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener, + ArrayList 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 { Class clazz = Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.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."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. @@ -653,7 +721,8 @@ private void buildExtensionRenderers(ArrayList renderersList, Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.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."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. @@ -666,7 +735,8 @@ private void buildExtensionRenderers(ArrayList renderersList, Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.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."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. @@ -675,6 +745,51 @@ private void buildExtensionRenderers(ArrayList renderersList, } } + /** + * 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 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 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 out) { + // Do nothing. + } + + // Internal methods. + private void removeSurfaceCallbacks() { if (textureView != null) { if (textureView.getSurfaceTextureListener() != componentListener) { diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java index 8aee6279933..da9f1591e71 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java @@ -21,11 +21,15 @@ 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.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 @@ protected final StreamingDrmSessionManager buildDrmSession } @Override - public MediaSource buildSource(HostActivity host, String userAgent, + protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, + MappingTrackSelector trackSelector, + DrmSessionManager drmSessionManager) { + SimpleExoPlayer player = new DebugSimpleExoPlayer(host, trackSelector, + new DefaultLoadControl(), drmSessionManager); + player.setVideoSurface(surface); + return player; + } + + @Override + protected MediaSource buildSource(HostActivity host, String userAgent, TransferListener mediaTransferListener) { DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent); DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent, diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugMediaCodecVideoRenderer.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugMediaCodecVideoRenderer.java deleted file mode 100644 index e38dea948ad..00000000000 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugMediaCodecVideoRenderer.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.playbacktests.util; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Handler; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; -import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; -import com.google.android.exoplayer2.video.VideoRendererEventListener; - -/** - * Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp - * assertions. - */ -@TargetApi(16) -public class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer { - - 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; - } - - @Override - protected void releaseCodec() { - super.releaseCodec(); - clearTimestamps(); - } - - @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 + ")."); - } - } - - 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]; - } -} diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugSimpleExoPlayer.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugSimpleExoPlayer.java new file mode 100644 index 00000000000..6620c0dcf18 --- /dev/null +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugSimpleExoPlayer.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.playbacktests.util; + +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; + +/** + * A debug extension of {@link SimpleExoPlayer}. Provides video buffer timestamp assertions. + */ +@TargetApi(16) +public class DebugSimpleExoPlayer extends SimpleExoPlayer { + + public DebugSimpleExoPlayer(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager) { + super(context, trackSelector, loadControl, drmSessionManager, + SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF, 0); + } + + @Override + protected void buildVideoRenderers(Context context, Handler mainHandler, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener, + long allowedVideoJoiningTimeMs, ArrayList out) { + out.add(new DebugMediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, + allowedVideoJoiningTimeMs, mainHandler, eventListener, + MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); + } + + /** + * Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp + * assertions. + */ + private static class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer { + + 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; + } + + @Override + protected void releaseCodec() { + super.releaseCodec(); + clearTimestamps(); + } + + @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 + ")."); + } + } + + 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]; + } + + } + +} diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java index 78f71e6415b..dfecdd236ad 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java @@ -320,7 +320,8 @@ protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, MappingTrackSelector trackSelector, DrmSessionManager 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; }