| /* |
| * Copyright (C) 2017 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; |
| |
| import android.content.Context; |
| import android.media.MediaCodec; |
| import android.os.Handler; |
| import android.os.Looper; |
| import androidx.annotation.IntDef; |
| import com.google.android.exoplayer2.audio.AudioCapabilities; |
| import com.google.android.exoplayer2.audio.AudioProcessor; |
| import com.google.android.exoplayer2.audio.AudioRendererEventListener; |
| import com.google.android.exoplayer2.audio.DefaultAudioSink; |
| import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; |
| import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; |
| import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; |
| import com.google.android.exoplayer2.metadata.MetadataOutput; |
| import com.google.android.exoplayer2.metadata.MetadataRenderer; |
| import com.google.android.exoplayer2.text.TextOutput; |
| import com.google.android.exoplayer2.text.TextRenderer; |
| import com.google.android.exoplayer2.trackselection.TrackSelector; |
| import com.google.android.exoplayer2.util.Log; |
| import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; |
| import com.google.android.exoplayer2.video.VideoRendererEventListener; |
| import com.google.android.exoplayer2.video.spherical.CameraMotionRenderer; |
| import java.lang.annotation.Documented; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.reflect.Constructor; |
| import java.util.ArrayList; |
| |
| /** |
| * Default {@link RenderersFactory} implementation. |
| */ |
| public class DefaultRenderersFactory implements RenderersFactory { |
| |
| /** |
| * The default maximum duration for which a video renderer can attempt to seamlessly join an |
| * ongoing playback. |
| */ |
| public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000; |
| |
| /** |
| * Modes for using extension renderers. One of {@link #EXTENSION_RENDERER_MODE_OFF}, {@link |
| * #EXTENSION_RENDERER_MODE_ON} or {@link #EXTENSION_RENDERER_MODE_PREFER}. |
| */ |
| @Documented |
| @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 = "DefaultRenderersFactory"; |
| |
| protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; |
| |
| private final Context context; |
| @ExtensionRendererMode private int extensionRendererMode; |
| private long allowedVideoJoiningTimeMs; |
| private boolean enableDecoderFallback; |
| private MediaCodecSelector mediaCodecSelector; |
| private @MediaCodecRenderer.MediaCodecOperationMode int mediaCodecOperationMode; |
| |
| /** @param context A {@link Context}. */ |
| public DefaultRenderersFactory(Context context) { |
| this.context = context; |
| extensionRendererMode = EXTENSION_RENDERER_MODE_OFF; |
| allowedVideoJoiningTimeMs = DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS; |
| mediaCodecSelector = MediaCodecSelector.DEFAULT; |
| mediaCodecOperationMode = MediaCodecRenderer.OPERATION_MODE_SYNCHRONOUS; |
| } |
| |
| /** |
| * @deprecated Use {@link #DefaultRenderersFactory(Context)} and {@link |
| * #setExtensionRendererMode(int)}. |
| */ |
| @Deprecated |
| @SuppressWarnings("deprecation") |
| public DefaultRenderersFactory( |
| Context context, @ExtensionRendererMode int extensionRendererMode) { |
| this(context, extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); |
| } |
| |
| /** |
| * @deprecated Use {@link #DefaultRenderersFactory(Context)}, {@link |
| * #setExtensionRendererMode(int)} and {@link #setAllowedVideoJoiningTimeMs(long)}. |
| */ |
| @Deprecated |
| public DefaultRenderersFactory( |
| Context context, |
| @ExtensionRendererMode int extensionRendererMode, |
| long allowedVideoJoiningTimeMs) { |
| this.context = context; |
| this.extensionRendererMode = extensionRendererMode; |
| this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs; |
| mediaCodecSelector = MediaCodecSelector.DEFAULT; |
| } |
| |
| /** |
| * Sets 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. |
| * |
| * <p>The default value is {@link #EXTENSION_RENDERER_MODE_OFF}. |
| * |
| * @param extensionRendererMode The extension renderer mode. |
| * @return This factory, for convenience. |
| */ |
| public DefaultRenderersFactory setExtensionRendererMode( |
| @ExtensionRendererMode int extensionRendererMode) { |
| this.extensionRendererMode = extensionRendererMode; |
| return this; |
| } |
| |
| /** |
| * Set the {@link MediaCodecRenderer.MediaCodecOperationMode} of {@link MediaCodecRenderer} |
| * instances. |
| * |
| * <p>This method is experimental, and will be renamed or removed in a future release. |
| * |
| * @param mode The {@link MediaCodecRenderer.MediaCodecOperationMode} to set. |
| * @return This factory, for convenience. |
| */ |
| public DefaultRenderersFactory experimental_setMediaCodecOperationMode( |
| @MediaCodecRenderer.MediaCodecOperationMode int mode) { |
| mediaCodecOperationMode = mode; |
| return this; |
| } |
| |
| /** |
| * Sets whether to enable fallback to lower-priority decoders if decoder initialization fails. |
| * This may result in using a decoder that is less efficient or slower than the primary decoder. |
| * |
| * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder |
| * initialization fails. |
| * @return This factory, for convenience. |
| */ |
| public DefaultRenderersFactory setEnableDecoderFallback(boolean enableDecoderFallback) { |
| this.enableDecoderFallback = enableDecoderFallback; |
| return this; |
| } |
| |
| /** |
| * Sets a {@link MediaCodecSelector} for use by {@link MediaCodec} based renderers. |
| * |
| * <p>The default value is {@link MediaCodecSelector#DEFAULT}. |
| * |
| * @param mediaCodecSelector The {@link MediaCodecSelector}. |
| * @return This factory, for convenience. |
| */ |
| public DefaultRenderersFactory setMediaCodecSelector(MediaCodecSelector mediaCodecSelector) { |
| this.mediaCodecSelector = mediaCodecSelector; |
| return this; |
| } |
| |
| /** |
| * Sets the maximum duration for which video renderers can attempt to seamlessly join an ongoing |
| * playback. |
| * |
| * <p>The default value is {@link #DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS}. |
| * |
| * @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt to |
| * seamlessly join an ongoing playback, in milliseconds. |
| * @return This factory, for convenience. |
| */ |
| public DefaultRenderersFactory setAllowedVideoJoiningTimeMs(long allowedVideoJoiningTimeMs) { |
| this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs; |
| return this; |
| } |
| |
| @Override |
| public Renderer[] createRenderers( |
| Handler eventHandler, |
| VideoRendererEventListener videoRendererEventListener, |
| AudioRendererEventListener audioRendererEventListener, |
| TextOutput textRendererOutput, |
| MetadataOutput metadataRendererOutput) { |
| ArrayList<Renderer> renderersList = new ArrayList<>(); |
| buildVideoRenderers( |
| context, |
| extensionRendererMode, |
| mediaCodecSelector, |
| enableDecoderFallback, |
| eventHandler, |
| videoRendererEventListener, |
| allowedVideoJoiningTimeMs, |
| renderersList); |
| buildAudioRenderers( |
| context, |
| extensionRendererMode, |
| mediaCodecSelector, |
| enableDecoderFallback, |
| buildAudioProcessors(), |
| eventHandler, |
| audioRendererEventListener, |
| renderersList); |
| buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(), |
| extensionRendererMode, renderersList); |
| buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(), |
| extensionRendererMode, renderersList); |
| buildCameraMotionRenderers(context, extensionRendererMode, renderersList); |
| buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); |
| return renderersList.toArray(new Renderer[0]); |
| } |
| |
| /** |
| * Builds video renderers for use by the player. |
| * |
| * @param context The {@link Context} associated with the player. |
| * @param extensionRendererMode The extension renderer mode. |
| * @param mediaCodecSelector A decoder selector. |
| * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder |
| * initialization fails. This may result in using a decoder that is slower/less efficient than |
| * the primary decoder. |
| * @param eventHandler A handler associated with the main thread's looper. |
| * @param eventListener An event listener. |
| * @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt to |
| * seamlessly join an ongoing playback, in milliseconds. |
| * @param out An array to which the built renderers should be appended. |
| */ |
| protected void buildVideoRenderers( |
| Context context, |
| @ExtensionRendererMode int extensionRendererMode, |
| MediaCodecSelector mediaCodecSelector, |
| boolean enableDecoderFallback, |
| Handler eventHandler, |
| VideoRendererEventListener eventListener, |
| long allowedVideoJoiningTimeMs, |
| ArrayList<Renderer> out) { |
| MediaCodecVideoRenderer videoRenderer = |
| new MediaCodecVideoRenderer( |
| context, |
| mediaCodecSelector, |
| allowedVideoJoiningTimeMs, |
| enableDecoderFallback, |
| eventHandler, |
| eventListener, |
| MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); |
| videoRenderer.experimental_setMediaCodecOperationMode(mediaCodecOperationMode); |
| out.add(videoRenderer); |
| |
| if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { |
| return; |
| } |
| int extensionRendererIndex = out.size(); |
| if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { |
| extensionRendererIndex--; |
| } |
| |
| try { |
| // Full class names used for constructor args so the LINT rule triggers if any of them move. |
| // LINT.IfChange |
| Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer"); |
| Constructor<?> constructor = |
| clazz.getConstructor( |
| long.class, |
| android.os.Handler.class, |
| com.google.android.exoplayer2.video.VideoRendererEventListener.class, |
| int.class); |
| // LINT.ThenChange(../../../../../../../proguard-rules.txt) |
| Renderer renderer = |
| (Renderer) |
| constructor.newInstance( |
| allowedVideoJoiningTimeMs, |
| eventHandler, |
| eventListener, |
| 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) { |
| // The extension is present, but instantiation failed. |
| throw new RuntimeException("Error instantiating VP9 extension", e); |
| } |
| |
| try { |
| // Full class names used for constructor args so the LINT rule triggers if any of them move. |
| // LINT.IfChange |
| Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.av1.Libgav1VideoRenderer"); |
| Constructor<?> constructor = |
| clazz.getConstructor( |
| long.class, |
| android.os.Handler.class, |
| com.google.android.exoplayer2.video.VideoRendererEventListener.class, |
| int.class); |
| // LINT.ThenChange(../../../../../../../proguard-rules.txt) |
| Renderer renderer = |
| (Renderer) |
| constructor.newInstance( |
| allowedVideoJoiningTimeMs, |
| eventHandler, |
| eventListener, |
| MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); |
| out.add(extensionRendererIndex++, renderer); |
| Log.i(TAG, "Loaded Libgav1VideoRenderer."); |
| } catch (ClassNotFoundException e) { |
| // Expected if the app was built without the extension. |
| } catch (Exception e) { |
| // The extension is present, but instantiation failed. |
| throw new RuntimeException("Error instantiating AV1 extension", e); |
| } |
| |
| try { |
| // Full class names used for constructor args so the LINT rule triggers if any of them move. |
| // LINT.IfChange |
| Class<?> clazz = |
| Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegVideoRenderer"); |
| Constructor<?> constructor = |
| clazz.getConstructor( |
| long.class, |
| android.os.Handler.class, |
| com.google.android.exoplayer2.video.VideoRendererEventListener.class, |
| int.class); |
| // LINT.ThenChange(../../../../../../../proguard-rules.txt) |
| Renderer renderer = |
| (Renderer) |
| constructor.newInstance( |
| allowedVideoJoiningTimeMs, |
| eventHandler, |
| eventListener, |
| MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); |
| out.add(extensionRendererIndex++, renderer); |
| Log.i(TAG, "Loaded FfmpegVideoRenderer."); |
| } catch (ClassNotFoundException e) { |
| // Expected if the app was built without the extension. |
| } catch (Exception e) { |
| // The extension is present, but instantiation failed. |
| throw new RuntimeException("Error instantiating FFmpeg extension", e); |
| } |
| } |
| |
| /** |
| * Builds audio renderers for use by the player. |
| * |
| * @param context The {@link Context} associated with the player. |
| * @param extensionRendererMode The extension renderer mode. |
| * @param mediaCodecSelector A decoder selector. |
| * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder |
| * initialization fails. This may result in using a decoder that is slower/less efficient than |
| * the primary decoder. |
| * @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio buffers |
| * before output. May be empty. |
| * @param eventHandler A handler to use when invoking event listeners and outputs. |
| * @param eventListener An event listener. |
| * @param out An array to which the built renderers should be appended. |
| */ |
| protected void buildAudioRenderers( |
| Context context, |
| @ExtensionRendererMode int extensionRendererMode, |
| MediaCodecSelector mediaCodecSelector, |
| boolean enableDecoderFallback, |
| AudioProcessor[] audioProcessors, |
| Handler eventHandler, |
| AudioRendererEventListener eventListener, |
| ArrayList<Renderer> out) { |
| MediaCodecAudioRenderer audioRenderer = |
| new MediaCodecAudioRenderer( |
| context, |
| mediaCodecSelector, |
| enableDecoderFallback, |
| eventHandler, |
| eventListener, |
| new DefaultAudioSink(AudioCapabilities.getCapabilities(context), audioProcessors)); |
| audioRenderer.experimental_setMediaCodecOperationMode(mediaCodecOperationMode); |
| out.add(audioRenderer); |
| |
| if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { |
| return; |
| } |
| int extensionRendererIndex = out.size(); |
| if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { |
| extensionRendererIndex--; |
| } |
| |
| try { |
| // Full class names used for constructor args so the LINT rule triggers if any of them move. |
| // LINT.IfChange |
| Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); |
| Constructor<?> constructor = |
| clazz.getConstructor( |
| android.os.Handler.class, |
| com.google.android.exoplayer2.audio.AudioRendererEventListener.class, |
| com.google.android.exoplayer2.audio.AudioProcessor[].class); |
| // LINT.ThenChange(../../../../../../../proguard-rules.txt) |
| Renderer renderer = |
| (Renderer) constructor.newInstance(eventHandler, eventListener, audioProcessors); |
| out.add(extensionRendererIndex++, renderer); |
| Log.i(TAG, "Loaded LibopusAudioRenderer."); |
| } catch (ClassNotFoundException e) { |
| // Expected if the app was built without the extension. |
| } catch (Exception e) { |
| // The extension is present, but instantiation failed. |
| throw new RuntimeException("Error instantiating Opus extension", e); |
| } |
| |
| try { |
| // Full class names used for constructor args so the LINT rule triggers if any of them move. |
| // LINT.IfChange |
| Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); |
| Constructor<?> constructor = |
| clazz.getConstructor( |
| android.os.Handler.class, |
| com.google.android.exoplayer2.audio.AudioRendererEventListener.class, |
| com.google.android.exoplayer2.audio.AudioProcessor[].class); |
| // LINT.ThenChange(../../../../../../../proguard-rules.txt) |
| Renderer renderer = |
| (Renderer) constructor.newInstance(eventHandler, eventListener, audioProcessors); |
| out.add(extensionRendererIndex++, renderer); |
| Log.i(TAG, "Loaded LibflacAudioRenderer."); |
| } catch (ClassNotFoundException e) { |
| // Expected if the app was built without the extension. |
| } catch (Exception e) { |
| // The extension is present, but instantiation failed. |
| throw new RuntimeException("Error instantiating FLAC extension", e); |
| } |
| |
| try { |
| // Full class names used for constructor args so the LINT rule triggers if any of them move. |
| // LINT.IfChange |
| Class<?> clazz = |
| Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); |
| Constructor<?> constructor = |
| clazz.getConstructor( |
| android.os.Handler.class, |
| com.google.android.exoplayer2.audio.AudioRendererEventListener.class, |
| com.google.android.exoplayer2.audio.AudioProcessor[].class); |
| // LINT.ThenChange(../../../../../../../proguard-rules.txt) |
| Renderer renderer = |
| (Renderer) constructor.newInstance(eventHandler, eventListener, audioProcessors); |
| out.add(extensionRendererIndex++, renderer); |
| Log.i(TAG, "Loaded FfmpegAudioRenderer."); |
| } catch (ClassNotFoundException e) { |
| // Expected if the app was built without the extension. |
| } catch (Exception e) { |
| // The extension is present, but instantiation failed. |
| throw new RuntimeException("Error instantiating FFmpeg extension", e); |
| } |
| } |
| |
| /** |
| * Builds text renderers for use by the player. |
| * |
| * @param context The {@link Context} associated with the player. |
| * @param output An output for the renderers. |
| * @param outputLooper The looper associated with the thread on which the output should be called. |
| * @param extensionRendererMode The extension renderer mode. |
| * @param out An array to which the built renderers should be appended. |
| */ |
| protected void buildTextRenderers( |
| Context context, |
| TextOutput output, |
| Looper outputLooper, |
| @ExtensionRendererMode int extensionRendererMode, |
| ArrayList<Renderer> out) { |
| out.add(new TextRenderer(output, outputLooper)); |
| } |
| |
| /** |
| * Builds metadata renderers for use by the player. |
| * |
| * @param context The {@link Context} associated with the player. |
| * @param output An output for the renderers. |
| * @param outputLooper The looper associated with the thread on which the output should be called. |
| * @param extensionRendererMode The extension renderer mode. |
| * @param out An array to which the built renderers should be appended. |
| */ |
| protected void buildMetadataRenderers( |
| Context context, |
| MetadataOutput output, |
| Looper outputLooper, |
| @ExtensionRendererMode int extensionRendererMode, |
| ArrayList<Renderer> out) { |
| out.add(new MetadataRenderer(output, outputLooper)); |
| } |
| |
| /** |
| * Builds camera motion renderers for use by the player. |
| * |
| * @param context The {@link Context} associated with the player. |
| * @param extensionRendererMode The extension renderer mode. |
| * @param out An array to which the built renderers should be appended. |
| */ |
| protected void buildCameraMotionRenderers( |
| Context context, @ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) { |
| out.add(new CameraMotionRenderer()); |
| } |
| |
| /** |
| * Builds any miscellaneous renderers used by the player. |
| * |
| * @param context The {@link Context} associated with the player. |
| * @param eventHandler A handler to use when invoking event listeners and outputs. |
| * @param extensionRendererMode The extension renderer mode. |
| * @param out An array to which the built renderers should be appended. |
| */ |
| protected void buildMiscellaneousRenderers(Context context, Handler eventHandler, |
| @ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) { |
| // Do nothing. |
| } |
| |
| /** |
| * Builds an array of {@link AudioProcessor}s that will process PCM audio before output. |
| */ |
| protected AudioProcessor[] buildAudioProcessors() { |
| return new AudioProcessor[0]; |
| } |
| |
| } |