Post AudioFocusManager.onAudioFocusChange events to eventHandler.

PiperOrigin-RevId: 276452333
This commit is contained in:
samrobinson 2019-10-24 11:17:02 +01:00 committed by Oliver Woodman
parent 3a84b787b1
commit b99203dada
4 changed files with 77 additions and 58 deletions

View File

@ -85,6 +85,8 @@
`VideoDecoderOutputBufferRenderer`, instead of `VideoDecoderSurfaceView`. `VideoDecoderOutputBufferRenderer`, instead of `VideoDecoderSurfaceView`.
* Add automatic audio becoming noisy handling to `SimpleExoPlayer`, * Add automatic audio becoming noisy handling to `SimpleExoPlayer`,
available through `SimpleExoPlayer.setHandleAudioBecomingNoisy`. available through `SimpleExoPlayer.setHandleAudioBecomingNoisy`.
* Post `AudioFocusManager.onAudioFocusChange` events to eventHandler, avoiding
multithreaded access to the player or audio focus manager.
* Add `Timeline.Window.isLive` to indicate that a window is a live stream * Add `Timeline.Window.isLive` to indicate that a window is a live stream
([#2668](https://github.com/google/ExoPlayer/issues/2668) and ([#2668](https://github.com/google/ExoPlayer/issues/2668) and
[#5973](https://github.com/google/ExoPlayer/issues/5973)). [#5973](https://github.com/google/ExoPlayer/issues/5973)).

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2;
import android.content.Context; import android.content.Context;
import android.media.AudioFocusRequest; import android.media.AudioFocusRequest;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Handler;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
@ -114,13 +115,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* Constructs an AudioFocusManager to automatically handle audio focus for a player. * Constructs an AudioFocusManager to automatically handle audio focus for a player.
* *
* @param context The current context. * @param context The current context.
* @param eventHandler A {@link Handler} to for the thread on which the player is used.
* @param playerControl A {@link PlayerControl} to handle commands from this instance. * @param playerControl A {@link PlayerControl} to handle commands from this instance.
*/ */
public AudioFocusManager(Context context, PlayerControl playerControl) { public AudioFocusManager(Context context, Handler eventHandler, PlayerControl playerControl) {
this.audioManager = this.audioManager =
(AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE); (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
this.playerControl = playerControl; this.playerControl = playerControl;
this.focusListener = new AudioFocusListener(); this.focusListener = new AudioFocusListener(eventHandler);
this.audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS; this.audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS;
} }
@ -380,65 +382,75 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
private void handleAudioFocusChange(int focusChange) {
// Convert the platform focus change to internal state.
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
audioFocusState = AUDIO_FOCUS_STATE_LOST_FOCUS;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
if (willPauseWhenDucked()) {
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
} else {
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK;
}
break;
case AudioManager.AUDIOFOCUS_GAIN:
audioFocusState = AUDIO_FOCUS_STATE_HAVE_FOCUS;
break;
default:
Log.w(TAG, "Unknown focus change type: " + focusChange);
// Early return.
return;
}
// Handle the internal state (change).
switch (audioFocusState) {
case AUDIO_FOCUS_STATE_NO_FOCUS:
// Focus was not requested; nothing to do.
break;
case AUDIO_FOCUS_STATE_LOST_FOCUS:
playerControl.executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
abandonAudioFocus(/* forceAbandon= */ true);
break;
case AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
playerControl.executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
break;
case AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK:
// Volume will be adjusted by the code below.
break;
case AUDIO_FOCUS_STATE_HAVE_FOCUS:
playerControl.executePlayerCommand(PLAYER_COMMAND_PLAY_WHEN_READY);
break;
default:
throw new IllegalStateException("Unknown audio focus state: " + audioFocusState);
}
float volumeMultiplier =
(audioFocusState == AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK)
? AudioFocusManager.VOLUME_MULTIPLIER_DUCK
: AudioFocusManager.VOLUME_MULTIPLIER_DEFAULT;
if (AudioFocusManager.this.volumeMultiplier != volumeMultiplier) {
AudioFocusManager.this.volumeMultiplier = volumeMultiplier;
playerControl.setVolumeMultiplier(volumeMultiplier);
}
}
// Internal audio focus listener. // Internal audio focus listener.
private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener { private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
private final Handler eventHandler;
public AudioFocusListener(Handler eventHandler) {
this.eventHandler = eventHandler;
}
@Override @Override
public void onAudioFocusChange(int focusChange) { public void onAudioFocusChange(int focusChange) {
// Convert the platform focus change to internal state. eventHandler.post(() -> handleAudioFocusChange(focusChange));
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
audioFocusState = AUDIO_FOCUS_STATE_LOST_FOCUS;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
if (willPauseWhenDucked()) {
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
} else {
audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK;
}
break;
case AudioManager.AUDIOFOCUS_GAIN:
audioFocusState = AUDIO_FOCUS_STATE_HAVE_FOCUS;
break;
default:
Log.w(TAG, "Unknown focus change type: " + focusChange);
// Early return.
return;
}
// Handle the internal state (change).
switch (audioFocusState) {
case AUDIO_FOCUS_STATE_NO_FOCUS:
// Focus was not requested; nothing to do.
break;
case AUDIO_FOCUS_STATE_LOST_FOCUS:
playerControl.executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
abandonAudioFocus(/* forceAbandon= */ true);
break;
case AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
playerControl.executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
break;
case AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK:
// Volume will be adjusted by the code below.
break;
case AUDIO_FOCUS_STATE_HAVE_FOCUS:
playerControl.executePlayerCommand(PLAYER_COMMAND_PLAY_WHEN_READY);
break;
default:
throw new IllegalStateException("Unknown audio focus state: " + audioFocusState);
}
float volumeMultiplier =
(audioFocusState == AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK)
? AudioFocusManager.VOLUME_MULTIPLIER_DUCK
: AudioFocusManager.VOLUME_MULTIPLIER_DEFAULT;
if (AudioFocusManager.this.volumeMultiplier != volumeMultiplier) {
AudioFocusManager.this.volumeMultiplier = volumeMultiplier;
playerControl.setVolumeMultiplier(volumeMultiplier);
}
} }
} }
} }

View File

@ -456,7 +456,7 @@ public class SimpleExoPlayer extends BasePlayer
} }
audioBecomingNoisyManager = audioBecomingNoisyManager =
new AudioBecomingNoisyManager(context, eventHandler, componentListener); new AudioBecomingNoisyManager(context, eventHandler, componentListener);
audioFocusManager = new AudioFocusManager(context, componentListener); audioFocusManager = new AudioFocusManager(context, eventHandler, componentListener);
wakeLockManager = new WakeLockManager(context); wakeLockManager = new WakeLockManager(context);
} }

View File

@ -25,6 +25,8 @@ import static org.robolectric.annotation.Config.TARGET_SDK;
import android.content.Context; import android.content.Context;
import android.media.AudioFocusRequest; import android.media.AudioFocusRequest;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
@ -54,7 +56,10 @@ public class AudioFocusManagerTest {
testPlayerControl = new TestPlayerControl(); testPlayerControl = new TestPlayerControl();
audioFocusManager = audioFocusManager =
new AudioFocusManager(ApplicationProvider.getApplicationContext(), testPlayerControl); new AudioFocusManager(
ApplicationProvider.getApplicationContext(),
new Handler(Looper.myLooper()),
testPlayerControl);
} }
@Test @Test