From 6af820adae631cba21d14f53d39135d7f638ead1 Mon Sep 17 00:00:00 2001 From: Artem Shtefan Date: Tue, 12 Aug 2025 20:38:44 +0300 Subject: [PATCH 1/3] Improve skipping silences. ExoPlayer allows controlling silence skipping by adjusting the silence duration that is removed by `SilenceSkippingAudioProcessor`. The default behavior with the default parameters for this component makes it hard to listen to content because it leaves almost no pauses. After this commit it is possible to adjust silence length that remains and shorten only long pauses. --- .../org/schabi/newpipe/player/Player.java | 19 +++++-- .../player/helper/CustomRenderersFactory.java | 50 +++++++++++++++++-- app/src/main/res/values/settings_keys.xml | 4 ++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/video_audio_settings.xml | 11 ++++ 5 files changed, 77 insertions(+), 8 deletions(-) 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 39f9416934f..4bd9088671a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -73,6 +73,7 @@ import com.google.android.exoplayer2.Player.PositionInfo; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.audio.SilenceSkippingAudioProcessor; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.CueGroup; @@ -299,10 +300,20 @@ public Player(@NonNull final PlayerService service, new DefaultBandwidthMeter.Builder(context).build()); loadController = new LoadController(); - renderFactory = prefs.getBoolean( - context.getString( - R.string.always_use_exoplayer_set_output_surface_workaround_key), false) - ? new CustomRenderersFactory(context) : new DefaultRenderersFactory(context); + final boolean alwaysUseExoplayerSetOutputSurfaceWorkaround = prefs.getBoolean( + context.getString(R.string.always_use_exoplayer_set_output_surface_workaround_key), + false); + final int maxSilenceDurationMillis = prefs.getInt( + context.getString(R.string.max_silence_duration_key), + Integer.parseInt(context.getString(R.string.max_silence_duration_value))); + final SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = + new SilenceSkippingAudioProcessor( + MILLISECONDS.toMicros(maxSilenceDurationMillis), + MILLISECONDS.toMicros(maxSilenceDurationMillis), + SilenceSkippingAudioProcessor.DEFAULT_SILENCE_THRESHOLD_LEVEL); + renderFactory = new CustomRenderersFactory( + context, alwaysUseExoplayerSetOutputSurfaceWorkaround, + silenceSkippingAudioProcessor); renderFactory.setEnableDecoderFallback( prefs.getBoolean( 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 index 668b48c306c..116cc3f11d0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java @@ -5,6 +5,12 @@ import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.audio.AudioCapabilities; +import com.google.android.exoplayer2.audio.AudioProcessor; +import com.google.android.exoplayer2.audio.AudioSink; +import com.google.android.exoplayer2.audio.DefaultAudioSink; +import com.google.android.exoplayer2.audio.SilenceSkippingAudioProcessor; +import com.google.android.exoplayer2.audio.SonicAudioProcessor; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -22,8 +28,17 @@ */ public final class CustomRenderersFactory extends DefaultRenderersFactory { - public CustomRenderersFactory(final Context context) { + private final boolean alwaysUseExoplayerSetOutputSurfaceWorkaround; + private final SilenceSkippingAudioProcessor silenceSkippingAudioProcessor; + + public CustomRenderersFactory( + final Context context, + final boolean alwaysUseExoplayerSetOutputSurfaceWorkaround, + final SilenceSkippingAudioProcessor silenceSkippingAudioProcessor) { super(context); + this.alwaysUseExoplayerSetOutputSurfaceWorkaround = + alwaysUseExoplayerSetOutputSurfaceWorkaround; + this.silenceSkippingAudioProcessor = silenceSkippingAudioProcessor; } @SuppressWarnings("checkstyle:ParameterNumber") @@ -36,8 +51,35 @@ protected void buildVideoRenderers(final Context context, 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)); + if (alwaysUseExoplayerSetOutputSurfaceWorkaround) { + out.add(new CustomMediaCodecVideoRenderer(context, getCodecAdapterFactory(), + mediaCodecSelector, allowedVideoJoiningTimeMs, enableDecoderFallback, + eventHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); + } else { + super.buildVideoRenderers(context, extensionRendererMode, mediaCodecSelector, + enableDecoderFallback, eventHandler, eventListener, allowedVideoJoiningTimeMs, + out); + } + } + + @Override + protected AudioSink buildAudioSink( + final Context context, + final boolean enableFloatOutput, + final boolean enableAudioTrackPlaybackParams, + final boolean enableOffload) { + return new DefaultAudioSink.Builder() + .setAudioCapabilities(AudioCapabilities.getCapabilities(context)) + .setEnableFloatOutput(enableFloatOutput) + .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams) + .setOffloadMode( + enableOffload + ? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED + : DefaultAudioSink.OFFLOAD_MODE_DISABLED) + .setAudioProcessorChain(new DefaultAudioSink.DefaultAudioProcessorChain( + new AudioProcessor[]{}, silenceSkippingAudioProcessor, + new SonicAudioProcessor() + )) + .build(); } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 352e4cec120..f4fd4ea5c36 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -123,6 +123,10 @@ default_popup_resolution 480p best_resolution + 1000 + 100 + 5000 + max_silence_duration 2160p diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 147c88938a9..bfb6f209c9c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ Default popup resolution Show higher resolutions Only some devices can play 2K/4K videos + Maximal silence duration in milliseconds that remains when "fast forwarding during silence" is enabled Play with Kodi Install missing Kore app\? Show \"Play with Kodi\" option diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml index 727ce4df40a..701211a835f 100644 --- a/app/src/main/res/xml/video_audio_settings.xml +++ b/app/src/main/res/xml/video_audio_settings.xml @@ -77,6 +77,17 @@ app:singleLineTitle="false" app:iconSpaceReserved="false"/> + + Date: Fri, 7 Nov 2025 00:31:46 +0200 Subject: [PATCH 2/3] Fix comments for pull request - Extract duplicated microsecond duration calculation - Update JavaDoc for CustomRenderersFactory --- app/src/main/java/org/schabi/newpipe/player/Player.java | 5 +++-- .../schabi/newpipe/player/helper/CustomRenderersFactory.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) 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 4bd9088671a..7db1b810184 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -306,10 +306,11 @@ public Player(@NonNull final PlayerService service, final int maxSilenceDurationMillis = prefs.getInt( context.getString(R.string.max_silence_duration_key), Integer.parseInt(context.getString(R.string.max_silence_duration_value))); + final long maxSilenceDurationMicros = MILLISECONDS.toMicros(maxSilenceDurationMillis); final SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor( - MILLISECONDS.toMicros(maxSilenceDurationMillis), - MILLISECONDS.toMicros(maxSilenceDurationMillis), + maxSilenceDurationMicros, + maxSilenceDurationMicros, SilenceSkippingAudioProcessor.DEFAULT_SILENCE_THRESHOLD_LEVEL); renderFactory = new CustomRenderersFactory( context, alwaysUseExoplayerSetOutputSurfaceWorkaround, 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 index 116cc3f11d0..04470612e51 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java @@ -17,8 +17,9 @@ import java.util.ArrayList; /** - * A {@link DefaultRenderersFactory} which only uses {@link CustomMediaCodecVideoRenderer} as an - * implementation of video codec renders. + * A {@link DefaultRenderersFactory} which uses {@link CustomMediaCodecVideoRenderer} as an + * implementation of video codec renders and uses a provided {@link SilenceSkippingAudioProcessor} + * to control silence skipping behavior more precisely. * *

* As no ExoPlayer extension is currently used, the reflection code used by ExoPlayer to try to From d55d6a1a9b4e7e64da78a45526f60e80a2de2f54 Mon Sep 17 00:00:00 2001 From: Artem Shtefan Date: Fri, 7 Nov 2025 11:57:04 +0200 Subject: [PATCH 3/3] Move silence skipping duration settings to ExoPlayer settings --- app/src/main/res/values/settings_keys.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/xml/exoplayer_settings.xml | 11 +++++++++++ app/src/main/res/xml/video_audio_settings.xml | 11 ----------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index f4fd4ea5c36..49886f4a6c1 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -123,7 +123,7 @@ default_popup_resolution 480p best_resolution - 1000 + 500 100 5000 max_silence_duration diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bfb6f209c9c..1d790dc7654 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,7 +50,7 @@ Default popup resolution Show higher resolutions Only some devices can play 2K/4K videos - Maximal silence duration in milliseconds that remains when "fast forwarding during silence" is enabled + Maximal silence duration in milliseconds that remains when \"fast forwarding during silence\" is enabled Play with Kodi Install missing Kore app\? Show \"Play with Kodi\" option diff --git a/app/src/main/res/xml/exoplayer_settings.xml b/app/src/main/res/xml/exoplayer_settings.xml index 7e903fff151..3ef2d443366 100644 --- a/app/src/main/res/xml/exoplayer_settings.xml +++ b/app/src/main/res/xml/exoplayer_settings.xml @@ -37,4 +37,15 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml index 701211a835f..727ce4df40a 100644 --- a/app/src/main/res/xml/video_audio_settings.xml +++ b/app/src/main/res/xml/video_audio_settings.xml @@ -77,17 +77,6 @@ app:singleLineTitle="false" app:iconSpaceReserved="false"/> - -