Merge pull request #197 from xbao/feature/duck-on-notification
Added support for ducking
This commit is contained in:
commit
73233751b3
@ -26,6 +26,10 @@ android {
|
|||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_5
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_5
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -150,6 +150,8 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
<string name="audio">Audio</string>
|
<string name="audio">Audio</string>
|
||||||
<string name="volume_title">Volume</string>
|
<string name="volume_title">Volume</string>
|
||||||
|
<string name="volume_during_ducking_title">Volume During Notification</string>
|
||||||
|
<string name="volume_during_ducking_summary">Playback volume: </string>
|
||||||
<string name="media_button_title">Headset/Bluetooth Controls</string>
|
<string name="media_button_title">Headset/Bluetooth Controls</string>
|
||||||
<string name="media_button_summary">This is also required for ICS lockscreen controls.</string>
|
<string name="media_button_summary">This is also required for ICS lockscreen controls.</string>
|
||||||
<string name="media_button_beep_title">Headset Control Beep</string>
|
<string name="media_button_beep_title">Headset Control Beep</string>
|
||||||
|
@ -31,6 +31,12 @@ THE SOFTWARE.
|
|||||||
android:fragment="ch.blinkenlights.android.vanilla.PreferencesActivity$ReplayGainFragment"
|
android:fragment="ch.blinkenlights.android.vanilla.PreferencesActivity$ReplayGainFragment"
|
||||||
android:title="@string/replaygain"
|
android:title="@string/replaygain"
|
||||||
android:summary="@string/replaygain_summary" />
|
android:summary="@string/replaygain_summary" />
|
||||||
|
<ch.blinkenlights.android.vanilla.SeekBarPreference
|
||||||
|
android:key="volume_during_ducking"
|
||||||
|
android:negativeButtonText="@string/restore_default"
|
||||||
|
android:dialogLayout="@layout/shake_pref"
|
||||||
|
android:title="@string/volume_during_ducking_title"
|
||||||
|
android:defaultValue="50" />
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="media_button"
|
android:key="media_button"
|
||||||
android:title="@string/media_button_title"
|
android:title="@string/media_button_title"
|
||||||
|
@ -233,12 +233,18 @@ public final class PlaybackService extends Service
|
|||||||
* These three bits will be one of SongTimeline.SHUFFLE_*.
|
* These three bits will be one of SongTimeline.SHUFFLE_*.
|
||||||
*/
|
*/
|
||||||
public static final int MASK_SHUFFLE = 0x7 << SHIFT_SHUFFLE;
|
public static final int MASK_SHUFFLE = 0x7 << SHIFT_SHUFFLE;
|
||||||
|
public static final int SHIFT_DUCKING = 10;
|
||||||
|
/**
|
||||||
|
* Whether we're 'ducking' (lowering the playback volume temporarily due to a transient system
|
||||||
|
* sound, such as a notification) or not
|
||||||
|
*/
|
||||||
|
public static final int FLAG_DUCKING = 0x1 << SHIFT_DUCKING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PlaybackService state, indicating if the service is playing,
|
* The PlaybackService state, indicating if the service is playing,
|
||||||
* repeating, etc.
|
* repeating, etc.
|
||||||
*
|
*
|
||||||
* The format of this is 0b00000000_00000000_0000000ff_feeedcba,
|
* The format of this is 0b00000000_00000000_000000gff_feeedcba,
|
||||||
* where each bit is:
|
* where each bit is:
|
||||||
* a: {@link PlaybackService#FLAG_PLAYING}
|
* a: {@link PlaybackService#FLAG_PLAYING}
|
||||||
* b: {@link PlaybackService#FLAG_NO_MEDIA}
|
* b: {@link PlaybackService#FLAG_NO_MEDIA}
|
||||||
@ -246,6 +252,7 @@ public final class PlaybackService extends Service
|
|||||||
* d: {@link PlaybackService#FLAG_EMPTY_QUEUE}
|
* d: {@link PlaybackService#FLAG_EMPTY_QUEUE}
|
||||||
* eee: {@link PlaybackService#MASK_FINISH}
|
* eee: {@link PlaybackService#MASK_FINISH}
|
||||||
* fff: {@link PlaybackService#MASK_SHUFFLE}
|
* fff: {@link PlaybackService#MASK_SHUFFLE}
|
||||||
|
* g: {@link PlaybackService#FLAG_DUCKING}
|
||||||
*/
|
*/
|
||||||
int mState;
|
int mState;
|
||||||
|
|
||||||
@ -384,6 +391,10 @@ public final class PlaybackService extends Service
|
|||||||
private boolean mReplayGainAlbumEnabled;
|
private boolean mReplayGainAlbumEnabled;
|
||||||
private int mReplayGainBump;
|
private int mReplayGainBump;
|
||||||
private int mReplayGainUntaggedDeBump;
|
private int mReplayGainUntaggedDeBump;
|
||||||
|
/**
|
||||||
|
* Percentage to set the volume as while a notification is playing (aka ducking)
|
||||||
|
*/
|
||||||
|
private int mVolumeDuringDucking;
|
||||||
/**
|
/**
|
||||||
* TRUE if the readahead feature is enabled
|
* TRUE if the readahead feature is enabled
|
||||||
*/
|
*/
|
||||||
@ -449,6 +460,9 @@ public final class PlaybackService extends Service
|
|||||||
mReplayGainBump = settings.getInt(PrefKeys.REPLAYGAIN_BUMP, 75); /* seek bar is 150 -> 75 == middle == 0 */
|
mReplayGainBump = settings.getInt(PrefKeys.REPLAYGAIN_BUMP, 75); /* seek bar is 150 -> 75 == middle == 0 */
|
||||||
mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150); /* seek bar is 150 -> == 0 */
|
mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150); /* seek bar is 150 -> == 0 */
|
||||||
|
|
||||||
|
mVolumeDuringDucking = settings.getInt(PrefKeys.VOLUME_DURING_DUCKING, 50);
|
||||||
|
refreshDuckingValues();
|
||||||
|
|
||||||
mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false);
|
mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false);
|
||||||
|
|
||||||
PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
|
PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
|
||||||
@ -621,6 +635,12 @@ public final class PlaybackService extends Service
|
|||||||
applyReplayGain(mPreparedMediaPlayer);
|
applyReplayGain(mPreparedMediaPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshDuckingValues() {
|
||||||
|
float duckingFactor = ((float) mVolumeDuringDucking)/100f;
|
||||||
|
mMediaPlayer.setDuckingFactor(duckingFactor);
|
||||||
|
mPreparedMediaPlayer.setDuckingFactor(duckingFactor);
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Reads the replay gain value of the media players data source
|
* Reads the replay gain value of the media players data source
|
||||||
* and adjusts the volume
|
* and adjusts the volume
|
||||||
@ -661,7 +681,7 @@ public final class PlaybackService extends Service
|
|||||||
} else if (rg_result < 0.0f) {
|
} else if (rg_result < 0.0f) {
|
||||||
rg_result = 0.0f;
|
rg_result = 0.0f;
|
||||||
}
|
}
|
||||||
mp.setVolume(rg_result, rg_result);
|
mp.setReplayGain(rg_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -842,6 +862,9 @@ public final class PlaybackService extends Service
|
|||||||
} else if (PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(key)) {
|
} else if (PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(key)) {
|
||||||
mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150);
|
mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150);
|
||||||
refreshReplayGainValues();
|
refreshReplayGainValues();
|
||||||
|
} else if (PrefKeys.VOLUME_DURING_DUCKING.equals(key)) {
|
||||||
|
mVolumeDuringDucking = settings.getInt(PrefKeys.VOLUME_DURING_DUCKING, 50);
|
||||||
|
refreshDuckingValues();
|
||||||
} else if (PrefKeys.ENABLE_READAHEAD.equals(key)) {
|
} else if (PrefKeys.ENABLE_READAHEAD.equals(key)) {
|
||||||
mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false);
|
mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false);
|
||||||
} else if (PrefKeys.USE_DARK_THEME.equals(key)) {
|
} else if (PrefKeys.USE_DARK_THEME.equals(key)) {
|
||||||
@ -979,6 +1002,12 @@ public final class PlaybackService extends Service
|
|||||||
mTimeline.setShuffleMode(shuffleMode(state));
|
mTimeline.setShuffleMode(shuffleMode(state));
|
||||||
if ((toggled & MASK_FINISH) != 0)
|
if ((toggled & MASK_FINISH) != 0)
|
||||||
mTimeline.setFinishAction(finishAction(state));
|
mTimeline.setFinishAction(finishAction(state));
|
||||||
|
|
||||||
|
if((toggled & FLAG_DUCKING) != 0) {
|
||||||
|
boolean isDucking = (state & FLAG_DUCKING) != 0;
|
||||||
|
mMediaPlayer.setIsDucking(isDucking);
|
||||||
|
mPreparedMediaPlayer.setIsDucking(isDucking);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void broadcastChange(int state, Song song, long uptime)
|
private void broadcastChange(int state, Song song, long uptime)
|
||||||
@ -2010,9 +2039,13 @@ public final class PlaybackService extends Service
|
|||||||
Log.d("VanillaMusic", "audio focus change: " + type);
|
Log.d("VanillaMusic", "audio focus change: " + type);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||||
|
synchronized (mStateLock) {
|
||||||
mDuckedLoss = (mState & FLAG_PLAYING) != 0;
|
mDuckedLoss = (mState & FLAG_PLAYING) != 0;
|
||||||
unsetFlag(FLAG_PLAYING);
|
if(mDuckedLoss) {
|
||||||
|
setFlag(FLAG_DUCKING);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case AudioManager.AUDIOFOCUS_LOSS:
|
case AudioManager.AUDIOFOCUS_LOSS:
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||||
mDuckedLoss = false;
|
mDuckedLoss = false;
|
||||||
@ -2022,7 +2055,7 @@ public final class PlaybackService extends Service
|
|||||||
case AudioManager.AUDIOFOCUS_GAIN:
|
case AudioManager.AUDIOFOCUS_GAIN:
|
||||||
if (mDuckedLoss) {
|
if (mDuckedLoss) {
|
||||||
mDuckedLoss = false;
|
mDuckedLoss = false;
|
||||||
setFlag(FLAG_PLAYING);
|
unsetFlag(FLAG_DUCKING);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -66,4 +66,5 @@ public class PrefKeys {
|
|||||||
public static final String ENABLE_READAHEAD = "enable_readahead";
|
public static final String ENABLE_READAHEAD = "enable_readahead";
|
||||||
public static final String USE_DARK_THEME = "use_dark_theme";
|
public static final String USE_DARK_THEME = "use_dark_theme";
|
||||||
public static final String FILESYSTEM_BROWSE_START = "filesystem_browse_start";
|
public static final String FILESYSTEM_BROWSE_START = "filesystem_browse_start";
|
||||||
|
public static final String VOLUME_DURING_DUCKING = "volume_during_ducking";
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,9 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
|
|||||||
} else if(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(getKey())) {
|
} else if(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(getKey())) {
|
||||||
String summary = (String)mContext.getResources().getText(R.string.replaygain_untagged_debump_summary);
|
String summary = (String)mContext.getResources().getText(R.string.replaygain_untagged_debump_summary);
|
||||||
return String.format("%s %.1fdB", summary, (value-150)/10f);
|
return String.format("%s %.1fdB", summary, (value-150)/10f);
|
||||||
|
} else if (PrefKeys.VOLUME_DURING_DUCKING.equals(getKey())) {
|
||||||
|
String summary = mContext.getString(R.string.volume_during_ducking_summary);
|
||||||
|
return summary + " " + value + "%";
|
||||||
} else {
|
} else {
|
||||||
return String.format("%d%% (%+.1fdB)", value, 20 * Math.log10(Math.pow(value / 100.0, 3)));
|
return String.format("%d%% (%+.1fdB)", value, 20 * Math.log10(Math.pow(value / 100.0, 3)));
|
||||||
}
|
}
|
||||||
@ -103,7 +106,15 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
|
|||||||
|
|
||||||
SeekBar seekBar = (SeekBar)view.findViewById(R.id.seek_bar);
|
SeekBar seekBar = (SeekBar)view.findViewById(R.id.seek_bar);
|
||||||
|
|
||||||
int maxValue = (PrefKeys.SHAKE_THRESHOLD.equals(getKey()) ? 300 : 150);
|
int maxValue;
|
||||||
|
String key = getKey();
|
||||||
|
if (PrefKeys.SHAKE_THRESHOLD.equals(key)) {
|
||||||
|
maxValue = 300;
|
||||||
|
} else if (PrefKeys.VOLUME_DURING_DUCKING.equals(key)) {
|
||||||
|
maxValue = 100;
|
||||||
|
} else {
|
||||||
|
maxValue = 150;
|
||||||
|
}
|
||||||
|
|
||||||
seekBar.setMax(maxValue);
|
seekBar.setMax(maxValue);
|
||||||
seekBar.setProgress(mValue);
|
seekBar.setProgress(mValue);
|
||||||
@ -115,6 +126,9 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
|
|||||||
@Override
|
@Override
|
||||||
protected void onDialogClosed(boolean positiveResult)
|
protected void onDialogClosed(boolean positiveResult)
|
||||||
{
|
{
|
||||||
|
if(!positiveResult && PrefKeys.VOLUME_DURING_DUCKING.equals(getKey())) {
|
||||||
|
setValue(50);
|
||||||
|
}
|
||||||
notifyChanged();
|
notifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,9 +136,7 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
|
|||||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
|
||||||
{
|
{
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
mValue = progress;
|
setValue(progress);
|
||||||
mValueText.setText(getSummary(progress));
|
|
||||||
persistInt(progress);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,4 +149,10 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
|
|||||||
public void onStopTrackingTouch(SeekBar seekBar)
|
public void onStopTrackingTouch(SeekBar seekBar)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setValue(int value) {
|
||||||
|
mValue = value;
|
||||||
|
mValueText.setText(getSummary(value));
|
||||||
|
persistInt(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,9 @@ public class VanillaMediaPlayer extends MediaPlayer {
|
|||||||
private Context mContext;
|
private Context mContext;
|
||||||
private String mDataSource;
|
private String mDataSource;
|
||||||
private boolean mHasNextMediaPlayer;
|
private boolean mHasNextMediaPlayer;
|
||||||
|
private float mReplayGain = Float.NaN;
|
||||||
|
private float mDuckingFactor = Float.NaN;
|
||||||
|
private boolean mIsDucking = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new VanillaMediaPlayer class
|
* Constructs a new VanillaMediaPlayer class
|
||||||
@ -111,4 +114,50 @@ public class VanillaMediaPlayer extends MediaPlayer {
|
|||||||
mContext.sendBroadcast(i);
|
mContext.sendBroadcast(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the desired scaling due to replay gain.
|
||||||
|
* @param replayGain the factor to adjust the volume by. Must be between 0 and 1 (inclusive)
|
||||||
|
* or {@link Float#NaN} to disable replay gain scaling
|
||||||
|
*/
|
||||||
|
public void setReplayGain(float replayGain) {
|
||||||
|
mReplayGain = replayGain;
|
||||||
|
updateVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether we are ducking or not. Ducking is when we temporarily decrease the volume for
|
||||||
|
* a transient sound to play from another application, such as a notification's beep.
|
||||||
|
* @param isDucking true if we are ducking, false if we are not
|
||||||
|
*/
|
||||||
|
public void setIsDucking(boolean isDucking) {
|
||||||
|
mIsDucking = isDucking;
|
||||||
|
updateVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the desired scaling while ducking.
|
||||||
|
* @param duckingFactor the factor to adjust the volume by while ducking. Must be between 0
|
||||||
|
* and 1 (inclusive) or {@link Float#NaN} to disable ducking completely
|
||||||
|
*
|
||||||
|
* See also {@link #setIsDucking(boolean)}
|
||||||
|
*/
|
||||||
|
public void setDuckingFactor(float duckingFactor) {
|
||||||
|
mDuckingFactor = duckingFactor;
|
||||||
|
updateVolume();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets the volume, using the replay gain and ducking if appropriate
|
||||||
|
*/
|
||||||
|
private void updateVolume() {
|
||||||
|
float volume = 1.0f;
|
||||||
|
if (!Float.isNaN(mReplayGain)) {
|
||||||
|
volume = mReplayGain;
|
||||||
|
}
|
||||||
|
if(mIsDucking && !Float.isNaN(mDuckingFactor)) {
|
||||||
|
volume *= mDuckingFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
setVolume(volume, volume);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user