#3248 Adapt volume for playing media if its volume reduction setting is changed

This commit is contained in:
Max Bechtold 2019-08-17 15:46:08 +02:00
parent 7f0e642069
commit 759ce1b357
4 changed files with 317 additions and 3 deletions

View File

@ -3,6 +3,7 @@ package de.danoeh.antennapod.fragment;
import android.arch.lifecycle.ViewModelProviders; import android.arch.lifecycle.ViewModelProviders;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v14.preference.SwitchPreference; import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.ListPreference; import android.support.v7.preference.ListPreference;
@ -13,6 +14,7 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedFilter; import de.danoeh.antennapod.core.feed.FeedFilter;
import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.AuthenticationDialog;
import de.danoeh.antennapod.dialog.EpisodeFilterDialog; import de.danoeh.antennapod.dialog.EpisodeFilterDialog;
@ -132,11 +134,20 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat {
} }
feed.savePreferences(); feed.savePreferences();
updateVolumeReductionValue(); updateVolumeReductionValue();
// TODO maxbechtold Check if we can call setVolume for the PlaybackService, if running. Else, show toast? sendVolumeReductionChangedIntent();
return false; return false;
}); });
} }
private void sendVolumeReductionChangedIntent() {
Context context = getContext();
Intent intent = new Intent(PlaybackService.ACTION_VOLUME_REDUCTION_CHANGED).setPackage(context.getPackageName());
intent.putExtra(PlaybackService.EXTRA_VOLUME_REDUCTION_AFFECTED_FEED, feed.getIdentifyingValue());
intent.putExtra(PlaybackService.EXTRA_VOLUME_REDUCTION_SETTING, feedPreferences.getVolumeReductionSetting());
context.sendBroadcast(intent);
}
private void updateVolumeReductionValue() { private void updateVolumeReductionValue() {
ListPreference volumeReductionPreference = (ListPreference) findPreference("volumeReduction"); ListPreference volumeReductionPreference = (ListPreference) findPreference("volumeReduction");

View File

@ -0,0 +1,49 @@
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.util.playback.Playable;
class PlaybackVolumeAdaptor {
public void adaptVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, String affectedFeedIdentifier, FeedPreferences.VolumeReductionSetting volumeReductionSetting) {
Playable playable = mediaPlayer.getPlayable();
boolean isFeedMedia = playable instanceof FeedMedia;
boolean isPlayableLoaded = isPlayableLoaded(mediaPlayer.getPlayerStatus());
if (isPlayableLoaded && isFeedMedia) {
adaptFeedMediaVolumeIfNecessary(mediaPlayer, affectedFeedIdentifier, volumeReductionSetting, (FeedMedia) playable);
}
}
private void adaptFeedMediaVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, String affectedFeedIdentifier, FeedPreferences.VolumeReductionSetting volumeReductionSetting, FeedMedia feedMedia) {
if (mediaBelongsToAffectedFeed(feedMedia, affectedFeedIdentifier)) {
FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences();
preferences.setVolumeReductionSetting(volumeReductionSetting);
if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
forceAdaptVolume(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 static void forceAdaptVolume(PlaybackServiceMediaPlayer mediaPlayer) {
mediaPlayer.pause(false, false);
mediaPlayer.resume();
}
}

View File

@ -12,7 +12,6 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.Uri; import android.net.Uri;
@ -43,6 +42,7 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.Target;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -54,6 +54,7 @@ import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia; 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.feed.MediaType;
import de.danoeh.antennapod.core.feed.SearchResult; import de.danoeh.antennapod.core.feed.SearchResult;
import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.ApGlideSettings;
@ -66,7 +67,6 @@ import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.FeedSearcher; import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.util.IntList;
import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.QueueAccess; import de.danoeh.antennapod.core.util.QueueAccess;
@ -80,6 +80,7 @@ import org.greenrobot.eventbus.EventBus;
* Controls the MediaPlayer that plays a FeedMedia-file * Controls the MediaPlayer that plays a FeedMedia-file
*/ */
public class PlaybackService extends MediaBrowserServiceCompat { public class PlaybackService extends MediaBrowserServiceCompat {
/** /**
* Logging tag * Logging tag
*/ */
@ -131,6 +132,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/ */
public static final String ACTION_PAUSE_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.pausePlayCurrentEpisode"; 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. * If the PlaybackService receives this action, it will resume playback.
@ -279,6 +286,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(ACTION_SKIP_CURRENT_EPISODE)); registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(ACTION_SKIP_CURRENT_EPISODE));
registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(ACTION_PAUSE_PLAY_CURRENT_EPISODE)); registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(ACTION_PAUSE_PLAY_CURRENT_EPISODE));
registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(ACTION_RESUME_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); taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback); flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback);
@ -1517,6 +1525,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);
FeedPreferences.VolumeReductionSetting volumeReductionSetting = (FeedPreferences.VolumeReductionSetting) volumeReductionExtra;
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, affectedFeed, volumeReductionSetting);
}
}
};
public static MediaType getCurrentMediaType() { public static MediaType getCurrentMediaType() {
return currentMediaType; return currentMediaType;
} }

View File

@ -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.FeedPreferences.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 PlaybackVolumeAdaptorTest {
private static final String FEED_ID = "feedId";
private PlaybackServiceMediaPlayer mediaPlayer;
@Before
public void setUp() throws Exception {
mediaPlayer = mock(PlaybackServiceMediaPlayer.class);
}
@Test
public void noChangeIfNoFeedMediaPlaying() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PAUSED);
Playable noFeedMedia = mock(Playable.class);
when(mediaPlayer.getPlayable()).thenReturn(noFeedMedia);
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
verify(mediaPlayer, never()).resume();
}
@Test
public void noChangeIfPlayerStatusIsError() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.ERROR);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
verify(mediaPlayer, never()).resume();
}
@Test
public void noChangeIfPlayerStatusIsIndeterminate() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.INDETERMINATE);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
verify(mediaPlayer, never()).resume();
}
@Test
public void noChangeIfPlayerStatusIsStopped() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.STOPPED);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
verify(mediaPlayer, never()).resume();
}
@Test
public void noChangeIfPlayableIsNoItemOfAffectedFeed() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
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");
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
verify(mediaPlayer, never()).resume();
}
@Test
public void adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsPaused() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PAUSED);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
playbackVolumeAdaptor.adaptVolumeIfNecessary(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 adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsPrepared() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PREPARED);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
playbackVolumeAdaptor.adaptVolumeIfNecessary(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 adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsInitializing() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.INITIALIZING);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
playbackVolumeAdaptor.adaptVolumeIfNecessary(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 adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsPreparing() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PREPARING);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
playbackVolumeAdaptor.adaptVolumeIfNecessary(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 adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsSeeking() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.SEEKING);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
playbackVolumeAdaptor.adaptVolumeIfNecessary(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 adaptsPreferencesAndForcesVolumeChangeForLoadedFeedMediaIfPlayerStatusIsPlaying() {
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PLAYING);
FeedMedia feedMedia = mock(FeedMedia.class);
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
playbackVolumeAdaptor.adaptVolumeIfNecessary(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;
}
}