From 3c58b34ac49de1c9f9b957c88fe539fe1636df94 Mon Sep 17 00:00:00 2001 From: Yubi Lee Date: Sun, 1 Feb 2026 17:05:44 +0900 Subject: [PATCH] Fix audio ducking issue by using Android's automatic audio focus management Previously, manual audio focus handling caused volume to not be restored after transient ducking on some devices. This change removes manual AudioFocusRequest management and relies on ExoPlayer's built-in audio focus handling (handleAudioFocus=true), which properly manages automatic ducking as recommended in: https://developer.android.com/media/optimize/audio-focus#automatic-ducking Fixes #9710 --- .../org/schabi/newpipe/player/Player.java | 23 ++--- .../newpipe/player/helper/AudioReactor.java | 95 +------------------ 2 files changed, 10 insertions(+), 108 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 b07b15a45..0fdcc972c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -641,6 +641,15 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK); simpleExoPlayer.setHandleAudioBecomingNoisy(true); + // Enable automatic audio focus management - let Android handle ducking automatically + simpleExoPlayer.setAudioAttributes( + new com.google.android.exoplayer2.audio.AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) + .build(), + true // handleAudioFocus = true for automatic management + ); + audioReactor = new AudioReactor(context, simpleExoPlayer); registerBroadcastReceiver(); @@ -1190,10 +1199,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onPrepared); - - if (playWhenReady && !isMuted()) { - audioReactor.requestAudioFocus(); - } } private void onBlocked() { @@ -1341,11 +1346,6 @@ public final class Player implements PlaybackListener, Listener { public void toggleMute() { final boolean wasMuted = isMuted(); simpleExoPlayer.setVolume(wasMuted ? 1 : 0); - if (wasMuted) { - audioReactor.requestAudioFocus(); - } else { - audioReactor.abandonAudioFocus(); - } UIs.call(playerUi -> playerUi.onMuteUnmuteChanged(!wasMuted)); notifyPlaybackUpdateToListeners(); } @@ -1757,10 +1757,6 @@ public final class Player implements PlaybackListener, Listener { return; } - if (!isMuted()) { - audioReactor.requestAudioFocus(); - } - if (currentState == STATE_COMPLETED) { if (playQueue.getIndex() == 0) { seekToDefault(); @@ -1781,7 +1777,6 @@ public final class Player implements PlaybackListener, Listener { return; } - audioReactor.abandonAudioFocus(); simpleExoPlayer.pause(); saveStreamProgressState(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index 084336d54..2ec33dc0f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -1,54 +1,36 @@ package org.schabi.newpipe.player.helper; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.media.audiofx.AudioEffect; -import android.util.Log; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; -import androidx.media.AudioFocusRequestCompat; import androidx.media.AudioManagerCompat; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.analytics.AnalyticsListener; -public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AnalyticsListener { +public class AudioReactor implements AnalyticsListener { private static final String TAG = "AudioFocusReactor"; - private static final int DUCK_DURATION = 1500; - private static final float DUCK_AUDIO_TO = .2f; - - private static final int FOCUS_GAIN_TYPE = AudioManagerCompat.AUDIOFOCUS_GAIN; private static final int STREAM_TYPE = AudioManager.STREAM_MUSIC; private final ExoPlayer player; private final Context context; private final AudioManager audioManager; - private final AudioFocusRequestCompat request; - public AudioReactor(@NonNull final Context context, @NonNull final ExoPlayer player) { this.player = player; this.context = context; this.audioManager = ContextCompat.getSystemService(context, AudioManager.class); player.addAnalyticsListener(this); - - request = new AudioFocusRequestCompat.Builder(FOCUS_GAIN_TYPE) - //.setAcceptsDelayedFocusGain(true) - .setWillPauseWhenDucked(true) - .setOnAudioFocusChangeListener(this) - .build(); } public void dispose() { - abandonAudioFocus(); player.removeAnalyticsListener(this); notifyAudioSessionUpdate(false, player.getAudioSessionId()); } @@ -57,14 +39,6 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An // Audio Manager //////////////////////////////////////////////////////////////////////////*/ - public void requestAudioFocus() { - AudioManagerCompat.requestAudioFocus(audioManager, request); - } - - public void abandonAudioFocus() { - AudioManagerCompat.abandonAudioFocusRequest(audioManager, request); - } - public int getVolume() { return audioManager.getStreamVolume(STREAM_TYPE); } @@ -77,73 +51,6 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An return AudioManagerCompat.getStreamMaxVolume(audioManager, STREAM_TYPE); } - /*////////////////////////////////////////////////////////////////////////// - // AudioFocus - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onAudioFocusChange(final int focusChange) { - Log.d(TAG, "onAudioFocusChange() called with: focusChange = [" + focusChange + "]"); - switch (focusChange) { - case AudioManager.AUDIOFOCUS_GAIN: - onAudioFocusGain(); - break; - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - onAudioFocusLossCanDuck(); - break; - case AudioManager.AUDIOFOCUS_LOSS: - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - onAudioFocusLoss(); - break; - } - } - - private void onAudioFocusGain() { - Log.d(TAG, "onAudioFocusGain() called"); - player.setVolume(DUCK_AUDIO_TO); - animateAudio(DUCK_AUDIO_TO, 1.0f); - - if (PlayerHelper.isResumeAfterAudioFocusGain(context)) { - player.play(); - } - } - - private void onAudioFocusLoss() { - Log.d(TAG, "onAudioFocusLoss() called"); - player.pause(); - } - - private void onAudioFocusLossCanDuck() { - Log.d(TAG, "onAudioFocusLossCanDuck() called"); - // Set the volume to 1/10 on ducking - player.setVolume(DUCK_AUDIO_TO); - } - - private void animateAudio(final float from, final float to) { - final ValueAnimator valueAnimator = new ValueAnimator(); - valueAnimator.setFloatValues(from, to); - valueAnimator.setDuration(AudioReactor.DUCK_DURATION); - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(final Animator animation) { - player.setVolume(from); - } - - @Override - public void onAnimationCancel(final Animator animation) { - player.setVolume(to); - } - - @Override - public void onAnimationEnd(final Animator animation) { - player.setVolume(to); - } - }); - valueAnimator.addUpdateListener(animation -> - player.setVolume(((float) animation.getAnimatedValue()))); - valueAnimator.start(); - } - /*////////////////////////////////////////////////////////////////////////// // Audio Processing //////////////////////////////////////////////////////////////////////////*/