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; }