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 39f941693..4bd908867 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.PlaybackParameters; 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 final class Player implements PlaybackListener, Listener { 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 668b48c30..116cc3f11 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 android.os.Handler; 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 @@ import java.util.ArrayList; */ 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 @@ public final class CustomRenderersFactory extends DefaultRenderersFactory { 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 352e4cec1..f4fd4ea5c 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 147c88938..bfb6f209c 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 727ce4df4..701211a83 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 4bd908867..7db1b8101 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 final class Player implements PlaybackListener, Listener { 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 116cc3f11..04470612e 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 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. + * 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 f4fd4ea5c..49886f4a6 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 bfb6f209c..1d790dc76 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 7e903fff1..3ef2d4433 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 701211a83..727ce4df4 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"/> - -