From 17b9579dfea12b9b6289982e3fd4251ecf12b86d Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Wed, 20 May 2020 12:20:24 +0200 Subject: [PATCH] New playback speed dialog --- .../test/antennapod/ui/PreferencesTest.java | 13 +- .../test/antennapod/ui/SpeedChangeTest.java | 3 +- .../activity/MediaplayerActivity.java | 3 +- .../dialog/PlaybackControlsDialog.java | 85 +------- .../dialog/VariableSpeedDialog.java | 206 ++++++++++++------ .../fragment/AudioPlayerFragment.java | 37 ++-- .../PlaybackPreferencesFragment.java | 2 +- .../antennapod/view/ItemOffsetDecoration.java | 24 ++ .../antennapod/view/PlaybackSpeedSeekBar.java | 86 ++++++++ app/src/main/res/layout/audio_controls.xml | 46 +--- .../main/res/layout/audioplayer_fragment.xml | 2 +- .../res/layout/playback_speed_seek_bar.xml | 43 ++++ .../main/res/layout/speed_select_dialog.xml | 45 ++++ app/src/main/res/xml/preferences_playback.xml | 2 +- .../core/preferences/UserPreferences.java | 23 +- .../util/playback/PlaybackController.java | 7 + core/src/main/res/values/strings.xml | 6 +- 17 files changed, 403 insertions(+), 230 deletions(-) create mode 100644 app/src/main/java/de/danoeh/antennapod/view/ItemOffsetDecoration.java create mode 100644 app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java create mode 100644 app/src/main/res/layout/playback_speed_seek_bar.xml create mode 100644 app/src/main/res/layout/speed_select_dialog.xml diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index 6741cbc86..ddce8b1e3 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -7,7 +7,6 @@ import android.preference.PreferenceManager; import androidx.annotation.StringRes; import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; -import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -31,7 +30,6 @@ import java.util.concurrent.TimeUnit; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.replaceText; -import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.action.ViewActions.swipeDown; import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; @@ -46,7 +44,6 @@ import static de.test.antennapod.EspressoTestUtils.clickPreference; import static de.test.antennapod.EspressoTestUtils.waitForView; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static junit.framework.TestCase.assertTrue; -import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.not; @@ -235,12 +232,10 @@ public class PreferencesTest { @Test public void testPlaybackSpeeds() { clickPreference(R.string.playback_pref); - clickPreference(R.string.media_player); - onView(withText(R.string.media_player_exoplayer)).perform(click()); - clickPreference(R.string.pref_playback_speed_title); - onView(isRoot()).perform(waitForView(withText("0.50"), 1000)); - onView(withText("0.50")).check(matches(isDisplayed())); - onView(withText(R.string.cancel_label)).perform(click()); + clickPreference(R.string.playback_speed); + onView(isRoot()).perform(waitForView(withText("0.75"), 1000)); + onView(withText("0.75")).check(matches(isDisplayed())); + onView(withText(R.string.close_label)).perform(click()); } @Test diff --git a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java index 79b8b29a4..ac5887069 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java @@ -25,6 +25,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -70,7 +71,7 @@ public class SpeedChangeTest { List queue = DBReader.getQueue(); PlaybackPreferences.writeMediaPlaying(queue.get(0).getMedia(), PlayerStatus.PAUSED, false); availableSpeeds = new String[] {"1.00", "2.00", "3.00"}; - UserPreferences.setPlaybackSpeedArray(availableSpeeds); + UserPreferences.setPlaybackSpeedArray(Arrays.asList(1.0f, 2.0f, 3.0f)); EspressoTestUtils.tryKillPlaybackService(); activityRule.launchActivity(new Intent().putExtra(MainActivity.EXTRA_OPEN_PLAYER, true)); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 61cbcbb01..b03d1e5cd 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -376,8 +376,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements new SleepTimerDialog().show(getSupportFragmentManager(), "SleepTimerDialog"); break; case R.id.audio_controls: - boolean isPlayingVideo = controller.getMedia().getMediaType() == MediaType.VIDEO; - PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(isPlayingVideo); + PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(); dialog.show(getSupportFragmentManager(), "playback_controls"); break; case R.id.open_feed_item: diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index d1ffdc148..e45533826 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -11,27 +11,21 @@ import android.widget.Button; import android.widget.CheckBox; import android.widget.SeekBar; import android.widget.TextView; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.view.PlaybackSpeedSeekBar; import java.util.List; import java.util.Locale; public class PlaybackControlsDialog extends DialogFragment { - private static final String ARGUMENT_IS_PLAYING_VIDEO = "isPlayingVideo"; - private PlaybackController controller; private AlertDialog dialog; - private boolean isPlayingVideo; - public static PlaybackControlsDialog newInstance(boolean isPlayingVideo) { + public static PlaybackControlsDialog newInstance() { Bundle arguments = new Bundle(); - arguments.putBoolean(ARGUMENT_IS_PLAYING_VIDEO, isPlayingVideo); PlaybackControlsDialog dialog = new PlaybackControlsDialog(); dialog.setArguments(arguments); return dialog; @@ -65,8 +59,6 @@ public class PlaybackControlsDialog extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - isPlayingVideo = getArguments() != null && getArguments().getBoolean(ARGUMENT_IS_PLAYING_VIDEO); - dialog = new AlertDialog.Builder(getContext()) .setTitle(R.string.audio_controls) .setView(R.layout.audio_controls) @@ -79,68 +71,18 @@ public class PlaybackControlsDialog extends DialogFragment { } private void setupUi() { - final SeekBar barPlaybackSpeed = dialog.findViewById(R.id.playback_speed); - final TextView butDecSpeed = dialog.findViewById(R.id.butDecSpeed); - butDecSpeed.setOnClickListener(v -> { - if (controller != null && controller.canSetPlaybackSpeed()) { - barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 2); - } else { - VariableSpeedDialog.showGetPluginDialog(getContext()); - } - }); - final TextView butIncSpeed = dialog.findViewById(R.id.butIncSpeed); - butIncSpeed.setOnClickListener(v -> { - if (controller != null && controller.canSetPlaybackSpeed()) { - barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 2); - } else { - VariableSpeedDialog.showGetPluginDialog(getContext()); - } - }); - final TextView txtvPlaybackSpeed = dialog.findViewById(R.id.txtvPlaybackSpeed); - float currentSpeed = getCurrentSpeed(); - txtvPlaybackSpeed.setText(String.format(Locale.getDefault(), "%.2fx", currentSpeed)); - barPlaybackSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (controller != null && controller.canSetPlaybackSpeed()) { - float playbackSpeed = (progress + 10) / 20.0f; - controller.setPlaybackSpeed(playbackSpeed); + PlaybackSpeedSeekBar speedSeekBar = dialog.findViewById(R.id.speed_seek_bar); + speedSeekBar.setController(controller); + speedSeekBar.setProgressChangedListener(speed + -> txtvPlaybackSpeed.setText(String.format(Locale.getDefault(), "%.2fx", speed))); - PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(playbackSpeed); - if (isPlayingVideo) { - UserPreferences.setVideoPlaybackSpeed(playbackSpeed); - } else { - UserPreferences.setPlaybackSpeed(playbackSpeed); - } - - String speedStr = String.format(Locale.getDefault(), "%.2fx", playbackSpeed); - txtvPlaybackSpeed.setText(speedStr); - } else if (fromUser) { - float speed = getCurrentSpeed(); - barPlaybackSpeed.post(() -> barPlaybackSpeed.setProgress(Math.round((20 * speed) - 10))); - } - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - if (controller != null && !controller.canSetPlaybackSpeed()) { - VariableSpeedDialog.showGetPluginDialog(getContext()); - } - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - }); - barPlaybackSpeed.setProgress(Math.round((20 * currentSpeed) - 10)); - - final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left); + final SeekBar barLeftVolume = dialog.findViewById(R.id.volume_left); barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage()); - final SeekBar barRightVolume = (SeekBar) dialog.findViewById(R.id.volume_right); + final SeekBar barRightVolume = dialog.findViewById(R.id.volume_right); barRightVolume.setProgress(UserPreferences.getRightVolumePercentage()); - final CheckBox stereoToMono = (CheckBox) dialog.findViewById(R.id.stereo_to_mono); + final CheckBox stereoToMono = dialog.findViewById(R.id.stereo_to_mono); stereoToMono.setChecked(UserPreferences.stereoToMono()); if (controller != null && !controller.canDownmix()) { stereoToMono.setEnabled(false); @@ -220,13 +162,4 @@ public class PlaybackControlsDialog extends DialogFragment { new Handler().postDelayed(this::setupAudioTracks, 500); }); } - - private float getCurrentSpeed() { - Playable media = null; - if (controller != null) { - media = controller.getMedia(); - } - - return PlaybackSpeedUtils.getCurrentPlaybackSpeed(media); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java index 3a6ba183f..ef8ed335d 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -1,97 +1,171 @@ package de.danoeh.antennapod.dialog; +import android.app.Dialog; import android.content.Context; -import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.chip.Chip; +import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.view.ItemOffsetDecoration; +import de.danoeh.antennapod.view.PlaybackSpeedSeekBar; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; -public class VariableSpeedDialog { +public class VariableSpeedDialog extends DialogFragment { + private SpeedSelectionAdapter adapter; + private final DecimalFormat speedFormat; + private PlaybackController controller; + private final List selectedSpeeds; + private PlaybackSpeedSeekBar speedSeekBar; + private Chip addCurrentSpeedChip; - private VariableSpeedDialog() { - } - - public static void showDialog(final Context context) { - if (UserPreferences.useSonic() - || UserPreferences.useExoplayer() - || Build.VERSION.SDK_INT >= 23) { - showSpeedSelectorDialog(context); - } else { - showGetPluginDialog(context, true); - } + public VariableSpeedDialog() { + DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US); + format.setDecimalSeparator('.'); + speedFormat = new DecimalFormat("0.00", format); + selectedSpeeds = new ArrayList<>(UserPreferences.getPlaybackSpeedArray()); } public static void showGetPluginDialog(final Context context) { - showGetPluginDialog(context, false); - } - - private static void showGetPluginDialog(final Context context, boolean showSpeedSelector) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.no_playback_plugin_title); builder.setMessage(R.string.no_playback_plugin_or_sonic_msg); builder.setPositiveButton(R.string.enable_sonic, (dialog, which) -> { UserPreferences.enableSonic(); - if (showSpeedSelector) { - showSpeedSelectorDialog(context); - } }); builder.setNeutralButton(R.string.close_label, null); builder.show(); } - private static void showSpeedSelectorDialog(final Context context) { - DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US); - format.setDecimalSeparator('.'); - DecimalFormat speedFormat = new DecimalFormat("0.00", format); + @Override + public void onStart() { + super.onStart(); + controller = new PlaybackController(getActivity()) { + @Override + public void setupGUI() { + updateSpeed(); + } - final String[] speedValues = context.getResources().getStringArray( - R.array.playback_speed_values); - // According to Java spec these get initialized to false on creation - final boolean[] speedChecked = new boolean[speedValues.length]; - - // Build the "isChecked" array so that multiChoice dialog is populated correctly - List selectedSpeedList = new ArrayList<>(); - float[] selectedSpeeds = UserPreferences.getPlaybackSpeedArray(); - for (float speed : selectedSpeeds) { - selectedSpeedList.add(speedFormat.format(speed)); - } - - for (int i = 0; i < speedValues.length; i++) { - speedChecked[i] = selectedSpeedList.contains(speedValues[i]); - } - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.set_playback_speed_label); - builder.setMultiChoiceItems(R.array.playback_speed_values, - speedChecked, (dialog, which, isChecked) -> speedChecked[which] = isChecked); - builder.setNegativeButton(android.R.string.cancel, null); - builder.setPositiveButton(android.R.string.ok, - (dialog, which) -> { - int choiceCount = 0; - for (boolean checked : speedChecked) { - if (checked) { - choiceCount++; - } - } - String[] newSpeedValues = new String[choiceCount]; - int newSpeedIndex = 0; - for (int i = 0; i < speedChecked.length; i++) { - if (speedChecked[i]) { - newSpeedValues[newSpeedIndex++] = speedValues[i]; - } - } - - UserPreferences.setPlaybackSpeedArray(newSpeedValues); - - }); - builder.create().show(); + @Override + public void onPlaybackSpeedChange() { + updateSpeed(); + } + }; + controller.init(); + speedSeekBar.setController(controller); } + private void updateSpeed() { + speedSeekBar.updateSpeed(); + addCurrentSpeedChip.setText(speedFormat.format(controller.getCurrentPlaybackSpeedMultiplier())); + } + + @Override + public void onStop() { + super.onStop(); + controller.release(); + controller = null; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setPositiveButton(R.string.close_label, null); + + View root = View.inflate(getContext(), R.layout.speed_select_dialog, null); + speedSeekBar = root.findViewById(R.id.speed_seek_bar); + RecyclerView selectedSpeedsGrid = root.findViewById(R.id.selected_speeds_grid); + selectedSpeedsGrid.setLayoutManager(new GridLayoutManager(getContext(), 3)); + selectedSpeedsGrid.addItemDecoration(new ItemOffsetDecoration(getContext(), 4)); + adapter = new SpeedSelectionAdapter(); + adapter.setHasStableIds(true); + selectedSpeedsGrid.setAdapter(adapter); + + addCurrentSpeedChip = root.findViewById(R.id.add_current_speed_chip); + addCurrentSpeedChip.setCloseIconVisible(true); + addCurrentSpeedChip.setCloseIconResource(R.drawable.ic_add_black); + addCurrentSpeedChip.setOnCloseIconClickListener(v -> addCurrentSpeed()); + addCurrentSpeedChip.setOnClickListener(v -> addCurrentSpeed()); + + builder.setView(root); + return builder.create(); + } + + private void addCurrentSpeed() { + float newSpeed = controller.getCurrentPlaybackSpeedMultiplier(); + if (selectedSpeeds.contains(newSpeed)) { + Snackbar.make(addCurrentSpeedChip, + getString(R.string.preset_already_exists, newSpeed), Snackbar.LENGTH_LONG).show(); + } else { + selectedSpeeds.add(newSpeed); + Collections.sort(selectedSpeeds); + UserPreferences.setPlaybackSpeedArray(selectedSpeeds); + adapter.notifyDataSetChanged(); + } + } + + public class SpeedSelectionAdapter extends RecyclerView.Adapter { + + @Override + @NonNull + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + Chip chip = new Chip(getContext()); + chip.setCloseIconVisible(true); + chip.setCloseIconResource(R.drawable.ic_delete_black); + return new ViewHolder(chip); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + float speed = selectedSpeeds.get(position); + + holder.chip.setText(speedFormat.format(speed)); + holder.chip.setOnCloseIconClickListener(v -> { + selectedSpeeds.remove(speed); + UserPreferences.setPlaybackSpeedArray(selectedSpeeds); + notifyDataSetChanged(); + }); + holder.chip.setOnClickListener(v -> { + if (controller != null) { + controller.setPlaybackSpeed(speed); + notifyDataSetChanged(); + } + }); + } + + @Override + public int getItemCount() { + return selectedSpeeds.size(); + } + + @Override + public long getItemId(int position) { + return selectedSpeeds.get(position).hashCode(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + Chip chip; + + ViewHolder(Chip itemView) { + super(itemView); + chip = itemView; + } + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java index 249d1e9f6..9c2030c2f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java @@ -13,7 +13,6 @@ import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.SeekBar; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -21,17 +20,8 @@ import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.widget.ViewPager2; - import com.google.android.material.bottomsheet.BottomSheetBehavior; - import com.google.android.material.snackbar.Snackbar; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.text.DecimalFormat; -import java.text.NumberFormat; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.CastEnabledActivity; import de.danoeh.antennapod.activity.MainActivity; @@ -40,7 +30,6 @@ import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.Converter; @@ -60,6 +49,13 @@ import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.List; /** * Shows the audio player. @@ -206,30 +202,29 @@ public class AudioPlayerFragment extends Fragment implements VariableSpeedDialog.showGetPluginDialog(getContext()); return; } - float[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); + List availableSpeeds = UserPreferences.getPlaybackSpeedArray(); float currentSpeed = controller.getCurrentPlaybackSpeedMultiplier(); int newSpeedIndex = 0; - while (newSpeedIndex < availableSpeeds.length && availableSpeeds[newSpeedIndex] < currentSpeed + EPSILON) { + while (newSpeedIndex < availableSpeeds.size() + && availableSpeeds.get(newSpeedIndex) < currentSpeed + EPSILON) { newSpeedIndex++; } float newSpeed; - if (availableSpeeds.length == 0) { + if (availableSpeeds.size() == 0) { newSpeed = 1.0f; - } else if (newSpeedIndex == availableSpeeds.length) { - newSpeed = availableSpeeds[0]; + } else if (newSpeedIndex == availableSpeeds.size()) { + newSpeed = availableSpeeds.get(0); } else { - newSpeed = availableSpeeds[newSpeedIndex]; + newSpeed = availableSpeeds.get(newSpeedIndex); } - PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(newSpeed); - UserPreferences.setPlaybackSpeed(newSpeed); controller.setPlaybackSpeed(newSpeed); loadMediaInfo(); }); butPlaybackSpeed.setOnLongClickListener(v -> { - VariableSpeedDialog.showDialog(getContext()); + new VariableSpeedDialog().show(getChildFragmentManager(), null); return true; }); butPlaybackSpeed.setVisibility(View.VISIBLE); @@ -494,7 +489,7 @@ public class AudioPlayerFragment extends Fragment implements new SleepTimerDialog().show(getChildFragmentManager(), "SleepTimerDialog"); return true; case R.id.audio_controls: - PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(false); + PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(); dialog.show(getChildFragmentManager(), "playback_controls"); return true; case R.id.open_feed_item: diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java index 18137f7f1..f07e59afe 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java @@ -45,7 +45,7 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { final Activity activity = getActivity(); findPreference(PREF_PLAYBACK_SPEED_LAUNCHER).setOnPreferenceClickListener(preference -> { - VariableSpeedDialog.showDialog(activity); + new VariableSpeedDialog().show(getChildFragmentManager(), null); return true; }); findPreference(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER).setOnPreferenceClickListener(preference -> { diff --git a/app/src/main/java/de/danoeh/antennapod/view/ItemOffsetDecoration.java b/app/src/main/java/de/danoeh/antennapod/view/ItemOffsetDecoration.java new file mode 100644 index 000000000..4a1267d81 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/ItemOffsetDecoration.java @@ -0,0 +1,24 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.graphics.Rect; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +/** + * Source: https://stackoverflow.com/a/30794046 + */ +public class ItemOffsetDecoration extends RecyclerView.ItemDecoration { + private final int itemOffset; + + public ItemOffsetDecoration(@NonNull Context context, int itemOffsetDp) { + itemOffset = (int) (itemOffsetDp * context.getResources().getDisplayMetrics().density); + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + outRect.set(itemOffset, itemOffset, itemOffset, itemOffset); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java new file mode 100644 index 000000000..47797e4a4 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java @@ -0,0 +1,86 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.SeekBar; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.util.Consumer; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.dialog.VariableSpeedDialog; + +public class PlaybackSpeedSeekBar extends FrameLayout { + private SeekBar seekBar; + private PlaybackController controller; + private Consumer progressChangedListener; + + public PlaybackSpeedSeekBar(@NonNull Context context) { + super(context); + setup(); + } + + public PlaybackSpeedSeekBar(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public PlaybackSpeedSeekBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + private void setup() { + View.inflate(getContext(), R.layout.playback_speed_seek_bar, this); + seekBar = findViewById(R.id.playback_speed); + findViewById(R.id.butDecSpeed).setOnClickListener(v -> seekBar.setProgress(seekBar.getProgress() - 2)); + findViewById(R.id.butIncSpeed).setOnClickListener(v -> seekBar.setProgress(seekBar.getProgress() + 2)); + + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (controller != null && controller.canSetPlaybackSpeed()) { + float playbackSpeed = (progress + 10) / 20.0f; + controller.setPlaybackSpeed(playbackSpeed); + + if (progressChangedListener != null) { + progressChangedListener.accept(playbackSpeed); + } + } else if (fromUser) { + seekBar.post(() -> updateSpeed()); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (controller != null && !controller.canSetPlaybackSpeed()) { + VariableSpeedDialog.showGetPluginDialog(getContext()); + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + } + + public void updateSpeed() { + if (controller != null) { + seekBar.setProgress(Math.round((20 * controller.getCurrentPlaybackSpeedMultiplier()) - 10)); + } + } + + public void setController(PlaybackController controller) { + this.controller = controller; + updateSpeed(); + if (progressChangedListener != null && controller != null) { + progressChangedListener.accept(controller.getCurrentPlaybackSpeedMultiplier()); + } + } + + public void setProgressChangedListener(Consumer progressChangedListener) { + this.progressChangedListener = progressChangedListener; + } +} diff --git a/app/src/main/res/layout/audio_controls.xml b/app/src/main/res/layout/audio_controls.xml index aa1bdf236..2c9665aad 100644 --- a/app/src/main/res/layout/audio_controls.xml +++ b/app/src/main/res/layout/audio_controls.xml @@ -1,5 +1,6 @@ - - - - - - - - - + android:layout_height="wrap_content"/> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/speed_select_dialog.xml b/app/src/main/res/layout/speed_select_dialog.xml new file mode 100644 index 000000000..e4d78c3fa --- /dev/null +++ b/app/src/main/res/layout/speed_select_dialog.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_playback.xml b/app/src/main/res/xml/preferences_playback.xml index 32bf383d7..f8afa08ed 100644 --- a/app/src/main/res/xml/preferences_playback.xml +++ b/app/src/main/res/xml/preferences_playback.xml @@ -67,7 +67,7 @@ + android:title="@string/playback_speed"/> getPlaybackSpeedArray() { return readPlaybackSpeedArray(prefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null)); } @@ -662,10 +666,13 @@ public class UserPreferences { .apply(); } - public static void setPlaybackSpeedArray(String[] speeds) { + public static void setPlaybackSpeedArray(List speeds) { + DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US); + format.setDecimalSeparator('.'); + DecimalFormat speedFormat = new DecimalFormat("0.00", format); JSONArray jsonArray = new JSONArray(); - for (String speed : speeds) { - jsonArray.put(speed); + for (float speed : speeds) { + jsonArray.put(speedFormat.format(speed)); } prefs.edit() .putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString()) @@ -775,13 +782,13 @@ public class UserPreferences { } } - private static float[] readPlaybackSpeedArray(String valueFromPrefs) { + private static List readPlaybackSpeedArray(String valueFromPrefs) { if (valueFromPrefs != null) { try { JSONArray jsonArray = new JSONArray(valueFromPrefs); - float[] selectedSpeeds = new float[jsonArray.length()]; + List selectedSpeeds = new ArrayList<>(); for (int i = 0; i < jsonArray.length(); i++) { - selectedSpeeds[i] = (float) jsonArray.getDouble(i); + selectedSpeeds.add((float) jsonArray.getDouble(i)); } return selectedSpeeds; } catch (JSONException e) { @@ -790,7 +797,7 @@ public class UserPreferences { } } // If this preference hasn't been set yet, return the default options - return new float[] { 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f }; + return Arrays.asList(0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f); } public static String getMediaPlayer() { diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index 44f5f9b11..d47d26af9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -599,6 +599,13 @@ public class PlaybackController { } public void setPlaybackSpeed(float speed) { + PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(speed); + if (getMedia() != null && getMedia().getMediaType() == MediaType.VIDEO) { + UserPreferences.setVideoPlaybackSpeed(speed); + } else { + UserPreferences.setPlaybackSpeed(speed); + } + if (playbackService != null) { playbackService.setSpeed(speed); } else { diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index b5b327a90..431133aff 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -311,8 +311,9 @@ Download Plugin Plugin Not Installed For variable speed playback to work, we recommend to enable the built-in Sonic mediaplayer. - Playback Speeds Enable Sonic + Presets + %1$.2fx is already saved as a preset. No queued episodes @@ -441,8 +442,7 @@ %1$s with device %2$s]]> Show sync error notifications This setting does not apply to authentication errors. - Playback Speeds - Customize the speeds available for variable speed audio playback + Customize the speeds available for variable speed playback The speed to use when starting audio playback for episodes in this podcast Auto Skip Skip introductions and ending credits.