Merge pull request #4175 from ByteHamster/playback-speed-selection

New playback speed dialog
This commit is contained in:
H. Lehmann 2020-07-16 17:18:33 +02:00 committed by GitHub
commit dea32051f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 403 additions and 230 deletions

View File

@ -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

View File

@ -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<FeedItem> 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));

View File

@ -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:

View File

@ -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);
}
}

View File

@ -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<Float> 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<String> 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<SpeedSelectionAdapter.ViewHolder> {
@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;
}
}
}
}

View File

@ -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<Float> 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:

View File

@ -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 -> {

View File

@ -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);
}
}

View File

@ -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<Float> 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<Float> progressChangedListener) {
this.progressChangedListener = progressChangedListener;
}
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -38,47 +39,10 @@
</LinearLayout>
<LinearLayout
<de.danoeh.antennapod.view.PlaybackSpeedSeekBar
android:id="@+id/speed_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/butDecSpeed"
android:layout_width="32dp"
android:layout_height="32dp"
android:gravity="center"
android:text="-"
android:clickable="true"
android:focusable="true"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="?attr/colorSecondary"
android:contentDescription="@string/decrease_speed"
android:background="?attr/selectableItemBackgroundBorderless"/>
<SeekBar
android:id="@+id/playback_speed"
android:layout_width="0dp"
android:layout_height="32dp"
android:max="70"
android:layout_weight="1" />
<TextView
android:id="@+id/butIncSpeed"
android:layout_width="32dp"
android:layout_height="32dp"
android:gravity="center"
android:text="+"
android:clickable="true"
android:focusable="true"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="?attr/colorSecondary"
android:contentDescription="@string/increase_speed"
android:background="?attr/selectableItemBackgroundBorderless" />
</LinearLayout>
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"

View File

@ -184,7 +184,7 @@
android:layout_toStartOf="@id/butRev"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/set_playback_speed_label"
android:contentDescription="@string/playback_speed"
tools:srcCompat="@drawable/ic_playback_speed_white"/>
<TextView

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/butDecSpeed"
android:layout_width="32dp"
android:layout_height="32dp"
android:gravity="center"
android:text="-"
android:clickable="true"
android:focusable="true"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="?attr/colorSecondary"
android:contentDescription="@string/decrease_speed"
android:background="?attr/selectableItemBackgroundBorderless"/>
<SeekBar
android:id="@+id/playback_speed"
android:layout_width="0dp"
android:layout_height="32dp"
android:max="70"
android:layout_weight="1" />
<TextView
android:id="@+id/butIncSpeed"
android:layout_width="32dp"
android:layout_height="32dp"
android:gravity="center"
android:text="+"
android:clickable="true"
android:focusable="true"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="?attr/colorSecondary"
android:contentDescription="@string/increase_speed"
android:background="?attr/selectableItemBackgroundBorderless" />
</LinearLayout>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="@string/playback_speed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"/>
<com.google.android.material.chip.Chip
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/add_current_speed_chip"/>
</LinearLayout>
<de.danoeh.antennapod.view.PlaybackSpeedSeekBar
android:id="@+id/speed_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp">
</de.danoeh.antennapod.view.PlaybackSpeedSeekBar>
<TextView
android:text="@string/speed_presets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
android:layout_marginBottom="8dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/selected_speeds_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -67,7 +67,7 @@
<Preference
android:key="prefPlaybackSpeedLauncher"
android:summary="@string/pref_playback_speed_sum"
android:title="@string/pref_playback_speed_title"/>
android:title="@string/playback_speed"/>
<SwitchPreference
android:defaultValue="false"
android:key="prefPlaybackTimeRespectsSpeed"

View File

@ -19,10 +19,14 @@ import org.json.JSONException;
import java.io.File;
import java.io.IOException;
import java.net.Proxy;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@ -414,7 +418,7 @@ public class UserPreferences {
return prefs.getBoolean(PREF_PLAYBACK_SKIP_SILENCE, false);
}
public static float[] getPlaybackSpeedArray() {
public static List<Float> 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<Float> 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<Float> readPlaybackSpeedArray(String valueFromPrefs) {
if (valueFromPrefs != null) {
try {
JSONArray jsonArray = new JSONArray(valueFromPrefs);
float[] selectedSpeeds = new float[jsonArray.length()];
List<Float> 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() {

View File

@ -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 {

View File

@ -311,8 +311,9 @@
<string name="download_plugin_label">Download Plugin</string>
<string name="no_playback_plugin_title">Plugin Not Installed</string>
<string name="no_playback_plugin_or_sonic_msg">For variable speed playback to work, we recommend to enable the built-in Sonic mediaplayer.</string>
<string name="set_playback_speed_label">Playback Speeds</string>
<string name="enable_sonic">Enable Sonic</string>
<string name="speed_presets">Presets</string>
<string name="preset_already_exists">%1$.2fx is already saved as a preset.</string>
<!-- Empty list labels -->
<string name="no_items_header_label">No queued episodes</string>
@ -441,8 +442,7 @@
<string name="pref_gpodnet_login_status"><![CDATA[Logged in as <i>%1$s</i> with device <i>%2$s</i>]]></string>
<string name="pref_gpodnet_notifications_title">Show sync error notifications</string>
<string name="pref_gpodnet_notifications_sum">This setting does not apply to authentication errors.</string>
<string name="pref_playback_speed_title">Playback Speeds</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed audio playback</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed playback</string>
<string name="pref_feed_playback_speed_sum">The speed to use when starting audio playback for episodes in this podcast</string>
<string name="pref_feed_skip">Auto Skip</string>
<string name="pref_feed_skip_sum">Skip introductions and ending credits.</string>