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
1 changed files with 224 additions and 502 deletions

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;
final double playbackPitch, private TextView pitchCurrentText;
final boolean playbackSkipSilence, private TextView pitchStepDownText;
final Callback callback) { 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(); 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) {
tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
tempoSlider.setProgress(strategy.progressOf(tempo));
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
}
} }
private void setupPitchControl(@NonNull final View rootView) { private void setupStepTextView(
pitchSlider = rootView.findViewById(R.id.pitchSeekbar); final View rootView,
final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); @IdRes final int idRes,
final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); final double stepSizeValue
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText); ) {
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue)
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue));
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 setupSemitoneControl(@NonNull final View rootView) { private void initUIData() {
semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar); // Tempo
semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText); tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE));
semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown); setAndUpdateTempo(tempo);
semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp); tempoSlider.setOnSeekBarChangeListener(
getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated));
if (semitoneCurrentText != null) { registerOnStepClickListener(
semitoneCurrentText.setText(getSignedSemitonesString(semitones)); tempoStepDownText, tempo, -1, this::onTempoSliderUpdated);
} registerOnStepClickListener(
tempoStepUpText, tempo, 1, this::onTempoSliderUpdated);
if (semitoneSlider != null) { // Pitch
setSemitoneSlider(semitones); pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE));
semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener()); 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) { // Steps
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); setAndUpdateStepSize(stepSize);
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));
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // Bottom controls
// saves whether pitch and tempo are unhooked or not // restore whether pitch and tempo are unhooked or not
PreferenceManager.getDefaultSharedPreferences(requireContext()) unhookingCheckbox.setChecked(PreferenceManager
.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
.getDefaultSharedPreferences(requireContext()) .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 unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not
PreferenceManager.getDefaultSharedPreferences(requireContext()) PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit() .edit()
.putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked) .putBoolean(getString(R.string.playback_unhook_key), isChecked)
.apply(); .apply();
togglePitchSliderType(rootView);
if (isChecked) { if (!isChecked) {
setPlaybackParameters( // when unchecked, slide back to the minimum of current tempo or pitch
getCurrentTempo(), setSliders(Math.min(pitch, tempo));
getCurrentPitch(), }
Integer.min(12, });
Integer.max(-12, percentToSemitones(getCurrentPitch())
)), setAndUpdateSkipSilence(skipSilence);
getCurrentSkipSilence() skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
); skipSilence = isChecked;
setSemitoneSlider(Integer.min(12, updateCallback();
Integer.max(-12, percentToSemitones(getCurrentPitch())) });
));
} else {
setPlaybackParameters(
getCurrentTempo(),
semitonesToPercent(getCurrentSemitones()),
getCurrentSemitones(),
getCurrentSkipSilence()
);
setPitchSlider(semitonesToPercent(getCurrentSemitones()));
}
});
}
} }
private void setupStepSizeSelector(@NonNull final View rootView) { private void registerOnStepClickListener(
setStepSize(PreferenceManager final TextView stepTextView,
.getDefaultSharedPreferences(requireContext()) final double currentValue,
.getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP)); final double direction, // -1 for step down, +1 for step up
final DoubleConsumer newValueConsumer
final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); ) {
final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); stepTextView.setOnClickListener(view ->
final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); newValueConsumer.accept(currentValue * direction)
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 setupTempoStepSizeSelector(@NonNull final View rootView) { private void setAndUpdateStepSize(final double newStepSize) {
final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); this.stepSize = newStepSize;
if (playbackStepTypeText != null) {
playbackStepTypeText.setText(R.string.playback_tempo_step); tempoStepUpText.setText(getStepUpPercentString(newStepSize));
} tempoStepDownText.setText(getStepDownPercentString(newStepSize));
setupStepSizeSelector(rootView);
pitchStepUpText.setText(getStepUpPercentString(newStepSize));
pitchStepDownText.setText(getStepDownPercentString(newStepSize));
} }
private void setupCombinedStepSizeSelector(@NonNull final View rootView) { private void setAndUpdateSkipSilence(final boolean newSkipSilence) {
final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); this.skipSilence = newSkipSilence;
if (playbackStepTypeText != null) { skipSilenceCheckbox.setChecked(newSkipSilence);
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()), if (DEBUG) {
getCurrentSemitones(), Log.d(TAG, "Updating callback: "
getCurrentSkipSilence() + "tempo = [" + tempo + "], "
); + "pitch = [" + pitch + "], "
} else { + "skipSilence = [" + skipSilence + "]"
setPlaybackParameters(
getCurrentTempo(),
getCurrentPitch(),
percentToSemitones(getCurrentPitch()),
getCurrentSkipSilence()
); );
} }
} callback.onPlaybackParameterChanged((float) tempo, (float) pitch, 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();
} }
@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));
}
} }