diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 1a55c21c3..b72b7ea7b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -9,10 +9,10 @@ import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.CheckBox; -import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -20,104 +20,88 @@ import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; -import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; +import java.util.Objects; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; + +import icepick.Icepick; +import icepick.State; + public class PlaybackParameterDialog extends DialogFragment { + private static final String TAG = "PlaybackParameterDialog"; + // Minimum allowable range in ExoPlayer private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; - private static final char STEP_UP_SIGN = '+'; - private static final char STEP_DOWN_SIGN = '-'; - - private static final double STEP_ONE_PERCENT_VALUE = 0.01f; - private static final double STEP_FIVE_PERCENT_VALUE = 0.05f; - private static final double STEP_TEN_PERCENT_VALUE = 0.10f; - private static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f; - private static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f; + private static final double STEP_1_PERCENT_VALUE = 0.01f; + private static final double STEP_5_PERCENT_VALUE = 0.05f; + private static final double STEP_10_PERCENT_VALUE = 0.10f; + private static final double STEP_25_PERCENT_VALUE = 0.25f; + private static final double STEP_100_PERCENT_VALUE = 1.00f; private static final double DEFAULT_TEMPO = 1.00f; private static final double DEFAULT_PITCH = 1.00f; - private static final int DEFAULT_SEMITONES = 0; - private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; + private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; - @NonNull - private static final String TAG = "PlaybackParameterDialog"; - @NonNull - private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; - @NonNull - private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; - - @NonNull - private static final String TEMPO_KEY = "tempo_key"; - @NonNull - private static final String PITCH_KEY = "pitch_key"; - @NonNull - private static final String STEP_SIZE_KEY = "step_size_key"; - - @NonNull - private final SliderStrategy strategy = new SliderStrategy.Quadratic( - MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE, - /*centerAt=*/1.00f, /*sliderGranularity=*/10000); + private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( + MINIMUM_PLAYBACK_VALUE, + MAXIMUM_PLAYBACK_VALUE, + 1.00f, + 10_000); @Nullable private Callback callback; - private double initialTempo = DEFAULT_TEMPO; - private double initialPitch = DEFAULT_PITCH; - private int initialSemitones = DEFAULT_SEMITONES; - private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; - private double tempo = DEFAULT_TEMPO; - private double pitch = DEFAULT_PITCH; - private int semitones = DEFAULT_SEMITONES; + @State + double initialTempo = DEFAULT_TEMPO; + @State + double initialPitch = DEFAULT_PITCH; + @State + boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; + + @State + double tempo = DEFAULT_TEMPO; + @State + double pitch = DEFAULT_PITCH; + @State + double stepSize = DEFAULT_STEP; + @State + boolean skipSilence = DEFAULT_SKIP_SILENCE; - @Nullable private SeekBar tempoSlider; - @Nullable private TextView tempoCurrentText; - @Nullable private TextView tempoStepDownText; - @Nullable private TextView tempoStepUpText; - @Nullable - private SeekBar pitchSlider; - @Nullable - private TextView pitchCurrentText; - @Nullable - private TextView pitchStepDownText; - @Nullable - private TextView pitchStepUpText; - @Nullable - private SeekBar semitoneSlider; - @Nullable - private TextView semitoneCurrentText; - @Nullable - private TextView semitoneStepDownText; - @Nullable - private TextView semitoneStepUpText; - @Nullable - private CheckBox unhookingCheckbox; - @Nullable - private CheckBox skipSilenceCheckbox; - @Nullable - private CheckBox adjustBySemitonesCheckbox; - public static PlaybackParameterDialog newInstance(final double playbackTempo, - final double playbackPitch, - final boolean playbackSkipSilence, - final Callback callback) { + private SeekBar pitchSlider; + private TextView pitchCurrentText; + private TextView pitchStepDownText; + private TextView pitchStepUpText; + + private CheckBox unhookingCheckbox; + private CheckBox skipSilenceCheckbox; + + public static PlaybackParameterDialog newInstance( + final double playbackTempo, + final double playbackPitch, + final boolean playbackSkipSilence, + final Callback callback + ) { final PlaybackParameterDialog dialog = new PlaybackParameterDialog(); dialog.callback = callback; + dialog.initialTempo = playbackTempo; dialog.initialPitch = playbackPitch; - - dialog.tempo = playbackTempo; - dialog.pitch = playbackPitch; - dialog.semitones = dialog.percentToSemitones(playbackPitch); - dialog.initialSkipSilence = playbackSkipSilence; + + dialog.tempo = dialog.initialTempo; + dialog.pitch = dialog.initialPitch; + dialog.skipSilence = dialog.initialSkipSilence; + return dialog; } @@ -126,7 +110,7 @@ public class PlaybackParameterDialog extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(@NonNull final Context context) { + public void onAttach(final Context context) { super.onAttach(context); if (context instanceof Callback) { callback = (Callback) context; @@ -136,28 +120,9 @@ public class PlaybackParameterDialog extends DialogFragment { } @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - assureCorrectAppLanguage(getContext()); - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); - initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH); - initialSemitones = percentToSemitones(initialPitch); - - tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO); - pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH); - semitones = percentToSemitones(pitch); - } - } - - @Override - public void onSaveInstanceState(@NonNull final Bundle outState) { + public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); - outState.putDouble(INITIAL_TEMPO_KEY, initialTempo); - outState.putDouble(INITIAL_PITCH_KEY, initialPitch); - - outState.putDouble(TEMPO_KEY, getCurrentTempo()); - outState.putDouble(PITCH_KEY, getCurrentPitch()); + Icepick.saveInstanceState(this, outState); } /*////////////////////////////////////////////////////////////////////////// @@ -168,20 +133,28 @@ public class PlaybackParameterDialog extends DialogFragment { @Override public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); + Icepick.restoreInstanceState(this, savedInstanceState); + final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - setupControlViews(view); + initUI(view); + initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setView(view) .setCancelable(true) - .setNegativeButton(R.string.cancel, (dialogInterface, i) -> - setPlaybackParameters(initialTempo, initialPitch, - initialSemitones, initialSkipSilence)) - .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> - setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, - DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE)) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - setCurrentPlaybackParameters()); + .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { + setAndUpdateTempo(initialTempo); + setAndUpdatePitch(initialPitch); + setAndUpdateSkipSilence(initialSkipSilence); + updateCallback(); + }) + .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> { + setAndUpdateTempo(DEFAULT_TEMPO); + setAndUpdatePitch(DEFAULT_PITCH); + setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE); + updateCallback(); + }) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback()); return dialogBuilder.create(); } @@ -190,353 +163,171 @@ public class PlaybackParameterDialog extends DialogFragment { // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void setupControlViews(@NonNull final View rootView) { - setupHookingControl(rootView); - setupSkipSilenceControl(rootView); - setupAdjustBySemitonesControl(rootView); + private void initUI(@NonNull final View rootView) { + // Tempo + tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar)); + tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText)); + tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp)); + tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown)); - setupTempoControl(rootView); - setupPitchControl(rootView); - setupSemitoneControl(rootView); + setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); + setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); - togglePitchSliderType(rootView); + // Pitch + pitchSlider = Objects.requireNonNull(rootView.findViewById(R.id.pitchSeekbar)); + pitchCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.pitchCurrentText)); + pitchStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepUp)); + pitchStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepDown)); - setupStepSizeSelector(rootView); + setText(rootView, R.id.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); + setText(rootView, R.id.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + + // Steps + setupStepTextView(rootView, R.id.stepSizeOnePercent, STEP_1_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeFivePercent, STEP_5_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeTenPercent, STEP_10_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); + + // Bottom controls + unhookingCheckbox = + Objects.requireNonNull(rootView.findViewById(R.id.unhookCheckbox)); + skipSilenceCheckbox = + Objects.requireNonNull(rootView.findViewById(R.id.skipSilenceCheckbox)); } - private void togglePitchSliderType(@NonNull final View rootView) { - final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl); - final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl); - - final View separatorStepSizeSelector = - rootView.findViewById(R.id.separatorStepSizeSelector); - final RelativeLayout.LayoutParams params = - (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams(); - if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) { - if (getCurrentAdjustBySemitones()) { - // replaces pitchControl slider with semitoneControl slider - pitchControl.setVisibility(View.GONE); - semitoneControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.semitoneControl); - - // forces unhook for semitones - unhookingCheckbox.setChecked(true); - unhookingCheckbox.setEnabled(false); - - setupTempoStepSizeSelector(rootView); - } else { - semitoneControl.setVisibility(View.GONE); - pitchControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.pitchControl); - - // (re)enables hooking selection - unhookingCheckbox.setEnabled(true); - setupCombinedStepSizeSelector(rootView); - } - } + private TextView setText( + final TextView textView, + final DoubleFunction formatter, + final double value + ) { + Objects.requireNonNull(textView).setText(formatter.apply(value)); + return textView; } - private void setupTempoControl(@NonNull final View rootView) { - tempoSlider = rootView.findViewById(R.id.tempoSeekbar); - final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); - final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); - tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText); - tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); - tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); - - if (tempoCurrentText != null) { - tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); - } - if (tempoMaximumText != null) { - tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); - } - if (tempoMinimumText != null) { - tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); - } - - if (tempoSlider != null) { - tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - tempoSlider.setProgress(strategy.progressOf(tempo)); - tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener()); - } + private TextView setText( + final View rootView, + @IdRes final int idRes, + final DoubleFunction formatter, + final double value + ) { + final TextView textView = rootView.findViewById(idRes); + setText(textView, formatter, value); + return textView; } - private void setupPitchControl(@NonNull final View rootView) { - pitchSlider = rootView.findViewById(R.id.pitchSeekbar); - final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); - final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); - pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText); - pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); - pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); - - if (pitchCurrentText != null) { - pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); - } - if (pitchMaximumText != null) { - pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); - } - if (pitchMinimumText != null) { - pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); - } - - if (pitchSlider != null) { - pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - pitchSlider.setProgress(strategy.progressOf(pitch)); - pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); - } + private void setupStepTextView( + final View rootView, + @IdRes final int idRes, + final double stepSizeValue + ) { + setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue) + .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } - private void setupSemitoneControl(@NonNull final View rootView) { - semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar); - semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText); - semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown); - semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp); + private void initUIData() { + // Tempo + tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdateTempo(tempo); + tempoSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); - if (semitoneCurrentText != null) { - semitoneCurrentText.setText(getSignedSemitonesString(semitones)); - } + registerOnStepClickListener( + tempoStepDownText, tempo, -1, this::onTempoSliderUpdated); + registerOnStepClickListener( + tempoStepUpText, tempo, 1, this::onTempoSliderUpdated); - if (semitoneSlider != null) { - setSemitoneSlider(semitones); - semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener()); - } + // Pitch + pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdatePitch(pitch); + pitchSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); - } + registerOnStepClickListener( + pitchStepDownText, pitch, -1, this::onPitchSliderUpdated); + registerOnStepClickListener( + pitchStepUpText, pitch, 1, this::onPitchSliderUpdated); - private void setupHookingControl(@NonNull final View rootView) { - unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); - if (unhookingCheckbox != null) { - // restores whether pitch and tempo are unhooked or not - unhookingCheckbox.setChecked(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_unhook_key), true)); + // Steps + setAndUpdateStepSize(stepSize); - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - // saves whether pitch and tempo are unhooked or not - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putBoolean(getString(R.string.playback_unhook_key), isChecked) - .apply(); - - if (!isChecked) { - // when unchecked, slides back to the minimum of current tempo or pitch - final double minimum = Math.min(getCurrentPitch(), getCurrentTempo()); - setSliders(minimum); - setCurrentPlaybackParameters(); - } - }); - } - } - - private void setupSkipSilenceControl(@NonNull final View rootView) { - skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox); - if (skipSilenceCheckbox != null) { - skipSilenceCheckbox.setChecked(initialSkipSilence); - skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> - setCurrentPlaybackParameters()); - } - } - - private void setupAdjustBySemitonesControl(@NonNull final View rootView) { - adjustBySemitonesCheckbox = rootView.findViewById(R.id.adjustBySemitonesCheckbox); - if (adjustBySemitonesCheckbox != null) { - // restores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setChecked(PreferenceManager + // Bottom controls + // restore whether pitch and tempo are unhooked or not + unhookingCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_adjust_by_semitones_key), true)); + .getBoolean(getString(R.string.playback_unhook_key), true)); - // stores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - PreferenceManager.getDefaultSharedPreferences(requireContext()) + unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + // save whether pitch and tempo are unhooked or not + PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() - .putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked) + .putBoolean(getString(R.string.playback_unhook_key), isChecked) .apply(); - togglePitchSliderType(rootView); - if (isChecked) { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch()) - )), - getCurrentSkipSilence() - ); - setSemitoneSlider(Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch())) - )); - } else { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - setPitchSlider(semitonesToPercent(getCurrentSemitones())); - } - }); - } + + if (!isChecked) { + // when unchecked, slide back to the minimum of current tempo or pitch + setSliders(Math.min(pitch, tempo)); + } + }); + + setAndUpdateSkipSilence(skipSilence); + skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + skipSilence = isChecked; + updateCallback(); + }); } - private void setupStepSizeSelector(@NonNull final View rootView) { - setStepSize(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP)); - - final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); - final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); - final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); - final TextView stepSizeTwentyFivePercentText = rootView - .findViewById(R.id.stepSizeTwentyFivePercent); - final TextView stepSizeOneHundredPercentText = rootView - .findViewById(R.id.stepSizeOneHundredPercent); - - if (stepSizeOnePercentText != null) { - stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); - stepSizeOnePercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE)); - } - - if (stepSizeFivePercentText != null) { - stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE)); - stepSizeFivePercentText - .setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE)); - } - - if (stepSizeTenPercentText != null) { - stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE)); - stepSizeTenPercentText - .setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE)); - } - - if (stepSizeTwentyFivePercentText != null) { - stepSizeTwentyFivePercentText - .setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); - stepSizeTwentyFivePercentText - .setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); - } - - if (stepSizeOneHundredPercentText != null) { - stepSizeOneHundredPercentText - .setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); - stepSizeOneHundredPercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); - } + private void registerOnStepClickListener( + final TextView stepTextView, + final double currentValue, + final double direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> + newValueConsumer.accept(currentValue * direction) + ); } - private void setupTempoStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_tempo_step); - } - setupStepSizeSelector(rootView); + private void setAndUpdateStepSize(final double newStepSize) { + this.stepSize = newStepSize; + + tempoStepUpText.setText(getStepUpPercentString(newStepSize)); + tempoStepDownText.setText(getStepDownPercentString(newStepSize)); + + pitchStepUpText.setText(getStepUpPercentString(newStepSize)); + pitchStepDownText.setText(getStepDownPercentString(newStepSize)); } - private void setupCombinedStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_step); - } - setupStepSizeSelector(rootView); - } - - private void setStepSize(final double stepSize) { - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putFloat(getString(R.string.adjustment_step_key), (float) stepSize) - .apply(); - - if (tempoStepUpText != null) { - tempoStepUpText.setText(getStepUpPercentString(stepSize)); - tempoStepUpText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() + stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (tempoStepDownText != null) { - tempoStepDownText.setText(getStepDownPercentString(stepSize)); - tempoStepDownText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() - stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (pitchStepUpText != null) { - pitchStepUpText.setText(getStepUpPercentString(stepSize)); - pitchStepUpText.setOnClickListener(view -> { - onPitchSliderUpdated(getCurrentPitch() + stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (pitchStepDownText != null) { - pitchStepDownText.setText(getStepDownPercentString(stepSize)); - pitchStepDownText.setOnClickListener(view -> { - onPitchSliderUpdated(getCurrentPitch() - stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (semitoneStepDownText != null) { - semitoneStepDownText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() - 1); - setCurrentPlaybackParameters(); - }); - } - - if (semitoneStepUpText != null) { - semitoneStepUpText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() + 1); - setCurrentPlaybackParameters(); - }); - } + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { + this.skipSilence = newSkipSilence; + skipSilenceCheckbox.setChecked(newSkipSilence); } /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ - private SimpleOnSeekBarChangeListener getOnTempoChangedListener() { - return new SimpleOnSeekBarChangeListener() { + private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( + final DoubleConsumer newValueConsumer + ) { + return new SeekBar.OnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - final double currentTempo = strategy.valueOf(progress); - if (fromUser) { - onTempoSliderUpdated(currentTempo); - setCurrentPlaybackParameters(); + if (fromUser) { // this change is first in chain + newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress)); + updateCallback(); } } - }; - } - private SimpleOnSeekBarChangeListener getOnPitchChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - final double currentPitch = strategy.valueOf(progress); - if (fromUser) { // this change is first in chain - onPitchSliderUpdated(currentPitch); - setCurrentPlaybackParameters(); - } + public void onStartTrackingTouch(final SeekBar seekBar) { + // Do nothing } - }; - } - private SimpleOnSeekBarChangeListener getOnSemitoneChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - // semitone slider supplies values 0 to 24, subtraction by 12 is required - final int currentSemitones = progress - 12; - if (fromUser) { // this change is first in chain - onSemitoneSliderUpdated(currentSemitones); - // line below also saves semitones as pitch percentages - onPitchSliderUpdated(semitonesToPercent(currentSemitones)); - setCurrentPlaybackParameters(); - } + public void onStopTrackingTouch(final SeekBar seekBar) { + // Do nothing } }; } @@ -545,7 +336,7 @@ public class PlaybackParameterDialog extends DialogFragment { if (!unhookingCheckbox.isChecked()) { setSliders(newTempo); } else { - setTempoSlider(newTempo); + setAndUpdateTempo(newTempo); } } @@ -553,109 +344,53 @@ public class PlaybackParameterDialog extends DialogFragment { if (!unhookingCheckbox.isChecked()) { setSliders(newPitch); } else { - setPitchSlider(newPitch); + setAndUpdatePitch(newPitch); } } - private void onSemitoneSliderUpdated(final int newSemitone) { - setSemitoneSlider(newSemitone); - } - private void setSliders(final double newValue) { - setTempoSlider(newValue); - setPitchSlider(newValue); + setAndUpdateTempo(newValue); + setAndUpdatePitch(newValue); } - private void setTempoSlider(final double newTempo) { - if (tempoSlider == null) { - return; - } - tempoSlider.setProgress(strategy.progressOf(newTempo)); + private void setAndUpdateTempo(final double newTempo) { + this.tempo = newTempo; + tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); + setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo); } - private void setPitchSlider(final double newPitch) { - if (pitchSlider == null) { - return; - } - pitchSlider.setProgress(strategy.progressOf(newPitch)); - } - - private void setSemitoneSlider(final int newSemitone) { - if (semitoneSlider == null) { - return; - } - semitoneSlider.setProgress(newSemitone + 12); + private void setAndUpdatePitch(final double newPitch) { + this.pitch = newPitch; + pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); + setText(pitchCurrentText, PlayerHelper::formatPitch, pitch); } /*////////////////////////////////////////////////////////////////////////// // Helper //////////////////////////////////////////////////////////////////////////*/ - private void setCurrentPlaybackParameters() { - if (getCurrentAdjustBySemitones()) { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - } else { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - percentToSemitones(getCurrentPitch()), - getCurrentSkipSilence() + private void updateCallback() { + if (callback == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "Updating callback: " + + "tempo = [" + tempo + "], " + + "pitch = [" + pitch + "], " + + "skipSilence = [" + skipSilence + "]" ); } - } - - private void setPlaybackParameters(final double newTempo, final double newPitch, - final int newSemitones, final boolean skipSilence) { - if (callback != null && tempoCurrentText != null - && pitchCurrentText != null && semitoneCurrentText != null) { - if (DEBUG) { - Log.d(TAG, "Setting playback parameters to " - + "tempo=[" + newTempo + "], " - + "pitch=[" + newPitch + "], " - + "semitones=[" + newSemitones + "]"); - } - - tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo)); - pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch)); - semitoneCurrentText.setText(getSignedSemitonesString(newSemitones)); - callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence); - } - } - - private double getCurrentTempo() { - return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress()); - } - - private double getCurrentPitch() { - return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress()); - } - - private int getCurrentSemitones() { - // semitoneSlider is absolute, that's why - 12 - return semitoneSlider == null ? semitones : semitoneSlider.getProgress() - 12; - } - - private boolean getCurrentSkipSilence() { - return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); - } - - private boolean getCurrentAdjustBySemitones() { - return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked(); + callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); } @NonNull private static String getStepUpPercentString(final double percent) { - return STEP_UP_SIGN + getPercentString(percent); + return '+' + getPercentString(percent); } @NonNull private static String getStepDownPercentString(final double percent) { - return STEP_DOWN_SIGN + getPercentString(percent); + return '-' + getPercentString(percent); } @NonNull @@ -663,21 +398,8 @@ public class PlaybackParameterDialog extends DialogFragment { return PlayerHelper.formatPitch(percent); } - @NonNull - private static String getSignedSemitonesString(final int semitones) { - return semitones > 0 ? "+" + semitones : "" + semitones; - } - public interface Callback { void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, boolean playbackSkipSilence); } - - public double semitonesToPercent(final int inSemitones) { - return Math.pow(2, inSemitones / 12.0); - } - - public int percentToSemitones(final double inPercent) { - return (int) Math.round(12 * Math.log(inPercent) / Math.log(2)); - } }