summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/VolumeReductionSetting.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java50
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java6
-rw-r--r--core/src/main/res/values/arrays.xml12
-rw-r--r--core/src/main/res/values/strings.xml5
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/VolumeReductionSettingTest.java64
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdaterTest.java230
13 files changed, 471 insertions, 11 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
index 6a7c97b7b..61a09565f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
@@ -160,7 +160,7 @@ public class Feed extends FeedFile implements ImageResource {
*/
public Feed(String url, String lastUpdate, String title, String username, String password) {
this(url, lastUpdate, title);
- preferences = new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL, username, password);
+ preferences = new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeReductionSetting.OFF, username, password);
}
public static Feed fromCursor(Cursor cursor) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
index 8fdf2034f..cb5a59a3b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
@@ -28,19 +28,23 @@ public class FeedPreferences {
NO
}
private AutoDeleteAction auto_delete_action;
+
+ private VolumeReductionSetting volumeReductionSetting;
+
private String username;
private String password;
private float feedPlaybackSpeed;
- public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, String username, String password) {
- this(feedID, autoDownload, true, auto_delete_action, username, password, new FeedFilter(), SPEED_USE_GLOBAL);
+ public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, VolumeReductionSetting volumeReductionSetting, String username, String password) {
+ this(feedID, autoDownload, true, auto_delete_action, volumeReductionSetting, username, password, new FeedFilter(), SPEED_USE_GLOBAL);
}
- private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed) {
+ private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, VolumeReductionSetting volumeReductionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed) {
this.feedID = feedID;
this.autoDownload = autoDownload;
this.keepUpdated = keepUpdated;
this.auto_delete_action = auto_delete_action;
+ this.volumeReductionSetting = volumeReductionSetting;
this.username = username;
this.password = password;
this.filter = filter;
@@ -52,6 +56,7 @@ public class FeedPreferences {
int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD);
int indexAutoRefresh = cursor.getColumnIndex(PodDBAdapter.KEY_KEEP_UPDATED);
int indexAutoDeleteAction = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DELETE_ACTION);
+ int indexVolumeReduction = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_VOLUME_REDUCTION);
int indexUsername = cursor.getColumnIndex(PodDBAdapter.KEY_USERNAME);
int indexPassword = cursor.getColumnIndex(PodDBAdapter.KEY_PASSWORD);
int indexIncludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_INCLUDE_FILTER);
@@ -63,12 +68,14 @@ public class FeedPreferences {
boolean autoRefresh = cursor.getInt(indexAutoRefresh) > 0;
int autoDeleteActionIndex = cursor.getInt(indexAutoDeleteAction);
AutoDeleteAction autoDeleteAction = AutoDeleteAction.values()[autoDeleteActionIndex];
+ int volumeReductionValue = cursor.getInt(indexVolumeReduction);
+ VolumeReductionSetting volumeReductionSetting = VolumeReductionSetting.fromInteger(volumeReductionValue);
String username = cursor.getString(indexUsername);
String password = cursor.getString(indexPassword);
String includeFilter = cursor.getString(indexIncludeFilter);
String excludeFilter = cursor.getString(indexExcludeFilter);
float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed);
- return new FeedPreferences(feedId, autoDownload, autoRefresh, autoDeleteAction, username, password, new FeedFilter(includeFilter, excludeFilter), feedPlaybackSpeed);
+ return new FeedPreferences(feedId, autoDownload, autoRefresh, autoDeleteAction, volumeReductionSetting, username, password, new FeedFilter(includeFilter, excludeFilter), feedPlaybackSpeed);
}
/**
@@ -144,10 +151,18 @@ public class FeedPreferences {
return auto_delete_action;
}
+ public VolumeReductionSetting getVolumeReductionSetting() {
+ return volumeReductionSetting;
+ }
+
public void setAutoDeleteAction(AutoDeleteAction auto_delete_action) {
this.auto_delete_action = auto_delete_action;
}
+ public void setVolumeReductionSetting(VolumeReductionSetting volumeReductionSetting) {
+ this.volumeReductionSetting = volumeReductionSetting;
+ }
+
public boolean getCurrentAutoDelete() {
switch (auto_delete_action) {
case GLOBAL:
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/VolumeReductionSetting.java b/core/src/main/java/de/danoeh/antennapod/core/feed/VolumeReductionSetting.java
new file mode 100644
index 000000000..53808580a
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/VolumeReductionSetting.java
@@ -0,0 +1,32 @@
+package de.danoeh.antennapod.core.feed;
+
+public enum VolumeReductionSetting {
+ OFF(0, 1.0f),
+ LIGHT(1, 0.5f),
+ HEAVY(2, 0.2f);
+
+ private final int value;
+ private float reductionFactor;
+
+ VolumeReductionSetting(int value, float reductionFactor) {
+ this.value = value;
+ this.reductionFactor = reductionFactor;
+ }
+
+ public static VolumeReductionSetting fromInteger(int value) {
+ for (VolumeReductionSetting setting : values()) {
+ if (setting.value == value) {
+ return setting;
+ }
+ }
+ throw new IllegalArgumentException("Cannot map value to VolumeReductionSetting: " + value);
+ }
+
+ public int toInteger() {
+ return value;
+ }
+
+ public float getReductionFactor() {
+ return reductionFactor;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index 296046031..b6e5de254 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -21,6 +21,7 @@ import android.util.Log;
import android.util.Pair;
import android.webkit.URLUtil;
+import de.danoeh.antennapod.core.feed.VolumeReductionSetting;
import org.apache.commons.io.FileUtils;
import org.greenrobot.eventbus.EventBus;
import org.xml.sax.SAXException;
@@ -809,7 +810,7 @@ public class DownloadService extends Service {
feed.setId(request.getFeedfileId());
feed.setDownloaded(true);
feed.setPreferences(new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL,
- request.getUsername(), request.getPassword()));
+ VolumeReductionSetting.OFF, request.getUsername(), request.getPassword()));
feed.setPageNr(request.getArguments().getInt(DownloadRequester.REQUEST_ARG_PAGE_NR, 0));
DownloadError reason = null;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
index 1584d29d2..8ac2721b6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
@@ -12,11 +12,11 @@ import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
-import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import org.antennapod.audio.MediaPlayer;
import java.io.File;
import java.io.IOException;
+import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
@@ -26,13 +26,17 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.PlaybackSpeedHelper;
+import de.danoeh.antennapod.core.feed.VolumeReductionSetting;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.RewindAfterPauseUtils;
import de.danoeh.antennapod.core.util.playback.AudioPlayer;
import de.danoeh.antennapod.core.util.playback.IPlayer;
import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.core.util.playback.VideoPlayer;
/**
@@ -308,7 +312,10 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
acquireWifiLockIfNecessary();
setPlaybackParams(PlaybackSpeedHelper.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence());
- setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume());
+
+ float leftVolume = UserPreferences.getLeftVolume();
+ float rightVolume = UserPreferences.getRightVolume();
+ setVolume(leftVolume, rightVolume);
if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
@@ -661,8 +668,19 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
*/
private void setVolumeSync(float volumeLeft, float volumeRight) {
playerLock.lock();
- mediaPlayer.setVolume(volumeLeft, volumeRight);
- Log.d(TAG, "Media player volume was set to " + volumeLeft + " " + volumeRight);
+ if (media != null && EnumSet.of(MediaType.AUDIO, MediaType.VIDEO).contains(media.getMediaType())) {
+ Playable playable = getPlayable();
+ if (playable instanceof FeedMedia) {
+ FeedMedia feedMedia = (FeedMedia) playable;
+ FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences();
+ VolumeReductionSetting volumeReductionSetting = preferences.getVolumeReductionSetting();
+ float reductionFactor = volumeReductionSetting.getReductionFactor();
+ volumeLeft *= reductionFactor;
+ volumeRight *= reductionFactor;
+ }
+ mediaPlayer.setVolume(volumeLeft, volumeRight);
+ Log.d(TAG, "Media player volume was set to " + volumeLeft + " " + volumeRight);
+ }
playerLock.unlock();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index 5002fb913..eb38a02d9 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -42,6 +42,7 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -57,6 +58,7 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.feed.SearchResult;
+import de.danoeh.antennapod.core.feed.VolumeReductionSetting;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
@@ -83,6 +85,7 @@ import org.greenrobot.eventbus.EventBus;
* Controls the MediaPlayer that plays a FeedMedia-file
*/
public class PlaybackService extends MediaBrowserServiceCompat {
+
/**
* Logging tag
*/
@@ -135,6 +138,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/
public static final String ACTION_PAUSE_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.pausePlayCurrentEpisode";
+ /**
+ * If the PlaybackService receives this action, it will try to apply the supplied volume reduction setting.
+ */
+ public static final String ACTION_VOLUME_REDUCTION_CHANGED = "action.de.danoeh.antennapod.core.service.volumedReductionChanged";
+ public static final String EXTRA_VOLUME_REDUCTION_SETTING = "PlaybackService.VolumeReductionSettingExtra";
+ public static final String EXTRA_VOLUME_REDUCTION_AFFECTED_FEED = "PlaybackService.VolumeReductionSettingAffectedFeed";
/**
* If the PlaybackService receives this action, it will resume playback.
@@ -283,6 +292,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(ACTION_SKIP_CURRENT_EPISODE));
registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(ACTION_PAUSE_PLAY_CURRENT_EPISODE));
registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(ACTION_RESUME_PLAY_CURRENT_EPISODE));
+ registerReceiver(volumeReductionChangedReceiver, new IntentFilter(ACTION_VOLUME_REDUCTION_CHANGED));
taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback);
@@ -1450,6 +1460,22 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
};
+ private final BroadcastReceiver volumeReductionChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TextUtils.equals(intent.getAction(), ACTION_VOLUME_REDUCTION_CHANGED)) {
+ Log.d(TAG, "Received ACTION_VOLUME_REDUCTION_CHANGED intent");
+
+ String affectedFeed = intent.getStringExtra(EXTRA_VOLUME_REDUCTION_AFFECTED_FEED);
+ Serializable volumeReductionExtra = intent.getSerializableExtra(EXTRA_VOLUME_REDUCTION_SETTING);
+ VolumeReductionSetting volumeReductionSetting = (VolumeReductionSetting) volumeReductionExtra;
+
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, affectedFeed, volumeReductionSetting);
+ }
+ }
+ };
+
public static MediaType getCurrentMediaType() {
return currentMediaType;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java
new file mode 100644
index 000000000..aff742105
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java
@@ -0,0 +1,50 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.feed.VolumeReductionSetting;
+import de.danoeh.antennapod.core.util.playback.Playable;
+
+class PlaybackVolumeUpdater {
+
+ public void updateVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, String affectedFeedIdentifier, VolumeReductionSetting volumeReductionSetting) {
+ Playable playable = mediaPlayer.getPlayable();
+ boolean isFeedMedia = playable instanceof FeedMedia;
+ boolean isPlayableLoaded = isPlayableLoaded(mediaPlayer.getPlayerStatus());
+
+ if (isFeedMedia && isPlayableLoaded) {
+ updateFeedMediaVolumeIfNecessary(mediaPlayer, affectedFeedIdentifier, volumeReductionSetting, (FeedMedia) playable);
+ }
+ }
+
+ private void updateFeedMediaVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, String affectedFeedIdentifier, VolumeReductionSetting volumeReductionSetting, FeedMedia feedMedia) {
+ if (mediaBelongsToAffectedFeed(feedMedia, affectedFeedIdentifier)) {
+ FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences();
+ preferences.setVolumeReductionSetting(volumeReductionSetting);
+
+ if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
+ forceUpdateVolume(mediaPlayer);
+ }
+ }
+ }
+
+ private static boolean isPlayableLoaded(PlayerStatus playerStatus) {
+ return playerStatus == PlayerStatus.PLAYING
+ || playerStatus == PlayerStatus.PAUSED
+ || playerStatus == PlayerStatus.SEEKING
+ || playerStatus == PlayerStatus.PREPARING
+ || playerStatus == PlayerStatus.PREPARED
+ || playerStatus == PlayerStatus.INITIALIZING;
+ }
+
+ private static boolean mediaBelongsToAffectedFeed(FeedMedia feedMedia, String affectedFeedIdentifier) {
+ return affectedFeedIdentifier != null
+ && affectedFeedIdentifier.equals(feedMedia.getItem().getFeed().getIdentifyingValue());
+ }
+
+ private void forceUpdateVolume(PlaybackServiceMediaPlayer mediaPlayer) {
+ mediaPlayer.pause(false, false);
+ mediaPlayer.resume();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
index 575b8d2ac..148252902 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
@@ -293,6 +293,9 @@ class DBUpgrader {
if (oldVersion < 1070400) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL);
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_FEED_VOLUME_REDUCTION + " INTEGER DEFAULT 0");
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
index 66f6d223d..fa72cd510 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
@@ -95,6 +95,7 @@ public class PodDBAdapter {
public static final String KEY_AUTO_DOWNLOAD = "auto_download";
public static final String KEY_KEEP_UPDATED = "keep_updated";
public static final String KEY_AUTO_DELETE_ACTION = "auto_delete_action";
+ public static final String KEY_FEED_VOLUME_REDUCTION = "feed_volume_reduction";
public static final String KEY_PLAYED_DURATION = "played_duration";
public static final String KEY_USERNAME = "username";
public static final String KEY_PASSWORD = "password";
@@ -140,7 +141,8 @@ public class PodDBAdapter {
+ KEY_HIDE + " TEXT,"
+ KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0,"
+ KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0,"
- + KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + ")";
+ + KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + ","
+ + KEY_FEED_VOLUME_REDUCTION + " INTEGER DEFAULT 0)";
private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -236,6 +238,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_HIDE,
TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DELETE_ACTION,
+ TABLE_NAME_FEEDS + "." + KEY_FEED_VOLUME_REDUCTION,
TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER,
TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER,
TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED
@@ -399,6 +402,7 @@ public class PodDBAdapter {
values.put(KEY_AUTO_DOWNLOAD, prefs.getAutoDownload());
values.put(KEY_KEEP_UPDATED, prefs.getKeepUpdated());
values.put(KEY_AUTO_DELETE_ACTION, prefs.getAutoDeleteAction().ordinal());
+ values.put(KEY_FEED_VOLUME_REDUCTION, prefs.getVolumeReductionSetting().toInteger());
values.put(KEY_USERNAME, prefs.getUsername());
values.put(KEY_PASSWORD, prefs.getPassword());
values.put(KEY_INCLUDE_FILTER, prefs.getFilter().getIncludeFilter());
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index 5e7eab1ea..795d06d8d 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -13,6 +13,18 @@
<item>never</item>
</string-array>
+ <string-array name="spnVolumeReductionItems">
+ <item>@string/feed_volume_reduction_off</item>
+ <item>@string/feed_volume_reduction_light</item>
+ <item>@string/feed_volume_reduction_heavy</item>
+ </string-array>
+
+ <string-array name="spnVolumeReductionValues">
+ <item>off</item>
+ <item>light</item>
+ <item>heavy</item>
+ </string-array>
+
<string-array name="smart_mark_as_played_values">
<item>0</item>
<item>15</item>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 5a0267232..c8a22eddb 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -91,6 +91,11 @@
<string name="auto_download_apply_to_items_title">Apply to Previous Episodes</string>
<string name="auto_download_apply_to_items_message">The new <i>Auto Download</i> setting will automatically be applied to new episodes.\nDo you also want to apply it to previously published episodes?</string>
<string name="auto_delete_label">Auto Delete Episode</string>
+ <string name="feed_volume_reduction">Volume Reduction</string>
+ <string name="feed_volume_reduction_summary">Turn down volume for episodes of this feed: \%s</string>
+ <string name="feed_volume_reduction_off">Off</string>
+ <string name="feed_volume_reduction_light">Light</string>
+ <string name="feed_volume_reduction_heavy">Heavy</string>
<string name="parallel_downloads_suffix">\u0020parallel downloads</string>
<string name="feed_auto_download_global">Global default</string>
<string name="feed_auto_download_always">Always</string>
diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/VolumeReductionSettingTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/VolumeReductionSettingTest.java
new file mode 100644
index 000000000..a97fb6f20
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/VolumeReductionSettingTest.java
@@ -0,0 +1,64 @@
+package de.danoeh.antennapod.core.feed;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class VolumeReductionSettingTest {
+
+ @Test
+ public void mapOffToInteger() {
+ VolumeReductionSetting setting = VolumeReductionSetting.OFF;
+ assertThat(setting.toInteger(), is(equalTo(0)));
+ }
+
+ @Test
+ public void mapLightToInteger() {
+ VolumeReductionSetting setting = VolumeReductionSetting.LIGHT;
+
+ assertThat(setting.toInteger(), is(equalTo(1)));
+ }
+
+ @Test
+ public void mapHeavyToInteger() {
+ VolumeReductionSetting setting = VolumeReductionSetting.HEAVY;
+
+ assertThat(setting.toInteger(), is(equalTo(2)));
+ }
+
+ @Test
+ public void mapIntegerToVolumeReductionSetting() {
+ assertThat(VolumeReductionSetting.fromInteger(0), is(equalTo(VolumeReductionSetting.OFF)));
+ assertThat(VolumeReductionSetting.fromInteger(1), is(equalTo(VolumeReductionSetting.LIGHT)));
+ assertThat(VolumeReductionSetting.fromInteger(2), is(equalTo(VolumeReductionSetting.HEAVY)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void cannotMapNegativeValues() {
+ VolumeReductionSetting.fromInteger(-1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void cannotMapValuesOutOfRange() {
+ VolumeReductionSetting.fromInteger(3);
+ }
+
+ @Test
+ public void noReductionIfTurnedOff() {
+ float reductionFactor = VolumeReductionSetting.OFF.getReductionFactor();
+ assertEquals(1.0f, reductionFactor, 0.01f);
+ }
+
+ @Test
+ public void lightReductionYieldsHigherValueThanHeavyReduction() {
+ float lightReductionFactor = VolumeReductionSetting.LIGHT.getReductionFactor();
+
+ float heavyReductionFactor = VolumeReductionSetting.HEAVY.getReductionFactor();
+
+ assertTrue("Light reduction must have higher factor than heavy reduction", lightReductionFactor > heavyReductionFactor);
+ }
+} \ No newline at end of file
diff --git a/core/src/test/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdaterTest.java b/core/src/test/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdaterTest.java
new file mode 100644
index 000000000..4ebe4e5ba
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdaterTest.java
@@ -0,0 +1,230 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.feed.VolumeReductionSetting;
+import de.danoeh.antennapod.core.util.playback.Playable;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class PlaybackVolumeUpdaterTest {
+
+ private static final String FEED_ID = "feedId";
+
+ private PlaybackServiceMediaPlayer mediaPlayer;
+
+ @Before
+ public void setUp() throws Exception {
+ mediaPlayer = mock(PlaybackServiceMediaPlayer.class);
+ }
+
+ @Test
+ public void noChangeIfNoFeedMediaPlaying() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PAUSED);
+
+ Playable noFeedMedia = mock(Playable.class);
+ when(mediaPlayer.getPlayable()).thenReturn(noFeedMedia);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void noChangeIfPlayerStatusIsError() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.ERROR);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void noChangeIfPlayerStatusIsIndeterminate() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.INDETERMINATE);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void noChangeIfPlayerStatusIsStopped() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.STOPPED);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void noChangeIfPlayableIsNoItemOfAffectedFeed() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PLAYING);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+ Feed feed = mockFeed(feedMedia, FEED_ID);
+ when(feed.getIdentifyingValue()).thenReturn("wrongFeedId");
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsPaused() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PAUSED);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+ FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
+
+ verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsPrepared() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PREPARED);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+ FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
+
+ verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsInitializing() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.INITIALIZING);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+ FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
+
+ verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsPreparing() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PREPARING);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+ FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
+
+ verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsSeeking() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.SEEKING);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+ FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
+
+ verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
+
+ verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
+ verify(mediaPlayer, never()).resume();
+ }
+
+ @Test
+ public void updatesPreferencesAndForcesVolumeChangeForLoadedFeedMediaIfPlayerStatusIsPlaying() {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+
+ when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PLAYING);
+
+ FeedMedia feedMedia = mock(FeedMedia.class);
+ when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
+ FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
+
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.HEAVY);
+
+ verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.HEAVY);
+
+ verify(mediaPlayer, times(1)).pause(false, false);
+ verify(mediaPlayer, times(1)).resume();
+ }
+
+ private FeedPreferences mockFeedPreferences(FeedMedia feedMedia, String feedId) {
+ Feed feed = mockFeed(feedMedia, feedId);
+ FeedPreferences feedPreferences = mock(FeedPreferences.class);
+ when(feed.getPreferences()).thenReturn(feedPreferences);
+ return feedPreferences;
+ }
+
+ private Feed mockFeed(FeedMedia feedMedia, String feedId) {
+ FeedItem feedItem = mock(FeedItem.class);
+ when(feedMedia.getItem()).thenReturn(feedItem);
+ Feed feed = mock(Feed.class);
+ when(feed.getIdentifyingValue()).thenReturn(feedId);
+ when(feedItem.getFeed()).thenReturn(feed);
+ return feed;
+ }
+}