Bug 656101 - support for audio ducking; r?ihsiao
MozReview-Commit-ID: 1OumuSeEIEi
--- a/mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java
@@ -16,16 +16,17 @@ public class AudioFocusAgent {
private static Context mContext;
private AudioManager mAudioManager;
private OnAudioFocusChangeListener mAfChangeListener;
public static final String OWN_FOCUS = "own_focus";
public static final String LOST_FOCUS = "lost_focus";
public static final String LOST_FOCUS_TRANSIENT = "lost_focus_transient";
+ public static final String LOST_FOCUS_TRANSIENT_CAN_DUCK = "lost_focus_transient_can_duck";
private String mAudioFocusState = LOST_FOCUS;
@WrapForJNI(calledFrom = "gecko")
public static void notifyStartedPlaying() {
if (!isAttachedToContext()) {
return;
}
@@ -60,23 +61,30 @@ public class AudioFocusAgent {
mAudioFocusState = LOST_FOCUS;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_LOSS_TRANSIENT");
notifyObservers("AudioFocusChanged", "lostAudioFocusTransiently");
notifyMediaControlService(MediaControlService.ACTION_PAUSE_BY_AUDIO_FOCUS);
mAudioFocusState = LOST_FOCUS_TRANSIENT;
break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
+ notifyMediaControlService(MediaControlService.ACTION_START_AUDIO_DUCK);
+ mAudioFocusState = LOST_FOCUS_TRANSIENT_CAN_DUCK;
+ break;
case AudioManager.AUDIOFOCUS_GAIN:
- if (!mAudioFocusState.equals(LOST_FOCUS_TRANSIENT)) {
- return;
+ if (mAudioFocusState.equals(LOST_FOCUS_TRANSIENT_CAN_DUCK)) {
+ Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_GAIN (from DUCKING)");
+ notifyMediaControlService(MediaControlService.ACTION_STOP_AUDIO_DUCK);
+ } else if (mAudioFocusState.equals(LOST_FOCUS_TRANSIENT)) {
+ Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_GAIN");
+ notifyObservers("AudioFocusChanged", "gainAudioFocus");
+ notifyMediaControlService(MediaControlService.ACTION_RESUME_BY_AUDIO_FOCUS);
}
- Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_GAIN");
- notifyObservers("AudioFocusChanged", "gainAudioFocus");
- notifyMediaControlService(MediaControlService.ACTION_RESUME_BY_AUDIO_FOCUS);
mAudioFocusState = OWN_FOCUS;
break;
default:
}
}
};
notifyMediaControlService(MediaControlService.ACTION_INIT);
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -8,16 +8,17 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.AudioManager;
+import android.media.VolumeProvider;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.CheckResult;
import android.support.annotation.VisibleForTesting;
import android.support.v4.app.NotificationManagerCompat;
@@ -38,20 +39,27 @@ public class MediaControlService extends
private static final String LOGTAG = "MediaControlService";
public static final String ACTION_INIT = "action_init";
public static final String ACTION_RESUME = "action_resume";
public static final String ACTION_PAUSE = "action_pause";
public static final String ACTION_STOP = "action_stop";
public static final String ACTION_RESUME_BY_AUDIO_FOCUS = "action_resume_audio_focus";
public static final String ACTION_PAUSE_BY_AUDIO_FOCUS = "action_pause_audio_focus";
-
+ public static final String ACTION_START_AUDIO_DUCK = "action_start_audio_duck";
+ public static final String ACTION_STOP_AUDIO_DUCK = "action_stop_audio_duck";
private static final int MEDIA_CONTROL_ID = 1;
private static final String MEDIA_CONTROL_PREF = "dom.audiochannel.mediaControl";
+ // This is maximum volume level difference when audio ducking. The number is arbitrary.
+ private static final int AUDIO_DUCK_MAX_STEPS = 3;
+ private enum AudioDucking { START, STOP };
+ private boolean mSupportsDucking = false;
+ private int mAudioDuckCurrentSteps = 0;
+
private MediaSession mSession;
private MediaController mController;
private HeadSetStateReceiver mHeadSetStateReceiver;
private PrefsHelper.PrefHandler mPrefsObserver;
private final String[] mPrefs = { MEDIA_CONTROL_PREF };
private boolean mInitialize = false;
@@ -208,16 +216,44 @@ public class MediaControlService extends
mController.getTransportControls().stop();
break;
case ACTION_PAUSE_BY_AUDIO_FOCUS :
mController.getTransportControls().sendCustomAction(ACTION_PAUSE_BY_AUDIO_FOCUS, null);
break;
case ACTION_RESUME_BY_AUDIO_FOCUS :
mController.getTransportControls().sendCustomAction(ACTION_RESUME_BY_AUDIO_FOCUS, null);
break;
+ case ACTION_START_AUDIO_DUCK:
+ handleAudioDucking(AudioDucking.START);
+ break;
+ case ACTION_STOP_AUDIO_DUCK :
+ handleAudioDucking(AudioDucking.STOP);
+ break;
+ }
+ }
+
+ private void handleAudioDucking(AudioDucking audioDucking) {
+ if (!mInitialize || !mSupportsDucking) {
+ return;
+ }
+
+ int currentVolume = mController.getPlaybackInfo().getCurrentVolume();
+ int maxVolume = mController.getPlaybackInfo().getMaxVolume();
+
+ int adjustDirection;
+ if (audioDucking == AudioDucking.START) {
+ mAudioDuckCurrentSteps = Math.min(AUDIO_DUCK_MAX_STEPS, currentVolume);
+ adjustDirection = AudioManager.ADJUST_LOWER;
+ } else {
+ mAudioDuckCurrentSteps = Math.min(mAudioDuckCurrentSteps, maxVolume - currentVolume);
+ adjustDirection = AudioManager.ADJUST_RAISE;
+ }
+
+ for (int i = 0; i < mAudioDuckCurrentSteps; i++) {
+ mController.adjustVolume(adjustDirection, 0);
}
}
private void getGeckoPreference() {
mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
@Override
public void prefValue(String pref, boolean value) {
if (pref.equals(MEDIA_CONTROL_PREF)) {
@@ -242,19 +278,27 @@ public class MediaControlService extends
}
};
PrefsHelper.addObserver(mPrefs, mPrefsObserver);
}
private void initMediaSession() {
// Android MediaSession is introduced since version L.
mSession = new MediaSession(getApplicationContext(),
- "fennec media session");
+ "fennec media session");
mController = new MediaController(getApplicationContext(),
- mSession.getSessionToken());
+ mSession.getSessionToken());
+
+ int volumeControl = mController.getPlaybackInfo().getVolumeControl();
+ if (volumeControl == VolumeProvider.VOLUME_CONTROL_ABSOLUTE ||
+ volumeControl == VolumeProvider.VOLUME_CONTROL_RELATIVE) {
+ mSupportsDucking = true;
+ } else {
+ Log.w(LOGTAG, "initMediaSession, Session does not support volume absolute or relative volume control");
+ }
mSession.setCallback(new MediaSession.Callback() {
@Override
public void onCustomAction(String action, Bundle extras) {
if (action.equals(ACTION_PAUSE_BY_AUDIO_FOCUS)) {
Log.d(LOGTAG, "Controller, pause by audio focus changed");
setState(State.PAUSED);
} else if (action.equals(ACTION_RESUME_BY_AUDIO_FOCUS)) {