mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-24 09:00:31 +00:00
-Added quadratic slider strategy implementation and tests.
-Modified playback speed control to use quadratic sliders instead of linear. -Modified number formatters in player helper to use double instead of float. -Simplified slider behavior in playback parameter dialog. -Fixed potential NPE in base local fragment.
This commit is contained in:
parent
e885822a34
commit
18d019c62a
@ -151,7 +151,10 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showListFooter(final boolean show) {
|
public void showListFooter(final boolean show) {
|
||||||
itemsList.post(() -> itemListAdapter.showFooter(show));
|
if (itemsList == null) return;
|
||||||
|
itemsList.post(() -> {
|
||||||
|
if (itemListAdapter != null) itemListAdapter.showFooter(show);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -14,59 +14,64 @@ import android.widget.SeekBar;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.SliderStrategy;
|
||||||
|
|
||||||
import static org.schabi.newpipe.player.BasePlayer.DEBUG;
|
import static org.schabi.newpipe.player.BasePlayer.DEBUG;
|
||||||
|
|
||||||
public class PlaybackParameterDialog extends DialogFragment {
|
public class PlaybackParameterDialog extends DialogFragment {
|
||||||
private static final String TAG = "PlaybackParameterDialog";
|
@NonNull private static final String TAG = "PlaybackParameterDialog";
|
||||||
|
|
||||||
public static final float MINIMUM_PLAYBACK_VALUE = 0.25f;
|
public static final double MINIMUM_PLAYBACK_VALUE = 0.25f;
|
||||||
public static final float MAXIMUM_PLAYBACK_VALUE = 3.00f;
|
public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f;
|
||||||
|
|
||||||
public static final String STEP_UP_SIGN = "+";
|
public static final char STEP_UP_SIGN = '+';
|
||||||
public static final String STEP_DOWN_SIGN = "-";
|
public static final char STEP_DOWN_SIGN = '-';
|
||||||
public static final float PLAYBACK_STEP_VALUE = 0.05f;
|
public static final double PLAYBACK_STEP_VALUE = 0.05f;
|
||||||
|
|
||||||
public static final float NIGHTCORE_TEMPO = 1.20f;
|
public static final double NIGHTCORE_TEMPO = 1.20f;
|
||||||
public static final float NIGHTCORE_PITCH_LOWER = 1.15f;
|
public static final double NIGHTCORE_PITCH_LOWER = 1.15f;
|
||||||
public static final float NIGHTCORE_PITCH_UPPER = 1.25f;
|
public static final double NIGHTCORE_PITCH_UPPER = 1.25f;
|
||||||
|
|
||||||
public static final float DEFAULT_TEMPO = 1.00f;
|
public static final double DEFAULT_TEMPO = 1.00f;
|
||||||
public static final float DEFAULT_PITCH = 1.00f;
|
public static final double DEFAULT_PITCH = 1.00f;
|
||||||
|
|
||||||
private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
|
@NonNull private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
|
||||||
private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
|
@NonNull private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch);
|
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Callback callback;
|
@Nullable private Callback callback;
|
||||||
|
|
||||||
private float initialTempo = DEFAULT_TEMPO;
|
@NonNull private final SliderStrategy strategy = new SliderStrategy.Quadratic(
|
||||||
private float initialPitch = DEFAULT_PITCH;
|
MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE,
|
||||||
|
/*centerAt=*/1.00f, /*sliderGranularity=*/10000);
|
||||||
|
|
||||||
private SeekBar tempoSlider;
|
private double initialTempo = DEFAULT_TEMPO;
|
||||||
private TextView tempoMinimumText;
|
private double initialPitch = DEFAULT_PITCH;
|
||||||
private TextView tempoMaximumText;
|
|
||||||
private TextView tempoCurrentText;
|
|
||||||
private TextView tempoStepDownText;
|
|
||||||
private TextView tempoStepUpText;
|
|
||||||
|
|
||||||
private SeekBar pitchSlider;
|
@Nullable private SeekBar tempoSlider;
|
||||||
private TextView pitchMinimumText;
|
@Nullable private TextView tempoMinimumText;
|
||||||
private TextView pitchMaximumText;
|
@Nullable private TextView tempoMaximumText;
|
||||||
private TextView pitchCurrentText;
|
@Nullable private TextView tempoCurrentText;
|
||||||
private TextView pitchStepDownText;
|
@Nullable private TextView tempoStepDownText;
|
||||||
private TextView pitchStepUpText;
|
@Nullable private TextView tempoStepUpText;
|
||||||
|
|
||||||
private CheckBox unhookingCheckbox;
|
@Nullable private SeekBar pitchSlider;
|
||||||
|
@Nullable private TextView pitchMinimumText;
|
||||||
|
@Nullable private TextView pitchMaximumText;
|
||||||
|
@Nullable private TextView pitchCurrentText;
|
||||||
|
@Nullable private TextView pitchStepDownText;
|
||||||
|
@Nullable private TextView pitchStepUpText;
|
||||||
|
|
||||||
private TextView nightCorePresetText;
|
@Nullable private CheckBox unhookingCheckbox;
|
||||||
private TextView resetPresetText;
|
|
||||||
|
|
||||||
public static PlaybackParameterDialog newInstance(final float playbackTempo,
|
@Nullable private TextView nightCorePresetText;
|
||||||
final float playbackPitch) {
|
@Nullable private TextView resetPresetText;
|
||||||
|
|
||||||
|
public static PlaybackParameterDialog newInstance(final double playbackTempo,
|
||||||
|
final double playbackPitch) {
|
||||||
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
||||||
dialog.initialTempo = playbackTempo;
|
dialog.initialTempo = playbackTempo;
|
||||||
dialog.initialPitch = playbackPitch;
|
dialog.initialPitch = playbackPitch;
|
||||||
@ -91,16 +96,16 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
initialTempo = savedInstanceState.getFloat(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
|
initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
|
||||||
initialPitch = savedInstanceState.getFloat(INITIAL_PITCH_KEY, DEFAULT_PITCH);
|
initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putFloat(INITIAL_TEMPO_KEY, initialTempo);
|
outState.putDouble(INITIAL_TEMPO_KEY, initialTempo);
|
||||||
outState.putFloat(INITIAL_PITCH_KEY, initialPitch);
|
outState.putDouble(INITIAL_PITCH_KEY, initialPitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -111,7 +116,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable Bundle 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);
|
||||||
setupView(view);
|
setupControlViews(view);
|
||||||
|
|
||||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.playback_speed_control)
|
.setTitle(R.string.playback_speed_control)
|
||||||
@ -120,16 +125,16 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
|
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
|
||||||
setPlaybackParameters(initialTempo, initialPitch))
|
setPlaybackParameters(initialTempo, initialPitch))
|
||||||
.setPositiveButton(R.string.finish, (dialogInterface, i) ->
|
.setPositiveButton(R.string.finish, (dialogInterface, i) ->
|
||||||
setPlaybackParameters(getCurrentTempo(), getCurrentPitch()));
|
setCurrentPlaybackParameters());
|
||||||
|
|
||||||
return dialogBuilder.create();
|
return dialogBuilder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Dialog Builder
|
// Control Views
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void setupView(@NonNull View rootView) {
|
private void setupControlViews(@NonNull View rootView) {
|
||||||
setupHookingControl(rootView);
|
setupHookingControl(rootView);
|
||||||
setupTempoControl(rootView);
|
setupTempoControl(rootView);
|
||||||
setupPitchControl(rootView);
|
setupPitchControl(rootView);
|
||||||
@ -144,45 +149,34 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
||||||
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
||||||
|
|
||||||
|
if (tempoCurrentText != null)
|
||||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo));
|
tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo));
|
||||||
|
if (tempoMaximumText != null)
|
||||||
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
|
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
if (tempoMinimumText != null)
|
||||||
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
|
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
|
||||||
|
|
||||||
|
if (tempoStepUpText != null) {
|
||||||
tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
||||||
tempoStepUpText.setOnClickListener(view ->
|
tempoStepUpText.setOnClickListener(view -> {
|
||||||
setTempo(getCurrentTempo() + PLAYBACK_STEP_VALUE));
|
onTempoSliderUpdated(getCurrentTempo() + PLAYBACK_STEP_VALUE);
|
||||||
|
setCurrentPlaybackParameters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempoStepDownText != null) {
|
||||||
tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
||||||
tempoStepDownText.setOnClickListener(view ->
|
tempoStepDownText.setOnClickListener(view -> {
|
||||||
setTempo(getCurrentTempo() - PLAYBACK_STEP_VALUE));
|
onTempoSliderUpdated(getCurrentTempo() - PLAYBACK_STEP_VALUE);
|
||||||
|
setCurrentPlaybackParameters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
tempoSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE));
|
if (tempoSlider != null) {
|
||||||
tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialTempo));
|
tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
tempoSlider.setProgress(strategy.progressOf(initialTempo));
|
||||||
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
|
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() {
|
|
||||||
return new SeekBar.OnSeekBarChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
|
||||||
final float currentTempo = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress);
|
|
||||||
if (fromUser) { // this change is first in chain
|
|
||||||
setTempo(currentTempo);
|
|
||||||
} else {
|
|
||||||
setPlaybackParameters(currentTempo, getCurrentPitch());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
|
||||||
// Do Nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
|
||||||
// Do Nothing.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupPitchControl(@NonNull View rootView) {
|
private void setupPitchControl(@NonNull View rootView) {
|
||||||
@ -193,32 +187,85 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
||||||
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
||||||
|
|
||||||
|
if (pitchCurrentText != null)
|
||||||
pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch));
|
pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch));
|
||||||
|
if (pitchMaximumText != null)
|
||||||
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
|
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
if (pitchMinimumText != null)
|
||||||
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
|
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
|
||||||
|
|
||||||
|
if (pitchStepUpText != null) {
|
||||||
pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
||||||
pitchStepUpText.setOnClickListener(view ->
|
pitchStepUpText.setOnClickListener(view -> {
|
||||||
setPitch(getCurrentPitch() + PLAYBACK_STEP_VALUE));
|
onPitchSliderUpdated(getCurrentPitch() + PLAYBACK_STEP_VALUE);
|
||||||
|
setCurrentPlaybackParameters();
|
||||||
pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
});
|
||||||
pitchStepDownText.setOnClickListener(view ->
|
|
||||||
setPitch(getCurrentPitch() - PLAYBACK_STEP_VALUE));
|
|
||||||
|
|
||||||
pitchSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE));
|
|
||||||
pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialPitch));
|
|
||||||
pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() {
|
if (pitchStepDownText != null) {
|
||||||
|
pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
||||||
|
pitchStepDownText.setOnClickListener(view -> {
|
||||||
|
onPitchSliderUpdated(getCurrentPitch() - PLAYBACK_STEP_VALUE);
|
||||||
|
setCurrentPlaybackParameters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pitchSlider != null) {
|
||||||
|
pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
pitchSlider.setProgress(strategy.progressOf(initialPitch));
|
||||||
|
pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupHookingControl(@NonNull View rootView) {
|
||||||
|
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
||||||
|
if (unhookingCheckbox != null) {
|
||||||
|
unhookingCheckbox.setChecked(initialPitch != initialTempo);
|
||||||
|
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
||||||
|
if (isChecked) return;
|
||||||
|
// When unchecked, slide back to the minimum of current tempo or pitch
|
||||||
|
final double minimum = Math.min(getCurrentPitch(), getCurrentTempo());
|
||||||
|
setSliders(minimum);
|
||||||
|
setCurrentPlaybackParameters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPresetControl(@NonNull View rootView) {
|
||||||
|
nightCorePresetText = rootView.findViewById(R.id.presetNightcore);
|
||||||
|
if (nightCorePresetText != null) {
|
||||||
|
nightCorePresetText.setOnClickListener(view -> {
|
||||||
|
final double randomPitch = NIGHTCORE_PITCH_LOWER +
|
||||||
|
Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER);
|
||||||
|
|
||||||
|
setTempoSlider(NIGHTCORE_TEMPO);
|
||||||
|
setPitchSlider(randomPitch);
|
||||||
|
setCurrentPlaybackParameters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resetPresetText = rootView.findViewById(R.id.presetReset);
|
||||||
|
if (resetPresetText != null) {
|
||||||
|
resetPresetText.setOnClickListener(view -> {
|
||||||
|
setTempoSlider(DEFAULT_TEMPO);
|
||||||
|
setPitchSlider(DEFAULT_PITCH);
|
||||||
|
setCurrentPlaybackParameters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Sliders
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() {
|
||||||
return new SeekBar.OnSeekBarChangeListener() {
|
return new SeekBar.OnSeekBarChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
final float currentPitch = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress);
|
final double currentTempo = strategy.valueOf(progress);
|
||||||
if (fromUser) { // this change is first in chain
|
if (fromUser) {
|
||||||
setPitch(currentPitch);
|
onTempoSliderUpdated(currentTempo);
|
||||||
} else {
|
setCurrentPlaybackParameters();
|
||||||
setPlaybackParameters(getCurrentTempo(), currentPitch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,38 +281,30 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupHookingControl(@NonNull View rootView) {
|
private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() {
|
||||||
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
return new SeekBar.OnSeekBarChangeListener() {
|
||||||
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
@Override
|
||||||
if (isChecked) return;
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
// When unchecked, slide back to the minimum of current tempo or pitch
|
final double currentPitch = strategy.valueOf(progress);
|
||||||
final float minimum = Math.min(getCurrentPitch(), getCurrentTempo());
|
if (fromUser) { // this change is first in chain
|
||||||
setSliders(minimum);
|
onPitchSliderUpdated(currentPitch);
|
||||||
});
|
setCurrentPlaybackParameters();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupPresetControl(@NonNull View rootView) {
|
@Override
|
||||||
nightCorePresetText = rootView.findViewById(R.id.presetNightcore);
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
nightCorePresetText.setOnClickListener(view -> {
|
// Do Nothing.
|
||||||
final float randomPitch = NIGHTCORE_PITCH_LOWER +
|
|
||||||
(float) Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER);
|
|
||||||
|
|
||||||
setTempoSlider(NIGHTCORE_TEMPO);
|
|
||||||
setPitchSlider(randomPitch);
|
|
||||||
});
|
|
||||||
|
|
||||||
resetPresetText = rootView.findViewById(R.id.presetReset);
|
|
||||||
resetPresetText.setOnClickListener(view -> {
|
|
||||||
setTempoSlider(DEFAULT_TEMPO);
|
|
||||||
setPitchSlider(DEFAULT_PITCH);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
@Override
|
||||||
// Helper
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
// Do Nothing.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void setTempo(final float newTempo) {
|
private void onTempoSliderUpdated(final double newTempo) {
|
||||||
if (unhookingCheckbox == null) return;
|
if (unhookingCheckbox == null) return;
|
||||||
if (!unhookingCheckbox.isChecked()) {
|
if (!unhookingCheckbox.isChecked()) {
|
||||||
setSliders(newTempo);
|
setSliders(newTempo);
|
||||||
@ -274,7 +313,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPitch(final float newPitch) {
|
private void onPitchSliderUpdated(final double newPitch) {
|
||||||
if (unhookingCheckbox == null) return;
|
if (unhookingCheckbox == null) return;
|
||||||
if (!unhookingCheckbox.isChecked()) {
|
if (!unhookingCheckbox.isChecked()) {
|
||||||
setSliders(newPitch);
|
setSliders(newPitch);
|
||||||
@ -283,25 +322,30 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSliders(final float newValue) {
|
private void setSliders(final double newValue) {
|
||||||
setTempoSlider(newValue);
|
setTempoSlider(newValue);
|
||||||
setPitchSlider(newValue);
|
setPitchSlider(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTempoSlider(final float newTempo) {
|
private void setTempoSlider(final double newTempo) {
|
||||||
if (tempoSlider == null) return;
|
if (tempoSlider == null) return;
|
||||||
// seekbar doesn't register progress if it is the same as the existing progress
|
tempoSlider.setProgress(strategy.progressOf(newTempo));
|
||||||
tempoSlider.setProgress(Integer.MAX_VALUE);
|
|
||||||
tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newTempo));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPitchSlider(final float newPitch) {
|
private void setPitchSlider(final double newPitch) {
|
||||||
if (pitchSlider == null) return;
|
if (pitchSlider == null) return;
|
||||||
pitchSlider.setProgress(Integer.MAX_VALUE);
|
pitchSlider.setProgress(strategy.progressOf(newPitch));
|
||||||
pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newPitch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPlaybackParameters(final float tempo, final float pitch) {
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helper
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private void setCurrentPlaybackParameters() {
|
||||||
|
setPlaybackParameters(getCurrentTempo(), getCurrentPitch());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPlaybackParameters(final double tempo, final double pitch) {
|
||||||
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) {
|
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) {
|
||||||
if (DEBUG) Log.d(TAG, "Setting playback parameters to " +
|
if (DEBUG) Log.d(TAG, "Setting playback parameters to " +
|
||||||
"tempo=[" + tempo + "], " +
|
"tempo=[" + tempo + "], " +
|
||||||
@ -309,40 +353,27 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
|
|
||||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
|
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
|
||||||
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
|
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
|
||||||
callback.onPlaybackParameterChanged(tempo, pitch);
|
callback.onPlaybackParameterChanged((float) tempo, (float) pitch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getCurrentTempo() {
|
private double getCurrentTempo() {
|
||||||
return tempoSlider == null ? initialTempo : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE,
|
return tempoSlider == null ? initialTempo : strategy.valueOf(
|
||||||
tempoSlider.getProgress());
|
tempoSlider.getProgress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getCurrentPitch() {
|
private double getCurrentPitch() {
|
||||||
return pitchSlider == null ? initialPitch : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE,
|
return pitchSlider == null ? initialPitch : strategy.valueOf(
|
||||||
pitchSlider.getProgress());
|
pitchSlider.getProgress());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@NonNull
|
||||||
* Converts from zeroed float with a minimum offset to the nearest rounded slider
|
private static String getStepUpPercentString(final double percent) {
|
||||||
* equivalent integer
|
|
||||||
* */
|
|
||||||
private static int getSliderEquivalent(final float minimumValue, final float floatValue) {
|
|
||||||
return Math.round((floatValue - minimumValue) * 100f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts from slider integer value to an equivalent float value with a given minimum offset
|
|
||||||
* */
|
|
||||||
private static float getSliderEquivalent(final float minimumValue, final int intValue) {
|
|
||||||
return ((float) intValue) / 100f + minimumValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getStepUpPercentString(final float percent) {
|
|
||||||
return STEP_UP_SIGN + PlayerHelper.formatPitch(percent);
|
return STEP_UP_SIGN + PlayerHelper.formatPitch(percent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getStepDownPercentString(final float percent) {
|
@NonNull
|
||||||
|
private static String getStepDownPercentString(final double percent) {
|
||||||
return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent);
|
return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,11 @@ public class PlayerHelper {
|
|||||||
: stringFormatter.format("%02d:%02d", minutes, seconds).toString();
|
: stringFormatter.format("%02d:%02d", minutes, seconds).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatSpeed(float speed) {
|
public static String formatSpeed(double speed) {
|
||||||
return speedFormatter.format(speed);
|
return speedFormatter.format(speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatPitch(float pitch) {
|
public static String formatPitch(double pitch) {
|
||||||
return pitchFormatter.format(pitch);
|
return pitchFormatter.format(pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
public interface SliderStrategy {
|
||||||
|
/**
|
||||||
|
* Converts from zeroed double with a minimum offset to the nearest rounded slider
|
||||||
|
* equivalent integer
|
||||||
|
* */
|
||||||
|
int progressOf(final double value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts from slider integer value to an equivalent double value with a given
|
||||||
|
* minimum offset
|
||||||
|
* */
|
||||||
|
double valueOf(final int progress);
|
||||||
|
|
||||||
|
// TODO: also implement linear strategy when needed
|
||||||
|
|
||||||
|
final class Quadratic implements SliderStrategy {
|
||||||
|
private final double leftGap;
|
||||||
|
private final double rightGap;
|
||||||
|
private final double center;
|
||||||
|
|
||||||
|
private final int centerProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quadratic slider strategy that scales the value of a slider given how far the slider
|
||||||
|
* progress is from the center of the slider. The further away from the center,
|
||||||
|
* the faster the interpreted value changes, and vice versa.
|
||||||
|
*
|
||||||
|
* @param minimum the minimum value of the interpreted value of the slider.
|
||||||
|
* @param maximum the maximum value of the interpreted value of the slider.
|
||||||
|
* @param center center of the interpreted value between the minimum and maximum, which
|
||||||
|
* will be used as the center value on the slider progress. Doesn't need
|
||||||
|
* to be the average of the minimum and maximum values, but must be in
|
||||||
|
* between the two.
|
||||||
|
* @param maxProgress the maximum possible progress of the slider, this is the
|
||||||
|
* value that is shown for the UI and controls the granularity of
|
||||||
|
* the slider. Should be as large as possible to avoid floating
|
||||||
|
* point round-off error. Using odd number is recommended.
|
||||||
|
* */
|
||||||
|
public Quadratic(double minimum, double maximum, double center, int maxProgress) {
|
||||||
|
if (center < minimum || center > maximum) {
|
||||||
|
throw new IllegalArgumentException("Center must be in between minimum and maximum");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.leftGap = minimum - center;
|
||||||
|
this.rightGap = maximum - center;
|
||||||
|
this.center = center;
|
||||||
|
|
||||||
|
this.centerProgress = maxProgress / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int progressOf(double value) {
|
||||||
|
final double difference = value - center;
|
||||||
|
final double root = difference >= 0 ?
|
||||||
|
Math.sqrt(difference / rightGap) :
|
||||||
|
-Math.sqrt(Math.abs(difference / leftGap));
|
||||||
|
final double offset = Math.round(root * centerProgress);
|
||||||
|
|
||||||
|
return (int) (centerProgress + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double valueOf(int progress) {
|
||||||
|
final int offset = progress - centerProgress;
|
||||||
|
final double square = Math.pow(((double) offset) / ((double) centerProgress), 2);
|
||||||
|
final double difference = square * (offset >= 0 ? rightGap : leftGap);
|
||||||
|
|
||||||
|
return difference + center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class QuadraticSliderStrategyTest {
|
||||||
|
private final static int STEP = 100;
|
||||||
|
private final static float DELTA = 1f / (float) STEP;
|
||||||
|
|
||||||
|
private final SliderStrategy.Quadratic standard =
|
||||||
|
new SliderStrategy.Quadratic(0f, 100f, 50f, STEP);
|
||||||
|
@Test
|
||||||
|
public void testLeftBound() throws Exception {
|
||||||
|
assertEquals(standard.progressOf(0), 0);
|
||||||
|
assertEquals(standard.valueOf(0), 0f, DELTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCenter() throws Exception {
|
||||||
|
assertEquals(standard.progressOf(50), 50);
|
||||||
|
assertEquals(standard.valueOf(50), 50f, DELTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRightBound() throws Exception {
|
||||||
|
assertEquals(standard.progressOf(100), 100);
|
||||||
|
assertEquals(standard.valueOf(100), 100f, DELTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLeftRegion() throws Exception {
|
||||||
|
final int leftProgress = standard.progressOf(25);
|
||||||
|
final double leftValue = standard.valueOf(25);
|
||||||
|
assertTrue(leftProgress > 0 && leftProgress < 50);
|
||||||
|
assertTrue(leftValue > 0f && leftValue < 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRightRegion() throws Exception {
|
||||||
|
final int leftProgress = standard.progressOf(75);
|
||||||
|
final double leftValue = standard.valueOf(75);
|
||||||
|
assertTrue(leftProgress > 50 && leftProgress < 100);
|
||||||
|
assertTrue(leftValue > 50f && leftValue < 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConversion() throws Exception {
|
||||||
|
assertEquals(standard.progressOf(standard.valueOf(0)), 0);
|
||||||
|
assertEquals(standard.progressOf(standard.valueOf(25)), 25);
|
||||||
|
assertEquals(standard.progressOf(standard.valueOf(50)), 50);
|
||||||
|
assertEquals(standard.progressOf(standard.valueOf(75)), 75);
|
||||||
|
assertEquals(standard.progressOf(standard.valueOf(100)), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReverseConversion() throws Exception {
|
||||||
|
// Need a larger delta since step size / granularity is too small and causes
|
||||||
|
// floating point round-off errors during conversion
|
||||||
|
final float largeDelta = 1f;
|
||||||
|
|
||||||
|
assertEquals(standard.valueOf(standard.progressOf(0)), 0f, largeDelta);
|
||||||
|
assertEquals(standard.valueOf(standard.progressOf(25)), 25f, largeDelta);
|
||||||
|
assertEquals(standard.valueOf(standard.progressOf(50)), 50f, largeDelta);
|
||||||
|
assertEquals(standard.valueOf(standard.progressOf(75)), 75f, largeDelta);
|
||||||
|
assertEquals(standard.valueOf(standard.progressOf(100)), 100f, largeDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuadraticPropertyLeftRegion() throws Exception {
|
||||||
|
final double differenceCloserToCenter =
|
||||||
|
Math.abs(standard.valueOf(40) - standard.valueOf(45));
|
||||||
|
final double differenceFurtherFromCenter =
|
||||||
|
Math.abs(standard.valueOf(10) - standard.valueOf(15));
|
||||||
|
assertTrue(differenceCloserToCenter < differenceFurtherFromCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuadraticPropertyRightRegion() throws Exception {
|
||||||
|
final double differenceCloserToCenter =
|
||||||
|
Math.abs(standard.valueOf(75) - standard.valueOf(70));
|
||||||
|
final double differenceFurtherFromCenter =
|
||||||
|
Math.abs(standard.valueOf(95) - standard.valueOf(90));
|
||||||
|
assertTrue(differenceCloserToCenter < differenceFurtherFromCenter);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user