2018-03-21 08:11:54 +01:00
|
|
|
package org.schabi.newpipe.player.helper;
|
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
|
2021-11-04 19:47:08 +01:00
|
|
|
import static org.schabi.newpipe.player.Player.DEBUG;
|
|
|
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
2022-03-17 18:34:44 +01:00
|
|
|
import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable;
|
2021-11-04 19:47:08 +01:00
|
|
|
|
2018-03-21 08:11:54 +01:00
|
|
|
import android.app.Dialog;
|
|
|
|
import android.content.Context;
|
2022-03-04 21:37:11 +01:00
|
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
import android.graphics.drawable.LayerDrawable;
|
2018-03-21 08:11:54 +01:00
|
|
|
import android.os.Bundle;
|
|
|
|
import android.util.Log;
|
2022-03-02 21:00:19 +01:00
|
|
|
import android.view.View;
|
|
|
|
import android.widget.CheckBox;
|
2018-03-21 08:11:54 +01:00
|
|
|
import android.widget.SeekBar;
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
2020-03-31 19:20:15 +02:00
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.Nullable;
|
2022-03-02 21:00:19 +01:00
|
|
|
import androidx.annotation.StringRes;
|
2020-03-31 19:20:15 +02:00
|
|
|
import androidx.appcompat.app.AlertDialog;
|
2022-07-18 05:30:11 +02:00
|
|
|
import androidx.core.math.MathUtils;
|
2020-03-31 19:20:15 +02:00
|
|
|
import androidx.fragment.app.DialogFragment;
|
2021-01-15 17:11:04 +01:00
|
|
|
import androidx.preference.PreferenceManager;
|
2020-03-31 19:20:15 +02:00
|
|
|
|
2018-03-21 08:11:54 +01:00
|
|
|
import org.schabi.newpipe.R;
|
2022-02-28 20:45:23 +01:00
|
|
|
import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
|
2022-04-08 09:35:14 +02:00
|
|
|
import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
2022-04-16 21:24:01 +02:00
|
|
|
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
|
2018-03-22 04:08:33 +01:00
|
|
|
import org.schabi.newpipe.util.SliderStrategy;
|
2018-03-21 08:11:54 +01:00
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
import java.util.Map;
|
2022-01-01 19:02:03 +01:00
|
|
|
import java.util.Objects;
|
2022-03-02 21:00:19 +01:00
|
|
|
import java.util.function.Consumer;
|
2022-01-01 19:02:03 +01:00
|
|
|
import java.util.function.DoubleConsumer;
|
|
|
|
import java.util.function.DoubleFunction;
|
2022-03-02 21:00:19 +01:00
|
|
|
import java.util.function.DoubleSupplier;
|
2022-01-01 19:02:03 +01:00
|
|
|
|
|
|
|
import icepick.Icepick;
|
|
|
|
import icepick.State;
|
|
|
|
|
2018-03-21 08:11:54 +01:00
|
|
|
public class PlaybackParameterDialog extends DialogFragment {
|
2022-01-01 19:02:03 +01:00
|
|
|
private static final String TAG = "PlaybackParameterDialog";
|
|
|
|
|
2018-05-12 05:09:02 +02:00
|
|
|
// Minimum allowable range in ExoPlayer
|
2022-03-16 14:53:33 +01:00
|
|
|
private static final double MIN_PITCH_OR_SPEED = 0.10f;
|
|
|
|
private static final double MAX_PITCH_OR_SPEED = 3.00f;
|
2018-03-21 08:11:54 +01:00
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
private static final boolean PITCH_CTRL_MODE_PERCENT = false;
|
|
|
|
private static final boolean PITCH_CTRL_MODE_SEMITONE = true;
|
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
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;
|
2018-03-21 08:11:54 +01:00
|
|
|
|
2020-04-02 13:51:10 +02:00
|
|
|
private static final double DEFAULT_TEMPO = 1.00f;
|
2022-03-02 21:00:19 +01:00
|
|
|
private static final double DEFAULT_PITCH_PERCENT = 1.00f;
|
2022-01-01 19:02:03 +01:00
|
|
|
private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE;
|
2020-04-02 13:51:10 +02:00
|
|
|
private static final boolean DEFAULT_SKIP_SILENCE = false;
|
2018-06-26 19:18:01 +02:00
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic(
|
2022-03-16 14:53:33 +01:00
|
|
|
MIN_PITCH_OR_SPEED,
|
|
|
|
MAX_PITCH_OR_SPEED,
|
2022-01-01 19:02:03 +01:00
|
|
|
1.00f,
|
|
|
|
10_000);
|
2018-03-21 08:11:54 +01:00
|
|
|
|
2022-03-02 21:00:19 +01:00
|
|
|
private static final SliderStrategy SEMITONE_STRATEGY = new SliderStrategy() {
|
|
|
|
@Override
|
|
|
|
public int progressOf(final double value) {
|
|
|
|
return PlayerSemitoneHelper.percentToSemitones(value) + 12;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public double valueOf(final int progress) {
|
|
|
|
return PlayerSemitoneHelper.semitonesToPercent(progress - 12);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-03-31 19:20:15 +02:00
|
|
|
@Nullable
|
|
|
|
private Callback callback;
|
2020-04-02 13:51:10 +02:00
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
@State
|
|
|
|
double initialTempo = DEFAULT_TEMPO;
|
|
|
|
@State
|
2022-03-02 21:00:19 +01:00
|
|
|
double initialPitchPercent = DEFAULT_PITCH_PERCENT;
|
2022-01-01 19:02:03 +01:00
|
|
|
@State
|
|
|
|
boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
|
|
|
|
|
|
|
|
@State
|
|
|
|
double tempo = DEFAULT_TEMPO;
|
|
|
|
@State
|
2022-03-02 21:00:19 +01:00
|
|
|
double pitchPercent = DEFAULT_PITCH_PERCENT;
|
2022-01-01 19:02:03 +01:00
|
|
|
@State
|
|
|
|
boolean skipSilence = DEFAULT_SKIP_SILENCE;
|
2018-06-26 19:18:01 +02:00
|
|
|
|
2022-02-28 20:45:23 +01:00
|
|
|
private DialogPlaybackParameterBinding binding;
|
2018-03-22 04:08:33 +01:00
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
public static PlaybackParameterDialog newInstance(
|
|
|
|
final double playbackTempo,
|
|
|
|
final double playbackPitch,
|
|
|
|
final boolean playbackSkipSilence,
|
|
|
|
final Callback callback
|
|
|
|
) {
|
2020-08-16 10:24:58 +02:00
|
|
|
final PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
2019-12-29 22:15:01 +01:00
|
|
|
dialog.callback = callback;
|
2022-01-01 19:02:03 +01:00
|
|
|
|
2018-03-21 08:11:54 +01:00
|
|
|
dialog.initialTempo = playbackTempo;
|
2022-03-02 21:00:19 +01:00
|
|
|
dialog.initialPitchPercent = playbackPitch;
|
2022-01-01 19:02:03 +01:00
|
|
|
dialog.initialSkipSilence = playbackSkipSilence;
|
2018-06-26 19:18:01 +02:00
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
dialog.tempo = dialog.initialTempo;
|
2022-03-02 21:00:19 +01:00
|
|
|
dialog.pitchPercent = dialog.initialPitchPercent;
|
2022-01-01 19:02:03 +01:00
|
|
|
dialog.skipSilence = dialog.initialSkipSilence;
|
2018-06-26 19:18:01 +02:00
|
|
|
|
2018-03-21 08:11:54 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Lifecycle
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
@Override
|
2022-02-28 20:45:23 +01:00
|
|
|
public void onAttach(@NonNull final Context context) {
|
2018-03-21 08:11:54 +01:00
|
|
|
super.onAttach(context);
|
2019-12-29 22:15:01 +01:00
|
|
|
if (context instanceof Callback) {
|
2018-03-21 08:11:54 +01:00
|
|
|
callback = (Callback) context;
|
2019-12-29 22:15:01 +01:00
|
|
|
} else if (callback == null) {
|
2018-03-21 08:11:54 +01:00
|
|
|
dismiss();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-02-28 20:45:23 +01:00
|
|
|
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
2018-03-21 08:11:54 +01:00
|
|
|
super.onSaveInstanceState(outState);
|
2022-01-01 19:02:03 +01:00
|
|
|
Icepick.saveInstanceState(this, outState);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Dialog
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
@NonNull
|
|
|
|
@Override
|
2020-03-31 19:20:15 +02:00
|
|
|
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
2020-01-28 20:48:42 +01:00
|
|
|
assureCorrectAppLanguage(getContext());
|
2022-01-01 19:02:03 +01:00
|
|
|
Icepick.restoreInstanceState(this, savedInstanceState);
|
|
|
|
|
2022-07-24 21:35:33 +02:00
|
|
|
binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater());
|
2022-02-28 20:45:23 +01:00
|
|
|
initUI();
|
2018-03-21 08:11:54 +01:00
|
|
|
|
|
|
|
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
2022-02-28 20:45:23 +01:00
|
|
|
.setView(binding.getRoot())
|
2018-03-21 08:11:54 +01:00
|
|
|
.setCancelable(true)
|
2022-01-01 19:02:03 +01:00
|
|
|
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
|
|
|
|
setAndUpdateTempo(initialTempo);
|
2022-03-02 21:00:19 +01:00
|
|
|
setAndUpdatePitch(initialPitchPercent);
|
2022-01-01 19:02:03 +01:00
|
|
|
setAndUpdateSkipSilence(initialSkipSilence);
|
|
|
|
updateCallback();
|
|
|
|
})
|
|
|
|
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> {
|
|
|
|
setAndUpdateTempo(DEFAULT_TEMPO);
|
2022-03-02 21:00:19 +01:00
|
|
|
setAndUpdatePitch(DEFAULT_PITCH_PERCENT);
|
2022-01-01 19:02:03 +01:00
|
|
|
setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE);
|
|
|
|
updateCallback();
|
|
|
|
})
|
|
|
|
.setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback());
|
2018-03-21 08:11:54 +01:00
|
|
|
|
|
|
|
return dialogBuilder.create();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
2022-03-04 22:02:39 +01:00
|
|
|
// UI Initialization and Control
|
2018-03-21 08:11:54 +01:00
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2022-02-28 20:45:23 +01:00
|
|
|
private void initUI() {
|
2022-01-01 19:02:03 +01:00
|
|
|
// Tempo
|
2022-03-16 14:53:33 +01:00
|
|
|
setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PITCH_OR_SPEED);
|
|
|
|
setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PITCH_OR_SPEED);
|
2022-01-01 19:02:03 +01:00
|
|
|
|
2022-03-16 14:53:33 +01:00
|
|
|
binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED));
|
2022-03-02 21:00:19 +01:00
|
|
|
setAndUpdateTempo(tempo);
|
|
|
|
binding.tempoSeekbar.setOnSeekBarChangeListener(
|
|
|
|
getTempoOrPitchSeekbarChangeListener(
|
|
|
|
QUADRATIC_STRATEGY,
|
|
|
|
this::onTempoSliderUpdated));
|
|
|
|
|
|
|
|
registerOnStepClickListener(
|
|
|
|
binding.tempoStepDown,
|
|
|
|
() -> tempo,
|
|
|
|
-1,
|
|
|
|
this::onTempoSliderUpdated);
|
|
|
|
registerOnStepClickListener(
|
|
|
|
binding.tempoStepUp,
|
|
|
|
() -> tempo,
|
|
|
|
1,
|
|
|
|
this::onTempoSliderUpdated);
|
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
// Pitch
|
|
|
|
binding.pitchToogleControlModes.setOnClickListener(v -> {
|
|
|
|
final boolean isCurrentlyVisible =
|
|
|
|
binding.pitchControlModeTabs.getVisibility() == View.GONE;
|
|
|
|
binding.pitchControlModeTabs.setVisibility(isCurrentlyVisible
|
|
|
|
? View.VISIBLE
|
|
|
|
: View.GONE);
|
|
|
|
animateRotation(binding.pitchToogleControlModes,
|
2022-04-08 09:35:14 +02:00
|
|
|
VideoPlayerUi.DEFAULT_CONTROLS_DURATION,
|
2022-03-04 21:37:11 +01:00
|
|
|
isCurrentlyVisible ? 180 : 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
getPitchControlModeComponentMappings()
|
|
|
|
.forEach(this::setupPitchControlModeTextView);
|
2022-05-03 21:42:09 +02:00
|
|
|
// Initialization is done at the end
|
2022-03-04 21:37:11 +01:00
|
|
|
|
2022-03-02 21:00:19 +01:00
|
|
|
// Pitch - Percent
|
2022-03-16 14:53:33 +01:00
|
|
|
setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PITCH_OR_SPEED);
|
|
|
|
setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PITCH_OR_SPEED);
|
2022-03-02 21:00:19 +01:00
|
|
|
|
2022-03-16 14:53:33 +01:00
|
|
|
binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED));
|
2022-03-02 21:00:19 +01:00
|
|
|
setAndUpdatePitch(pitchPercent);
|
|
|
|
binding.pitchPercentSeekbar.setOnSeekBarChangeListener(
|
|
|
|
getTempoOrPitchSeekbarChangeListener(
|
|
|
|
QUADRATIC_STRATEGY,
|
|
|
|
this::onPitchPercentSliderUpdated));
|
|
|
|
|
|
|
|
registerOnStepClickListener(
|
|
|
|
binding.pitchPercentStepDown,
|
|
|
|
() -> pitchPercent,
|
|
|
|
-1,
|
|
|
|
this::onPitchPercentSliderUpdated);
|
|
|
|
registerOnStepClickListener(
|
|
|
|
binding.pitchPercentStepUp,
|
|
|
|
() -> pitchPercent,
|
|
|
|
1,
|
|
|
|
this::onPitchPercentSliderUpdated);
|
|
|
|
|
|
|
|
// Pitch - Semitone
|
|
|
|
binding.pitchSemitoneSeekbar.setOnSeekBarChangeListener(
|
|
|
|
getTempoOrPitchSeekbarChangeListener(
|
|
|
|
SEMITONE_STRATEGY,
|
|
|
|
this::onPitchPercentSliderUpdated));
|
|
|
|
|
|
|
|
registerOnSemitoneStepClickListener(
|
|
|
|
binding.pitchSemitoneStepDown,
|
|
|
|
-1,
|
|
|
|
this::onPitchPercentSliderUpdated);
|
|
|
|
registerOnSemitoneStepClickListener(
|
|
|
|
binding.pitchSemitoneStepUp,
|
|
|
|
1,
|
|
|
|
this::onPitchPercentSliderUpdated);
|
2022-01-01 19:02:03 +01:00
|
|
|
|
|
|
|
// Steps
|
2022-03-04 21:34:45 +01:00
|
|
|
getStepSizeComponentMappings()
|
|
|
|
.forEach(this::setupStepTextView);
|
|
|
|
// Initialize UI
|
|
|
|
setStepSizeToUI(getCurrentStepSize());
|
2022-03-02 21:00:19 +01:00
|
|
|
|
|
|
|
// Bottom controls
|
|
|
|
bindCheckboxWithBoolPref(
|
|
|
|
binding.unhookCheckbox,
|
|
|
|
R.string.playback_unhook_key,
|
|
|
|
true,
|
|
|
|
isChecked -> {
|
|
|
|
if (!isChecked) {
|
|
|
|
// when unchecked, slide back to the minimum of current tempo or pitch
|
2022-03-04 22:02:39 +01:00
|
|
|
ensureHookIsValidAndUpdateCallBack();
|
2022-03-02 21:00:19 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
setAndUpdateSkipSilence(skipSilence);
|
|
|
|
binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
|
|
|
skipSilence = isChecked;
|
|
|
|
updateCallback();
|
|
|
|
});
|
2022-05-03 21:42:09 +02:00
|
|
|
|
|
|
|
// PitchControlMode has to be initialized at the end because it requires the unhookCheckbox
|
|
|
|
changePitchControlMode(isCurrentPitchControlModeSemitone());
|
2022-01-01 19:02:03 +01:00
|
|
|
}
|
|
|
|
|
2022-03-04 22:02:39 +01:00
|
|
|
// -- General formatting --
|
|
|
|
|
2022-03-04 21:34:45 +01:00
|
|
|
private void setText(
|
2022-01-01 19:02:03 +01:00
|
|
|
final TextView textView,
|
|
|
|
final DoubleFunction<String> formatter,
|
|
|
|
final double value
|
|
|
|
) {
|
|
|
|
Objects.requireNonNull(textView).setText(formatter.apply(value));
|
|
|
|
}
|
|
|
|
|
2022-03-04 22:02:39 +01:00
|
|
|
// -- Steps --
|
|
|
|
|
2022-03-02 21:00:19 +01:00
|
|
|
private void registerOnStepClickListener(
|
|
|
|
final TextView stepTextView,
|
|
|
|
final DoubleSupplier currentValueSupplier,
|
|
|
|
final double direction, // -1 for step down, +1 for step up
|
|
|
|
final DoubleConsumer newValueConsumer
|
|
|
|
) {
|
|
|
|
stepTextView.setOnClickListener(view -> {
|
|
|
|
newValueConsumer.accept(
|
2022-03-04 21:34:45 +01:00
|
|
|
currentValueSupplier.getAsDouble() + 1 * getCurrentStepSize() * direction);
|
2022-03-02 21:00:19 +01:00
|
|
|
updateCallback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void registerOnSemitoneStepClickListener(
|
|
|
|
final TextView stepTextView,
|
|
|
|
final int direction, // -1 for step down, +1 for step up
|
|
|
|
final DoubleConsumer newValueConsumer
|
|
|
|
) {
|
|
|
|
stepTextView.setOnClickListener(view -> {
|
|
|
|
newValueConsumer.accept(PlayerSemitoneHelper.semitonesToPercent(
|
|
|
|
PlayerSemitoneHelper.percentToSemitones(this.pitchPercent) + direction));
|
|
|
|
updateCallback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-04 22:02:39 +01:00
|
|
|
// -- Pitch --
|
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
private void setupPitchControlModeTextView(
|
|
|
|
final boolean semitones,
|
|
|
|
final TextView textView
|
|
|
|
) {
|
|
|
|
textView.setOnClickListener(view -> {
|
|
|
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
|
|
.edit()
|
|
|
|
.putBoolean(getString(R.string.playback_adjust_by_semitones_key), semitones)
|
|
|
|
.apply();
|
|
|
|
|
|
|
|
changePitchControlMode(semitones);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private Map<Boolean, TextView> getPitchControlModeComponentMappings() {
|
2022-07-19 05:22:16 +02:00
|
|
|
return Map.of(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent,
|
|
|
|
PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone);
|
2022-03-04 21:37:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void changePitchControlMode(final boolean semitones) {
|
|
|
|
// Bring all textviews into a normal state
|
|
|
|
final Map<Boolean, TextView> pitchCtrlModeComponentMapping =
|
|
|
|
getPitchControlModeComponentMappings();
|
|
|
|
pitchCtrlModeComponentMapping.forEach((v, textView) -> textView.setBackground(
|
|
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
|
|
|
|
|
|
|
|
// Mark the selected textview
|
|
|
|
final TextView textView = pitchCtrlModeComponentMapping.get(semitones);
|
|
|
|
if (textView != null) {
|
|
|
|
textView.setBackground(new LayerDrawable(new Drawable[]{
|
|
|
|
resolveDrawable(requireContext(), R.attr.dashed_border),
|
|
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show or hide component
|
|
|
|
binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
|
|
|
|
binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
|
|
|
|
|
|
|
|
if (semitones) {
|
|
|
|
// Recalculate pitch percent when changing to semitone
|
|
|
|
// (as it could be an invalid semitone value)
|
|
|
|
final double newPitchPercent = calcValidPitch(pitchPercent);
|
|
|
|
|
|
|
|
// If the values differ set the new pitch
|
|
|
|
if (this.pitchPercent != newPitchPercent) {
|
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
|
|
|
|
+ "currentPitchPercent = " + pitchPercent + ", "
|
|
|
|
+ "newPitchPercent = " + newPitchPercent
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.onPitchPercentSliderUpdated(newPitchPercent);
|
|
|
|
updateCallback();
|
|
|
|
}
|
2022-03-04 22:02:39 +01:00
|
|
|
} else if (!binding.unhookCheckbox.isChecked()) {
|
|
|
|
// When changing to percent it's possible that tempo is != pitch
|
|
|
|
ensureHookIsValidAndUpdateCallBack();
|
2022-03-04 21:37:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isCurrentPitchControlModeSemitone() {
|
|
|
|
return PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
|
|
.getBoolean(
|
|
|
|
getString(R.string.playback_adjust_by_semitones_key),
|
|
|
|
PITCH_CTRL_MODE_PERCENT);
|
|
|
|
}
|
|
|
|
|
2022-03-04 22:02:39 +01:00
|
|
|
// -- Steps (Set) --
|
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
private void setupStepTextView(
|
2022-03-04 21:37:11 +01:00
|
|
|
final double stepSizeValue,
|
|
|
|
final TextView textView
|
2022-01-01 19:02:03 +01:00
|
|
|
) {
|
2022-03-04 21:37:11 +01:00
|
|
|
setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue);
|
|
|
|
textView.setOnClickListener(view -> {
|
|
|
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
|
|
.edit()
|
|
|
|
.putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue)
|
|
|
|
.apply();
|
|
|
|
|
|
|
|
setStepSizeToUI(stepSizeValue);
|
|
|
|
});
|
2022-01-01 19:02:03 +01:00
|
|
|
}
|
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
private Map<Double, TextView> getStepSizeComponentMappings() {
|
2022-07-19 05:22:16 +02:00
|
|
|
return Map.of(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent,
|
|
|
|
STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent,
|
|
|
|
STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent,
|
|
|
|
STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent,
|
|
|
|
STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent);
|
2022-03-04 21:37:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void setStepSizeToUI(final double newStepSize) {
|
|
|
|
// Bring all textviews into a normal state
|
|
|
|
final Map<Double, TextView> stepSiteComponentMapping = getStepSizeComponentMappings();
|
|
|
|
stepSiteComponentMapping.forEach((v, textView) -> textView.setBackground(
|
|
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
|
|
|
|
|
|
|
|
// Mark the selected textview
|
|
|
|
final TextView textView = stepSiteComponentMapping.get(newStepSize);
|
|
|
|
if (textView != null) {
|
|
|
|
textView.setBackground(new LayerDrawable(new Drawable[]{
|
|
|
|
resolveDrawable(requireContext(), R.attr.dashed_border),
|
|
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
|
|
|
|
}));
|
|
|
|
}
|
2022-01-01 19:02:03 +01:00
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
// Bind to the corresponding control components
|
2022-03-02 21:00:19 +01:00
|
|
|
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
|
|
|
|
binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
|
2022-01-01 19:02:03 +01:00
|
|
|
|
2022-03-02 21:00:19 +01:00
|
|
|
binding.pitchPercentStepUp.setText(getStepUpPercentString(newStepSize));
|
|
|
|
binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize));
|
|
|
|
}
|
2022-01-01 19:02:03 +01:00
|
|
|
|
2022-03-04 21:34:45 +01:00
|
|
|
private double getCurrentStepSize() {
|
|
|
|
return PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
|
|
.getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP);
|
|
|
|
}
|
|
|
|
|
2022-03-04 22:02:39 +01:00
|
|
|
// -- Additional options --
|
|
|
|
|
2022-03-02 21:00:19 +01:00
|
|
|
private void setAndUpdateSkipSilence(final boolean newSkipSilence) {
|
|
|
|
this.skipSilence = newSkipSilence;
|
|
|
|
binding.skipSilenceCheckbox.setChecked(newSkipSilence);
|
|
|
|
}
|
2022-01-01 19:02:03 +01:00
|
|
|
|
2022-03-04 21:34:45 +01:00
|
|
|
@SuppressWarnings("SameParameterValue") // this method was written to be reusable
|
2022-03-02 21:00:19 +01:00
|
|
|
private void bindCheckboxWithBoolPref(
|
|
|
|
@NonNull final CheckBox checkBox,
|
|
|
|
@StringRes final int resId,
|
|
|
|
final boolean defaultValue,
|
2022-03-04 21:34:45 +01:00
|
|
|
@NonNull final Consumer<Boolean> onInitialValueOrValueChange
|
2022-03-02 21:00:19 +01:00
|
|
|
) {
|
|
|
|
final boolean prefValue = PreferenceManager
|
2021-12-21 18:17:48 +01:00
|
|
|
.getDefaultSharedPreferences(requireContext())
|
2022-03-02 21:00:19 +01:00
|
|
|
.getBoolean(getString(resId), defaultValue);
|
|
|
|
|
|
|
|
checkBox.setChecked(prefValue);
|
2021-12-21 18:17:48 +01:00
|
|
|
|
2022-03-04 21:34:45 +01:00
|
|
|
onInitialValueOrValueChange.accept(prefValue);
|
2022-03-02 21:00:19 +01:00
|
|
|
|
|
|
|
checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
2022-01-01 19:02:03 +01:00
|
|
|
// save whether pitch and tempo are unhooked or not
|
|
|
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
2021-12-21 18:17:48 +01:00
|
|
|
.edit()
|
2022-03-02 21:00:19 +01:00
|
|
|
.putBoolean(getString(resId), isChecked)
|
2021-12-21 18:17:48 +01:00
|
|
|
.apply();
|
2018-05-08 20:24:42 +02:00
|
|
|
|
2022-03-04 21:34:45 +01:00
|
|
|
onInitialValueOrValueChange.accept(isChecked);
|
2022-01-01 19:02:03 +01:00
|
|
|
});
|
2021-12-21 18:17:48 +01:00
|
|
|
}
|
|
|
|
|
2022-03-04 22:02:39 +01:00
|
|
|
/**
|
|
|
|
* Ensures that the slider hook is valid and if not sets and updates the sliders accordingly.
|
|
|
|
* <br/>
|
|
|
|
* You have to ensure by yourself that the hooking is active.
|
|
|
|
*/
|
|
|
|
private void ensureHookIsValidAndUpdateCallBack() {
|
|
|
|
if (tempo != pitchPercent) {
|
|
|
|
setSliders(Math.min(tempo, pitchPercent));
|
|
|
|
updateCallback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-22 04:08:33 +01:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Sliders
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener(
|
2022-03-02 21:00:19 +01:00
|
|
|
final SliderStrategy sliderStrategy,
|
2022-01-01 19:02:03 +01:00
|
|
|
final DoubleConsumer newValueConsumer
|
|
|
|
) {
|
2022-04-16 21:24:01 +02:00
|
|
|
return new SimpleOnSeekBarChangeListener() {
|
2018-03-21 08:11:54 +01:00
|
|
|
@Override
|
2022-04-30 21:46:06 +02:00
|
|
|
public void onProgressChanged(@NonNull final SeekBar seekBar,
|
2022-04-16 21:24:01 +02:00
|
|
|
final int progress,
|
2020-03-31 19:20:15 +02:00
|
|
|
final boolean fromUser) {
|
2022-03-02 21:00:19 +01:00
|
|
|
if (fromUser) { // ensure that the user triggered the change
|
|
|
|
newValueConsumer.accept(sliderStrategy.valueOf(progress));
|
2022-01-01 19:02:03 +01:00
|
|
|
updateCallback();
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
}
|
2021-12-21 18:17:48 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-03-22 04:08:33 +01:00
|
|
|
private void onTempoSliderUpdated(final double newTempo) {
|
2022-02-28 20:45:23 +01:00
|
|
|
if (!binding.unhookCheckbox.isChecked()) {
|
2018-03-21 08:11:54 +01:00
|
|
|
setSliders(newTempo);
|
|
|
|
} else {
|
2022-01-01 19:02:03 +01:00
|
|
|
setAndUpdateTempo(newTempo);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-02 21:00:19 +01:00
|
|
|
private void onPitchPercentSliderUpdated(final double newPitch) {
|
2022-02-28 20:45:23 +01:00
|
|
|
if (!binding.unhookCheckbox.isChecked()) {
|
2018-03-21 08:11:54 +01:00
|
|
|
setSliders(newPitch);
|
|
|
|
} else {
|
2022-01-01 19:02:03 +01:00
|
|
|
setAndUpdatePitch(newPitch);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-22 04:08:33 +01:00
|
|
|
private void setSliders(final double newValue) {
|
2022-01-01 19:02:03 +01:00
|
|
|
setAndUpdateTempo(newValue);
|
|
|
|
setAndUpdatePitch(newValue);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
private void setAndUpdateTempo(final double newTempo) {
|
2022-07-18 05:30:11 +02:00
|
|
|
this.tempo = MathUtils.clamp(newTempo, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED);
|
2022-03-02 21:00:19 +01:00
|
|
|
|
2022-02-28 20:45:23 +01:00
|
|
|
binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo));
|
|
|
|
setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
private void setAndUpdatePitch(final double newPitch) {
|
2022-03-02 21:00:19 +01:00
|
|
|
this.pitchPercent = calcValidPitch(newPitch);
|
|
|
|
|
|
|
|
binding.pitchPercentSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitchPercent));
|
|
|
|
binding.pitchSemitoneSeekbar.setProgress(SEMITONE_STRATEGY.progressOf(pitchPercent));
|
|
|
|
setText(binding.pitchPercentCurrentText,
|
|
|
|
PlayerHelper::formatPitch,
|
|
|
|
pitchPercent);
|
|
|
|
setText(binding.pitchSemitoneCurrentText,
|
|
|
|
PlayerSemitoneHelper::formatPitchSemitones,
|
|
|
|
pitchPercent);
|
|
|
|
}
|
|
|
|
|
|
|
|
private double calcValidPitch(final double newPitch) {
|
2022-07-18 05:30:11 +02:00
|
|
|
final double calcPitch = MathUtils.clamp(newPitch, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED);
|
2022-03-02 21:00:19 +01:00
|
|
|
|
2022-03-04 21:37:11 +01:00
|
|
|
if (!isCurrentPitchControlModeSemitone()) {
|
2022-03-02 21:00:19 +01:00
|
|
|
return calcPitch;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PlayerSemitoneHelper.semitonesToPercent(
|
|
|
|
PlayerSemitoneHelper.percentToSemitones(calcPitch));
|
2021-12-21 18:17:48 +01:00
|
|
|
}
|
|
|
|
|
2018-03-22 04:08:33 +01:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Helper
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2022-01-01 19:02:03 +01:00
|
|
|
private void updateCallback() {
|
|
|
|
if (callback == null) {
|
|
|
|
return;
|
2021-12-21 18:17:48 +01:00
|
|
|
}
|
2022-01-01 19:02:03 +01:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(TAG, "Updating callback: "
|
2022-03-02 21:00:19 +01:00
|
|
|
+ "tempo = " + tempo + ", "
|
|
|
|
+ "pitchPercent = " + pitchPercent + ", "
|
|
|
|
+ "skipSilence = " + skipSilence
|
2022-01-01 19:02:03 +01:00
|
|
|
);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
2022-03-02 21:00:19 +01:00
|
|
|
callback.onPlaybackParameterChanged((float) tempo, (float) pitchPercent, skipSilence);
|
2021-12-21 18:17:48 +01:00
|
|
|
}
|
|
|
|
|
2018-03-22 04:08:33 +01:00
|
|
|
@NonNull
|
|
|
|
private static String getStepUpPercentString(final double percent) {
|
2022-01-01 19:02:03 +01:00
|
|
|
return '+' + getPercentString(percent);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
|
2018-03-22 04:08:33 +01:00
|
|
|
@NonNull
|
|
|
|
private static String getStepDownPercentString(final double percent) {
|
2022-01-01 19:02:03 +01:00
|
|
|
return '-' + getPercentString(percent);
|
2018-05-08 20:24:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
|
|
|
private static String getPercentString(final double percent) {
|
|
|
|
return PlayerHelper.formatPitch(percent);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
2020-04-02 13:51:10 +02:00
|
|
|
|
2020-03-31 19:20:15 +02:00
|
|
|
public interface Callback {
|
|
|
|
void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
|
|
|
|
boolean playbackSkipSilence);
|
2018-03-21 08:11:54 +01:00
|
|
|
}
|
|
|
|
}
|