diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 5dc9382e7..b446cbb81 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -69,7 +69,6 @@ import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; -import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.source.MediaSource; @@ -95,6 +94,7 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; +import org.schabi.newpipe.player.helper.CustomRenderersFactory; import org.schabi.newpipe.player.helper.LoadController; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -196,7 +196,7 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; - @NonNull private final RenderersFactory renderFactory; + @NonNull private final DefaultRenderersFactory renderFactory; @NonNull private final VideoPlaybackResolver videoResolver; @NonNull private final AudioPlaybackResolver audioResolver; @@ -261,9 +261,16 @@ public final class Player implements PlaybackListener, Listener { final PlayerDataSource dataSource = new PlayerDataSource(context, new DefaultBandwidthMeter.Builder(context).build()); loadController = new LoadController(); - renderFactory = new DefaultRenderersFactory(context) - .setEnableDecoderFallback(prefs.getBoolean( - context.getString(R.string.use_exoplayer_decoder_fallback_key), false)); + + renderFactory = prefs.getBoolean( + context.getString( + R.string.always_use_exoplayer_set_output_surface_workaround_key), false) + ? new CustomRenderersFactory(context) : new DefaultRenderersFactory(context); + + renderFactory.setEnableDecoderFallback( + prefs.getBoolean( + context.getString( + R.string.use_exoplayer_decoder_fallback_key), false)); videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CustomMediaCodecVideoRenderer.java b/app/src/main/java/org/schabi/newpipe/player/helper/CustomMediaCodecVideoRenderer.java new file mode 100644 index 000000000..66ac6d50b --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CustomMediaCodecVideoRenderer.java @@ -0,0 +1,54 @@ +package org.schabi.newpipe.player.helper; + +import android.content.Context; +import android.os.Handler; + +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; +import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; +import com.google.android.exoplayer2.video.VideoRendererEventListener; + +/** + * A {@link MediaCodecVideoRenderer} which always enable the output surface workaround that + * ExoPlayer enables on several devices which are known to implement + * {@link android.media.MediaCodec#setOutputSurface(android.view.Surface) + * MediaCodec.setOutputSurface(Surface)} incorrectly. + * + *

+ * See {@link MediaCodecVideoRenderer#codecNeedsSetOutputSurfaceWorkaround(String)} for more + * details. + *

+ * + *

+ * This custom {@link MediaCodecVideoRenderer} may be useful in the case a device is affected by + * this issue but is not present in ExoPlayer's list. + *

+ * + *

+ * This class has only effect on devices with Android 6 and higher, as the {@code setOutputSurface} + * method is only implemented in these Android versions and the method used as a workaround is + * always applied on older Android versions (releasing and re-instantiating video codec instances). + *

+ */ +public final class CustomMediaCodecVideoRenderer extends MediaCodecVideoRenderer { + + @SuppressWarnings({"checkstyle:ParameterNumber", "squid:S107"}) + public CustomMediaCodecVideoRenderer(final Context context, + final MediaCodecAdapter.Factory codecAdapterFactory, + final MediaCodecSelector mediaCodecSelector, + final long allowedJoiningTimeMs, + final boolean enableDecoderFallback, + @Nullable final Handler eventHandler, + @Nullable final VideoRendererEventListener eventListener, + final int maxDroppedFramesToNotify) { + super(context, codecAdapterFactory, mediaCodecSelector, allowedJoiningTimeMs, + enableDecoderFallback, eventHandler, eventListener, maxDroppedFramesToNotify); + } + + @Override + protected boolean codecNeedsSetOutputSurfaceWorkaround(final String name) { + return true; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java new file mode 100644 index 000000000..668b48c30 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java @@ -0,0 +1,43 @@ +package org.schabi.newpipe.player.helper; + +import android.content.Context; +import android.os.Handler; + +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; +import com.google.android.exoplayer2.video.VideoRendererEventListener; + +import java.util.ArrayList; + +/** + * A {@link DefaultRenderersFactory} which only uses {@link CustomMediaCodecVideoRenderer} as an + * implementation of video codec renders. + * + *

+ * As no ExoPlayer extension is currently used, the reflection code used by ExoPlayer to try to + * load video extension libraries is not needed in our case and has been removed. This should be + * changed in the case an extension is shipped with the app, such as the AV1 one. + *

+ */ +public final class CustomRenderersFactory extends DefaultRenderersFactory { + + public CustomRenderersFactory(final Context context) { + super(context); + } + + @SuppressWarnings("checkstyle:ParameterNumber") + @Override + protected void buildVideoRenderers(final Context context, + @ExtensionRendererMode final int extensionRendererMode, + final MediaCodecSelector mediaCodecSelector, + final boolean enableDecoderFallback, + final Handler eventHandler, + final VideoRendererEventListener eventListener, + final long allowedVideoJoiningTimeMs, + final ArrayList out) { + out.add(new CustomMediaCodecVideoRenderer(context, getCodecAdapterFactory(), + mediaCodecSelector, allowedVideoJoiningTimeMs, enableDecoderFallback, eventHandler, + eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); + } +} diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 8ff617172..f9d0572e8 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1370,4 +1370,5 @@ exoplayer_settings_key disable_media_tunneling_key use_exoplayer_decoder_fallback_key + always_use_exoplayer_set_output_surface_workaround_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 767dadab0..5730e063e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -777,4 +777,6 @@ Manage some ExoPlayer settings. These changes require a player restart to take effect Use ExoPlayer\'s decoder fallback feature Enable this option if you have decoder initialization issues, which falls back to lower-priority decoders if primary decoders initialization fail. This may result in poor playback performance than when using primary decoders + Always use ExoPlayer\'s video output surface setting workaround + This workaround releases and re-instantiates video codecs when a surface change occurs, instead of setting the surface to the codec directly. Already used by ExoPlayer on some devices with this issue, this setting has only an effect on Android 6 and higher\n\nEnabling this option may prevent playback errors when switching the current video player or switching to fullscreen \ No newline at end of file diff --git a/app/src/main/res/xml/exoplayer_settings.xml b/app/src/main/res/xml/exoplayer_settings.xml index d855ad936..7e903fff1 100644 --- a/app/src/main/res/xml/exoplayer_settings.xml +++ b/app/src/main/res/xml/exoplayer_settings.xml @@ -29,4 +29,12 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> - \ No newline at end of file + + +