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
This commit is contained in:
Yubi Lee 2026-02-01 17:05:44 +09:00 committed by tobigr
parent 35b70c5e9e
commit 3c58b34ac4
2 changed files with 10 additions and 108 deletions

View File

@ -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();
}

View File

@ -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
//////////////////////////////////////////////////////////////////////////*/