Major refactoring of PlaybackParameterDialog

* Removed/Renamed methods
* Use ``IcePick``
* Better structuring
* Keep skipSilence when rotating the device (PlayQueueActivity only)
This commit is contained in:
litetex 2022-01-01 19:02:03 +01:00
parent 6e81f2430b
commit 652d50173e

View file

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