From 2c7a9d53cdbf87904c6c454ddb5ad707f4c03cbd Mon Sep 17 00:00:00 2001 From: Xiao Bao Clark Date: Sat, 22 Aug 2015 12:23:22 +1000 Subject: [PATCH 1/4] Added support for ducking (#45) --- .../android/vanilla/PlaybackService.java | 21 ++++++++--- .../android/vanilla/VanillaMediaPlayer.java | 36 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/ch/blinkenlights/android/vanilla/PlaybackService.java b/src/ch/blinkenlights/android/vanilla/PlaybackService.java index f1802fd3..048959d7 100644 --- a/src/ch/blinkenlights/android/vanilla/PlaybackService.java +++ b/src/ch/blinkenlights/android/vanilla/PlaybackService.java @@ -233,12 +233,18 @@ public final class PlaybackService extends Service * These three bits will be one of SongTimeline.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, * repeating, etc. * - * The format of this is 0b00000000_00000000_0000000ff_feeedcba, + * The format of this is 0b00000000_00000000_000000gff_feeedcba, * where each bit is: * a: {@link PlaybackService#FLAG_PLAYING} * b: {@link PlaybackService#FLAG_NO_MEDIA} @@ -246,6 +252,7 @@ public final class PlaybackService extends Service * d: {@link PlaybackService#FLAG_EMPTY_QUEUE} * eee: {@link PlaybackService#MASK_FINISH} * fff: {@link PlaybackService#MASK_SHUFFLE} + * g: {@link PlaybackService#FLAG_DUCKING} */ int mState; @@ -661,7 +668,7 @@ public final class PlaybackService extends Service } else if (rg_result < 0.0f) { rg_result = 0.0f; } - mp.setVolume(rg_result, rg_result); + mp.setReplayGain(rg_result); } /** @@ -979,6 +986,10 @@ public final class PlaybackService extends Service mTimeline.setShuffleMode(shuffleMode(state)); if ((toggled & MASK_FINISH) != 0) mTimeline.setFinishAction(finishAction(state)); + + if((toggled & FLAG_DUCKING) != 0) { + mMediaPlayer.setIsDucking((state & FLAG_DUCKING) != 0); + } } private void broadcastChange(int state, Song song, long uptime) @@ -2010,8 +2021,8 @@ public final class PlaybackService extends Service Log.d("VanillaMusic", "audio focus change: " + type); switch (type) { case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - mDuckedLoss = (mState & FLAG_PLAYING) != 0; - unsetFlag(FLAG_PLAYING); + mDuckedLoss = true; + setFlag(FLAG_DUCKING); break; case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: @@ -2022,7 +2033,7 @@ public final class PlaybackService extends Service case AudioManager.AUDIOFOCUS_GAIN: if (mDuckedLoss) { mDuckedLoss = false; - setFlag(FLAG_PLAYING); + unsetFlag(FLAG_DUCKING); } break; } diff --git a/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java b/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java index dce062dd..a43bccc5 100644 --- a/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java +++ b/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java @@ -29,6 +29,8 @@ public class VanillaMediaPlayer extends MediaPlayer { private Context mContext; private String mDataSource; private boolean mHasNextMediaPlayer; + private float mReplayGain = Float.NaN; + private float mDuckingFactor = Float.NaN; /** * Constructs a new VanillaMediaPlayer class @@ -107,4 +109,38 @@ public class VanillaMediaPlayer extends MediaPlayer { mContext.sendBroadcast(i); } + /** + * Sets the desired scaling due to replay gain. + * @param mReplayGain 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 mReplayGain) { + this.mReplayGain = mReplayGain; + 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) { + mDuckingFactor = (isDucking ? 0.2f : Float.NaN); + updateVolume(); + } + + /** + * Sets the volume. Ducking takes precedence over replay gain. If neither ducking nor replay + * gain is set, uses the default value of 1.0f + */ + private void updateVolume() { + float volume = 1.0f; + if(!Float.isNaN(mDuckingFactor)) { + volume = mDuckingFactor; + } else if (!Float.isNaN(mReplayGain)) { + volume = mReplayGain; + } + setVolume(volume, volume); + } + } From ea9302aa0ad70e9bcfbb4eb4e1735762d59fb81a Mon Sep 17 00:00:00 2001 From: Xiao Bao Clark Date: Sun, 23 Aug 2015 09:38:09 +1000 Subject: [PATCH 2/4] Added ducking volume as a preference --- res/values/translatable.xml | 2 ++ res/xml/preference_audio.xml | 6 ++++ .../android/vanilla/PlaybackService.java | 16 +++++++++ .../android/vanilla/PrefKeys.java | 1 + .../android/vanilla/SeekBarPreference.java | 28 +++++++++++++--- .../android/vanilla/VanillaMediaPlayer.java | 33 +++++++++++++------ 6 files changed, 72 insertions(+), 14 deletions(-) diff --git a/res/values/translatable.xml b/res/values/translatable.xml index a3541d57..ede87f8c 100644 --- a/res/values/translatable.xml +++ b/res/values/translatable.xml @@ -150,6 +150,8 @@ THE SOFTWARE. Audio Volume + Volume During Notification + Playback volume: Headset/Bluetooth Controls This is also required for ICS lockscreen controls. Headset Control Beep diff --git a/res/xml/preference_audio.xml b/res/xml/preference_audio.xml index 4da7f060..3a2ec227 100644 --- a/res/xml/preference_audio.xml +++ b/res/xml/preference_audio.xml @@ -31,6 +31,12 @@ THE SOFTWARE. android:fragment="ch.blinkenlights.android.vanilla.PreferencesActivity$ReplayGainFragment" android:title="@string/replaygain" android:summary="@string/replaygain_summary" /> + 75 == middle == 0 */ mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150); /* seek bar is 150 -> == 0 */ + mVolumeOnNotification = settings.getInt(PrefKeys.VOLUME_ON_NOTIFICATION, 50); + refreshDuckingValues(); + mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false); PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE); @@ -628,6 +635,12 @@ public final class PlaybackService extends Service applyReplayGain(mPreparedMediaPlayer); } + private void refreshDuckingValues() { + float duckingFactor = ((float)mVolumeOnNotification)/100f; + mMediaPlayer.setDuckingFactor(duckingFactor); + mPreparedMediaPlayer.setDuckingFactor(duckingFactor); + } + /*** * Reads the replay gain value of the media players data source * and adjusts the volume @@ -849,6 +862,9 @@ public final class PlaybackService extends Service } else if (PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(key)) { mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150); refreshReplayGainValues(); + } else if (PrefKeys.VOLUME_ON_NOTIFICATION.equals(key)) { + mVolumeOnNotification = settings.getInt(PrefKeys.VOLUME_ON_NOTIFICATION, 50); + refreshDuckingValues(); } else if (PrefKeys.ENABLE_READAHEAD.equals(key)) { mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false); } else if (PrefKeys.USE_DARK_THEME.equals(key)) { diff --git a/src/ch/blinkenlights/android/vanilla/PrefKeys.java b/src/ch/blinkenlights/android/vanilla/PrefKeys.java index 747783a4..12e366ce 100644 --- a/src/ch/blinkenlights/android/vanilla/PrefKeys.java +++ b/src/ch/blinkenlights/android/vanilla/PrefKeys.java @@ -66,4 +66,5 @@ public class PrefKeys { public static final String ENABLE_READAHEAD = "enable_readahead"; public static final String USE_DARK_THEME = "use_dark_theme"; public static final String FILESYSTEM_BROWSE_START = "filesystem_browse_start"; + public static final String VOLUME_ON_NOTIFICATION = "volume_on_notification"; } diff --git a/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java b/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java index 56774074..b0ac54e8 100644 --- a/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java +++ b/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java @@ -88,6 +88,9 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee } else if(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(getKey())) { String summary = (String)mContext.getResources().getText(R.string.replaygain_untagged_debump_summary); return String.format("%s %.1fdB", summary, (value-150)/10f); + } else if (PrefKeys.VOLUME_ON_NOTIFICATION.equals(getKey())) { + String summary = mContext.getString(R.string.volume_on_notification_summary); + return summary + " " + value + "%"; } else { return String.format("%d%% (%+.1fdB)", value, 20 * Math.log10(Math.pow(value / 100.0, 3))); } @@ -103,7 +106,17 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee SeekBar seekBar = (SeekBar)view.findViewById(R.id.seek_bar); - int maxValue = (PrefKeys.SHAKE_THRESHOLD.equals(getKey()) ? 300 : 150); + int maxValue; + switch (getKey()) { + case PrefKeys.SHAKE_THRESHOLD: + maxValue = 300; + break; + case PrefKeys.VOLUME_ON_NOTIFICATION: + maxValue = 100; + break; + default: + maxValue = 150; + } seekBar.setMax(maxValue); seekBar.setProgress(mValue); @@ -115,6 +128,9 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee @Override protected void onDialogClosed(boolean positiveResult) { + if(!positiveResult && PrefKeys.VOLUME_ON_NOTIFICATION.equals(getKey())) { + setValue(50); + } notifyChanged(); } @@ -122,9 +138,7 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { - mValue = progress; - mValueText.setText(getSummary(progress)); - persistInt(progress); + setValue(progress); } } @@ -137,4 +151,10 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee public void onStopTrackingTouch(SeekBar seekBar) { } + + private void setValue(int value) { + mValue = value; + mValueText.setText(getSummary(value)); + persistInt(value); + } } diff --git a/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java b/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java index a43bccc5..51d30f17 100644 --- a/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java +++ b/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java @@ -31,6 +31,7 @@ public class VanillaMediaPlayer extends MediaPlayer { private boolean mHasNextMediaPlayer; private float mReplayGain = Float.NaN; private float mDuckingFactor = Float.NaN; + private boolean mIsDucking = false; /** * Constructs a new VanillaMediaPlayer class @@ -111,11 +112,11 @@ public class VanillaMediaPlayer extends MediaPlayer { /** * Sets the desired scaling due to replay gain. - * @param mReplayGain the factor to adjust the volume by. Must be between 0 and 1 (inclusive) + * @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 mReplayGain) { - this.mReplayGain = mReplayGain; + public void setReplayGain(float replayGain) { + mReplayGain = replayGain; updateVolume(); } @@ -125,22 +126,34 @@ public class VanillaMediaPlayer extends MediaPlayer { * @param isDucking true if we are ducking, false if we are not */ public void setIsDucking(boolean isDucking) { - mDuckingFactor = (isDucking ? 0.2f : Float.NaN); + mIsDucking = isDucking; updateVolume(); } /** - * Sets the volume. Ducking takes precedence over replay gain. If neither ducking nor replay - * gain is set, uses the default value of 1.0f + * 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(mDuckingFactor)) { - volume = mDuckingFactor; - } else if (!Float.isNaN(mReplayGain)) { + if (!Float.isNaN(mReplayGain)) { volume = mReplayGain; } + if(mIsDucking && !Float.isNaN(mDuckingFactor)) { + volume *= mDuckingFactor; + } + setVolume(volume, volume); } - + } From f73bd0fdde78387bc2c062279c89284c1a08d673 Mon Sep 17 00:00:00 2001 From: Xiao Bao Clark Date: Sat, 29 Aug 2015 08:54:34 +1000 Subject: [PATCH 3/4] Applied ducking state to prepared media play. Renamed *OnVolume to *OnDucking for consistency. Added is-playing check before initiating ducking. Replaced switch on String to if else for source compatability. --- res/values/translatable.xml | 4 ++-- res/xml/preference_audio.xml | 4 ++-- .../android/vanilla/PlaybackService.java | 24 ++++++++++++------- .../android/vanilla/PrefKeys.java | 2 +- .../android/vanilla/SeekBarPreference.java | 22 ++++++++--------- .../android/vanilla/VanillaMediaPlayer.java | 2 +- 6 files changed, 31 insertions(+), 27 deletions(-) diff --git a/res/values/translatable.xml b/res/values/translatable.xml index ede87f8c..2f647618 100644 --- a/res/values/translatable.xml +++ b/res/values/translatable.xml @@ -150,8 +150,8 @@ THE SOFTWARE. Audio Volume - Volume During Notification - Playback volume: + Volume During Notification + Playback volume: Headset/Bluetooth Controls This is also required for ICS lockscreen controls. Headset Control Beep diff --git a/res/xml/preference_audio.xml b/res/xml/preference_audio.xml index 3a2ec227..4fd14092 100644 --- a/res/xml/preference_audio.xml +++ b/res/xml/preference_audio.xml @@ -32,10 +32,10 @@ THE SOFTWARE. android:title="@string/replaygain" android:summary="@string/replaygain_summary" /> 75 == middle == 0 */ mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150); /* seek bar is 150 -> == 0 */ - mVolumeOnNotification = settings.getInt(PrefKeys.VOLUME_ON_NOTIFICATION, 50); + mVolumeDuringDucking = settings.getInt(PrefKeys.VOLUME_DURING_DUCKING, 50); refreshDuckingValues(); mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false); @@ -636,7 +636,7 @@ public final class PlaybackService extends Service } private void refreshDuckingValues() { - float duckingFactor = ((float)mVolumeOnNotification)/100f; + float duckingFactor = ((float) mVolumeDuringDucking)/100f; mMediaPlayer.setDuckingFactor(duckingFactor); mPreparedMediaPlayer.setDuckingFactor(duckingFactor); } @@ -862,8 +862,8 @@ public final class PlaybackService extends Service } else if (PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(key)) { mReplayGainUntaggedDeBump = settings.getInt(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP, 150); refreshReplayGainValues(); - } else if (PrefKeys.VOLUME_ON_NOTIFICATION.equals(key)) { - mVolumeOnNotification = settings.getInt(PrefKeys.VOLUME_ON_NOTIFICATION, 50); + } else if (PrefKeys.VOLUME_DURING_DUCKING.equals(key)) { + mVolumeDuringDucking = settings.getInt(PrefKeys.VOLUME_DURING_DUCKING, 50); refreshDuckingValues(); } else if (PrefKeys.ENABLE_READAHEAD.equals(key)) { mReadaheadEnabled = settings.getBoolean(PrefKeys.ENABLE_READAHEAD, false); @@ -1004,7 +1004,9 @@ public final class PlaybackService extends Service mTimeline.setFinishAction(finishAction(state)); if((toggled & FLAG_DUCKING) != 0) { - mMediaPlayer.setIsDucking((state & FLAG_DUCKING) != 0); + boolean isDucking = (state & FLAG_DUCKING) != 0; + mMediaPlayer.setIsDucking(isDucking); + mPreparedMediaPlayer.setIsDucking(isDucking); } } @@ -2037,9 +2039,13 @@ public final class PlaybackService extends Service Log.d("VanillaMusic", "audio focus change: " + type); switch (type) { case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - mDuckedLoss = true; - setFlag(FLAG_DUCKING); - break; + synchronized (mStateLock) { + mDuckedLoss = (mState & FLAG_PLAYING) != 0; + if(mDuckedLoss) { + setFlag(FLAG_DUCKING); + } + break; + } case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: mDuckedLoss = false; diff --git a/src/ch/blinkenlights/android/vanilla/PrefKeys.java b/src/ch/blinkenlights/android/vanilla/PrefKeys.java index 12e366ce..3fd6850a 100644 --- a/src/ch/blinkenlights/android/vanilla/PrefKeys.java +++ b/src/ch/blinkenlights/android/vanilla/PrefKeys.java @@ -66,5 +66,5 @@ public class PrefKeys { public static final String ENABLE_READAHEAD = "enable_readahead"; public static final String USE_DARK_THEME = "use_dark_theme"; public static final String FILESYSTEM_BROWSE_START = "filesystem_browse_start"; - public static final String VOLUME_ON_NOTIFICATION = "volume_on_notification"; + public static final String VOLUME_DURING_DUCKING = "volume_during_ducking"; } diff --git a/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java b/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java index b0ac54e8..e77a54fa 100644 --- a/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java +++ b/src/ch/blinkenlights/android/vanilla/SeekBarPreference.java @@ -88,8 +88,8 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee } else if(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(getKey())) { String summary = (String)mContext.getResources().getText(R.string.replaygain_untagged_debump_summary); return String.format("%s %.1fdB", summary, (value-150)/10f); - } else if (PrefKeys.VOLUME_ON_NOTIFICATION.equals(getKey())) { - String summary = mContext.getString(R.string.volume_on_notification_summary); + } else if (PrefKeys.VOLUME_DURING_DUCKING.equals(getKey())) { + String summary = mContext.getString(R.string.volume_during_ducking_summary); return summary + " " + value + "%"; } else { return String.format("%d%% (%+.1fdB)", value, 20 * Math.log10(Math.pow(value / 100.0, 3))); @@ -107,15 +107,13 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee SeekBar seekBar = (SeekBar)view.findViewById(R.id.seek_bar); int maxValue; - switch (getKey()) { - case PrefKeys.SHAKE_THRESHOLD: - maxValue = 300; - break; - case PrefKeys.VOLUME_ON_NOTIFICATION: - maxValue = 100; - break; - default: - maxValue = 150; + 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); @@ -128,7 +126,7 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee @Override protected void onDialogClosed(boolean positiveResult) { - if(!positiveResult && PrefKeys.VOLUME_ON_NOTIFICATION.equals(getKey())) { + if(!positiveResult && PrefKeys.VOLUME_DURING_DUCKING.equals(getKey())) { setValue(50); } notifyChanged(); diff --git a/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java b/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java index 51d30f17..55a344d4 100644 --- a/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java +++ b/src/ch/blinkenlights/android/vanilla/VanillaMediaPlayer.java @@ -155,5 +155,5 @@ public class VanillaMediaPlayer extends MediaPlayer { setVolume(volume, volume); } - + } From 757c0a5e85fd62fac2183d13f78000105b3fdba1 Mon Sep 17 00:00:00 2001 From: Xiao Bao Clark Date: Sat, 29 Aug 2015 08:57:51 +1000 Subject: [PATCH 4/4] Configured source and target language to Java 1.5 for gradle builds --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 4c18c0e7..2729fb1d 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,10 @@ android { targetSdkVersion 22 } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_5 + targetCompatibility JavaVersion.VERSION_1_5 + } sourceSets {