From 2e771cd65ab1eff2cb090351a4083d1d129fe53a Mon Sep 17 00:00:00 2001 From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com> Date: Mon, 4 Apr 2022 23:58:39 +0800 Subject: [PATCH 001/240] Fix crash when rotating device on unsupported channels --- .../fragments/list/channel/ChannelFragment.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 869503b5bed..6ad81132002 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -77,6 +77,8 @@ public class ChannelFragment extends BaseListInfoFragment Date: Sat, 1 Jan 2022 19:02:03 +0100 Subject: [PATCH 002/240] Major refactoring of PlaybackParameterDialog * Removed/Renamed methods * Use ``IcePick`` * Better structuring * Keep skipSilence when rotating the device (PlayQueueActivity only) --- .../helper/PlaybackParameterDialog.java | 738 ++++++------------ 1 file changed, 230 insertions(+), 508 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 1a55c21c342..b72b7ea7b0f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -9,10 +9,10 @@ import android.util.Log; import android.view.View; import android.widget.CheckBox; -import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -20,104 +20,88 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; -import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; +import java.util.Objects; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; + +import icepick.Icepick; +import icepick.State; + public class PlaybackParameterDialog extends DialogFragment { + private static final String TAG = "PlaybackParameterDialog"; + // Minimum allowable range in ExoPlayer private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; - private static final char STEP_UP_SIGN = '+'; - private static final char STEP_DOWN_SIGN = '-'; - - private static final double STEP_ONE_PERCENT_VALUE = 0.01f; - private static final double STEP_FIVE_PERCENT_VALUE = 0.05f; - private static final double STEP_TEN_PERCENT_VALUE = 0.10f; - private static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f; - private static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f; + private static final double STEP_1_PERCENT_VALUE = 0.01f; + private static final double STEP_5_PERCENT_VALUE = 0.05f; + private static final double STEP_10_PERCENT_VALUE = 0.10f; + private static final double STEP_25_PERCENT_VALUE = 0.25f; + private static final double STEP_100_PERCENT_VALUE = 1.00f; private static final double DEFAULT_TEMPO = 1.00f; private static final double DEFAULT_PITCH = 1.00f; - private static final int DEFAULT_SEMITONES = 0; - private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; + private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; - @NonNull - private static final String TAG = "PlaybackParameterDialog"; - @NonNull - private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; - @NonNull - private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; - - @NonNull - private static final String TEMPO_KEY = "tempo_key"; - @NonNull - private static final String PITCH_KEY = "pitch_key"; - @NonNull - private static final String STEP_SIZE_KEY = "step_size_key"; - - @NonNull - private final SliderStrategy strategy = new SliderStrategy.Quadratic( - MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE, - /*centerAt=*/1.00f, /*sliderGranularity=*/10000); + private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( + MINIMUM_PLAYBACK_VALUE, + MAXIMUM_PLAYBACK_VALUE, + 1.00f, + 10_000); @Nullable private Callback callback; - private double initialTempo = DEFAULT_TEMPO; - private double initialPitch = DEFAULT_PITCH; - private int initialSemitones = DEFAULT_SEMITONES; - private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; - private double tempo = DEFAULT_TEMPO; - private double pitch = DEFAULT_PITCH; - private int semitones = DEFAULT_SEMITONES; + @State + double initialTempo = DEFAULT_TEMPO; + @State + double initialPitch = DEFAULT_PITCH; + @State + boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; + + @State + double tempo = DEFAULT_TEMPO; + @State + double pitch = DEFAULT_PITCH; + @State + double stepSize = DEFAULT_STEP; + @State + boolean skipSilence = DEFAULT_SKIP_SILENCE; - @Nullable private SeekBar tempoSlider; - @Nullable private TextView tempoCurrentText; - @Nullable private TextView tempoStepDownText; - @Nullable private TextView tempoStepUpText; - @Nullable + private SeekBar pitchSlider; - @Nullable private TextView pitchCurrentText; - @Nullable private TextView pitchStepDownText; - @Nullable private TextView pitchStepUpText; - @Nullable - private SeekBar semitoneSlider; - @Nullable - private TextView semitoneCurrentText; - @Nullable - private TextView semitoneStepDownText; - @Nullable - private TextView semitoneStepUpText; - @Nullable + private CheckBox unhookingCheckbox; - @Nullable private CheckBox skipSilenceCheckbox; - @Nullable - private CheckBox adjustBySemitonesCheckbox; - public static PlaybackParameterDialog newInstance(final double playbackTempo, - final double playbackPitch, - final boolean playbackSkipSilence, - final Callback callback) { + public static PlaybackParameterDialog newInstance( + final double playbackTempo, + final double playbackPitch, + final boolean playbackSkipSilence, + final Callback callback + ) { final PlaybackParameterDialog dialog = new PlaybackParameterDialog(); dialog.callback = callback; + dialog.initialTempo = playbackTempo; dialog.initialPitch = playbackPitch; + dialog.initialSkipSilence = playbackSkipSilence; - dialog.tempo = playbackTempo; - dialog.pitch = playbackPitch; - dialog.semitones = dialog.percentToSemitones(playbackPitch); + dialog.tempo = dialog.initialTempo; + dialog.pitch = dialog.initialPitch; + dialog.skipSilence = dialog.initialSkipSilence; - dialog.initialSkipSilence = playbackSkipSilence; return dialog; } @@ -126,7 +110,7 @@ public static PlaybackParameterDialog newInstance(final double playbackTempo, //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(@NonNull final Context context) { + public void onAttach(final Context context) { super.onAttach(context); if (context instanceof Callback) { callback = (Callback) context; @@ -136,28 +120,9 @@ public void onAttach(@NonNull final Context context) { } @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - assureCorrectAppLanguage(getContext()); - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); - initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH); - initialSemitones = percentToSemitones(initialPitch); - - tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO); - pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH); - semitones = percentToSemitones(pitch); - } - } - - @Override - public void onSaveInstanceState(@NonNull final Bundle outState) { + public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); - outState.putDouble(INITIAL_TEMPO_KEY, initialTempo); - outState.putDouble(INITIAL_PITCH_KEY, initialPitch); - - outState.putDouble(TEMPO_KEY, getCurrentTempo()); - outState.putDouble(PITCH_KEY, getCurrentPitch()); + Icepick.saveInstanceState(this, outState); } /*////////////////////////////////////////////////////////////////////////// @@ -168,20 +133,28 @@ public void onSaveInstanceState(@NonNull final Bundle outState) { @Override public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); + Icepick.restoreInstanceState(this, savedInstanceState); + final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - setupControlViews(view); + initUI(view); + initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setView(view) .setCancelable(true) - .setNegativeButton(R.string.cancel, (dialogInterface, i) -> - setPlaybackParameters(initialTempo, initialPitch, - initialSemitones, initialSkipSilence)) - .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> - setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, - DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE)) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - setCurrentPlaybackParameters()); + .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { + setAndUpdateTempo(initialTempo); + setAndUpdatePitch(initialPitch); + setAndUpdateSkipSilence(initialSkipSilence); + updateCallback(); + }) + .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> { + setAndUpdateTempo(DEFAULT_TEMPO); + setAndUpdatePitch(DEFAULT_PITCH); + setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE); + updateCallback(); + }) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback()); return dialogBuilder.create(); } @@ -190,353 +163,171 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void setupControlViews(@NonNull final View rootView) { - setupHookingControl(rootView); - setupSkipSilenceControl(rootView); - setupAdjustBySemitonesControl(rootView); - - setupTempoControl(rootView); - setupPitchControl(rootView); - setupSemitoneControl(rootView); - - togglePitchSliderType(rootView); - - setupStepSizeSelector(rootView); - } - - private void togglePitchSliderType(@NonNull final View rootView) { - final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl); - final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl); - - final View separatorStepSizeSelector = - rootView.findViewById(R.id.separatorStepSizeSelector); - final RelativeLayout.LayoutParams params = - (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams(); - if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) { - if (getCurrentAdjustBySemitones()) { - // replaces pitchControl slider with semitoneControl slider - pitchControl.setVisibility(View.GONE); - semitoneControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.semitoneControl); - - // forces unhook for semitones - unhookingCheckbox.setChecked(true); - unhookingCheckbox.setEnabled(false); - - setupTempoStepSizeSelector(rootView); - } else { - semitoneControl.setVisibility(View.GONE); - pitchControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.pitchControl); - - // (re)enables hooking selection - unhookingCheckbox.setEnabled(true); - setupCombinedStepSizeSelector(rootView); - } - } - } - - private void setupTempoControl(@NonNull final View rootView) { - tempoSlider = rootView.findViewById(R.id.tempoSeekbar); - final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); - final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); - tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText); - tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); - tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); - - if (tempoCurrentText != null) { - tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); - } - if (tempoMaximumText != null) { - tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); - } - if (tempoMinimumText != null) { - tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); - } - - if (tempoSlider != null) { - tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - tempoSlider.setProgress(strategy.progressOf(tempo)); - tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener()); - } - } - - private void setupPitchControl(@NonNull final View rootView) { - pitchSlider = rootView.findViewById(R.id.pitchSeekbar); - final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); - final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); - pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText); - pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); - pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); - - if (pitchCurrentText != null) { - pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); - } - if (pitchMaximumText != null) { - pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); - } - if (pitchMinimumText != null) { - pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); - } - - if (pitchSlider != null) { - pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - pitchSlider.setProgress(strategy.progressOf(pitch)); - pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); - } - } - - private void setupSemitoneControl(@NonNull final View rootView) { - semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar); - semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText); - semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown); - semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp); - - if (semitoneCurrentText != null) { - semitoneCurrentText.setText(getSignedSemitonesString(semitones)); - } - - if (semitoneSlider != null) { - setSemitoneSlider(semitones); - semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener()); - } - - } - - private void setupHookingControl(@NonNull final View rootView) { - unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); - if (unhookingCheckbox != null) { - // restores whether pitch and tempo are unhooked or not - unhookingCheckbox.setChecked(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_unhook_key), true)); - - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - // saves whether pitch and tempo are unhooked or not - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putBoolean(getString(R.string.playback_unhook_key), isChecked) - .apply(); - - if (!isChecked) { - // when unchecked, slides back to the minimum of current tempo or pitch - final double minimum = Math.min(getCurrentPitch(), getCurrentTempo()); - setSliders(minimum); - setCurrentPlaybackParameters(); - } - }); - } - } - - private void setupSkipSilenceControl(@NonNull final View rootView) { - skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox); - if (skipSilenceCheckbox != null) { - skipSilenceCheckbox.setChecked(initialSkipSilence); - skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> - setCurrentPlaybackParameters()); - } - } - - private void setupAdjustBySemitonesControl(@NonNull final View rootView) { - adjustBySemitonesCheckbox = rootView.findViewById(R.id.adjustBySemitonesCheckbox); - if (adjustBySemitonesCheckbox != null) { - // restores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setChecked(PreferenceManager + private void initUI(@NonNull final View rootView) { + // Tempo + tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar)); + tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText)); + tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp)); + tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown)); + + setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); + setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); + + // 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)); + + 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 TextView setText( + final TextView textView, + final DoubleFunction formatter, + final double value + ) { + Objects.requireNonNull(textView).setText(formatter.apply(value)); + return textView; + } + + private TextView setText( + final View rootView, + @IdRes final int idRes, + final DoubleFunction formatter, + final double value + ) { + final TextView textView = rootView.findViewById(idRes); + setText(textView, formatter, value); + return textView; + } + + private void setupStepTextView( + final View rootView, + @IdRes final int idRes, + final double stepSizeValue + ) { + setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue) + .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); + } + + private void initUIData() { + // Tempo + tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdateTempo(tempo); + tempoSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); + + registerOnStepClickListener( + tempoStepDownText, tempo, -1, this::onTempoSliderUpdated); + registerOnStepClickListener( + tempoStepUpText, tempo, 1, this::onTempoSliderUpdated); + + // Pitch + pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdatePitch(pitch); + pitchSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); + + registerOnStepClickListener( + pitchStepDownText, pitch, -1, this::onPitchSliderUpdated); + registerOnStepClickListener( + pitchStepUpText, pitch, 1, this::onPitchSliderUpdated); + + // Steps + setAndUpdateStepSize(stepSize); + + // Bottom controls + // restore whether pitch and tempo are unhooked or not + unhookingCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_adjust_by_semitones_key), true)); + .getBoolean(getString(R.string.playback_unhook_key), true)); - // stores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - PreferenceManager.getDefaultSharedPreferences(requireContext()) + unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + // save whether pitch and tempo are unhooked or not + PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() - .putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked) + .putBoolean(getString(R.string.playback_unhook_key), isChecked) .apply(); - togglePitchSliderType(rootView); - if (isChecked) { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch()) - )), - getCurrentSkipSilence() - ); - setSemitoneSlider(Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch())) - )); - } else { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - setPitchSlider(semitonesToPercent(getCurrentSemitones())); - } - }); - } - } - - private void setupStepSizeSelector(@NonNull final View rootView) { - setStepSize(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP)); - - final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); - final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); - final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); - final TextView stepSizeTwentyFivePercentText = rootView - .findViewById(R.id.stepSizeTwentyFivePercent); - final TextView stepSizeOneHundredPercentText = rootView - .findViewById(R.id.stepSizeOneHundredPercent); - - if (stepSizeOnePercentText != null) { - stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); - stepSizeOnePercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE)); - } - if (stepSizeFivePercentText != null) { - stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE)); - stepSizeFivePercentText - .setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE)); - } - - if (stepSizeTenPercentText != null) { - stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE)); - stepSizeTenPercentText - .setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE)); - } - - if (stepSizeTwentyFivePercentText != null) { - stepSizeTwentyFivePercentText - .setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); - stepSizeTwentyFivePercentText - .setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); - } + if (!isChecked) { + // when unchecked, slide back to the minimum of current tempo or pitch + setSliders(Math.min(pitch, tempo)); + } + }); - if (stepSizeOneHundredPercentText != null) { - stepSizeOneHundredPercentText - .setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); - stepSizeOneHundredPercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); - } + setAndUpdateSkipSilence(skipSilence); + skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + skipSilence = isChecked; + updateCallback(); + }); } - private void setupTempoStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_tempo_step); - } - setupStepSizeSelector(rootView); + private void registerOnStepClickListener( + final TextView stepTextView, + final double currentValue, + final double direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> + newValueConsumer.accept(currentValue * direction) + ); } - private void setupCombinedStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_step); - } - setupStepSizeSelector(rootView); - } + private void setAndUpdateStepSize(final double newStepSize) { + this.stepSize = newStepSize; - 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(); - }); - } + tempoStepUpText.setText(getStepUpPercentString(newStepSize)); + tempoStepDownText.setText(getStepDownPercentString(newStepSize)); - if (pitchStepDownText != null) { - pitchStepDownText.setText(getStepDownPercentString(stepSize)); - pitchStepDownText.setOnClickListener(view -> { - onPitchSliderUpdated(getCurrentPitch() - stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (semitoneStepDownText != null) { - semitoneStepDownText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() - 1); - setCurrentPlaybackParameters(); - }); - } + pitchStepUpText.setText(getStepUpPercentString(newStepSize)); + pitchStepDownText.setText(getStepDownPercentString(newStepSize)); + } - if (semitoneStepUpText != null) { - semitoneStepUpText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() + 1); - setCurrentPlaybackParameters(); - }); - } + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { + this.skipSilence = newSkipSilence; + skipSilenceCheckbox.setChecked(newSkipSilence); } /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ - private SimpleOnSeekBarChangeListener getOnTempoChangedListener() { - return new SimpleOnSeekBarChangeListener() { + private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( + final DoubleConsumer newValueConsumer + ) { + return new SeekBar.OnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - final double currentTempo = strategy.valueOf(progress); - if (fromUser) { - onTempoSliderUpdated(currentTempo); - setCurrentPlaybackParameters(); + if (fromUser) { // this change is first in chain + newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress)); + updateCallback(); } } - }; - } - private SimpleOnSeekBarChangeListener getOnPitchChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - final double currentPitch = strategy.valueOf(progress); - if (fromUser) { // this change is first in chain - onPitchSliderUpdated(currentPitch); - setCurrentPlaybackParameters(); - } + public void onStartTrackingTouch(final SeekBar seekBar) { + // Do nothing } - }; - } - private SimpleOnSeekBarChangeListener getOnSemitoneChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - // semitone slider supplies values 0 to 24, subtraction by 12 is required - final int currentSemitones = progress - 12; - if (fromUser) { // this change is first in chain - onSemitoneSliderUpdated(currentSemitones); - // line below also saves semitones as pitch percentages - onPitchSliderUpdated(semitonesToPercent(currentSemitones)); - setCurrentPlaybackParameters(); - } + public void onStopTrackingTouch(final SeekBar seekBar) { + // Do nothing } }; } @@ -545,7 +336,7 @@ private void onTempoSliderUpdated(final double newTempo) { if (!unhookingCheckbox.isChecked()) { setSliders(newTempo); } else { - setTempoSlider(newTempo); + setAndUpdateTempo(newTempo); } } @@ -553,109 +344,53 @@ private void onPitchSliderUpdated(final double newPitch) { if (!unhookingCheckbox.isChecked()) { setSliders(newPitch); } else { - setPitchSlider(newPitch); + setAndUpdatePitch(newPitch); } } - private void onSemitoneSliderUpdated(final int newSemitone) { - setSemitoneSlider(newSemitone); - } - private void setSliders(final double newValue) { - setTempoSlider(newValue); - setPitchSlider(newValue); + setAndUpdateTempo(newValue); + setAndUpdatePitch(newValue); } - private void setTempoSlider(final double newTempo) { - if (tempoSlider == null) { - return; - } - tempoSlider.setProgress(strategy.progressOf(newTempo)); + private void setAndUpdateTempo(final double newTempo) { + this.tempo = newTempo; + tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); + setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo); } - private void setPitchSlider(final double newPitch) { - if (pitchSlider == null) { - return; - } - pitchSlider.setProgress(strategy.progressOf(newPitch)); - } - - private void setSemitoneSlider(final int newSemitone) { - if (semitoneSlider == null) { - return; - } - semitoneSlider.setProgress(newSemitone + 12); + private void setAndUpdatePitch(final double newPitch) { + this.pitch = newPitch; + pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); + setText(pitchCurrentText, PlayerHelper::formatPitch, pitch); } /*////////////////////////////////////////////////////////////////////////// // Helper //////////////////////////////////////////////////////////////////////////*/ - private void setCurrentPlaybackParameters() { - if (getCurrentAdjustBySemitones()) { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - } else { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - percentToSemitones(getCurrentPitch()), - getCurrentSkipSilence() - ); + private void updateCallback() { + if (callback == null) { + return; } - } - - 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); + if (DEBUG) { + Log.d(TAG, "Updating callback: " + + "tempo = [" + tempo + "], " + + "pitch = [" + pitch + "], " + + "skipSilence = [" + skipSilence + "]" + ); } - } - - private double getCurrentTempo() { - return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress()); - } - - private double getCurrentPitch() { - return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress()); - } - - private int getCurrentSemitones() { - // semitoneSlider is absolute, that's why - 12 - return semitoneSlider == null ? semitones : semitoneSlider.getProgress() - 12; - } - - private boolean getCurrentSkipSilence() { - return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); - } - - private boolean getCurrentAdjustBySemitones() { - return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked(); + callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); } @NonNull private static String getStepUpPercentString(final double percent) { - return STEP_UP_SIGN + getPercentString(percent); + return '+' + getPercentString(percent); } @NonNull private static String getStepDownPercentString(final double percent) { - return STEP_DOWN_SIGN + getPercentString(percent); + return '-' + getPercentString(percent); } @NonNull @@ -663,21 +398,8 @@ private static String getPercentString(final double percent) { return PlayerHelper.formatPitch(percent); } - @NonNull - private static String getSignedSemitonesString(final int semitones) { - return semitones > 0 ? "+" + semitones : "" + semitones; - } - public interface Callback { void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, boolean playbackSkipSilence); } - - public double semitonesToPercent(final int inSemitones) { - return Math.pow(2, inSemitones / 12.0); - } - - public int percentToSemitones(final double inPercent) { - return (int) Math.round(12 * Math.log(inPercent) / Math.log(2)); - } } From 4cdf6eda2cf3d9073d9ccad7c339bfb1856189c2 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 28 Feb 2022 20:45:23 +0100 Subject: [PATCH 003/240] Use viewbinding --- .../helper/PlaybackParameterDialog.java | 124 ++++++------------ 1 file changed, 42 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index b72b7ea7b0f..e1874fec0b9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -7,12 +7,10 @@ import android.content.Context; import android.os.Bundle; import android.util.Log; -import android.view.View; -import android.widget.CheckBox; +import android.view.LayoutInflater; import android.widget.SeekBar; import android.widget.TextView; -import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -20,6 +18,7 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.util.SliderStrategy; import java.util.Objects; @@ -72,18 +71,7 @@ public class PlaybackParameterDialog extends DialogFragment { @State boolean skipSilence = DEFAULT_SKIP_SILENCE; - private SeekBar tempoSlider; - private TextView tempoCurrentText; - private TextView tempoStepDownText; - private TextView tempoStepUpText; - - private SeekBar pitchSlider; - private TextView pitchCurrentText; - private TextView pitchStepDownText; - private TextView pitchStepUpText; - - private CheckBox unhookingCheckbox; - private CheckBox skipSilenceCheckbox; + private DialogPlaybackParameterBinding binding; public static PlaybackParameterDialog newInstance( final double playbackTempo, @@ -110,7 +98,7 @@ public static PlaybackParameterDialog newInstance( //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(final Context context) { + public void onAttach(@NonNull final Context context) { super.onAttach(context); if (context instanceof Callback) { callback = (Callback) context; @@ -120,7 +108,7 @@ public void onAttach(final Context context) { } @Override - public void onSaveInstanceState(final Bundle outState) { + public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } @@ -135,12 +123,12 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); Icepick.restoreInstanceState(this, savedInstanceState); - final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - initUI(view); + binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); + initUI(); initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) - .setView(view) + .setView(binding.getRoot()) .setCancelable(true) .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { setAndUpdateTempo(initialTempo); @@ -163,37 +151,21 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void initUI(@NonNull final View rootView) { + private void initUI() { // Tempo - tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar)); - tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText)); - tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp)); - tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown)); - - setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); - setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); // 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)); - - setText(rootView, R.id.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); - setText(rootView, R.id.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + setText(binding.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); + setText(binding.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)); + setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); + setupStepTextView(binding.stepSizeFivePercent, STEP_5_PERCENT_VALUE); + setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); + setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); + setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); } private TextView setText( @@ -205,59 +177,47 @@ private TextView setText( return textView; } - private TextView setText( - final View rootView, - @IdRes final int idRes, - final DoubleFunction formatter, - final double value - ) { - final TextView textView = rootView.findViewById(idRes); - setText(textView, formatter, value); - return textView; - } - private void setupStepTextView( - final View rootView, - @IdRes final int idRes, + final TextView textView, final double stepSizeValue ) { - setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue) + setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue) .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } private void initUIData() { // Tempo - tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); setAndUpdateTempo(tempo); - tempoSlider.setOnSeekBarChangeListener( + binding.tempoSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); registerOnStepClickListener( - tempoStepDownText, tempo, -1, this::onTempoSliderUpdated); + binding.tempoStepDown, tempo, -1, this::onTempoSliderUpdated); registerOnStepClickListener( - tempoStepUpText, tempo, 1, this::onTempoSliderUpdated); + binding.tempoStepUp, tempo, 1, this::onTempoSliderUpdated); // Pitch - pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + binding.pitchSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); setAndUpdatePitch(pitch); - pitchSlider.setOnSeekBarChangeListener( + binding.pitchSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); registerOnStepClickListener( - pitchStepDownText, pitch, -1, this::onPitchSliderUpdated); + binding.pitchStepDown, pitch, -1, this::onPitchSliderUpdated); registerOnStepClickListener( - pitchStepUpText, pitch, 1, this::onPitchSliderUpdated); + binding.pitchStepUp, pitch, 1, this::onPitchSliderUpdated); // Steps setAndUpdateStepSize(stepSize); // Bottom controls // restore whether pitch and tempo are unhooked or not - unhookingCheckbox.setChecked(PreferenceManager + binding.unhookCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) .getBoolean(getString(R.string.playback_unhook_key), true)); - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + binding.unhookCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() @@ -271,7 +231,7 @@ private void initUIData() { }); setAndUpdateSkipSilence(skipSilence); - skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { skipSilence = isChecked; updateCallback(); }); @@ -291,16 +251,16 @@ private void registerOnStepClickListener( private void setAndUpdateStepSize(final double newStepSize) { this.stepSize = newStepSize; - tempoStepUpText.setText(getStepUpPercentString(newStepSize)); - tempoStepDownText.setText(getStepDownPercentString(newStepSize)); + binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); + binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); - pitchStepUpText.setText(getStepUpPercentString(newStepSize)); - pitchStepDownText.setText(getStepDownPercentString(newStepSize)); + binding.pitchStepUp.setText(getStepUpPercentString(newStepSize)); + binding.pitchStepDown.setText(getStepDownPercentString(newStepSize)); } private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; - skipSilenceCheckbox.setChecked(newSkipSilence); + binding.skipSilenceCheckbox.setChecked(newSkipSilence); } /*////////////////////////////////////////////////////////////////////////// @@ -333,7 +293,7 @@ public void onStopTrackingTouch(final SeekBar seekBar) { } private void onTempoSliderUpdated(final double newTempo) { - if (!unhookingCheckbox.isChecked()) { + if (!binding.unhookCheckbox.isChecked()) { setSliders(newTempo); } else { setAndUpdateTempo(newTempo); @@ -341,7 +301,7 @@ private void onTempoSliderUpdated(final double newTempo) { } private void onPitchSliderUpdated(final double newPitch) { - if (!unhookingCheckbox.isChecked()) { + if (!binding.unhookCheckbox.isChecked()) { setSliders(newPitch); } else { setAndUpdatePitch(newPitch); @@ -355,14 +315,14 @@ private void setSliders(final double newValue) { private void setAndUpdateTempo(final double newTempo) { this.tempo = newTempo; - tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); - setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo); + binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); + setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); } private void setAndUpdatePitch(final double newPitch) { this.pitch = newPitch; - pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); - setText(pitchCurrentText, PlayerHelper::formatPitch, pitch); + binding.pitchSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); + setText(binding.pitchCurrentText, PlayerHelper::formatPitch, pitch); } /*////////////////////////////////////////////////////////////////////////// From 6e0c3804097a1e3749d329c82e8cca3c72d7ec18 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 28 Feb 2022 21:02:43 +0100 Subject: [PATCH 004/240] Remove redundant attributes --- .../res/layout/dialog_playback_parameter.xml | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 862b2ea671f..27cf0dbd696 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -39,7 +39,6 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="?attr/selectableItemBackground" android:clickable="true" @@ -57,9 +56,7 @@ android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_toStartOf="@id/tempoStepUp" - android:layout_toLeftOf="@id/tempoStepUp" android:layout_toEndOf="@id/tempoStepDown" - android:layout_toRightOf="@id/tempoStepDown" android:orientation="horizontal"> Date: Mon, 28 Feb 2022 21:04:24 +0100 Subject: [PATCH 005/240] Remove invalid parameters --- app/src/main/res/layout/dialog_playback_parameter.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 27cf0dbd696..87c26b831da 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -463,8 +463,6 @@ android:id="@+id/adjustBySemitonesCheckbox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@id/skipSilenceCheckbox" - android:layout_centerHorizontal="true" android:checked="false" android:clickable="true" android:focusable="true" From a4c083e7f98c535993c9e22d9d3ae99d5c410a0c Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 1 Mar 2022 20:52:48 +0100 Subject: [PATCH 006/240] Rework dialog * De-Duplicated some fields * Use a container for the pitch controls * Name pitch related elements correctly --- .../res/layout/dialog_playback_parameter.xml | 302 +++++++++--------- 1 file changed, 153 insertions(+), 149 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 87c26b831da..47394b724ee 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -29,7 +29,7 @@ @@ -146,203 +146,207 @@ android:textStyle="bold" /> - - + android:layout_marginTop="3dp"> - - + tools:text="-5%" /> + + + + + + + + + + + - - + tools:text="+5%" /> - - - - - - - - - + tools:ignore="HardcodedText" /> + + + + + + + + + + + - - - - Date: Tue, 1 Mar 2022 21:19:30 +0100 Subject: [PATCH 007/240] Shrunk dialog a bit --- app/src/main/res/layout/dialog_playback_parameter.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 47394b724ee..1ab9a95e953 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -356,7 +356,7 @@ From dae5aa38a83a892f43a3589a21bada854601273d Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:58:14 +0100 Subject: [PATCH 008/240] Fine tuned dialog (no scrollers in fullscreen on 5in phone) --- .../res/layout/dialog_playback_parameter.xml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 1ab9a95e953..75269ffd3e0 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -31,7 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/tempoControlText" - android:layout_marginTop="3dp" + android:layout_marginTop="1dp" android:orientation="horizontal"> @@ -129,9 +129,9 @@ android:layout_height="1dp" android:layout_below="@id/tempoControl" android:layout_marginStart="12dp" - android:layout_marginTop="6dp" - android:layout_marginEnd="6dp" - android:layout_marginBottom="6dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="12dp" + android:layout_marginBottom="4dp" android:background="?attr/separator_color" /> + android:layout_marginTop="1dp"> @@ -319,7 +319,7 @@ android:layout_height="wrap_content" android:layout_below="@+id/pitchSemitoneCurrentText" android:max="24" - android:paddingBottom="4dp" + android:paddingBottom="2dp" android:progress="12" /> @@ -348,9 +348,9 @@ android:layout_height="1dp" android:layout_below="@+id/pitchControlContainer" android:layout_marginStart="12dp" - android:layout_marginTop="6dp" + android:layout_marginTop="4dp" android:layout_marginEnd="12dp" - android:layout_marginBottom="6dp" + android:layout_marginBottom="4dp" android:background="?attr/separator_color" /> Date: Wed, 2 Mar 2022 21:00:19 +0100 Subject: [PATCH 009/240] Reworked/Implemented PlaybackParameterDialog functionallity * Add support for semitones * Fixed some minor bugs * Improved some methods --- .../helper/PlaybackParameterDialog.java | 312 +++++++++++++----- .../player/helper/PlayerSemitoneHelper.java | 37 +++ 2 files changed, 260 insertions(+), 89 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index e1874fec0b9..709216ece47 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -8,11 +8,14 @@ import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; import android.widget.SeekBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; @@ -22,8 +25,10 @@ import org.schabi.newpipe.util.SliderStrategy; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.DoubleConsumer; import java.util.function.DoubleFunction; +import java.util.function.DoubleSupplier; import icepick.Icepick; import icepick.State; @@ -32,8 +37,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final String TAG = "PlaybackParameterDialog"; // Minimum allowable range in ExoPlayer - private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; - private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; + private static final double MIN_PLAYBACK_VALUE = 0.10f; + private static final double MAX_PLAYBACK_VALUE = 3.00f; private static final double STEP_1_PERCENT_VALUE = 0.01f; private static final double STEP_5_PERCENT_VALUE = 0.05f; @@ -42,30 +47,42 @@ public class PlaybackParameterDialog extends DialogFragment { private static final double STEP_100_PERCENT_VALUE = 1.00f; private static final double DEFAULT_TEMPO = 1.00f; - private static final double DEFAULT_PITCH = 1.00f; + private static final double DEFAULT_PITCH_PERCENT = 1.00f; private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( - MINIMUM_PLAYBACK_VALUE, - MAXIMUM_PLAYBACK_VALUE, + MIN_PLAYBACK_VALUE, + MAX_PLAYBACK_VALUE, 1.00f, 10_000); + 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); + } + }; + @Nullable private Callback callback; @State double initialTempo = DEFAULT_TEMPO; @State - double initialPitch = DEFAULT_PITCH; + double initialPitchPercent = DEFAULT_PITCH_PERCENT; @State boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; @State double tempo = DEFAULT_TEMPO; @State - double pitch = DEFAULT_PITCH; + double pitchPercent = DEFAULT_PITCH_PERCENT; @State double stepSize = DEFAULT_STEP; @State @@ -83,11 +100,11 @@ public static PlaybackParameterDialog newInstance( dialog.callback = callback; dialog.initialTempo = playbackTempo; - dialog.initialPitch = playbackPitch; + dialog.initialPitchPercent = playbackPitch; dialog.initialSkipSilence = playbackSkipSilence; dialog.tempo = dialog.initialTempo; - dialog.pitch = dialog.initialPitch; + dialog.pitchPercent = dialog.initialPitchPercent; dialog.skipSilence = dialog.initialSkipSilence; return dialog; @@ -125,20 +142,19 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); initUI(); - initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setView(binding.getRoot()) .setCancelable(true) .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { setAndUpdateTempo(initialTempo); - setAndUpdatePitch(initialPitch); + setAndUpdatePitch(initialPitchPercent); setAndUpdateSkipSilence(initialSkipSilence); updateCallback(); }) .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> { setAndUpdateTempo(DEFAULT_TEMPO); - setAndUpdatePitch(DEFAULT_PITCH); + setAndUpdatePitch(DEFAULT_PITCH_PERCENT); setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE); updateCallback(); }) @@ -153,12 +169,63 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { private void initUI() { // Tempo - setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); - setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PLAYBACK_VALUE); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PLAYBACK_VALUE); - // Pitch - setText(binding.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); - setText(binding.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + setAndUpdateTempo(tempo); + binding.tempoSeekbar.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener( + QUADRATIC_STRATEGY, + this::onTempoSliderUpdated)); + + registerOnStepClickListener( + binding.tempoStepDown, + () -> tempo, + -1, + this::onTempoSliderUpdated); + registerOnStepClickListener( + binding.tempoStepUp, + () -> tempo, + 1, + this::onTempoSliderUpdated); + + // Pitch - Percent + setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); + setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); + + binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + 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); // Steps setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); @@ -166,6 +233,34 @@ private void initUI() { setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); + + setAndUpdateStepSize(stepSize); + + // 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 + setSliders(Math.min(pitchPercent, tempo)); + updateCallback(); + } + }); + + setAndUpdateSkipSilence(skipSilence); + binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + skipSilence = isChecked; + updateCallback(); + }); + + bindCheckboxWithBoolPref( + binding.adjustBySemitonesCheckbox, + R.string.playback_adjust_by_semitones_key, + false, + this::showPitchSemitonesOrPercent + ); } private TextView setText( @@ -177,6 +272,31 @@ private TextView setText( return textView; } + 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( + currentValueSupplier.getAsDouble() + 1 * stepSize * direction); + 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(); + }); + } + private void setupStepTextView( final TextView textView, final double stepSizeValue @@ -185,82 +305,71 @@ private void setupStepTextView( .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } - private void initUIData() { - // Tempo - binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); - setAndUpdateTempo(tempo); - binding.tempoSeekbar.setOnSeekBarChangeListener( - getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); - - registerOnStepClickListener( - binding.tempoStepDown, tempo, -1, this::onTempoSliderUpdated); - registerOnStepClickListener( - binding.tempoStepUp, tempo, 1, this::onTempoSliderUpdated); + private void setAndUpdateStepSize(final double newStepSize) { + this.stepSize = newStepSize; - // Pitch - binding.pitchSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); - setAndUpdatePitch(pitch); - binding.pitchSeekbar.setOnSeekBarChangeListener( - getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); + binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); + binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); - registerOnStepClickListener( - binding.pitchStepDown, pitch, -1, this::onPitchSliderUpdated); - registerOnStepClickListener( - binding.pitchStepUp, pitch, 1, this::onPitchSliderUpdated); + binding.pitchPercentStepUp.setText(getStepUpPercentString(newStepSize)); + binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize)); + } - // Steps - setAndUpdateStepSize(stepSize); + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { + this.skipSilence = newSkipSilence; + binding.skipSilenceCheckbox.setChecked(newSkipSilence); + } - // Bottom controls - // restore whether pitch and tempo are unhooked or not - binding.unhookCheckbox.setChecked(PreferenceManager + private void bindCheckboxWithBoolPref( + @NonNull final CheckBox checkBox, + @StringRes final int resId, + final boolean defaultValue, + @Nullable final Consumer onInitialValueOrValueChange + ) { + final boolean prefValue = PreferenceManager .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_unhook_key), true)); + .getBoolean(getString(resId), defaultValue); + + checkBox.setChecked(prefValue); - binding.unhookCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + if (onInitialValueOrValueChange != null) { + onInitialValueOrValueChange.accept(prefValue); + } + + checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() - .putBoolean(getString(R.string.playback_unhook_key), isChecked) + .putBoolean(getString(resId), isChecked) .apply(); - if (!isChecked) { - // when unchecked, slide back to the minimum of current tempo or pitch - setSliders(Math.min(pitch, tempo)); + if (onInitialValueOrValueChange != null) { + onInitialValueOrValueChange.accept(isChecked); } }); - - setAndUpdateSkipSilence(skipSilence); - binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - skipSilence = isChecked; - updateCallback(); - }); - } - - private void registerOnStepClickListener( - final TextView stepTextView, - final double currentValue, - final double direction, // -1 for step down, +1 for step up - final DoubleConsumer newValueConsumer - ) { - stepTextView.setOnClickListener(view -> - newValueConsumer.accept(currentValue * direction) - ); } - private void setAndUpdateStepSize(final double newStepSize) { - this.stepSize = newStepSize; - - binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); - binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); - - binding.pitchStepUp.setText(getStepUpPercentString(newStepSize)); - binding.pitchStepDown.setText(getStepDownPercentString(newStepSize)); - } - - private void setAndUpdateSkipSilence(final boolean newSkipSilence) { - this.skipSilence = newSkipSilence; - binding.skipSilenceCheckbox.setChecked(newSkipSilence); + private void showPitchSemitonesOrPercent(final boolean semitones) { + 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(); + } + } } /*////////////////////////////////////////////////////////////////////////// @@ -268,14 +377,15 @@ private void setAndUpdateSkipSilence(final boolean newSkipSilence) { //////////////////////////////////////////////////////////////////////////*/ private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( + final SliderStrategy sliderStrategy, final DoubleConsumer newValueConsumer ) { return new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - if (fromUser) { // this change is first in chain - newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress)); + if (fromUser) { // ensure that the user triggered the change + newValueConsumer.accept(sliderStrategy.valueOf(progress)); updateCallback(); } } @@ -300,7 +410,7 @@ private void onTempoSliderUpdated(final double newTempo) { } } - private void onPitchSliderUpdated(final double newPitch) { + private void onPitchPercentSliderUpdated(final double newPitch) { if (!binding.unhookCheckbox.isChecked()) { setSliders(newPitch); } else { @@ -314,15 +424,39 @@ private void setSliders(final double newValue) { } private void setAndUpdateTempo(final double newTempo) { - this.tempo = newTempo; + this.tempo = calcValidTempo(newTempo); + binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); } private void setAndUpdatePitch(final double newPitch) { - this.pitch = newPitch; - binding.pitchSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); - setText(binding.pitchCurrentText, PlayerHelper::formatPitch, pitch); + 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 calcValidTempo(final double newTempo) { + return Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newTempo)); + } + + private double calcValidPitch(final double newPitch) { + final double calcPitch = + Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); + + if (!binding.adjustBySemitonesCheckbox.isChecked()) { + return calcPitch; + } + + return PlayerSemitoneHelper.semitonesToPercent( + PlayerSemitoneHelper.percentToSemitones(calcPitch)); } /*////////////////////////////////////////////////////////////////////////// @@ -335,12 +469,12 @@ private void updateCallback() { } if (DEBUG) { Log.d(TAG, "Updating callback: " - + "tempo = [" + tempo + "], " - + "pitch = [" + pitch + "], " - + "skipSilence = [" + skipSilence + "]" + + "tempo = " + tempo + ", " + + "pitchPercent = " + pitchPercent + ", " + + "skipSilence = " + skipSilence ); } - callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); + callback.onPlaybackParameterChanged((float) tempo, (float) pitchPercent, skipSilence); } @NonNull diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java new file mode 100644 index 00000000000..abbcc2c822f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -0,0 +1,37 @@ +package org.schabi.newpipe.player.helper; + +/** + * Converts between percent and 12-tone equal temperament semitones. + *
+ * @see + * + * Wikipedia: Equal temperament#Twelve-tone equal temperament + * + */ +public final class PlayerSemitoneHelper { + public static final int TONES = 12; + + private PlayerSemitoneHelper() { + // No impl + } + + public static String formatPitchSemitones(final double percent) { + return formatPitchSemitones(percentToSemitones(percent)); + } + + public static String formatPitchSemitones(final int semitones) { + return semitones > 0 ? "+" + semitones : "" + semitones; + } + + public static double semitonesToPercent(final int semitones) { + return Math.pow(2, ensureSemitonesInRange(semitones) / (double) TONES); + } + + public static int percentToSemitones(final double percent) { + return ensureSemitonesInRange((int) Math.round(TONES * Math.log(percent) / Math.log(2))); + } + + private static int ensureSemitonesInRange(final int semitones) { + return Math.max(-TONES, Math.min(TONES, semitones)); + } +} From 321cf8bf7d7a419913f58697cb4cb7e5630212d7 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 21:33:21 +0100 Subject: [PATCH 010/240] Fine tuned dialog --- .../res/layout/dialog_playback_parameter.xml | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 75269ffd3e0..640475f392a 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -1,5 +1,6 @@ + android:orientation="horizontal" + tools:visibility="gone"> + tools:text="0" + tools:ignore="RelativeOverlap" /> + android:textColor="?attr/colorAccent" + tools:text="1%" /> + android:textColor="?attr/colorAccent" + tools:text="5%" /> + android:textColor="?attr/colorAccent" + tools:text="10%" /> + android:textColor="?attr/colorAccent" + tools:text="25%" /> - + android:textColor="?attr/colorAccent" + tools:text="100%" /> Date: Fri, 4 Mar 2022 21:34:45 +0100 Subject: [PATCH 011/240] Code improvements regarding stepSize --- .../helper/PlaybackParameterDialog.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 709216ece47..eab64e48349 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -84,8 +84,6 @@ public double valueOf(final int progress) { @State double pitchPercent = DEFAULT_PITCH_PERCENT; @State - double stepSize = DEFAULT_STEP; - @State boolean skipSilence = DEFAULT_SKIP_SILENCE; private DialogPlaybackParameterBinding binding; @@ -228,13 +226,10 @@ private void initUI() { this::onPitchPercentSliderUpdated); // Steps - setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); - setupStepTextView(binding.stepSizeFivePercent, STEP_5_PERCENT_VALUE); - setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); - setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); - setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); - - setAndUpdateStepSize(stepSize); + getStepSizeComponentMappings() + .forEach(this::setupStepTextView); + // Initialize UI + setStepSizeToUI(getCurrentStepSize()); // Bottom controls bindCheckboxWithBoolPref( @@ -263,13 +258,12 @@ private void initUI() { ); } - private TextView setText( + private void setText( final TextView textView, final DoubleFunction formatter, final double value ) { Objects.requireNonNull(textView).setText(formatter.apply(value)); - return textView; } private void registerOnStepClickListener( @@ -280,7 +274,7 @@ private void registerOnStepClickListener( ) { stepTextView.setOnClickListener(view -> { newValueConsumer.accept( - currentValueSupplier.getAsDouble() + 1 * stepSize * direction); + currentValueSupplier.getAsDouble() + 1 * getCurrentStepSize() * direction); updateCallback(); }); } @@ -315,16 +309,22 @@ private void setAndUpdateStepSize(final double newStepSize) { binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize)); } + private double getCurrentStepSize() { + return PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP); + } + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; binding.skipSilenceCheckbox.setChecked(newSkipSilence); } + @SuppressWarnings("SameParameterValue") // this method was written to be reusable private void bindCheckboxWithBoolPref( @NonNull final CheckBox checkBox, @StringRes final int resId, final boolean defaultValue, - @Nullable final Consumer onInitialValueOrValueChange + @NonNull final Consumer onInitialValueOrValueChange ) { final boolean prefValue = PreferenceManager .getDefaultSharedPreferences(requireContext()) @@ -332,9 +332,7 @@ private void bindCheckboxWithBoolPref( checkBox.setChecked(prefValue); - if (onInitialValueOrValueChange != null) { - onInitialValueOrValueChange.accept(prefValue); - } + onInitialValueOrValueChange.accept(prefValue); checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not @@ -343,9 +341,7 @@ private void bindCheckboxWithBoolPref( .putBoolean(getString(resId), isChecked) .apply(); - if (onInitialValueOrValueChange != null) { - onInitialValueOrValueChange.accept(isChecked); - } + onInitialValueOrValueChange.accept(isChecked); }); } From 4b0653658273f456ddbc56a86b1695073d982895 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 21:37:11 +0100 Subject: [PATCH 012/240] Reworked switching to semitones Using an expandable Tab-like component instead of a combobox --- .../schabi/newpipe/local/feed/FeedFragment.kt | 16 +- .../helper/PlaybackParameterDialog.java | 167 ++++++++++++++---- .../schabi/newpipe/util/DrawableResolver.kt | 26 +++ .../res/layout/dialog_playback_parameter.xml | 59 +++++-- app/src/main/res/values/strings.xml | 2 + 5 files changed, 208 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index e97629f31a9..e8e78fedae9 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -25,7 +25,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Typeface -import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.os.Parcelable @@ -37,7 +36,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.Button -import androidx.annotation.AttrRes import androidx.annotation.Nullable import androidx.appcompat.app.AlertDialog import androidx.appcompat.content.res.AppCompatResources @@ -77,6 +75,7 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils +import org.schabi.newpipe.util.DrawableResolver.Companion.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams @@ -579,19 +578,6 @@ class FeedFragment : BaseStateFragment() { lastNewItemsCount = highlightCount } - private fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return androidx.core.content.ContextCompat.getDrawable( - context, - android.util.TypedValue().apply { - context.theme.resolveAttribute( - attrResId, - this, - true - ) - }.resourceId - ) - } - private fun showNewItemsLoaded() { tryGetNewItemsLoadedButton()?.clearAnimation() tryGetNewItemsLoadedButton() diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index eab64e48349..902222cc5b3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -1,10 +1,14 @@ package org.schabi.newpipe.player.helper; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; import static org.schabi.newpipe.player.Player.DEBUG; +import static org.schabi.newpipe.util.DrawableResolver.resolveDrawable; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import android.app.Dialog; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -22,8 +26,11 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.SliderStrategy; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import java.util.function.DoubleConsumer; @@ -40,6 +47,9 @@ public class PlaybackParameterDialog extends DialogFragment { private static final double MIN_PLAYBACK_VALUE = 0.10f; private static final double MAX_PLAYBACK_VALUE = 3.00f; + private static final boolean PITCH_CTRL_MODE_PERCENT = false; + private static final boolean PITCH_CTRL_MODE_SEMITONE = true; + 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; @@ -188,6 +198,22 @@ private void initUI() { 1, this::onTempoSliderUpdated); + // Pitch + binding.pitchToogleControlModes.setOnClickListener(v -> { + final boolean isCurrentlyVisible = + binding.pitchControlModeTabs.getVisibility() == View.GONE; + binding.pitchControlModeTabs.setVisibility(isCurrentlyVisible + ? View.VISIBLE + : View.GONE); + animateRotation(binding.pitchToogleControlModes, + Player.DEFAULT_CONTROLS_DURATION, + isCurrentlyVisible ? 180 : 0); + }); + + getPitchControlModeComponentMappings() + .forEach(this::setupPitchControlModeTextView); + changePitchControlMode(isCurrentPitchControlModeSemitone()); + // Pitch - Percent setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); @@ -249,13 +275,6 @@ private void initUI() { skipSilence = isChecked; updateCallback(); }); - - bindCheckboxWithBoolPref( - binding.adjustBySemitonesCheckbox, - R.string.playback_adjust_by_semitones_key, - false, - this::showPitchSemitonesOrPercent - ); } private void setText( @@ -291,17 +310,114 @@ private void registerOnSemitoneStepClickListener( }); } + 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 getPitchControlModeComponentMappings() { + final Map mappings = new HashMap<>(); + mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent); + mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); + return mappings; + } + + private void changePitchControlMode(final boolean semitones) { + // Bring all textviews into a normal state + final Map 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(); + } + } + } + + private boolean isCurrentPitchControlModeSemitone() { + return PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getBoolean( + getString(R.string.playback_adjust_by_semitones_key), + PITCH_CTRL_MODE_PERCENT); + } + private void setupStepTextView( - final TextView textView, - final double stepSizeValue + final double stepSizeValue, + final TextView textView ) { - setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue) - .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); + setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue); + textView.setOnClickListener(view -> { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue) + .apply(); + + setStepSizeToUI(stepSizeValue); + }); } - private void setAndUpdateStepSize(final double newStepSize) { - this.stepSize = newStepSize; + private Map getStepSizeComponentMappings() { + final Map mappings = new HashMap<>(); + mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent); + mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent); + mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent); + mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent); + mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent); + return mappings; + } + + private void setStepSizeToUI(final double newStepSize) { + // Bring all textviews into a normal state + final Map 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) + })); + } + // Bind to the corresponding control components binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); @@ -345,29 +461,6 @@ private void bindCheckboxWithBoolPref( }); } - private void showPitchSemitonesOrPercent(final boolean semitones) { - 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(); - } - } - } - /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ @@ -447,7 +540,7 @@ private double calcValidPitch(final double newPitch) { final double calcPitch = Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); - if (!binding.adjustBySemitonesCheckbox.isChecked()) { + if (!isCurrentPitchControlModeSemitone()) { return calcPitch; } diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt new file mode 100644 index 00000000000..50f875257fc --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -0,0 +1,26 @@ +package org.schabi.newpipe.util + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.annotation.AttrRes + +/** + * Utility class for resolving [Drawables](Drawable) + */ +class DrawableResolver { + companion object { + @JvmStatic + fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { + return androidx.core.content.ContextCompat.getDrawable( + context, + android.util.TypedValue().apply { + context.theme.resolveAttribute( + attrResId, + this, + true + ) + }.resourceId + ) + } + } +} diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 640475f392a..e402f4fb170 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -146,11 +146,59 @@ android:textColor="?attr/colorAccent" android:textStyle="bold" /> + + + + + + + + + + - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 792e6414b52..af2921ccaa0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -501,6 +501,8 @@ Step Tempo step Reset + Percent + Semitone In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully. \nYou must accept it to send us the bug report. From 20602889be38e38c25e6022f56be83860ef34313 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:02:39 +0100 Subject: [PATCH 013/240] Added some doc and abstracted more methods --- .../helper/PlaybackParameterDialog.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 902222cc5b3..4ab8f9248b4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -172,7 +172,7 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { } /*////////////////////////////////////////////////////////////////////////// - // Control Views + // UI Initialization and Control //////////////////////////////////////////////////////////////////////////*/ private void initUI() { @@ -265,8 +265,7 @@ private void initUI() { isChecked -> { if (!isChecked) { // when unchecked, slide back to the minimum of current tempo or pitch - setSliders(Math.min(pitchPercent, tempo)); - updateCallback(); + ensureHookIsValidAndUpdateCallBack(); } }); @@ -277,6 +276,8 @@ private void initUI() { }); } + // -- General formatting -- + private void setText( final TextView textView, final DoubleFunction formatter, @@ -285,6 +286,8 @@ private void setText( Objects.requireNonNull(textView).setText(formatter.apply(value)); } + // -- Steps -- + private void registerOnStepClickListener( final TextView stepTextView, final DoubleSupplier currentValueSupplier, @@ -310,6 +313,8 @@ private void registerOnSemitoneStepClickListener( }); } + // -- Pitch -- + private void setupPitchControlModeTextView( final boolean semitones, final TextView textView @@ -367,6 +372,9 @@ private void changePitchControlMode(final boolean semitones) { this.onPitchPercentSliderUpdated(newPitchPercent); updateCallback(); } + } else if (!binding.unhookCheckbox.isChecked()) { + // When changing to percent it's possible that tempo is != pitch + ensureHookIsValidAndUpdateCallBack(); } } @@ -377,6 +385,8 @@ private boolean isCurrentPitchControlModeSemitone() { PITCH_CTRL_MODE_PERCENT); } + // -- Steps (Set) -- + private void setupStepTextView( final double stepSizeValue, final TextView textView @@ -430,6 +440,8 @@ private double getCurrentStepSize() { .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP); } + // -- Additional options -- + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; binding.skipSilenceCheckbox.setChecked(newSkipSilence); @@ -461,6 +473,18 @@ private void bindCheckboxWithBoolPref( }); } + /** + * Ensures that the slider hook is valid and if not sets and updates the sliders accordingly. + *
+ * You have to ensure by yourself that the hooking is active. + */ + private void ensureHookIsValidAndUpdateCallBack() { + if (tempo != pitchPercent) { + setSliders(Math.min(tempo, pitchPercent)); + updateCallback(); + } + } + /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ From 1b8c517e3ea1924787eeff075a575414a5f3b544 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:21:17 +0100 Subject: [PATCH 014/240] Removed unused strings --- app/src/main/res/values/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af2921ccaa0..1f87ae9fb6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -497,9 +497,7 @@ Pitch Unhook (may cause distortion) Fast-forward during silence - Adjust pitch by musical semitones Step - Tempo step Reset Percent Semitone From 44dada9e60b23f500c3904af8578a874e78c0c5a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 6 Mar 2022 16:10:42 +0100 Subject: [PATCH 015/240] Use better Kotlin syntax From the PR review --- .../schabi/newpipe/local/feed/FeedFragment.kt | 2 +- .../schabi/newpipe/util/DrawableResolver.kt | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index e8e78fedae9..55810284f2f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -75,7 +75,7 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils -import org.schabi.newpipe.util.DrawableResolver.Companion.resolveDrawable +import org.schabi.newpipe.util.DrawableResolver.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt index 50f875257fc..ccc9e7dd4af 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -2,25 +2,24 @@ package org.schabi.newpipe.util import android.content.Context import android.graphics.drawable.Drawable +import android.util.TypedValue import androidx.annotation.AttrRes /** * Utility class for resolving [Drawables](Drawable) */ -class DrawableResolver { - companion object { - @JvmStatic - fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return androidx.core.content.ContextCompat.getDrawable( - context, - android.util.TypedValue().apply { - context.theme.resolveAttribute( - attrResId, - this, - true - ) - }.resourceId - ) - } +object DrawableResolver { + @JvmStatic + fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { + return androidx.core.content.ContextCompat.getDrawable( + context, + TypedValue().apply { + context.theme.resolveAttribute( + attrResId, + this, + true + ) + }.resourceId + ) } } From b9190eddfe1563cc5269caf2ca90a997db85a920 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 7 Mar 2022 20:30:25 +0100 Subject: [PATCH 016/240] Update DrawableResolver.kt Nicer import :wink: --- app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt index ccc9e7dd4af..8a728bfbfd2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.drawable.Drawable import android.util.TypedValue import androidx.annotation.AttrRes +import androidx.core.content.ContextCompat /** * Utility class for resolving [Drawables](Drawable) @@ -11,7 +12,7 @@ import androidx.annotation.AttrRes object DrawableResolver { @JvmStatic fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return androidx.core.content.ContextCompat.getDrawable( + return ContextCompat.getDrawable( context, TypedValue().apply { context.theme.resolveAttribute( From 0f551baf3729121dadc0597d615aab58ea13e941 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 16 Mar 2022 14:53:33 +0100 Subject: [PATCH 017/240] Refactored code --- .../helper/PlaybackParameterDialog.java | 24 +++++++++---------- .../player/helper/PlayerSemitoneHelper.java | 9 +++---- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 4ab8f9248b4..26caa1b2017 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -44,8 +44,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final String TAG = "PlaybackParameterDialog"; // Minimum allowable range in ExoPlayer - private static final double MIN_PLAYBACK_VALUE = 0.10f; - private static final double MAX_PLAYBACK_VALUE = 3.00f; + private static final double MIN_PITCH_OR_SPEED = 0.10f; + private static final double MAX_PITCH_OR_SPEED = 3.00f; private static final boolean PITCH_CTRL_MODE_PERCENT = false; private static final boolean PITCH_CTRL_MODE_SEMITONE = true; @@ -62,8 +62,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final boolean DEFAULT_SKIP_SILENCE = false; private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( - MIN_PLAYBACK_VALUE, - MAX_PLAYBACK_VALUE, + MIN_PITCH_OR_SPEED, + MAX_PITCH_OR_SPEED, 1.00f, 10_000); @@ -177,10 +177,10 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { private void initUI() { // Tempo - setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PLAYBACK_VALUE); - setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PITCH_OR_SPEED); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PITCH_OR_SPEED); - binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED)); setAndUpdateTempo(tempo); binding.tempoSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener( @@ -215,10 +215,10 @@ private void initUI() { changePitchControlMode(isCurrentPitchControlModeSemitone()); // Pitch - Percent - setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); - setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); + setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PITCH_OR_SPEED); + setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PITCH_OR_SPEED); - binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED)); setAndUpdatePitch(pitchPercent); binding.pitchPercentSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener( @@ -557,12 +557,12 @@ private void setAndUpdatePitch(final double newPitch) { } private double calcValidTempo(final double newTempo) { - return Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newTempo)); + return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo)); } private double calcValidPitch(final double newPitch) { final double calcPitch = - Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); + Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch)); if (!isCurrentPitchControlModeSemitone()) { return calcPitch; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java index abbcc2c822f..f3a71d7cd9e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -9,7 +9,7 @@ * */ public final class PlayerSemitoneHelper { - public static final int TONES = 12; + public static final int SEMITONE_COUNT = 12; private PlayerSemitoneHelper() { // No impl @@ -24,14 +24,15 @@ public static String formatPitchSemitones(final int semitones) { } public static double semitonesToPercent(final int semitones) { - return Math.pow(2, ensureSemitonesInRange(semitones) / (double) TONES); + return Math.pow(2, ensureSemitonesInRange(semitones) / (double) SEMITONE_COUNT); } public static int percentToSemitones(final double percent) { - return ensureSemitonesInRange((int) Math.round(TONES * Math.log(percent) / Math.log(2))); + return ensureSemitonesInRange( + (int) Math.round(SEMITONE_COUNT * Math.log(percent) / Math.log(2))); } private static int ensureSemitonesInRange(final int semitones) { - return Math.max(-TONES, Math.min(TONES, semitones)); + return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones)); } } From 1dc146322c5666f2a4af9f9d14b5f2aa56626b32 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:34:44 +0100 Subject: [PATCH 018/240] Merged ``DrawableResolver`` into ``ThemeHelper`` --- .../schabi/newpipe/local/feed/FeedFragment.kt | 2 +- .../helper/PlaybackParameterDialog.java | 2 +- .../schabi/newpipe/util/DrawableResolver.kt | 26 ------------------- .../org/schabi/newpipe/util/ThemeHelper.java | 18 +++++++++++++ 4 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 55810284f2f..b291aa03568 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -75,10 +75,10 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils -import org.schabi.newpipe.util.DrawableResolver.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams +import org.schabi.newpipe.util.ThemeHelper.resolveDrawable import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout import java.time.OffsetDateTime import java.util.function.Consumer diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 26caa1b2017..62446b50ec1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -2,8 +2,8 @@ import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; import static org.schabi.newpipe.player.Player.DEBUG; -import static org.schabi.newpipe.util.DrawableResolver.resolveDrawable; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; +import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable; import android.app.Dialog; import android.content.Context; diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt deleted file mode 100644 index 8a728bfbfd2..00000000000 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.schabi.newpipe.util - -import android.content.Context -import android.graphics.drawable.Drawable -import android.util.TypedValue -import androidx.annotation.AttrRes -import androidx.core.content.ContextCompat - -/** - * Utility class for resolving [Drawables](Drawable) - */ -object DrawableResolver { - @JvmStatic - fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return ContextCompat.getDrawable( - context, - TypedValue().apply { - context.theme.resolveAttribute( - attrResId, - this, - true - ) - }.resourceId - ) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 7c47d387f9e..7d06e57b699 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -23,9 +23,11 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.util.TypedValue; import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.app.ActionBar; @@ -227,6 +229,22 @@ public static int resolveColorFromAttr(final Context context, @AttrRes final int return value.data; } + /** + * Resolves a {@link Drawable} by it's id. + * + * @param context Context + * @param attrResId Resource id + * @return the {@link Drawable} + */ + public static Drawable resolveDrawable( + @NonNull final Context context, + @AttrRes final int attrResId + ) { + final TypedValue typedValue = new TypedValue(); + context.getTheme().resolveAttribute(attrResId, typedValue, true); + return ContextCompat.getDrawable(context, typedValue.resourceId); + } + private static String getSelectedThemeKey(final Context context) { final String themeKey = context.getString(R.string.theme_key); final String defaultTheme = context.getResources().getString(R.string.default_theme_value); From a311519314085d5e79e1a55afc7c16dc744cb57b Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 16 Apr 2022 21:24:01 +0200 Subject: [PATCH 019/240] Fix merge conflicts --- .../player/helper/PlaybackParameterDialog.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 62446b50ec1..2d1461aaff0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -27,6 +27,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; import java.util.HashMap; @@ -37,6 +38,8 @@ import java.util.function.DoubleFunction; import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + import icepick.Icepick; import icepick.State; @@ -493,25 +496,16 @@ private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( final SliderStrategy sliderStrategy, final DoubleConsumer newValueConsumer ) { - return new SeekBar.OnSeekBarChangeListener() { + return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(final SeekBar seekBar, final int progress, + public void onProgressChanged(@Nonnull final SeekBar seekBar, + final int progress, final boolean fromUser) { if (fromUser) { // ensure that the user triggered the change newValueConsumer.accept(sliderStrategy.valueOf(progress)); updateCallback(); } } - - @Override - public void onStartTrackingTouch(final SeekBar seekBar) { - // Do nothing - } - - @Override - public void onStopTrackingTouch(final SeekBar seekBar) { - // Do nothing - } }; } From 8ea98b64aa81ac15f3d01e2764be38b50220644e Mon Sep 17 00:00:00 2001 From: LingYinTianMeng <2632252014@qq.com> Date: Sun, 17 Apr 2022 22:23:03 +0800 Subject: [PATCH 020/240] fix issue #7563 --- .../local/playlist/LocalPlaylistFragment.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 0eb56d7169c..7cd2a3ec169 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -414,14 +414,21 @@ public void removeWatchedStreams(final boolean removePartiallyWatched) { } else { final Iterator streamStatesIter = recordManager .loadLocalStreamStateBatch(playlist).blockingGet().iterator(); - while (playlistIter.hasNext()) { final PlaylistStreamEntry playlistItem = playlistIter.next(); final int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); - - final boolean hasState = streamStatesIter.next() != null; - if (indexInHistory < 0 || hasState) { + final StreamStateEntity streamStateEntity = streamStatesIter.next(); + final long duration = playlistItem.toStreamInfoItem().getDuration(); + boolean isFinished = false; + if (streamStateEntity != null) { + isFinished = streamStateEntity.isFinished(duration); + } + final boolean isNotWatchedItem = (streamStateEntity != null + && !isFinished); + if (indexInHistory < 0) { + notWatchedItems.add(playlistItem); + } else if (isNotWatchedItem) { notWatchedItems.add(playlistItem); } else if (!thumbnailVideoRemoved && playlistManager.getPlaylistThumbnail(playlistId) From 047fe21c143fd945de34aa01c5efa4df033cb977 Mon Sep 17 00:00:00 2001 From: kt programs Date: Sat, 30 Apr 2022 17:43:30 +0800 Subject: [PATCH 021/240] Fix hiding player controls when playing from media button DefaultControlDispatcher was removed in ExoPlayer 2.16.0, so the class extending it that handled play/pause was removed in #8020. The new solution is to use an instance of ForwardingPlayer. Call sessionConnector.setPlayer with an instance of ForwardingPlayer that overrides play() and pause() and calls the callback methods. --- .../newpipe/player/helper/MediaSessionManager.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java index c12ba754ad4..a8735dc08bc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java @@ -13,6 +13,7 @@ import androidx.annotation.Nullable; import androidx.media.session.MediaButtonReceiver; +import com.google.android.exoplayer2.ForwardingPlayer; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; @@ -55,7 +56,17 @@ public MediaSessionManager(@NonNull final Context context, sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); - sessionConnector.setPlayer(player); + sessionConnector.setPlayer(new ForwardingPlayer(player) { + @Override + public void play() { + callback.play(); + } + + @Override + public void pause() { + callback.pause(); + } + }); } @Nullable From 173b6c3f00df02e64151ee642323205400d412f2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 30 Apr 2022 21:46:06 +0200 Subject: [PATCH 022/240] Fix wrong NonNull --- .../schabi/newpipe/player/helper/PlaybackParameterDialog.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 2d1461aaff0..7220335d182 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -38,8 +38,6 @@ import java.util.function.DoubleFunction; import java.util.function.DoubleSupplier; -import javax.annotation.Nonnull; - import icepick.Icepick; import icepick.State; @@ -498,7 +496,7 @@ private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( ) { return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@Nonnull final SeekBar seekBar, + public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, final boolean fromUser) { if (fromUser) { // ensure that the user triggered the change From 7e50eed95e25928cc4a40d9308ff38c216a3de0e Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 1 May 2022 20:50:37 +0200 Subject: [PATCH 023/240] Removed unused string resources --- app/src/main/res/values-ar/strings.xml | 3 --- app/src/main/res/values-b+zh+HANS+CN/strings.xml | 3 --- app/src/main/res/values-ca/strings.xml | 1 - app/src/main/res/values-ckb/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 3 --- app/src/main/res/values-de/strings.xml | 3 --- app/src/main/res/values-el/strings.xml | 3 --- app/src/main/res/values-es/strings.xml | 3 --- app/src/main/res/values-et/strings.xml | 3 --- app/src/main/res/values-eu/strings.xml | 3 --- app/src/main/res/values-fa/strings.xml | 3 --- app/src/main/res/values-fr/strings.xml | 3 --- app/src/main/res/values-gl/strings.xml | 2 -- app/src/main/res/values-he/strings.xml | 3 --- app/src/main/res/values-hu/strings.xml | 1 - app/src/main/res/values-ia/strings.xml | 1 - app/src/main/res/values-in/strings.xml | 3 --- app/src/main/res/values-it/strings.xml | 3 --- app/src/main/res/values-ja/strings.xml | 2 -- app/src/main/res/values-nb-rNO/strings.xml | 3 --- app/src/main/res/values-nl/strings.xml | 3 --- app/src/main/res/values-pl/strings.xml | 3 --- app/src/main/res/values-pt-rBR/strings.xml | 3 --- app/src/main/res/values-pt-rPT/strings.xml | 3 --- app/src/main/res/values-pt/strings.xml | 4 ---- app/src/main/res/values-ro/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 5 ----- app/src/main/res/values-sc/strings.xml | 3 --- app/src/main/res/values-sk/strings.xml | 4 ---- app/src/main/res/values-sv/strings.xml | 3 --- app/src/main/res/values-ta/strings.xml | 1 - app/src/main/res/values-te/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 3 --- app/src/main/res/values-uk/strings.xml | 4 ---- app/src/main/res/values-vi/strings.xml | 2 -- app/src/main/res/values-zh-rHK/strings.xml | 2 -- app/src/main/res/values-zh-rTW/strings.xml | 6 ------ 37 files changed, 101 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3bb3048487a..e7da4611ac8 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -730,11 +730,8 @@ إظهار خطأ snackbar لم يتم العثور على مدير ملفات مناسب لهذا الإجراء. \nالرجاء تثبيت مدير ملفات متوافق مع Storage Access Framework. - يتم تشغيله في الخلفية تعليق مثبت LeakCanary غير متوفر - ضبط الصوت من خلال النغمات الموسيقية النصفية - خطوة الإيقاع الافتراضي ExoPlayer تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل. تكوين إشعار مشغل البث الحالي diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index c5b9e96462d..9170166751a 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -670,11 +670,8 @@ 找不到适合此操作的文件管理器。 \n请安装与存储访问框架(SAF)兼容的文件管理器。 NewPipe 遇到了一个错误,点击此处报告此错误 - 已经在后台播放 置顶评论 LeakCanary 不可用 - 以音乐半音调整音高 - 节奏步长 改变加载间隔的大小(当前%s),较低的值可以加快初始的视频加载速度,改变需要重启播放器。 ExoPlayer 默认 配置当前正在播放的串流的通知 diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 42113987505..a42e6894089 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -650,7 +650,6 @@ Inicia el reproductor principal en pantalla completa Llisqueu els elements per eliminar-los Si la rotació automàtica està bloquejada, no inicieu vídeos al mini reproductor, sinó que aneu directament al mode de pantalla completa. Podeu accedir igualment al mini reproductor sortint de pantalla completa - Ja s\'està reproduint en segon pla Notificació d\'informe d\'error Tancar abruptament el reproductor Comprovar si hi ha actualitzacions diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index f621b88bd15..399d2360e6c 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -672,7 +672,6 @@ پیشاندانی ”کڕاش کردنی لێدەرەکە“ سازاندنی پەیامی کێشەیەک پشکنین بۆ نوێکردنەوە - وا لە پاشبنەمادا لێدەدرێت کێشە لە سکاڵا کردنی پەیام پەیامەکانی سکاڵاکردن لە کێشەکان بابەتە نوێیەکانی فیید diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 84c957f1c27..9ba0d1e98ea 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -684,7 +684,6 @@ Vytvořit oznámení o chybě Kontrola aktualizací… Ukázat „Shodit přehrávač“ - Hraje již v pozadí Nové položky feedů Pro tuto akci nebyl nalezen žádný vhodný správce souborů. \nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework. @@ -698,7 +697,5 @@ Shodit přehrávač Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače. LeakCanary není dostupné - Upravit výšku tónů po půltónech - Krok tempa Výchozí ExoPlayer \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1b47fba2fd3..ffc501c6351 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -684,12 +684,9 @@ \nBitte installiere einen Dateimanager oder versuche, \'%s\' in den Downloadeinstellungen zu deaktivieren.
Es wurde kein geeigneter Dateimanager für diese Aktion gefunden. \nBitte installiere einen Storage Access Framework kompatiblen Dateimanager. - Wird bereits im Hintergrund abgespielt Angehefteter Kommentar LeakCanary ist nicht verfügbar - Tonhöhe nach musikalischen Halbtönen anpassen Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players. - Geschwindigkeitsstufe ExoPlayer Standard Benachrichtigungen Benachrichtigen über neue abonnierbare Streams diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index b54b34d609b..e202d2b34f0 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -682,11 +682,8 @@ \nΕγκαταστήστε έναν συμβατό με το Πλαίσιο Πρόσβασης Αποθήκευσης.
Το NewPipe παρουσίασε ένα σφάλμα. Πατήστε για αναφορά Εμφάνιση μιας snackbar σφάλματος - Αναπαράγεται ήδη στο παρασκήνιο Καρφιτσωμένο σχόλιο Το LeakCanary δεν είναι διαθέσιμο - Προσαρμόστε τον τόνο με βάση τα μουσικά ημιτόνια - Βήμα τέμπο Εξ\' ορισμού ExoPlayer Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής. Ειδοποιήσεις diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f386801e297..bb0decc9afa 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -686,12 +686,9 @@ No se encontró ningún gestor de archivos adecuado para esta acción. \nPor favor instale un gestor de archivos compatible con \"Sistema de Acceso al Almacenamiento\". Comentario fijado - Ya se reproduce en segundo plano LeakCanary no está disponible ExoPlayer valor por defecto - Paso de tempo Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor. - Ajustar el tono por semitonos musicales Notificaciones Nuevos streams Notificación del reproductor diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index d04834f8a50..029ff9878d2 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -674,7 +674,6 @@ NewPipe töös tekkis viga, sellest teavitamiseks klõpsi Jooksuta meediamängija kokku Näita veateate akent - Meedia esitamine taustal toimib juba Teavitus vigadest Teavitused vigadest informeerimiseks Tekkis viga, vaata vastavat teadet @@ -709,6 +708,4 @@ Sa oled nüüd selle kanali tellija , Lülita kõik sisse - Reguleeri helikõrgust muusikaliste pooltoonide kaupa - Tempo samm \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 1c710641ebb..d4bddf4a8e3 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -666,7 +666,6 @@ Gehitu bideo hau isatsari Erakutsi \"Itxi erreproduzigailua\" Prozesatzen... Itxoin mesedez - Atzeko planoan erreproduzitzen dagoeneko Erroreen txostenen jakinarazpena Jakinarazpenak erroreen berri emateko NewPipe-k errore bat aurkitu du, sakatu berri emateko @@ -695,8 +694,6 @@ Kanal honetara harpidetu zara , Txandakatu denak - Doitu tonua semitono musikalen arabera - Tempo urratsa Aldatu karga maiztasun tamaina (unean %s). Balio txikiago batek bideoaren hasierako karga azkartu dezake. Erreproduzigailuaren berrabiarazte bat behar du. Harpidetzen jario berriei buruz jakinarazi Ezabatu deskargatutako fitxategi guztiak biltegitik\? diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index d0f4d3d841e..dac49883d88 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -683,10 +683,7 @@ نیوپایپ به خطایی برخورد. برای گزارش، بزنید خطایی رخ داد. آگاهی را ببینید نظر سنجاق شده - در حال پخش در پس‌زمینه لیک‌کاناری موجود نیست - گام سرعت - تنظیم زیر و بم با شبه‌تن‌ها تغییر اندازهٔ بازهٔ بار (هم‌اکنون %s). مقداری پایین‌تر، می‌تواند بار کردن نخستین ویدیو را سرعت بخشد. تغییرها نیاز به یک آغاز دوبارهٔ پخش‌کننده دارند. پیش‌گزیدهٔ اگزوپلیر آگاهی‌ها diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4a4559477c3..1716cc06b06 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -685,11 +685,8 @@ Aucun gestionnaire de fichier approprié n\'a été trouvé pour cette action. \nVeuillez installer un gestionnaire de fichiers ou essayez de désactiver \'%s\' dans les paramètres de téléchargement. Commentaire épinglé - Une lecture est déjà en arrière-plan LeakCanary n\'est pas disponible Modifie la taille de l\'intervalle de chargement (actuellement %s). Une valeur plus faible peut accélérer le chargement initial des vidéos . - Règler la hauteur par demi-tons musicaux - Pas du tempo Valeur par défaut d’ExoPlayer Nouveaux flux Configurer la notification du flux en cours de lecture diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 52225b8439b..7b49adc6d45 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -672,8 +672,6 @@ Enfileirado Procurar actualizacións Procurar manualmente novas versións - Axustar o ton do semitóns musicais - Paso do tempo A procurar actualizacións… A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado Cambia o tamaño do intervalo de carga (actualmente %s). Un valor menor pode acelerar o carregamento do vídeo. Cambios poden precisar un reinicio do reprodutor. diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index a0e6c32a917..4d4350f077d 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -706,11 +706,8 @@ התראת דיווח שגיאה לא נמצאו מנהלי קבצים שמתאימים לפעולה הזאת. \nנא להתקין מנהל קבצים שתומך בתשתית גישה לאחסון. - כבר מתנגן ברקע הערה ננעצה LeakCanary אינה זמינה - התאמת גובה הצליל לפי חצאי טונים מוזיקליים - צעד מקצב ברירת מחדל של ExoPlayer שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש. התראות על תזרימים חדשים להרשמה diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index adaf6a8c028..e5457d3e104 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -683,6 +683,5 @@ %1$s letöltés törölve Rögzített megjegyzés - Már megy a lejátszás a háttérben LeakCanary nem elérhető \ No newline at end of file diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index e9d400be2f5..d5bc90e6f55 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -229,7 +229,6 @@ Aperir con Suggestiones de recerca remote Cargar miniaturas - Notification Monstrante resultatos pro: %s Solmente alicun apparatos pote reproducer videos 2K/4K Initiar le reproductor principal in schermo plen diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 056cdfce40b..1a88bb7471e 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -671,11 +671,8 @@ Tidak ada manajer file yang ditemukan untuk tindakan ini. \nMohon instal sebuah manajer file yang kompatibel dengan Storage Access Framework. Komentar dipin - Sudah diputar di latar belakang LeakCanary tidak tersedia - Langkah tempo Default ExoPlayer - Atur nada berdasarkan semitone musik Ubah ukuran interval pemuatan (saat ini %s). Sebuah nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Membutuhkan sebuah pemulaian ulang pada pemain. Memuat detail stream… Frekuensi pemeriksaan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9f7b74d8ea9..9671f861770 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -683,12 +683,9 @@ Non è stato trovato alcun gestore di file appropriato per questa azione. \nInstallane uno compatibile con Storage Access Framework. Commento in primo piano - Già in riproduzione in sottofondo LeakCanary non è disponibile - Regola il tono secondo i semitoni musicali Predefinito ExoPlayer Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore. - Passo tempo Notifiche di nuove stream dalle iscrizioni Frequenza controllo Richiesta connessione alla rete diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a0699f22907..9a15b25d121 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -663,7 +663,6 @@ エラーが発生しました。通知をご覧ください NewPipe はエラーに遭遇しました。タップして報告 スナックバーにエラーを表示 - 既にバックグラウンドで再生されています 固定されたコメント この動作に適切なファイルマネージャが見つかりませんでした。 \nStorage Access Frameworkと互換性のあるファイルマネージャをインストールしてください。 @@ -673,7 +672,6 @@ エラー通知を作成 エラーを報告する通知 LeakCanaryが利用不可能です - 緩急音階 プレイヤー通知 ストリームの詳細を読み込んでいます… 登録チャンネルの新しいストリームについて通知する diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 92b24fbc80a..b2829ba27c7 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -672,7 +672,6 @@ Viser et krasjalternativ ved bruk av avspilleren Det oppstod en feil. Sjekk merknaden. Festet kommentar - Spilles allerede i bakgrunnen Feilrapport-merknad Merknader for innrapportering av feil NewPipe-feil. Trykk for å rapportere. @@ -682,6 +681,4 @@ Installer en filbehandler som støtter lagringstilgangsrammeverk først. LeakCanary er ikke tilgjengelig ExoPlayer-forvalg - Juster toneart etter musikalske halvtoner - Tempo-steg \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index dd405963ae0..3a943d2cad1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -677,7 +677,6 @@ NewPipe meldt fout, tik voor bericht Foutmelding Maak een foutmelding - Speelt al op de achtergrond Korte foutmelding weergeven Er is geen geschikte bestandsbeheerder gevonden voor deze actie. \nInstalleer een bestandsbeheerder of probeer \'%s\' uit te schakelen in de download instellingen. @@ -686,7 +685,5 @@ Vastgemaakt commentaar LeakCanary is niet beschikbaar Verander de laad interval tijd (nu %s). Een lagere waarde kan het initiële laden van de video versnellen. De wijziging vereist een herstart van de speler. - Pas de toonhoogte aan met muzikale halve tonen - Tempo stap ExoPlayer standaard \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index cfa995212a2..6e2c95119a8 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -701,14 +701,11 @@ \nZainstaluj menedżer plików lub spróbuj wyłączyć „%s” w ustawieniach pobierania.
Nie znaleziono odpowiedniego menedżera plików dla tej akcji. \nZainstaluj menedżer plików zgodny z Storage Access Framework. - Już jest odtwarzane w tle Przypięty komentarz LeakCanary jest niedostępne Rozmiar interwału ładowania odtwarzania Zmień rozmiar interwału ładowania (aktualnie %s). Niższa wartość może przyspieszyć początkowe ładowanie wideo. Zmiany wymagają ponownego uruchomienia odtwarzacza domyślny ExoPlayera - Dostosuj wysokość półtonami - Krok tempa Powiadomienie odtwarzacza Skonfiguruj powiadomienie aktualnie odtwarzanego strumienia Uruchom sprawdzenie nowych strumieni diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0850034f66e..d19196c0644 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -682,12 +682,9 @@ Mostrar um snackbar de erro Nenhum gerenciador de arquivos apropriado foi encontrado para esta ação. \nInstale um gerenciador de arquivos ou tente desativar \'%s\' nas configurações de download. - Já está tocando em segundo plano Comentário fixado O LeakCanary não está disponível - Passo do tempo Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. As alterações exigem que o player reinicie. - Ajustar o tom por semitons musicais ExoPlayer padrão Notificação do reprodutor Configurar a notificação do fluxo da reprodução atual diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 43e78225148..aa3862b6be0 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -683,11 +683,8 @@ Nenhum gestor de ficheiros apropriado foi encontrado para esta ação. \nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework. Comentário fixado - Já está a reproduzir em segundo plano LeakCanary não está disponível Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. - Ajustar o tom por semitons musicais - Passo do tempo Predefinido do ExoPlayer Notificações A carregar detalhes do fluxo… diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 9e1a8091621..9563c9a6fe9 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -554,7 +554,6 @@ URL não reconhecido. Abrir com outra aplicação\? Enfileiramento automático Embaralhar - Notificação Apenas em Wi-Fi Nada Mudar de um reprodutor para outro pode substituir a sua fila @@ -683,13 +682,10 @@ \nPor favor, instale um gestor de ficheiros ou tente desativar \'%s\' nas configurações de descarregar.
Nenhum gestor de ficheiros apropriado foi encontrado para esta ação. \nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework. - Já está a reproduzir em segundo plano Comentário fixado LeakCanary não está disponível - Ajustar o tom por semitons musicais Predefinido do ExoPlayer Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. - Passo do tempo Notificação do reprodutor Configurar a notificação da reprodução do fluxo atual Notificações diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index e83fc5462cd..67108b500cb 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -679,7 +679,6 @@ Procesarea.. Poate dura un moment Verifică dacă există actualizări Verifică manual dacă există versiuni noi - Se redă deja pe fundal Comentariu lipit Notificare cu raport de eroare Afișează opțiunea de a întrerupe atunci când utilizați playerul diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fc4c03f42c7..2a16a45731f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -691,8 +691,6 @@ Проверить обновления Проверка обновлений… Новое на канале - Отчёт об ошибках плеера - Подробные отчёты об ошибках плеера вместо коротких всплывающих сообщений (полезно при диагностике проблем) Уведомления Новые видео Уведомления о новых видео в подписках @@ -718,11 +716,8 @@ \nПожалуйста, установите файловый менеджер, или попробуйте отключить \'%s\' в настройках загрузок. Для этого действия не найдено подходящего файлового менеджера. \nПожалуйста, установите файловый менеджер, совместимый со Storage Access Framework (SAF). - Уже проигрывается в фоне Закреплённый комментарий LeakCanary недоступна - Регулировка высоты тона по музыкальным полутонам - Шаг темпа Стандартное значение ExoPlayer Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. Загрузка деталей трансляции… diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 38a7c330312..7c6120832b6 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -683,10 +683,7 @@ \nPro praghere installa unu gestore de documentos cumpatìbile cun su \"Sistema de Atzessu a s\'Archiviatzione\". Faghe serrare su riproduidore Cummentu apicadu - Giai in riprodutzione in s\'isfundu LeakCanary no est a disponimentu - Règula s\'intonatzione in base a sos semitonos musicales - Passu de tempus Valore ExoPlayer predefinidu Muda sa mannària de s\'intervallu de carrigamentu (in custu momentu %s). Unu valore prus bassu diat pòdere allestrare su carrigamentu de incumintzu de su vìdeu. Sas modìficas tenent bisòngiu de torrare a allùghere su riproduidore. Cunfigura sa notìfica de su flussu in cursu de riprodutzione diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 9b7a621450a..c47562bb00e 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -647,8 +647,6 @@ Pri každom sťahovaní sa zobrazí výzva kam uložiť súbor Nie je nastavený adresár na sťahovanie, nastavte ho teraz Označiť ako videné - Načítavanie podrobností o kanáli… - Chyba pri zobrazení podrobností kanála Vypnuté Zapnuté Režim tabletu @@ -698,8 +696,6 @@ Zobrazí možnosť zlyhania pri používaní prehrávača Zobraziť krátke oznámenie chyby Oznámte chybu - Upraviť výšku poltónov - Krok tempa ExoPlayer preddefinovaný Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart. Upozornenia diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 63af6d4d613..b7e66a85baa 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -682,13 +682,10 @@ \nInstallera en filhanterare eller testa att inaktivera \'%s\' i nedladdningsinställningarna. Ingen lämplig filhanterare hittades för denna åtgärd. \nInstallera en filhanterare som är kompatibel med Storage Access Framework. - Spelas redan i bakgrunden Fäst kommentar LeakCanary är inte tillgänglig - Justera tonhöjden med musikaliska halvtoner ExoPlayer standard Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren. - Temposteg Validera frekvens Kräver nätverksanslutning Alla nätverk diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index d7c920f2ba1..827da452a6f 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -253,7 +253,6 @@ இயக்குதலைத் மறுதொடர் நி நிகழ்வு ஏற்கனவே உள்ளது - அறிவிப்பு யூடியூபின் \"கட்டுப்பாடு பயன்முறை\"ஐ இயக்கு பாடல்கள் பிழைகளைப் புகாரளிக்க அறிவிப்புகள் diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 60542e0b6d4..3958e02b005 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -371,7 +371,6 @@ reCAPTCHA సవాలు reCAPTCHA సవాలు అభ్యర్థించబడింది ప్లేజాబితాను ఎంచుకోండి - ఇప్పటికే వెనుకగా ప్లే అవుతోంది డాటాబేసుని ఎగుమతిచేయుము యాప్ పునఃప్రారంభించబడిన తర్వాత భాష మారుతుంది ఛానెల్ వివరాలను చూపు diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 958dc5eeb0d..a9bbe30e586 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -682,13 +682,10 @@ Hata raporları için bildirimler Oynatıcı kullanırken çöktürme seçeneği gösterir Oynatıcıyı çöktür - Zaten arka planda oynuyor Sabitlenmiş yorum LeakCanary yok Yükleme ara boyutunu değiştir (şu anda %s). Düşük bir değer videonun ilk yüklenişini hızlandırabilir. Değişiklikler oynatıcının yeniden başlatılmasını gerektirir. ExoPlayer öntanımlısı - Tempo adımı - Perdeyi müzikal yarım tonlarla uyarla Yeni akış bildirimleri Bildirimler diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f2a3e986e15..0768f241b44 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -58,7 +58,6 @@ Завантаження Звіт про помилку Усе - Так Вимкнено Збій застосунку/інтерфейсу Ваш коментар (англійською): @@ -699,11 +698,8 @@ \nУстановіть файловий менеджер, сумісний зі Storage Access Framework. Показати панель помилок Створити сповіщення про помилку - Уже відтворюється у фоновому режимі Закріплений коментар LeakCanary недоступний - Крок темпу - Регулювання висоти звуку за музичними півтонами Типовий ExoPlayer Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача. Ви підписалися на цей канал diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 7ea1a1ed5ee..08bf7cae08b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -672,8 +672,6 @@ \nVui lòng cài đặt ứng dụng quản lý tệp hoặc tắt \'%s\' trong cài đặt tải xuống. Thay đổi kích thước khoảng thời gian tải (tầm khoảng %s). Để ở giá trị thấp hơn có thể sẽ tăng tốc độ tải video hơn ban đầu. Khởi động lại trình phát để áp dụng thay đổi. LeakCanary không khả dụng - Điều chỉnh cao độ theo nhạc nền âm nhạc - Nhịp độ tiếp theo ExoPlayer mặc định Bình luận được ghim \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 0e79f5a5833..d527edc9b34 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -603,10 +603,8 @@ %s 個新加串流 - 按樂音半度調整音高 新加串流通知 係咪要喺磁碟機上面消除晒全部下載咗嘅檔案? 通知已停用 單曲 - 節奏步伐 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fd129fb6c76..8b5a246611d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -539,7 +539,6 @@ 作用中播放器的佇列可能會被取代 從一個播放器切換到另一個可能會取代您的佇列 清除佇列前要求確認 - 通知 正在緩衝 隨機播放 @@ -638,8 +637,6 @@ 拖動列縮圖預覽 被創作者加心號 標記為已看過 - 正在載入頻道詳細資訊…… - 顯示頻道詳細資訊時發生錯誤 在圖片頂部顯示畢卡索彩色絲帶,指示其來源:紅色代表網路、藍色代表磁碟、綠色代表記憶體 顯示圖片指示器 遠端搜尋建議 @@ -674,12 +671,9 @@ NewPipe 遇到錯誤,點擊以回報 發生錯誤,請檢視通知 釘選的留言 - 已經在背景播放 LeakCanary 無法使用 - 步進時間 ExoPlayer 預設值 變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器。 - 按音樂半音調整音高 播放器通知 通知 正在載入串流詳細資訊…… From a67927c29ce5e3f60ed628fb307bda3333e28774 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 1 May 2022 21:56:49 +0200 Subject: [PATCH 024/240] Fix dialogs having incorrect color when opened via RouterActivity --- app/src/main/res/values/styles.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7c126558079..894e70bad92 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -131,10 +131,6 @@ + + + + + - - + diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 8fa00d0d8cb..00000000000 --- a/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml index d6cd73d5210..f3487810ac0 100644 --- a/app/src/main/res/values/colors_services.xml +++ b/app/src/main/res/values/colors_services.xml @@ -2,53 +2,37 @@ #e53935 - #992722 #000000 - #e53935 #992722 - #7a1717 #FFFFFF - #992722 #f57c00 - #995700 #000000 - #f57c00 #a35300 - #7d4000 #FFFFFF - #a35300 #ff6f00 - #c43e00 #000000 - #ff6f00 #a34700 - #942f00 #FFFFFF - #a34700 #9e9e9e #000000 - #9e9e9e #878787 #FFFFFF - #878787 #17a0c4 #000000 - #17a0c4 #1383a1 #FFFFFF - #1383a1 \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e711b35ab2c..164f1067224 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,27 +1,29 @@ - + + - - - - diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml index ce67f468b49..db1fba397f5 100644 --- a/app/src/main/res/values/styles_services.xml +++ b/app/src/main/res/values/styles_services.xml @@ -1,16 +1,22 @@ - - - From 4ec9cbe379a55579648ec2ac28e14bf5e232db76 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 6 Jul 2022 17:47:12 -0400 Subject: [PATCH 119/240] Remove AndroidX Webkit --- app/build.gradle | 1 - .../org/schabi/newpipe/error/ReCaptchaActivity.java | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 67026df7e4c..9867037e6a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -216,7 +216,6 @@ dependencies { // Newer version specified to prevent accessibility regressions with RecyclerView, see: // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' - implementation 'androidx.webkit:webkit:1.4.0' implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" implementation 'com.google.android.material:material:1.5.0' diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java index c9f3a82d988..e2780d215cb 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java @@ -8,8 +8,10 @@ import android.view.Menu; import android.view.MenuItem; import android.webkit.CookieManager; +import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; +import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -17,7 +19,6 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NavUtils; import androidx.preference.PreferenceManager; -import androidx.webkit.WebViewClientCompat; import org.schabi.newpipe.databinding.ActivityRecaptchaBinding; import org.schabi.newpipe.DownloaderImpl; @@ -85,14 +86,15 @@ protected void onCreate(final Bundle savedInstanceState) { webSettings.setJavaScriptEnabled(true); webSettings.setUserAgentString(DownloaderImpl.USER_AGENT); - recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClientCompat() { + recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClient() { @Override - public boolean shouldOverrideUrlLoading(final WebView view, final String url) { + public boolean shouldOverrideUrlLoading(final WebView view, + final WebResourceRequest request) { if (MainActivity.DEBUG) { - Log.d(TAG, "shouldOverrideUrlLoading: url=" + url); + Log.d(TAG, "shouldOverrideUrlLoading: url=" + request.getUrl().toString()); } - handleCookiesFromUrl(url); + handleCookiesFromUrl(request.getUrl().toString()); return false; } From 189c92affadf40f3bedd7f52eec1b1813fce98a3 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 6 Jul 2022 17:48:38 -0400 Subject: [PATCH 120/240] More minSdk 21 cleanup --- .../main/java/org/schabi/newpipe/error/ErrorUtil.kt | 8 +------- app/src/main/java/org/schabi/newpipe/ktx/View.kt | 11 +++-------- .../settings/PlayerNotificationSettingsFragment.kt | 10 ---------- .../custom/NotificationActionsPreference.java | 2 +- 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index e4dd2e16d03..86e2e1028a4 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -114,13 +114,7 @@ class ErrorUtil { context, context.getString(R.string.error_report_channel_id) ) - .setSmallIcon( - // the vector drawable icon causes crashes on KitKat devices - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - R.drawable.ic_bug_report - else - android.R.drawable.stat_notify_error - ) + .setSmallIcon(R.drawable.ic_bug_report) .setContentTitle(context.getString(R.string.error_report_notification_title)) .setContentText(context.getString(errorInfo.messageStringId)) .setAutoCancel(true) diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt index ace1dbf7ef2..ea680dd60ba 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -12,7 +12,6 @@ import android.view.View import androidx.annotation.ColorInt import androidx.annotation.FloatRange import androidx.core.animation.addListener -import androidx.core.view.ViewCompat import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -21,10 +20,6 @@ import org.schabi.newpipe.MainActivity private const val TAG = "ViewUtils" -inline var View.backgroundTintListCompat: ColorStateList? - get() = ViewCompat.getBackgroundTintList(this) - set(value) = ViewCompat.setBackgroundTintList(this, value) - /** * Animate the view. * @@ -106,11 +101,11 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - backgroundTintListCompat = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) + backgroundTintList = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) } viewPropertyAnimator.addListener( - onCancel = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) }, - onEnd = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) } + onCancel = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) }, + onEnd = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) } ) viewPropertyAnimator.start() } diff --git a/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt index 3549bff42e0..7d95433a49c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt @@ -1,19 +1,9 @@ package org.schabi.newpipe.settings -import android.os.Build import android.os.Bundle -import androidx.preference.Preference -import org.schabi.newpipe.R class PlayerNotificationSettingsFragment : BasePreferenceFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResourceRegistry() - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key)) - colorizePref?.let { - preferenceScreen.removePreference(it) - } - } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 798d299c026..849574171c1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -218,7 +218,7 @@ void openActionChooserDialog() { final int color = ThemeHelper.resolveColorFromAttr(getContext(), android.R.attr.textColorPrimary); drawable = DrawableCompat.wrap(drawable).mutate(); - DrawableCompat.setTint(drawable, color); + drawable.setTint(color); radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, drawable, null); } From 76ced59b62322537eb818cd3fea7ddd1b9c2d711 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 8 Apr 2022 09:35:14 +0200 Subject: [PATCH 121/240] Refactor player: separate UIs and more --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../org/schabi/newpipe/RouterActivity.java | 6 +- .../fragments/detail/VideoDetailFragment.java | 155 +- .../list/channel/ChannelFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../newpipe/player/NotificationUtil.java | 18 +- .../newpipe/player/PlayQueueActivity.java | 50 +- .../org/schabi/newpipe/player/Player.java | 2656 ++--------------- .../{MainPlayer.java => PlayerService.java} | 102 +- .../player/event/BasePlayerGestureListener.kt | 520 ---- .../player/event/PlayerEventListener.java | 1 - .../player/event/PlayerGestureListener.java | 256 -- .../event/PlayerServiceEventListener.java | 2 + .../PlayerServiceExtendedEventListener.java | 4 +- .../gesture/BasePlayerGestureListener.kt | 182 ++ .../CustomBottomSheetBehavior.java | 2 +- .../{event => gesture}/DisplayPortion.kt | 2 +- .../{event => gesture}/DoubleTapListener.kt | 2 +- .../gesture/MainPlayerGestureListener.kt | 232 ++ .../gesture/PopupPlayerGestureListener.kt | 287 ++ .../helper/PlaybackParameterDialog.java | 4 +- .../newpipe/player/helper/PlayerHelper.java | 61 +- .../newpipe/player/helper/PlayerHolder.java | 23 +- .../view/PlaybackSpeedClickListener.kt | 47 - .../listeners/view/QualityClickListener.kt | 41 - .../player/playback/PlayerMediaSession.java | 5 +- .../newpipe/player/ui/MainPlayerUi.java | 937 ++++++ .../player/ui/NotificationPlayerUi.java | 26 + .../schabi/newpipe/player/ui/PlayerUi.java | 120 + .../newpipe/player/ui/PlayerUiList.java | 36 + .../newpipe/player/ui/PopupPlayerUi.java | 460 +++ .../newpipe/player/ui/VideoPlayerUi.java | 1523 ++++++++++ .../custom/NotificationActionsPreference.java | 4 +- .../schabi/newpipe/util/NavigationHelper.java | 28 +- .../views/player/PlayerFastSeekOverlay.kt | 4 +- app/src/main/res/layout/activity_main.xml | 2 +- 38 files changed, 4243 insertions(+), 3565 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{MainPlayer.java => PlayerService.java} (63%) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/CustomBottomSheetBehavior.java (98%) rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/DisplayPortion.kt (65%) rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/DoubleTapListener.kt (81%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java diff --git a/app/build.gradle b/app/build.gradle index 9867037e6a9..46eee8d00c6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,7 +166,7 @@ afterEvaluate { if (!System.properties.containsKey('skipFormatKtlint')) { preDebugBuild.dependsOn formatKtlint } - preDebugBuild.dependsOn runCheckstyle, runKtlint + //preDebugBuild.dependsOn runCheckstyle, runKtlint } sonarqube { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f9c99819c4a..04e28c1eac8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,7 +44,7 @@ diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 1fe6ce7ec7a..1194b406850 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -60,7 +60,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.dialog.PlaylistDialog; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; @@ -630,8 +630,8 @@ private boolean canHandleChoiceLikeShowInfo(final String selectedChoiceKey) { } // ...the player is not running or in normal Video-mode/type - final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); - return playerType == null || playerType == MainPlayer.PlayerType.VIDEO; + final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); + return playerType == null || playerType == PlayerService.PlayerType.MAIN; } private void openAddToPlaylistDialog() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 278d472d4c6..5ecc35034dc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -43,6 +43,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; +import androidx.viewbinding.ViewBinding; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -77,8 +78,8 @@ import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; @@ -87,6 +88,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.player.ui.MainPlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -106,6 +109,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.TimeUnit; import icepick.State; @@ -202,7 +206,7 @@ public final class VideoDetailFragment private ContentObserver settingsContentObserver; @Nullable - private MainPlayer playerService; + private PlayerService playerService; private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); @@ -211,7 +215,7 @@ public final class VideoDetailFragment //////////////////////////////////////////////////////////////////////////*/ @Override public void onServiceConnected(final Player connectedPlayer, - final MainPlayer connectedPlayerService, + final PlayerService connectedPlayerService, final boolean playAfterConnect) { player = connectedPlayer; playerService = connectedPlayerService; @@ -219,6 +223,7 @@ public void onServiceConnected(final Player connectedPlayer, // It will do nothing if the player is not in fullscreen mode hideSystemUiIfNeeded(); + final Optional playerUi = player.UIs().get(MainPlayerUi.class); if (!player.videoPlayerSelected() && !playAfterConnect) { return; } @@ -227,22 +232,23 @@ public void onServiceConnected(final Player connectedPlayer, // If the video is playing but orientation changed // let's make the video in fullscreen again checkLandscape(); - } else if (player.isFullscreen() && !player.isVerticalVideo() + } else if (playerUi.map(ui -> ui.isFullscreen() && !ui.isVerticalVideo()).orElse(false) // Tablet UI has orientation-independent fullscreen && !DeviceUtils.isTablet(activity)) { // Device is in portrait orientation after rotation but UI is in fullscreen. // Return back to non-fullscreen state - player.toggleFullscreen(); + playerUi.ifPresent(MainPlayerUi::toggleFullscreen); } if (playerIsNotStopped() && player.videoPlayerSelected()) { addVideoPlayerView(); } + //noinspection SimplifyOptionalCallChains if (playAfterConnect || (currentInfo != null && isAutoplayEnabled() - && player.getParentActivity() == null)) { + && !playerUi.isPresent())) { autoPlayEnabled = true; // forcefully start playing openVideoPlayerAutoFullscreen(); } @@ -518,7 +524,7 @@ public void onClick(final View v) { case R.id.overlay_play_pause_button: if (playerIsNotStopped()) { player.playPause(); - player.hideControls(0, 0); + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); showSystemUi(); } else { autoPlayEnabled = true; // forcefully start playing @@ -583,12 +589,12 @@ private void toggleTitleAndSecondaryControls() { if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) { binding.detailVideoTitleView.setMaxLines(10); animateRotation(binding.detailToggleSecondaryControlsView, - Player.DEFAULT_CONTROLS_DURATION, 180); + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 180); binding.detailSecondaryControlPanel.setVisibility(View.VISIBLE); } else { binding.detailVideoTitleView.setMaxLines(1); animateRotation(binding.detailToggleSecondaryControlsView, - Player.DEFAULT_CONTROLS_DURATION, 0); + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 0); binding.detailSecondaryControlPanel.setVisibility(View.GONE); } // view pager height has changed, update the tab layout @@ -746,7 +752,9 @@ public void onError(final Exception e) { @Override public boolean onKeyDown(final int keyCode) { - return isPlayerAvailable() && player.onKeyDown(keyCode); + return isPlayerAvailable() + && player.UIs().get(VideoPlayerUi.class) + .map(playerUi -> playerUi.onKeyDown(keyCode)).orElse(false); } @Override @@ -756,7 +764,7 @@ public boolean onBackPressed() { } // If we are in fullscreen mode just exit from it via first back press - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { if (!DeviceUtils.isTablet(activity)) { player.pause(); } @@ -1006,8 +1014,7 @@ private void updateTabs(@NonNull final StreamInfo info) { getChildFragmentManager().beginTransaction() .replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info)) .commitAllowingStateLoss(); - binding.relatedItemsLayout.setVisibility( - isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.VISIBLE); + binding.relatedItemsLayout.setVisibility(isFullscreen() ? View.GONE : View.VISIBLE); } } @@ -1087,8 +1094,12 @@ public void scrollToTop() { private void toggleFullscreenIfInFullscreenMode() { // If a user watched video inside fullscreen mode and than chose another player // return to non-fullscreen mode - if (isPlayerAvailable() && player.isFullscreen()) { - player.toggleFullscreen(); + if (isPlayerAvailable()) { + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + if (playerUi.isFullscreen()) { + playerUi.toggleFullscreen(); + } + }); } } @@ -1214,16 +1225,10 @@ private void openMainPlayer() { } final PlayQueue queue = setupPlayQueueForIntent(false); - - // Video view can have elements visible from popup, - // We hide it here but once it ready the view will be shown in handleIntent() - if (playerService.getView() != null) { - playerService.getView().setVisibility(View.GONE); - } addVideoPlayerView(); final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), - MainPlayer.class, queue, true, autoPlayEnabled); + PlayerService.class, queue, true, autoPlayEnabled); ContextCompat.startForegroundService(activity, playerIntent); } @@ -1235,8 +1240,8 @@ private void openMainPlayer() { * be reused in a few milliseconds and the flickering would be annoying. */ private void hideMainPlayerOnLoadingNewStream() { - if (!isPlayerServiceAvailable() - || playerService.getView() == null + //noinspection SimplifyOptionalCallChains + if (!isPlayerServiceAvailable() || !getRoot().isPresent() || !player.videoPlayerSelected()) { return; } @@ -1244,7 +1249,7 @@ private void hideMainPlayerOnLoadingNewStream() { removeVideoPlayerView(); if (isAutoplayEnabled()) { playerService.stopForImmediateReusing(); - playerService.getView().setVisibility(View.GONE); + getRoot().ifPresent(view -> view.setVisibility(View.GONE)); } else { playerHolder.stopService(); } @@ -1302,26 +1307,33 @@ private boolean isAutoplayEnabled() { } private void addVideoPlayerView() { - if (!isPlayerAvailable() || getView() == null) { + if (!isPlayerAvailable()) { return; } - // Check if viewHolder already contains a child - if (player.getRootView().getParent() != binding.playerPlaceholder) { + final Optional root = player.UIs().get(VideoPlayerUi.class) + .map(VideoPlayerUi::getBinding) + .map(ViewBinding::getRoot); + + // Check if viewHolder already contains a child TODO TODO whaat + /*if (playerService != null + && root.map(View::getParent).orElse(null) != binding.playerPlaceholder) { playerService.removeViewFromParent(); - } + }*/ setHeightThumbnail(); // Prevent from re-adding a view multiple times - if (player.getRootView().getParent() == null) { - binding.playerPlaceholder.addView(player.getRootView()); + if (root.isPresent() && root.get().getParent() == null) { + binding.playerPlaceholder.addView(root.get()); } } private void removeVideoPlayerView() { makeDefaultHeightForVideoPlaceholder(); - playerService.removeViewFromParent(); + if (player != null) { + player.UIs().get(VideoPlayerUi.class).ifPresent(VideoPlayerUi::removeViewFromParent); + } } private void makeDefaultHeightForVideoPlaceholder() { @@ -1362,7 +1374,7 @@ private void setHeightThumbnail() { final boolean isPortrait = metrics.heightPixels > metrics.widthPixels; requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener); - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { final int height = (DeviceUtils.isInMultiWindow(activity) ? requireView() : activity.getWindow().getDecorView()).getHeight(); @@ -1387,8 +1399,9 @@ private void setHeightThumbnail(final int newHeight, final DisplayMetrics metric binding.detailThumbnailImageView.setMinimumHeight(newHeight); if (isPlayerAvailable()) { final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT); - player.getSurfaceView() - .setHeights(newHeight, player.isFullscreen() ? newHeight : maxHeight); + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> + ui.getBinding().surfaceView.setHeights(newHeight, + ui.isFullscreen() ? newHeight : maxHeight)); } } @@ -1517,7 +1530,7 @@ public void showLoading() { if (binding.relatedItemsLayout != null) { if (showRelatedItems) { binding.relatedItemsLayout.setVisibility( - isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.INVISIBLE); + isFullscreen() ? View.GONE : View.INVISIBLE); } else { binding.relatedItemsLayout.setVisibility(View.GONE); } @@ -1778,6 +1791,14 @@ private void showPlaybackProgress(final long progress, final long duration) { // Player event listener //////////////////////////////////////////////////////////////////////////*/ + @Override + public void onViewCreated() { + // Video view can have elements visible from popup, + // We hide it here but once it ready the view will be shown in handleIntent() + getRoot().ifPresent(view -> view.setVisibility(View.GONE)); + addVideoPlayerView(); + } + @Override public void onQueueUpdate(final PlayQueue queue) { playQueue = queue; @@ -1898,15 +1919,10 @@ public void onServiceStopped() { @Override public void onFullscreenStateChanged(final boolean fullscreen) { setupBrightness(); + //noinspection SimplifyOptionalCallChains if (!isPlayerAndPlayerServiceAvailable() - || playerService.getView() == null - || player.getParentActivity() == null) { - return; - } - - final View view = playerService.getView(); - final ViewGroup parent = (ViewGroup) view.getParent(); - if (parent == null) { + || !player.UIs().get(MainPlayerUi.class).isPresent() + || getRoot().map(View::getParent).orElse(null) == null) { return; } @@ -1934,7 +1950,7 @@ public void onScreenRotationButtonClicked() { final boolean isLandscape = DeviceUtils.isLandscape(requireContext()); if (DeviceUtils.isTablet(activity) && (!globalScreenOrientationLocked(activity) || isLandscape)) { - player.toggleFullscreen(); + player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::toggleFullscreen); return; } @@ -2017,7 +2033,7 @@ private void hideSystemUi() { } activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - if (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen())) { + if (isInMultiWindow || isFullscreen()) { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); } @@ -2026,13 +2042,17 @@ private void hideSystemUi() { // Listener implementation public void hideSystemUiIfNeeded() { - if (isPlayerAvailable() - && player.isFullscreen() + if (isFullscreen() && bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { hideSystemUi(); } } + private boolean isFullscreen() { + return isPlayerAvailable() && player.UIs().get(VideoPlayerUi.class) + .map(VideoPlayerUi::isFullscreen).orElse(false); + } + private boolean playerIsNotStopped() { return isPlayerAvailable() && !player.isStopped(); } @@ -2055,10 +2075,7 @@ private void setupBrightness() { } final WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); - if (!isPlayerAvailable() - || !player.videoPlayerSelected() - || !player.isFullscreen() - || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) { + if (!isFullscreen() || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) { // Apply system brightness when the player is not in fullscreen restoreDefaultBrightness(); } else { @@ -2082,7 +2099,7 @@ private void checkLandscape() { setAutoPlay(true); } - player.checkLandscape(); + player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::checkLandscape); // Let's give a user time to look at video information page if video is not playing if (globalScreenOrientationLocked(activity) && !player.isPlaying()) { player.play(); @@ -2309,10 +2326,10 @@ public void onStateChanged(@NonNull final View bottomSheet, final int newState) if (DeviceUtils.isLandscape(requireContext()) && isPlayerAvailable() && player.isPlaying() - && !player.isFullscreen() - && !DeviceUtils.isTablet(activity) - && player.videoPlayerSelected()) { - player.toggleFullscreen(); + && !isFullscreen() + && !DeviceUtils.isTablet(activity)) { + player.UIs().get(MainPlayerUi.class) + .ifPresent(MainPlayerUi::toggleFullscreen); } setOverlayLook(binding.appBarLayout, behavior, 1); break; @@ -2325,17 +2342,22 @@ && isPlayerAvailable() // Re-enable clicks setOverlayElementsClickable(true); if (isPlayerAvailable()) { - player.closeItemsList(); + player.UIs().get(MainPlayerUi.class) + .ifPresent(MainPlayerUi::closeItemsList); } setOverlayLook(binding.appBarLayout, behavior, 0); break; case BottomSheetBehavior.STATE_DRAGGING: case BottomSheetBehavior.STATE_SETTLING: - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { showSystemUi(); } - if (isPlayerAvailable() && player.isControlsVisible()) { - player.hideControls(0, 0); + if (isPlayerAvailable()) { + player.UIs().get(MainPlayerUi.class).ifPresent(ui -> { + if (ui.isControlsVisible()) { + ui.hideControls(0, 0); + } + }); } break; } @@ -2409,4 +2431,13 @@ boolean isPlayerServiceAvailable() { boolean isPlayerAndPlayerServiceAvailable() { return (player != null && playerService != null); } + + public Optional getRoot() { + if (player == null) { + return Optional.empty(); + } + + return player.UIs().get(VideoPlayerUi.class) + .map(playerUi -> playerUi.getBinding().getRoot()); + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index fa8f5fdbd96..aabd64744c0 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.local.feed.notifications.NotificationHelper; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index ed63c6fd7b5..65fd8ada1c6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 6023d4b10af..3bec07dcc59 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.Localization; diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index 2060d67c4aa..f5caf2c7974 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -26,14 +26,14 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; +import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; +import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; /** * This is a utility class for player notifications. @@ -173,7 +173,7 @@ boolean shouldUpdateBufferingSlot() { } - void createNotificationAndStartForeground(final Player player, final Service service) { + public void createNotificationAndStartForeground(final Player player, final Service service) { if (notificationBuilder == null) { notificationBuilder = createNotification(player); } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index 676d634584a..d00e6265e0c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -51,7 +51,9 @@ public final class PlayQueueActivity extends AppCompatActivity private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; - protected Player player; + private Player player; + + private PlayQueueAdapter adapter = null; private boolean serviceBound; private ServiceConnection serviceConnection; @@ -132,7 +134,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { openPlaybackParameterDialog(); return true; case R.id.action_mute: - player.onMuteUnmuteButtonClicked(); + player.toggleMute(); return true; case R.id.action_system_audio: startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS)); @@ -168,7 +170,7 @@ protected void onDestroy() { //////////////////////////////////////////////////////////////////////////// private void bind() { - final Intent bindIntent = new Intent(this, MainPlayer.class); + final Intent bindIntent = new Intent(this, PlayerService.class); final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE); if (!success) { unbindService(serviceConnection); @@ -184,10 +186,7 @@ private void unbind() { player.removeActivityListener(this); } - if (player != null && player.getPlayQueueAdapter() != null) { - player.getPlayQueueAdapter().unsetSelectedListener(); - } - queueControlBinding.playQueue.setAdapter(null); + onQueueUpdate(null); if (itemTouchHelper != null) { itemTouchHelper.attachToRecyclerView(null); } @@ -210,15 +209,15 @@ public void onServiceConnected(final ComponentName name, final IBinder service) if (service instanceof PlayerServiceBinder) { player = ((PlayerServiceBinder) service).getPlayerInstance(); - } else if (service instanceof MainPlayer.LocalBinder) { - player = ((MainPlayer.LocalBinder) service).getPlayer(); + } else if (service instanceof PlayerService.LocalBinder) { + player = ((PlayerService.LocalBinder) service).getPlayer(); } - if (player == null || player.getPlayQueue() == null - || player.getPlayQueueAdapter() == null || player.exoPlayerIsNull()) { + if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) { unbind(); finish(); } else { + onQueueUpdate(player.getPlayQueue()); buildComponents(); if (player != null) { player.setActivityListener(PlayQueueActivity.this); @@ -241,7 +240,6 @@ private void buildComponents() { private void buildQueue() { queueControlBinding.playQueue.setLayoutManager(new LinearLayoutManager(this)); - queueControlBinding.playQueue.setAdapter(player.getPlayQueueAdapter()); queueControlBinding.playQueue.setClickable(true); queueControlBinding.playQueue.setLongClickable(true); queueControlBinding.playQueue.clearOnScrollListeners(); @@ -249,8 +247,6 @@ private void buildQueue() { itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(queueControlBinding.playQueue); - - player.getPlayQueueAdapter().setSelectedListener(getOnSelectedListener()); } private void buildMetadata() { @@ -370,7 +366,7 @@ public void onClick(final View view) { } if (view.getId() == queueControlBinding.controlRepeat.getId()) { - player.onRepeatClicked(); + player.cycleNextRepeatMode(); } else if (view.getId() == queueControlBinding.controlBackward.getId()) { player.playPrevious(); } else if (view.getId() == queueControlBinding.controlFastRewind.getId()) { @@ -382,7 +378,7 @@ public void onClick(final View view) { } else if (view.getId() == queueControlBinding.controlForward.getId()) { player.playNext(); } else if (view.getId() == queueControlBinding.controlShuffle.getId()) { - player.onShuffleClicked(); + player.toggleShuffleModeEnabled(); } else if (view.getId() == queueControlBinding.metadata.getId()) { scrollToSelected(); } else if (view.getId() == queueControlBinding.liveSync.getId()) { @@ -445,7 +441,15 @@ public void onStopTrackingTouch(final SeekBar seekBar) { //////////////////////////////////////////////////////////////////////////// @Override - public void onQueueUpdate(final PlayQueue queue) { + public void onQueueUpdate(@Nullable final PlayQueue queue) { + if (queue == null) { + adapter = null; + queueControlBinding.playQueue.setAdapter(null); + } else { + adapter = new PlayQueueAdapter(this, queue); + adapter.setSelectedListener(getOnSelectedListener()); + queueControlBinding.playQueue.setAdapter(adapter); + } } @Override @@ -454,7 +458,6 @@ public void onPlaybackUpdate(final int state, final int repeatMode, final boolea onStateChanged(state); onPlayModeChanged(repeatMode, shuffled); onPlaybackParameterChanged(parameters); - onMaybePlaybackAdapterChanged(); onMaybeMuteChanged(); } @@ -582,17 +585,6 @@ private void onPlaybackParameterChanged(@Nullable final PlaybackParameters param } } - private void onMaybePlaybackAdapterChanged() { - if (player == null) { - return; - } - final PlayQueueAdapter maybeNewAdapter = player.getPlayQueueAdapter(); - if (maybeNewAdapter != null - && queueControlBinding.playQueue.getAdapter() != maybeNewAdapter) { - queueControlBinding.playQueue.setAdapter(maybeNewAdapter); - } - } - private void onMaybeMuteChanged() { if (menu != null && player != null) { final MenuItem item = menu.findItem(R.id.action_mute); diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 100563765d4..284ab74d8c3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -24,39 +24,25 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SKIP; import static com.google.android.exoplayer2.Player.DiscontinuityReason; import static com.google.android.exoplayer2.Player.Listener; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static com.google.android.exoplayer2.Player.RepeatMode; -import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; -import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION; -import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; +import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; -import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; -import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; -import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; -import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; @@ -64,50 +50,17 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.media.AudioManager; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.provider.Settings; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.Gravity; -import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.AnticipateInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.SeekBar; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -122,13 +75,10 @@ import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.RecyclerView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; @@ -139,13 +89,9 @@ import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; -import com.google.android.exoplayer2.ui.CaptionStyleCompat; -import com.google.android.exoplayer2.ui.SubtitleView; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; @@ -153,64 +99,47 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlayerBinding; -import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.Info; -import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.extractor.stream.StreamSegment; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; -import org.schabi.newpipe.info_list.StreamSegmentAdapter; -import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; -import org.schabi.newpipe.player.event.DisplayPortion; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; -import org.schabi.newpipe.player.event.PlayerGestureListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; -import org.schabi.newpipe.player.listeners.view.PlaybackSpeedClickListener; -import org.schabi.newpipe.player.listeners.view.QualityClickListener; import org.schabi.newpipe.player.mediaitem.MediaItemTag; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; -import org.schabi.newpipe.player.playback.SurfaceHolderCallback; import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; -import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; -import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; -import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; -import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; +import org.schabi.newpipe.player.ui.MainPlayerUi; +import org.schabi.newpipe.player.ui.NotificationPlayerUi; +import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.player.ui.PlayerUiList; +import org.schabi.newpipe.player.ui.PopupPlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import org.schabi.newpipe.util.external_communication.KoreUtils; -import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.views.ExpandableSurfaceView; -import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -221,14 +150,7 @@ import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.SerialDisposable; -public final class Player implements - PlaybackListener, - Listener, - SeekBar.OnSeekBarChangeListener, - View.OnClickListener, - PopupMenu.OnMenuItemClickListener, - PopupMenu.OnDismissListener, - View.OnLongClickListener { +public final class Player implements PlaybackListener, Listener { public static final boolean DEBUG = MainActivity.DEBUG; public static final String TAG = Player.class.getSimpleName(); @@ -264,18 +186,12 @@ public final class Player implements public static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 1000; // 1 second - public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis - public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds - public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds - public static final int SEEK_OVERLAY_DURATION = 450; // 450 millis /*////////////////////////////////////////////////////////////////////////// // Other constants //////////////////////////////////////////////////////////////////////////*/ - private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f}; - - private static final int RENDERER_UNAVAILABLE = -1; + public static final int RENDERER_UNAVAILABLE = -1; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -283,8 +199,6 @@ public final class Player implements // play queue might be null e.g. while player is starting @Nullable private PlayQueue playQueue; - private PlayQueueAdapter playQueueAdapter; - private StreamSegmentAdapter segmentAdapter; @Nullable private MediaSourceManager playQueueManager; @@ -299,7 +213,6 @@ public final class Player implements private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; private MediaSessionManager mediaSessionManager; - @Nullable private SurfaceHolderCallback surfaceHolderCallback; @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -308,13 +221,13 @@ public final class Player implements @NonNull private final VideoPlaybackResolver videoResolver; @NonNull private final AudioPlaybackResolver audioResolver; - private final MainPlayer service; //TODO try to remove and replace everything with context + private final PlayerService service; //TODO try to remove and replace everything with context /*////////////////////////////////////////////////////////////////////////// // Player states //////////////////////////////////////////////////////////////////////////*/ - private PlayerType playerType = PlayerType.VIDEO; + private PlayerType playerType = PlayerType.MAIN; private int currentState = STATE_PREFLIGHT; // audio only mode does not mean that player type is background, but that the player was @@ -322,81 +235,17 @@ public final class Player implements private boolean isAudioOnly = false; private boolean isPrepared = false; private boolean wasPlaying = false; - private boolean isFullscreen = false; - private boolean isVerticalVideo = false; - private boolean fragmentIsVisible = false; - - private List availableStreams; - private int selectedStreamIndex; - - /*////////////////////////////////////////////////////////////////////////// - // Views - //////////////////////////////////////////////////////////////////////////*/ - - private PlayerBinding binding; - - private final Handler controlsVisibilityHandler = new Handler(); - - // fullscreen player - private boolean isQueueVisible = false; - private boolean areSegmentsVisible = false; - private ItemTouchHelper itemTouchHelper; - - /*////////////////////////////////////////////////////////////////////////// - // Popup menus ("popup" means that they pop up, not that they belong to the popup player) - //////////////////////////////////////////////////////////////////////////*/ - - private static final int POPUP_MENU_ID_QUALITY = 69; - private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79; - private static final int POPUP_MENU_ID_CAPTION = 89; - - private boolean isSomePopupMenuVisible = false; - private PopupMenu qualityPopupMenu; - private PopupMenu playbackSpeedPopupMenu; - private PopupMenu captionPopupMenu; - - /*////////////////////////////////////////////////////////////////////////// - // Popup player - //////////////////////////////////////////////////////////////////////////*/ - - private PlayerPopupCloseOverlayBinding closeOverlayBinding; - - private boolean isPopupClosing = false; - - private float screenWidth; - private float screenHeight; - - /*////////////////////////////////////////////////////////////////////////// - // Popup player window manager - //////////////////////////////////////////////////////////////////////////*/ - - public static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - public static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - - @Nullable private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup - @Nullable private final WindowManager windowManager; /*////////////////////////////////////////////////////////////////////////// - // Gestures + // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ - private static final float MAX_GESTURE_LENGTH = 0.75f; - - private int maxGestureLength; // scaled - private GestureDetector gestureDetector; - private PlayerGestureListener playerGestureListener; - - /*////////////////////////////////////////////////////////////////////////// - // Listeners and disposables - //////////////////////////////////////////////////////////////////////////*/ + private final PlayerUiList UIs = new PlayerUiList(); private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; - private PlayerServiceEventListener fragmentListener; - private PlayerEventListener activityListener; - private ContentObserver settingsContentObserver; + @Nullable private PlayerServiceEventListener fragmentListener = null; + @Nullable private PlayerEventListener activityListener = null; @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); @@ -409,16 +258,13 @@ public final class Player implements @NonNull private final SharedPreferences prefs; @NonNull private final HistoryRecordManager recordManager; - @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = - new SeekbarPreviewThumbnailHolder(); - /*////////////////////////////////////////////////////////////////////////// // Constructor //////////////////////////////////////////////////////////////////////////*/ //region Constructor - public Player(@NonNull final MainPlayer service) { + public Player(@NonNull final PlayerService service) { this.service = service; context = service; prefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -434,8 +280,6 @@ public Player(@NonNull final MainPlayer service) { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); - - windowManager = ContextCompat.getSystemService(context, WindowManager.class); } private VideoPlaybackResolver.QualityResolver getQualityResolver() { @@ -460,235 +304,6 @@ public int getOverrideResolutionIndex(final List sortedVideos, - /*////////////////////////////////////////////////////////////////////////// - // Setup and initialization - //////////////////////////////////////////////////////////////////////////*/ - //region Setup and initialization - - public void setupFromView(@NonNull final PlayerBinding playerBinding) { - initViews(playerBinding); - if (exoPlayerIsNull()) { - initPlayer(true); - } - initListeners(); - - setupPlayerSeekOverlay(); - } - - private void initViews(@NonNull final PlayerBinding playerBinding) { - binding = playerBinding; - setupSubtitleView(); - - binding.resizeTextView - .setText(PlayerHelper.resizeTypeOf(context, binding.surfaceView.getResizeMode())); - - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - binding.playbackSeekBar.getProgressDrawable() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); - - final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(getContext(), - R.style.DarkPopupMenu); - - qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); - playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); - captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); - - binding.progressBarLoadingPanel.getIndeterminateDrawable() - .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); - - binding.titleTextView.setSelected(true); - binding.channelTextView.setSelected(true); - - // Prevent hiding of bottom sheet via swipe inside queue - binding.itemsList.setNestedScrollingEnabled(false); - } - - private void initPlayer(final boolean playOnReady) { - if (DEBUG) { - Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]"); - } - - simpleExoPlayer = new ExoPlayer.Builder(context, renderFactory) - .setTrackSelector(trackSelector) - .setLoadControl(loadController) - .setUsePlatformDiagnostics(false) - .build(); - simpleExoPlayer.addListener(this); - simpleExoPlayer.setPlayWhenReady(playOnReady); - simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); - simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK); - simpleExoPlayer.setHandleAudioBecomingNoisy(true); - - audioReactor = new AudioReactor(context, simpleExoPlayer); - mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, - new PlayerMediaSession(this)); - - registerBroadcastReceiver(); - - // Setup video view - setupVideoSurface(); - - // enable media tunneling - if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) { - Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] " - + "media tunneling disabled in debug preferences"); - } else if (DeviceUtils.shouldSupportMediaTunneling()) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setTunnelingEnabled(true)); - } else if (DEBUG) { - Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling"); - } - } - - private void initListeners() { - binding.qualityTextView.setOnClickListener( - new QualityClickListener(this, qualityPopupMenu)); - binding.playbackSpeed.setOnClickListener( - new PlaybackSpeedClickListener(this, playbackSpeedPopupMenu)); - - binding.playbackSeekBar.setOnSeekBarChangeListener(this); - binding.captionTextView.setOnClickListener(this); - binding.resizeTextView.setOnClickListener(this); - binding.playbackLiveSync.setOnClickListener(this); - - playerGestureListener = new PlayerGestureListener(this, service); - gestureDetector = new GestureDetector(context, playerGestureListener); - binding.getRoot().setOnTouchListener(playerGestureListener); - - binding.queueButton.setOnClickListener(v -> onQueueClicked()); - binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); - binding.repeatButton.setOnClickListener(v -> onRepeatClicked()); - binding.shuffleButton.setOnClickListener(v -> onShuffleClicked()); - binding.addToPlaylistButton.setOnClickListener(v -> { - if (getParentActivity() != null) { - onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager()); - } - }); - - binding.playPauseButton.setOnClickListener(this); - binding.playPreviousButton.setOnClickListener(this); - binding.playNextButton.setOnClickListener(this); - - binding.moreOptionsButton.setOnClickListener(this); - binding.moreOptionsButton.setOnLongClickListener(this); - binding.share.setOnClickListener(this); - binding.share.setOnLongClickListener(this); - binding.fullScreenButton.setOnClickListener(this); - binding.screenRotationButton.setOnClickListener(this); - binding.playWithKodi.setOnClickListener(this); - binding.openInBrowser.setOnClickListener(this); - binding.playerCloseButton.setOnClickListener(this); - binding.switchMute.setOnClickListener(this); - - settingsContentObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(final boolean selfChange) { - setupScreenRotationButton(); - } - }; - context.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, - settingsContentObserver); - binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); - - ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> { - final Insets cutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()); - if (!cutout.equals(Insets.NONE)) { - view.setPadding(cutout.left, cutout.top, cutout.right, cutout.bottom); - } - return windowInsets; - }); - - // PlaybackControlRoot already consumed window insets but we should pass them to - // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - binding.playbackControlRoot.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - binding.playerOverlays.setPadding( - v.getPaddingLeft(), - v.getPaddingTop(), - v.getPaddingRight(), - v.getPaddingBottom()); - - // If we added padding to the fast seek overlay, too, it would not go under the - // system ui. Instead we apply negative margins equal to the window insets of - // the opposite side, so that the view covers all of the player (overflowing on - // some sides) and its center coincides with the center of other controls. - final RelativeLayout.LayoutParams fastSeekParams = (RelativeLayout.LayoutParams) - binding.fastSeekOverlay.getLayoutParams(); - fastSeekParams.leftMargin = -v.getPaddingRight(); - fastSeekParams.topMargin = -v.getPaddingBottom(); - fastSeekParams.rightMargin = -v.getPaddingLeft(); - fastSeekParams.bottomMargin = -v.getPaddingTop(); - }); - } - - /** - * Initializes the Fast-For/Backward overlay. - */ - private void setupPlayerSeekOverlay() { - binding.fastSeekOverlay - .seekSecondsSupplier(() -> retrieveSeekDurationFromPreferences(this) / 1000) - .performListener(new PlayerFastSeekOverlay.PerformListener() { - - @Override - public void onDoubleTap() { - animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION); - } - - @Override - public void onDoubleTapEnd() { - animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION); - } - - @NonNull - @Override - public FastSeekDirection getFastSeekDirection( - @NonNull final DisplayPortion portion - ) { - if (exoPlayerIsNull()) { - // Abort seeking - playerGestureListener.endMultiDoubleTap(); - return FastSeekDirection.NONE; - } - if (portion == DisplayPortion.LEFT) { - // Check if it's possible to rewind - // Small puffer to eliminate infinite rewind seeking - if (simpleExoPlayer.getCurrentPosition() < 500L) { - return FastSeekDirection.NONE; - } - return FastSeekDirection.BACKWARD; - } else if (portion == DisplayPortion.RIGHT) { - // Check if it's possible to fast-forward - if (currentState == STATE_COMPLETED - || simpleExoPlayer.getCurrentPosition() - >= simpleExoPlayer.getDuration()) { - return FastSeekDirection.NONE; - } - return FastSeekDirection.FORWARD; - } - /* portion == DisplayPortion.MIDDLE */ - return FastSeekDirection.NONE; - } - - @Override - public void seek(final boolean forward) { - playerGestureListener.keepInDoubleTapMode(); - if (forward) { - fastForward(); - } else { - fastRewind(); - } - } - }); - playerGestureListener.doubleTapControls(binding.fastSeekOverlay); - } - - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Playback initialization via intent //////////////////////////////////////////////////////////////////////////*/ @@ -708,6 +323,7 @@ public void handleIntent(@NonNull final Intent intent) { final PlayerType oldPlayerType = playerType; playerType = retrievePlayerTypeFromIntent(intent); + initUIsForCurrentPlayerType(); // We need to setup audioOnly before super(), see "sourceOf" isAudioOnly = audioPlayerSelected(); @@ -728,9 +344,6 @@ public void handleIntent(@NonNull final Intent intent) { return; } - // needed for tablets, check the function for a better explanation - directlyOpenFullscreenIfNeeded(); - final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this); final float playbackSpeed = savedParameters.speed; final float playbackPitch = savedParameters.pitch; @@ -828,46 +441,49 @@ && isPlaybackResumeEnabled(this) reloadPlayQueueManager(); } - setupElementsVisibility(); - setupElementsSize(); - - if (audioPlayerSelected()) { - service.removeViewFromParent(); - } else if (popupPlayerSelected()) { - binding.getRoot().setVisibility(View.VISIBLE); - initPopup(); - initPopupCloseOverlay(); - binding.playPauseButton.requestFocus(); - } else { - binding.getRoot().setVisibility(View.VISIBLE); - initVideoPlayer(); - closeItemsList(); - // Android TV: without it focus will frame the whole player - binding.playPauseButton.requestFocus(); - - // Note: This is for automatically playing (when "Resume playback" is off), see #6179 - if (getPlayWhenReady()) { - play(); - } else { - pause(); - } - } + UIs.call(PlayerUi::setupAfterIntent); NavigationHelper.sendPlayerStartedEvent(context); } - /** - * Open fullscreen on tablets where the option to have the main player start automatically in - * fullscreen mode is on. Rotating the device to landscape is already done in {@link - * VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's - * enough for phones, but not for tablets since the mini player can be also shown in landscape. - */ - private void directlyOpenFullscreenIfNeeded() { - if (fragmentListener != null - && PlayerHelper.isStartMainPlayerFullscreenEnabled(service) - && DeviceUtils.isTablet(service) - && videoPlayerSelected() - && PlayerHelper.globalScreenOrientationLocked(service)) { - fragmentListener.onScreenRotationButtonClicked(); + private void initUIsForCurrentPlayerType() { + //noinspection SimplifyOptionalCallChains + if (!UIs.get(NotificationPlayerUi.class).isPresent()) { + UIs.add(new NotificationPlayerUi(this)); + } + + if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) + || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { + // correct UI already in place + return; + } + + // try to reuse binding if possible + final PlayerBinding binding = UIs.get(VideoPlayerUi.class).map(VideoPlayerUi::getBinding) + .orElseGet(() -> { + if (playerType == PlayerType.AUDIO) { + return null; + } else { + return PlayerBinding.inflate(LayoutInflater.from(context)); + } + }); + + switch (playerType) { + case MAIN: + UIs.destroyAll(PopupPlayerUi.class); + UIs.add(new MainPlayerUi(this, binding)); + break; + case AUDIO: + UIs.destroyAll(VideoPlayerUi.class); + break; + case POPUP: + UIs.destroyAll(MainPlayerUi.class); + UIs.add(new PopupPlayerUi(this, binding)); + break; + } + + if (fragmentListener != null) { + // make sure UIs know whether a service is connected or not + UIs.call(PlayerUi::onFragmentListenerSet); } } @@ -881,23 +497,55 @@ private void initPlayback(@NonNull final PlayQueue queue, destroyPlayer(); initPlayer(playOnReady); setRepeatMode(repeatMode); - // #6825 - Ensure that the shuffle-button is in the correct state on the UI - setShuffleButton(binding.shuffleButton, simpleExoPlayer.getShuffleModeEnabled()); setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence); playQueue = queue; playQueue.init(); reloadPlayQueueManager(); - if (playQueueAdapter != null) { - playQueueAdapter.dispose(); - } - playQueueAdapter = new PlayQueueAdapter(context, playQueue); - segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); + UIs.call(PlayerUi::initPlayback); simpleExoPlayer.setVolume(isMuted ? 0 : 1); notifyQueueUpdateToListeners(); } + + private void initPlayer(final boolean playOnReady) { + if (DEBUG) { + Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]"); + } + + simpleExoPlayer = new ExoPlayer.Builder(context, renderFactory) + .setTrackSelector(trackSelector) + .setLoadControl(loadController) + .setUsePlatformDiagnostics(false) + .build(); + simpleExoPlayer.addListener(this); + simpleExoPlayer.setPlayWhenReady(playOnReady); + simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); + simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK); + simpleExoPlayer.setHandleAudioBecomingNoisy(true); + + audioReactor = new AudioReactor(context, simpleExoPlayer); + mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, + new PlayerMediaSession(this)); + + registerBroadcastReceiver(); + + // Setup UIs + UIs.call(PlayerUi::initPlayer); + + // enable media tunneling + if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) { + Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] " + + "media tunneling disabled in debug preferences"); + } else if (DeviceUtils.shouldSupportMediaTunneling()) { + trackSelector.setParameters(trackSelector.buildUponParameters() + .setTunnelingEnabled(true)); + } else if (DEBUG) { + Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling"); + } + } //endregion @@ -911,8 +559,7 @@ private void destroyPlayer() { if (DEBUG) { Log.d(TAG, "destroyPlayer() called"); } - - cleanupVideoSurface(); + UIs.call(PlayerUi::destroyPlayer); if (!exoPlayerIsNull()) { simpleExoPlayer.removeListener(this); @@ -934,17 +581,17 @@ private void destroyPlayer() { if (mediaSessionManager != null) { mediaSessionManager.dispose(); } - - if (playQueueAdapter != null) { - playQueueAdapter.unsetSelectedListener(); - playQueueAdapter.dispose(); - } } public void destroy() { if (DEBUG) { Log.d(TAG, "destroy() called"); } + + saveStreamProgressState(); + setRecovery(); + stopActivityBinding(); + destroyPlayer(); unregisterBroadcastReceiver(); @@ -952,11 +599,7 @@ public void destroy() { progressUpdateDisposable.set(null); PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading - if (binding != null) { - binding.endScreen.setImageBitmap(null); - } - - context.getContentResolver().unregisterContentObserver(settingsContentObserver); + UIs.call(PlayerUi::destroy); } public void setRecovery() { @@ -983,7 +626,7 @@ private void setRecovery(final int queuePos, final long windowPos) { playQueue.setRecovery(queuePos, windowPos); } - private void reloadPlayQueueManager() { + public void reloadPlayQueueManager() { if (playQueueManager != null) { playQueueManager.dispose(); } @@ -1002,185 +645,11 @@ public void onPlaybackShutdown() { service.stopService(); } - public void smoothStopPlayer() { + public void smoothStopForImmediateReusing() { // Pausing would make transition from one stream to a new stream not smooth, so only stop simpleExoPlayer.stop(); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Player type specific setup - //////////////////////////////////////////////////////////////////////////*/ - //region Player type specific setup - - private void initVideoPlayer() { - // restore last resize mode - setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(this)); - binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - } - - @SuppressLint("RtlHardcoded") - private void initPopup() { - if (DEBUG) { - Log.d(TAG, "initPopup() called"); - } - - // Popup is already added to windowManager - if (popupHasParent()) { - return; - } - - updateScreenSize(); - - popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); - binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); - - checkPopupPositionBounds(); - - binding.loadingPanel.setMinimumWidth(popupLayoutParams.width); - binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); - - service.removeViewFromParent(); - Objects.requireNonNull(windowManager).addView(binding.getRoot(), popupLayoutParams); - - // Popup doesn't have aspectRatio selector, using FIT automatically - setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); - } - - @SuppressLint("RtlHardcoded") - private void initPopupCloseOverlay() { - if (DEBUG) { - Log.d(TAG, "initPopupCloseOverlay() called"); - } - - // closeOverlayView is already added to windowManager - if (closeOverlayBinding != null) { - return; - } - - closeOverlayBinding = PlayerPopupCloseOverlayBinding.inflate(LayoutInflater.from(context)); - - final WindowManager.LayoutParams closeOverlayLayoutParams = buildCloseOverlayLayoutParams(); - closeOverlayBinding.closeButton.setVisibility(View.GONE); - Objects.requireNonNull(windowManager).addView( - closeOverlayBinding.getRoot(), closeOverlayLayoutParams); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Elements visibility and size: popup and main players have different look - //////////////////////////////////////////////////////////////////////////*/ - //region Elements visibility and size: popup and main players have different look - - /** - * This method ensures that popup and main players have different look. - * We use one layout for both players and need to decide what to show and what to hide. - * Additional measuring should be done inside {@link #setupElementsSize}. - */ - private void setupElementsVisibility() { - if (popupPlayerSelected()) { - binding.fullScreenButton.setVisibility(View.VISIBLE); - binding.screenRotationButton.setVisibility(View.GONE); - binding.resizeTextView.setVisibility(View.GONE); - binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); - binding.queueButton.setVisibility(View.GONE); - binding.segmentsButton.setVisibility(View.GONE); - binding.moreOptionsButton.setVisibility(View.GONE); - binding.topControls.setOrientation(LinearLayout.HORIZONTAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.WRAP_CONTENT; - binding.secondaryControls.setAlpha(1.0f); - binding.secondaryControls.setVisibility(View.VISIBLE); - binding.secondaryControls.setTranslationY(0); - binding.share.setVisibility(View.GONE); - binding.playWithKodi.setVisibility(View.GONE); - binding.openInBrowser.setVisibility(View.GONE); - binding.switchMute.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility(View.GONE); - binding.topControls.bringToFront(); - binding.topControls.setClickable(false); - binding.topControls.setFocusable(false); - binding.bottomControls.bringToFront(); - closeItemsList(); - } else if (videoPlayerSelected()) { - binding.fullScreenButton.setVisibility(View.GONE); - setupScreenRotationButton(); - binding.resizeTextView.setVisibility(View.VISIBLE); - binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); - binding.moreOptionsButton.setVisibility(View.VISIBLE); - binding.topControls.setOrientation(LinearLayout.VERTICAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.MATCH_PARENT; - binding.secondaryControls.setVisibility(View.INVISIBLE); - binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, - R.drawable.ic_expand_more)); - binding.share.setVisibility(View.VISIBLE); - binding.openInBrowser.setVisibility(View.VISIBLE); - binding.switchMute.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); - // Top controls have a large minHeight which is allows to drag the player - // down in fullscreen mode (just larger area to make easy to locate by finger) - binding.topControls.setClickable(true); - binding.topControls.setFocusable(true); - } - showHideKodiButton(); - - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - } - setMuteButton(binding.switchMute, isMuted()); - - animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0); - } - - /** - * Changes padding, size of elements based on player selected right now. - * Popup player has small padding in comparison with the main player - */ - private void setupElementsSize() { - final Resources res = context.getResources(); - final int buttonsMinWidth; - final int playerTopPad; - final int controlsPad; - final int buttonsPad; - - if (popupPlayerSelected()) { - buttonsMinWidth = 0; - playerTopPad = 0; - controlsPad = res.getDimensionPixelSize(R.dimen.player_popup_controls_padding); - buttonsPad = res.getDimensionPixelSize(R.dimen.player_popup_buttons_padding); - } else if (videoPlayerSelected()) { - buttonsMinWidth = res.getDimensionPixelSize(R.dimen.player_main_buttons_min_width); - playerTopPad = res.getDimensionPixelSize(R.dimen.player_main_top_padding); - controlsPad = res.getDimensionPixelSize(R.dimen.player_main_controls_padding); - buttonsPad = res.getDimensionPixelSize(R.dimen.player_main_buttons_padding); - } else { - return; - } - - binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); - binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); - binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); - binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - } - - private void showHideKodiButton() { - // show kodi button if it supports the current service and it is enabled in settings - binding.playWithKodi.setVisibility(videoPlayerSelected() - && playQueue != null && playQueue.getItem() != null - && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) - ? View.VISIBLE : View.GONE); + setRecovery(); + UIs.call(PlayerUi::smoothStopForImmediateReusing); } //endregion @@ -1243,11 +712,6 @@ private void onBroadcastReceived(final Intent intent) { break; case ACTION_PLAY_PAUSE: playPause(); - if (!fragmentIsVisible) { - // Ensure that we have audio-only stream playing when a user - // started to play from notification's play button from outside of the app - onFragmentStopped(); - } break; case ACTION_PLAY_PREVIOUS: playPrevious(); @@ -1262,55 +726,19 @@ private void onBroadcastReceived(final Intent intent) { fastForward(); break; case ACTION_REPEAT: - onRepeatClicked(); + cycleNextRepeatMode(); break; case ACTION_SHUFFLE: - onShuffleClicked(); + toggleShuffleModeEnabled(); break; case ACTION_RECREATE_NOTIFICATION: NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); break; - case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED: - fragmentIsVisible = true; - useVideoSource(true); - break; - case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED: - fragmentIsVisible = false; - onFragmentStopped(); - break; case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { Log.d(TAG, "onConfigurationChanged() called"); } - if (popupPlayerSelected()) { - updateScreenSize(); - changePopupSize(popupLayoutParams.width); - checkPopupPositionBounds(); - } - // Close it because when changing orientation from portrait - // (in fullscreen mode) the size of queue layout can be larger than the screen size - closeItemsList(); - // When the orientation changed, the screen height might be smaller. - // If the end screen thumbnail is not re-scaled, - // it can be larger than the current screen height - // and thus enlarging the whole player. - // This causes the seekbar to be ouf the visible area. - updateEndScreenThumbnail(); - break; - case Intent.ACTION_SCREEN_ON: - // Interrupt playback only when screen turns on - // and user is watching video in popup player. - // Same actions for video player will be handled in ACTION_VIDEO_FRAGMENT_RESUMED - if (popupPlayerSelected() && (isPlaying() || isLoading())) { - useVideoSource(true); - } - break; - case Intent.ACTION_SCREEN_OFF: - // Interrupt playback only when screen turns off with popup player working - if (popupPlayerSelected() && (isPlaying() || isLoading())) { - useVideoSource(false); - } break; case Intent.ACTION_HEADSET_PLUG: //FIXME /*notificationManager.cancel(NOTIFICATION_ID); @@ -1318,6 +746,8 @@ private void onBroadcastReceived(final Intent intent) { mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/ break; } + + UIs.call(playerUi -> playerUi.onBroadcastReceived(intent)); } private void registerBroadcastReceiver() { @@ -1366,12 +796,12 @@ public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { NotificationUtil.getInstance() .createNotificationIfNeededAndUpdate(Player.this, false); // there is a new thumbnail, so changed the end screen thumbnail, too. - updateEndScreenThumbnail(); + UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @Override public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called with: url = [" + url + "]", e); + Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); currentThumbnail = null; NotificationUtil.getInstance() .createNotificationIfNeededAndUpdate(Player.this, false); @@ -1380,259 +810,12 @@ public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { @Override public void onPrepareLoad(final Drawable placeHolderDrawable) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingStarted() called with: url = [" + url + "]"); + Log.d(TAG, "Thumbnail - onPrepareLoad() called: url = [" + url + "]"); } } }); } - - /** - * Scale the player audio / end screen thumbnail down if necessary. - *

- * This is necessary when the thumbnail's height is larger than the device's height - * and thus is enlarging the player's height - * causing the bottom playback controls to be out of the visible screen. - *

- */ - public void updateEndScreenThumbnail() { - if (currentThumbnail == null) { - return; - } - - final float endScreenHeight = calculateMaxEndScreenThumbnailHeight(); - - final Bitmap endScreenBitmap = Bitmap.createScaledBitmap( - currentThumbnail, - (int) (currentThumbnail.getWidth() - / (currentThumbnail.getHeight() / endScreenHeight)), - (int) endScreenHeight, - true); - - if (DEBUG) { - Log.d(TAG, "Thumbnail - updateEndScreenThumbnail() called with: " - + "currentThumbnail = [" + currentThumbnail + "], " - + currentThumbnail.getWidth() + "x" + currentThumbnail.getHeight() - + ", scaled end screen height = " + endScreenHeight - + ", scaled end screen width = " + endScreenBitmap.getWidth()); - } - - binding.endScreen.setImageBitmap(endScreenBitmap); - } - - /** - * Calculate the maximum allowed height for the {@link R.id.endScreen} - * to prevent it from enlarging the player. - *

- * The calculating follows these rules: - *

    - *
  • - * Show at least stream title and content creator on TVs and tablets - * when in landscape (always the case for TVs) and not in fullscreen mode. - * This requires to have at least 85dp free space for {@link R.id.detail_root} - * and additional space for the stream title text size - * ({@link R.id.detail_title_root_layout}). - * The text size is 15sp on tablets and 16sp on TVs, - * see {@link R.id.titleTextView}. - *
  • - *
  • - * Otherwise, the max thumbnail height is the screen height. - *
  • - *
- * - * @return the maximum height for the end screen thumbnail - */ - private float calculateMaxEndScreenThumbnailHeight() { - // ensure that screenHeight is initialized and thus not 0 - updateScreenSize(); - - if (DeviceUtils.isTv(context) && !isFullscreen) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); - return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight); - } else if (DeviceUtils.isTablet(context) && service.isLandscape() && !isFullscreen) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); - return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight); - } else { // fullscreen player: max height is the device height - return Math.min(currentThumbnail.getHeight(), screenHeight); - } - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Popup player utils - //////////////////////////////////////////////////////////////////////////*/ - //region Popup player utils - - /** - * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary - * that goes from (0, 0) to (screenWidth, screenHeight). - *

- * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed - * and {@code true} is returned to represent this change. - *

- */ - public void checkPopupPositionBounds() { - if (DEBUG) { - Log.d(TAG, "checkPopupPositionBounds() called with: " - + "screenWidth = [" + screenWidth + "], " - + "screenHeight = [" + screenHeight + "]"); - } - if (popupLayoutParams == null) { - return; - } - - if (popupLayoutParams.x < 0) { - popupLayoutParams.x = 0; - } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { - popupLayoutParams.x = (int) (screenWidth - popupLayoutParams.width); - } - - if (popupLayoutParams.y < 0) { - popupLayoutParams.y = 0; - } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { - popupLayoutParams.y = (int) (screenHeight - popupLayoutParams.height); - } - } - - public void updateScreenSize() { - if (windowManager != null) { - final DisplayMetrics metrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(metrics); - - screenWidth = metrics.widthPixels; - screenHeight = metrics.heightPixels; - if (DEBUG) { - Log.d(TAG, "updateScreenSize() called: screenWidth = [" - + screenWidth + "], screenHeight = [" + screenHeight + "]"); - } - } - } - - /** - * Changes the size of the popup based on the width. - * @param width the new width, height is calculated with - * {@link PlayerHelper#getMinimumVideoHeight(float)} - */ - public void changePopupSize(final int width) { - if (DEBUG) { - Log.d(TAG, "changePopupSize() called with: width = [" + width + "]"); - } - - if (anyPopupViewIsNull()) { - return; - } - - final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); - final int actualWidth = (int) (width > screenWidth ? screenWidth - : (width < minimumWidth ? minimumWidth : width)); - final int actualHeight = (int) getMinimumVideoHeight(width); - if (DEBUG) { - Log.d(TAG, "updatePopupSize() updated values:" - + " width = [" + actualWidth + "], height = [" + actualHeight + "]"); - } - - popupLayoutParams.width = actualWidth; - popupLayoutParams.height = actualHeight; - binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); - Objects.requireNonNull(windowManager) - .updateViewLayout(binding.getRoot(), popupLayoutParams); - } - - private void changePopupWindowFlags(final int flags) { - if (DEBUG) { - Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); - } - - if (!anyPopupViewIsNull()) { - popupLayoutParams.flags = flags; - Objects.requireNonNull(windowManager) - .updateViewLayout(binding.getRoot(), popupLayoutParams); - } - } - - public void closePopup() { - if (DEBUG) { - Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); - } - if (isPopupClosing) { - return; - } - isPopupClosing = true; - - saveStreamProgressState(); - Objects.requireNonNull(windowManager).removeView(binding.getRoot()); - - animatePopupOverlayAndFinishService(); - } - - public void removePopupFromView() { - if (windowManager != null) { - // wrap in try-catch since it could sometimes generate errors randomly - try { - if (popupHasParent()) { - windowManager.removeView(binding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup from window manager", e); - } - - try { - final boolean closeOverlayHasParent = closeOverlayBinding != null - && closeOverlayBinding.getRoot().getParent() != null; - if (closeOverlayHasParent) { - windowManager.removeView(closeOverlayBinding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup overlay from window manager", e); - } - } - } - - private void animatePopupOverlayAndFinishService() { - final int targetTranslationY = - (int) (closeOverlayBinding.closeButton.getRootView().getHeight() - - closeOverlayBinding.closeButton.getY()); - - closeOverlayBinding.closeButton.animate().setListener(null).cancel(); - closeOverlayBinding.closeButton.animate() - .setInterpolator(new AnticipateInterpolator()) - .translationY(targetTranslationY) - .setDuration(400) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(final Animator animation) { - end(); - } - - @Override - public void onAnimationEnd(final Animator animation) { - end(); - } - - private void end() { - Objects.requireNonNull(windowManager) - .removeView(closeOverlayBinding.getRoot()); - closeOverlayBinding = null; - service.stopService(); - } - }).start(); - } - - private boolean popupHasParent() { - return binding != null - && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams - && binding.getRoot().getParent() != null; - } - - private boolean anyPopupViewIsNull() { - // TODO understand why checking getParentActivity() != null - return popupLayoutParams == null || windowManager == null - || getParentActivity() != null || binding.getRoot().getParent() == null; - } - //endregion + //endregion @@ -1645,7 +828,7 @@ public float getPlaybackSpeed() { return getPlaybackParameters().speed; } - private void setPlaybackSpeed(final float speed) { + public void setPlaybackSpeed(final float speed) { setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence()); } @@ -1694,40 +877,13 @@ public void setPlaybackParameters(final float speed, final float pitch, private void onUpdateProgress(final int currentProgress, final int duration, final int bufferPercent) { - if (!isPrepared) { - return; - } - - if (duration != binding.playbackSeekBar.getMax()) { - setVideoDurationToControls(duration); - } - if (currentState != STATE_PAUSED) { - updatePlayBackElementsCurrentDuration(currentProgress); - } - if (simpleExoPlayer.isLoading() || bufferPercent > 90) { - binding.playbackSeekBar.setSecondaryProgress( - (int) (binding.playbackSeekBar.getMax() * ((float) bufferPercent / 100))); - } - if (DEBUG && bufferPercent % 20 == 0) { //Limit log - Log.d(TAG, "notifyProgressUpdateToListeners() called with: " - + "isVisible = " + isControlsVisible() + ", " - + "currentProgress = [" + currentProgress + "], " - + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); - } - binding.playbackLiveSync.setClickable(!isLiveEdge()); - - notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); - - if (areSegmentsVisible) { - segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); - } - - if (isQueueVisible) { - updateQueueTime(currentProgress); + if (isPrepared) { + UIs.call(ui -> ui.onUpdateProgress(currentProgress, duration, bufferPercent)); + notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); } } - private void startProgressLoop() { + public void startProgressLoop() { progressUpdateDisposable.set(getProgressUpdateDisposable()); } @@ -1735,11 +891,11 @@ private void stopProgressLoop() { progressUpdateDisposable.set(null); } - private boolean isProgressLoopRunning() { + public boolean isProgressLoopRunning() { return progressUpdateDisposable.get() != null; } - private void triggerProgressUpdate() { + public void triggerProgressUpdate() { if (exoPlayerIsNull()) { return; } @@ -1756,228 +912,12 @@ private Disposable getProgressUpdateDisposable() { error -> Log.e(TAG, "Progress update failure: ", error)); } - @Override // seekbar listener - public void onProgressChanged(final SeekBar seekBar, final int progress, - final boolean fromUser) { - // Currently we don't need method execution when fromUser is false - if (!fromUser) { - return; - } - if (DEBUG) { - Log.d(TAG, "onProgressChanged() called with: " - + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); - } - - binding.currentDisplaySeek.setText(getTimeString(progress)); - - // Seekbar Preview Thumbnail - SeekbarPreviewThumbnailHelper - .tryResizeAndSetSeekbarPreviewThumbnail( - getContext(), - seekbarPreviewThumbnailHolder.getBitmapAt(progress), - binding.currentSeekbarPreviewThumbnail, - binding.subtitleView::getWidth); - - adjustSeekbarPreviewContainer(); - } - - private void adjustSeekbarPreviewContainer() { - try { - // Should only be required when an error occurred before - // and the layout was positioned in the center - binding.bottomSeekbarPreviewLayout.setGravity(Gravity.NO_GRAVITY); - - // Calculate the current left position of seekbar progress in px - // More info: https://stackoverflow.com/q/20493577 - final int currentSeekbarLeft = - binding.playbackSeekBar.getLeft() - + binding.playbackSeekBar.getPaddingLeft() - + binding.playbackSeekBar.getThumb().getBounds().left; - - // Calculate the (unchecked) left position of the container - final int uncheckedContainerLeft = - currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); - - // Fix the position so it's within the boundaries - final int checkedContainerLeft = - Math.max( - Math.min( - uncheckedContainerLeft, - // Max left - binding.playbackWindowRoot.getWidth() - - binding.seekbarPreviewContainer.getWidth() - ), - 0 // Min left - ); - - // See also: https://stackoverflow.com/a/23249734 - final LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams( - binding.seekbarPreviewContainer.getLayoutParams()); - params.setMarginStart(checkedContainerLeft); - binding.seekbarPreviewContainer.setLayoutParams(params); - } catch (final Exception ex) { - Log.e(TAG, "Failed to adjust seekbarPreviewContainer", ex); - // Fallback - position in the middle - binding.bottomSeekbarPreviewLayout.setGravity(Gravity.CENTER); - } - } - - @Override // seekbar listener - public void onStartTrackingTouch(final SeekBar seekBar) { - if (DEBUG) { - Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); - } - if (currentState != STATE_PAUSED_SEEK) { - changeState(STATE_PAUSED_SEEK); - } - - saveWasPlaying(); - if (isPlaying()) { - simpleExoPlayer.pause(); - } - - showControls(0); - animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SCALE_AND_ALPHA); - animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SCALE_AND_ALPHA); - } - - @Override // seekbar listener - public void onStopTrackingTouch(final SeekBar seekBar) { - if (DEBUG) { - Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); - } - - seekTo(seekBar.getProgress()); - if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) { - simpleExoPlayer.play(); - } - - binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA); - - if (currentState == STATE_PAUSED_SEEK) { - changeState(STATE_BUFFERING); - } - if (!isProgressLoopRunning()) { - startProgressLoop(); - } - if (wasPlaying) { - showControlsThenHide(); - } - } - public void saveWasPlaying() { this.wasPlaying = getPlayWhenReady(); } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Controls showing / hiding - //////////////////////////////////////////////////////////////////////////*/ - //region Controls showing / hiding - - public boolean isControlsVisible() { - return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE; - } - - public void showControlsThenHide() { - if (DEBUG) { - Log.d(TAG, "showControlsThenHide() called"); - } - showOrHideButtons(); - showSystemUIPartially(); - - final int hideTime = binding.playbackControlRoot.isInTouchMode() - ? DEFAULT_CONTROLS_HIDE_TIME - : DPAD_CONTROLS_HIDE_TIME; - - showHideShadow(true, DEFAULT_CONTROLS_DURATION); - animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, - AnimationType.ALPHA, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); - } - - public void showControls(final long duration) { - if (DEBUG) { - Log.d(TAG, "showControls() called"); - } - showOrHideButtons(); - showSystemUIPartially(); - controlsVisibilityHandler.removeCallbacksAndMessages(null); - showHideShadow(true, duration); - animate(binding.playbackControlRoot, true, duration); - } - - public void hideControls(final long duration, final long delay) { - if (DEBUG) { - Log.d(TAG, "hideControls() called with: duration = [" + duration - + "], delay = [" + delay + "]"); - } - - showOrHideButtons(); - - controlsVisibilityHandler.removeCallbacksAndMessages(null); - controlsVisibilityHandler.postDelayed(() -> { - showHideShadow(false, duration); - animate(binding.playbackControlRoot, false, duration, AnimationType.ALPHA, - 0, this::hideSystemUIIfNeeded); - }, delay); - } - - public void showHideShadow(final boolean show, final long duration) { - animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null); - animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null); - animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null); - } - - private void showOrHideButtons() { - if (playQueue == null) { - return; - } - - final boolean showPrev = playQueue.getIndex() != 0; - final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); - final boolean showQueue = playQueue.getStreams().size() > 1 && !popupPlayerSelected(); - /* only when stream has segments and is not playing in popup player */ - final boolean showSegment = !popupPlayerSelected() - && !getCurrentStreamInfo() - .map(StreamInfo::getStreamSegments) - .map(List::isEmpty) - .orElse(/*no stream info=*/true); - - binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); - binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); - binding.playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE); - binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); - binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); - binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f); - binding.segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE); - binding.segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f); - } - - private void showSystemUIPartially() { - final AppCompatActivity activity = getParentActivity(); - if (isFullscreen && activity != null) { - activity.getWindow().setStatusBarColor(Color.TRANSPARENT); - activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); - - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - } - private void hideSystemUIIfNeeded() { - if (fragmentListener != null) { - fragmentListener.hideSystemUiIfNeeded(); - } + public boolean wasPlaying() { + return wasPlaying; } //endregion @@ -2011,7 +951,7 @@ public void onPlaybackStateChanged(final int playbackState) { private void updatePlaybackState(final boolean playWhenReady, final int playbackState) { if (DEBUG) { - Log.d(TAG, "ExoPlayer - onPlayerStateChanged() called with: " + Log.d(TAG, "ExoPlayer - updatePlaybackState() called with: " + "playWhenReady = [" + playWhenReady + "], " + "playbackState = [" + playbackState + "]"); } @@ -2122,9 +1062,7 @@ private void onPrepared(final boolean playWhenReady) { Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); } - setVideoDurationToControls((int) simpleExoPlayer.getDuration()); - - binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); + UIs.call(PlayerUi::onPrepared); if (playWhenReady) { audioReactor.requestAudioFocus(); @@ -2139,20 +1077,7 @@ private void onBlocked() { startProgressLoop(); } - // if we are e.g. switching players, hide controls - hideControls(DEFAULT_CONTROLS_DURATION, 0); - - binding.playbackSeekBar.setEnabled(false); - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - - binding.loadingPanel.setBackgroundColor(Color.BLACK); - animate(binding.loadingPanel, true, 0); - animate(binding.surfaceForeground, true, 100); - - binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); - animatePlayButtons(false, 100); - binding.getRoot().setKeepScreenOn(false); + UIs.call(PlayerUi::onBlocked); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2165,28 +1090,7 @@ private void onPlaying() { startProgressLoop(); } - updateStreamRelatedViews(); - - binding.playbackSeekBar.setEnabled(true); - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - - binding.loadingPanel.setVisibility(View.GONE); - - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - - animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_pause); - animatePlayButtons(true, 200); - if (!isQueueVisible) { - binding.playPauseButton.requestFocus(); - } - }); - - changePopupWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); - checkLandscape(); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onPlaying); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2195,10 +1099,8 @@ private void onBuffering() { if (DEBUG) { Log.d(TAG, "onBuffering() called"); } - binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT); - binding.loadingPanel.setVisibility(View.VISIBLE); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onBuffering); if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) { NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); @@ -2214,22 +1116,7 @@ private void onPaused() { stopProgressLoop(); } - // Don't let UI elements popup during double tap seeking. This state is entered sometimes - // during seeking/loading. This if-else check ensures that the controls aren't popping up. - if (!playerGestureListener.isDoubleTapping()) { - showControls(400); - binding.loadingPanel.setVisibility(View.GONE); - - animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); - animatePlayButtons(true, 200); - if (!isQueueVisible) { - binding.playPauseButton.requestFocus(); - } - }); - } - changePopupWindowFlags(IDLE_WINDOW_FLAGS); + UIs.call(PlayerUi::onPaused); // Remove running notification when user does not want minimization to background or popup if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE @@ -2238,8 +1125,6 @@ && videoPlayerSelected()) { } else { NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - - binding.getRoot().setKeepScreenOn(false); } private void onPausedSeek() { @@ -2247,8 +1132,7 @@ private void onPausedSeek() { Log.d(TAG, "onPausedSeek() called"); } - animatePlayButtons(false, 100); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onPausedSeek); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2261,19 +1145,8 @@ private void onCompleted() { return; } - animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_replay); - animatePlayButtons(true, DEFAULT_CONTROLS_DURATION); - }); - - binding.getRoot().setKeepScreenOn(false); - changePopupWindowFlags(IDLE_WINDOW_FLAGS); - + UIs.call(PlayerUi::onCompleted); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - if (isFullscreen) { - toggleFullscreen(); - } if (playQueue.getIndex() < playQueue.size() - 1) { playQueue.offsetIndex(+1); @@ -2281,38 +1154,6 @@ private void onCompleted() { if (isProgressLoopRunning()) { stopProgressLoop(); } - - // When a (short) video ends the elements have to display the correct values - see #6180 - updatePlayBackElementsCurrentDuration(binding.playbackSeekBar.getMax()); - - showControls(500); - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - binding.loadingPanel.setVisibility(View.GONE); - animate(binding.surfaceForeground, true, 100); - } - - private void animatePlayButtons(final boolean show, final int duration) { - animate(binding.playPauseButton, show, duration, AnimationType.SCALE_AND_ALPHA); - - boolean showQueueButtons = show; - if (playQueue == null) { - showQueueButtons = false; - } - - if (!showQueueButtons || playQueue.getIndex() > 0) { - animate( - binding.playPreviousButton, - showQueueButtons, - duration, - AnimationType.SCALE_AND_ALPHA); - } - if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) { - animate( - binding.playNextButton, - showQueueButtons, - duration, - AnimationType.SCALE_AND_ALPHA); - } } //endregion @@ -2323,34 +1164,20 @@ private void animatePlayButtons(final boolean show, final int duration) { //////////////////////////////////////////////////////////////////////////*/ //region Repeat and shuffle - public void onRepeatClicked() { - if (DEBUG) { - Log.d(TAG, "onRepeatClicked() called"); - } - setRepeatMode(nextRepeatMode(getRepeatMode())); - } - - public void onShuffleClicked() { - if (DEBUG) { - Log.d(TAG, "onShuffleClicked() called"); - } - - if (exoPlayerIsNull()) { - return; - } - simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); - } - @RepeatMode public int getRepeatMode() { return exoPlayerIsNull() ? REPEAT_MODE_OFF : simpleExoPlayer.getRepeatMode(); } - private void setRepeatMode(@RepeatMode final int repeatMode) { + public void setRepeatMode(@RepeatMode final int repeatMode) { if (!exoPlayerIsNull()) { simpleExoPlayer.setRepeatMode(repeatMode); } } + + public void cycleNextRepeatMode() { + setRepeatMode(nextRepeatMode(getRepeatMode())); + } @Override public void onRepeatModeChanged(@RepeatMode final int repeatMode) { @@ -2358,7 +1185,7 @@ public void onRepeatModeChanged(@RepeatMode final int repeatMode) { Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: " + "repeatMode = [" + repeatMode + "]"); } - setRepeatModeButton(binding.repeatButton, repeatMode); + UIs.call(playerUi -> playerUi.onRepeatModeChanged(repeatMode)); onShuffleOrRepeatModeChanged(); } @@ -2377,39 +1204,26 @@ public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { } } - setShuffleButton(binding.shuffleButton, shuffleModeEnabled); + UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); onShuffleOrRepeatModeChanged(); } + + public void toggleShuffleModeEnabled() { + if (!exoPlayerIsNull()) { + simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); + } + } private void onShuffleOrRepeatModeChanged() { notifyPlaybackUpdateToListeners(); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - - private void setRepeatModeButton(final AppCompatImageButton imageButton, - @RepeatMode final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - } - } - - private void setShuffleButton(@NonNull final ImageButton button, final boolean shuffled) { - button.setImageAlpha(shuffled ? 255 : 77); - } //endregion /*////////////////////////////////////////////////////////////////////////// - // Playlist append + // Playlist append TODO this does not make sense here //////////////////////////////////////////////////////////////////////////*/ //region Playlist append @@ -2439,23 +1253,16 @@ public void onAddToPlaylistClicked(@NonNull final FragmentManager fragmentManage //////////////////////////////////////////////////////////////////////////*/ //region Mute / Unmute - public void onMuteUnmuteButtonClicked() { - if (DEBUG) { - Log.d(TAG, "onMuteUnmuteButtonClicked() called"); - } - simpleExoPlayer.setVolume(isMuted() ? 1 : 0); + public void toggleMute() { + final boolean wasMuted = isMuted(); + simpleExoPlayer.setVolume(wasMuted ? 1 : 0); + UIs.call(playerUi -> playerUi.onMuteUnmuteChanged(!wasMuted)); notifyPlaybackUpdateToListeners(); - setMuteButton(binding.switchMute, isMuted()); } - boolean isMuted() { + public boolean isMuted() { return !exoPlayerIsNull() && simpleExoPlayer.getVolume() == 0; } - - private void setMuteButton(@NonNull final ImageButton button, final boolean isMuted) { - button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted - ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); - } //endregion @@ -2519,7 +1326,7 @@ public void onTracksChanged(@NonNull final Tracks tracks) { Log.d(TAG, "ExoPlayer - onTracksChanged(), " + "track group size = " + tracks.getGroups().size()); } - onTextTracksChanged(tracks); + UIs.call(playerUi -> playerUi.onTextTracksChanged(tracks)); } @Override @@ -2528,7 +1335,7 @@ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playba Log.d(TAG, "ExoPlayer - playbackParameters(), speed = [" + playbackParameters.speed + "], pitch = [" + playbackParameters.pitch + "]"); } - binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed)); + UIs.call(playerUi -> playerUi.onPlaybackParametersChanged(playbackParameters)); } @Override @@ -2580,13 +1387,12 @@ public void onPositionDiscontinuity(@NonNull final PositionInfo oldPosition, @Override public void onRenderedFirstFrame() { - //TODO check if this causes black screen when switching to fullscreen - animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); + UIs.call(PlayerUi::onRenderedFirstFrame); } @Override public void onCues(@NonNull final CueGroup cueGroup) { - binding.subtitleView.setCues(cueGroup.cues); + UIs.call(playerUi -> playerUi.onCues(cueGroup.cues)); } //endregion @@ -2627,7 +1433,7 @@ public void onCues(@NonNull final CueGroup cueGroup) { // Any error code not explicitly covered here are either unrelated to NewPipe use case // (e.g. DRM) or not recoverable (e.g. Decoder error). In both cases, the player should // shutdown. - @SuppressLint("SwitchIntDef") + @SuppressWarnings("SwitchIntDef") @Override public void onPlayerError(@NonNull final PlaybackException error) { Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error); @@ -2706,18 +1512,6 @@ private void createErrorNotification(@NonNull final PlaybackException error) { //////////////////////////////////////////////////////////////////////////*/ //region Playback position and seek - /** - * Sets the current duration into the corresponding elements. - * @param currentProgress - */ - private void updatePlayBackElementsCurrentDuration(final int currentProgress) { - // Don't set seekbar progress while user is seeking - if (currentState != STATE_PAUSED_SEEK) { - binding.playbackSeekBar.setProgress(currentProgress); - } - binding.playbackCurrentTime.setText(getTimeString(currentProgress)); - } - @Override // own playback listener (this is a getter) public boolean isApproachingPlaybackEdge(final long timeToEndMillis) { // If live, then not near playback edge @@ -2835,20 +1629,6 @@ public void seekToDefault() { simpleExoPlayer.seekToDefaultPosition(); } } - - /** - * Sets the video duration time into all control components (e.g. seekbar). - * @param duration - */ - private void setVideoDurationToControls(final int duration) { - binding.playbackEndTime.setText(getTimeString(duration)); - - binding.playbackSeekBar.setMax(duration); - // This is important for Android TVs otherwise it would apply the default from - // setMax/Min methods which is (max - min) / 20 - binding.playbackSeekBar.setKeyProgressIncrement( - PlayerHelper.retrieveSeekDurationFromPreferences(this)); - } //endregion @@ -2972,6 +1752,7 @@ private void registerStreamViewed() { } private void saveStreamProgressState(final long progressMillis) { + //noinspection SimplifyOptionalCallChains if (!getCurrentStreamInfo().isPresent() || !prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) { return; @@ -3026,17 +1807,10 @@ private void onMetadataChanged(@NonNull final StreamInfo info) { Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); } + UIs.call(playerUi -> playerUi.onMetadataChanged(info)); + initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); - updateStreamRelatedViews(); - showHideKodiButton(); - - binding.titleTextView.setText(info.getName()); - binding.channelTextView.setText(info.getUploaderName()); - - this.seekbarPreviewThumbnailHolder.resetFrom(this.getContext(), info.getPreviewFrames()); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); final boolean showThumbnail = prefs.getBoolean( context.getString(R.string.show_thumbnail_key), true); @@ -3048,17 +1822,7 @@ private void onMetadataChanged(@NonNull final StreamInfo info) { ); notifyMetadataUpdateToListeners(); - - if (areSegmentsVisible) { - if (segmentAdapter.setItems(info)) { - final int adapterPosition = getNearestStreamSegmentPosition( - simpleExoPlayer.getCurrentPosition()); - segmentAdapter.selectSegmentAt(adapterPosition); - binding.itemsList.scrollToPosition(adapterPosition); - } else { - closeItemsList(); - } - } + NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { @@ -3072,15 +1836,15 @@ private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { } @NonNull - private String getVideoUrl() { + public String getVideoUrl() { return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getStreamUrl(); } @NonNull - private String getVideoUrlAtCurrentTime() { - final int timeSeconds = binding.playbackSeekBar.getProgress() / 1000; + public String getVideoUrlAtCurrentTime() { + final long timeSeconds = simpleExoPlayer.getCurrentPosition() / 1000; String videoUrl = getVideoUrl(); if (!isLive() && timeSeconds >= 0 && currentMetadata != null && currentMetadata.getServiceId() == YouTube.getServiceId()) { @@ -3156,190 +1920,10 @@ public void selectQueueItem(final PlayQueueItem item) { @Override public void onPlayQueueEdited() { notifyPlaybackUpdateToListeners(); - showOrHideButtons(); + UIs.call(PlayerUi::onPlayQueueEdited); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - private void onQueueClicked() { - isQueueVisible = true; - - hideSystemUIIfNeeded(); - buildQueue(); - - binding.itemsListHeaderTitle.setVisibility(View.GONE); - binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); - binding.shuffleButton.setVisibility(View.VISIBLE); - binding.repeatButton.setVisibility(View.VISIBLE); - binding.addToPlaylistButton.setVisibility(View.VISIBLE); - - hideControls(0, 0); - binding.itemsListPanel.requestFocus(); - animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA); - - binding.itemsList.scrollToPosition(playQueue.getIndex()); - - updateQueueTime((int) simpleExoPlayer.getCurrentPosition()); - } - - private void buildQueue() { - binding.itemsList.setAdapter(playQueueAdapter); - binding.itemsList.setClickable(true); - binding.itemsList.setLongClickable(true); - - binding.itemsList.clearOnScrollListeners(); - binding.itemsList.addOnScrollListener(getQueueScrollListener()); - - itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); - itemTouchHelper.attachToRecyclerView(binding.itemsList); - - playQueueAdapter.setSelectedListener(getOnSelectedListener()); - - binding.itemsListClose.setOnClickListener(view -> closeItemsList()); - } - - private void onSegmentsClicked() { - areSegmentsVisible = true; - - hideSystemUIIfNeeded(); - buildSegments(); - - binding.itemsListHeaderTitle.setVisibility(View.VISIBLE); - binding.itemsListHeaderDuration.setVisibility(View.GONE); - binding.shuffleButton.setVisibility(View.GONE); - binding.repeatButton.setVisibility(View.GONE); - binding.addToPlaylistButton.setVisibility(View.GONE); - - hideControls(0, 0); - binding.itemsListPanel.requestFocus(); - animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA); - - final int adapterPosition = getNearestStreamSegmentPosition(simpleExoPlayer - .getCurrentPosition()); - segmentAdapter.selectSegmentAt(adapterPosition); - binding.itemsList.scrollToPosition(adapterPosition); - } - - private void buildSegments() { - binding.itemsList.setAdapter(segmentAdapter); - binding.itemsList.setClickable(true); - binding.itemsList.setLongClickable(false); - - binding.itemsList.clearOnScrollListeners(); - if (itemTouchHelper != null) { - itemTouchHelper.attachToRecyclerView(null); - } - - getCurrentStreamInfo().ifPresent(segmentAdapter::setItems); - - binding.shuffleButton.setVisibility(View.GONE); - binding.repeatButton.setVisibility(View.GONE); - binding.addToPlaylistButton.setVisibility(View.GONE); - binding.itemsListClose.setOnClickListener(view -> closeItemsList()); - } - - public void closeItemsList() { - if (isQueueVisible || areSegmentsVisible) { - isQueueVisible = false; - areSegmentsVisible = false; - - if (itemTouchHelper != null) { - itemTouchHelper.attachToRecyclerView(null); - } - - animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { - // Even when queueLayout is GONE it receives touch events - // and ruins normal behavior of the app. This line fixes it - binding.itemsListPanel.setTranslationY( - -binding.itemsListPanel.getHeight() * 5); - }); - - // clear focus, otherwise a white rectangle remains on top of the player - binding.itemsListClose.clearFocus(); - binding.playPauseButton.requestFocus(); - } - } - - private OnScrollBelowItemsListener getQueueScrollListener() { - return new OnScrollBelowItemsListener() { - @Override - public void onScrolledDown(final RecyclerView recyclerView) { - if (playQueue != null && !playQueue.isComplete()) { - playQueue.fetch(); - } else if (binding != null) { - binding.itemsList.clearOnScrollListeners(); - } - } - }; - } - - private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() { - return (item, seconds) -> { - segmentAdapter.selectSegment(item); - seekTo(seconds * 1000L); - triggerProgressUpdate(); - }; - } - - private int getNearestStreamSegmentPosition(final long playbackPosition) { - int nearestPosition = 0; - final List segments = getCurrentStreamInfo() - .map(StreamInfo::getStreamSegments) - .orElse(Collections.emptyList()); - - for (int i = 0; i < segments.size(); i++) { - if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { - break; - } - nearestPosition++; - } - return Math.max(0, nearestPosition - 1); - } - - private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new PlayQueueItemTouchCallback() { - @Override - public void onMove(final int sourceIndex, final int targetIndex) { - if (playQueue != null) { - playQueue.move(sourceIndex, targetIndex); - } - } - - @Override - public void onSwiped(final int index) { - if (index != -1) { - playQueue.remove(index); - } - } - }; - } - - private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { - return new PlayQueueItemBuilder.OnSelectedListener() { - @Override - public void selected(final PlayQueueItem item, final View view) { - selectQueueItem(item); - } - - @Override - public void held(final PlayQueueItem item, final View view) { - if (playQueue.indexOf(item) != -1) { - openPopupMenu(playQueue, item, view, true, - getParentActivity().getSupportFragmentManager(), context); - } - } - - @Override - public void onStartDrag(final PlayQueueItemHolder viewHolder) { - if (itemTouchHelper != null) { - itemTouchHelper.startDrag(viewHolder); - } - } - }; - } - @Override // own playback listener @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { @@ -3372,279 +1956,21 @@ public void disablePreloadingOfCurrentTrack() { @Nullable public VideoStream getSelectedVideoStream() { - return (selectedStreamIndex >= 0 && availableStreams != null - && availableStreams.size() > selectedStreamIndex) - ? availableStreams.get(selectedStreamIndex) : null; - } - - private void updateStreamRelatedViews() { - if (!getCurrentStreamInfo().isPresent()) { - return; - } - final StreamInfo info = getCurrentStreamInfo().get(); - - binding.qualityTextView.setVisibility(View.GONE); - binding.playbackSpeed.setVisibility(View.GONE); - - binding.playbackEndTime.setVisibility(View.GONE); - binding.playbackLiveSync.setVisibility(View.GONE); - - switch (info.getStreamType()) { - case AUDIO_STREAM: - case POST_LIVE_AUDIO_STREAM: - binding.surfaceView.setVisibility(View.GONE); - binding.endScreen.setVisibility(View.VISIBLE); - binding.playbackEndTime.setVisibility(View.VISIBLE); - break; - - case AUDIO_LIVE_STREAM: - binding.surfaceView.setVisibility(View.GONE); - binding.endScreen.setVisibility(View.VISIBLE); - binding.playbackLiveSync.setVisibility(View.VISIBLE); - break; - - case LIVE_STREAM: - binding.surfaceView.setVisibility(View.VISIBLE); - binding.endScreen.setVisibility(View.GONE); - binding.playbackLiveSync.setVisibility(View.VISIBLE); - break; - - case VIDEO_STREAM: - case POST_LIVE_STREAM: - if (currentMetadata == null - || !currentMetadata.getMaybeQuality().isPresent() - || (info.getVideoStreams().isEmpty() - && info.getVideoOnlyStreams().isEmpty())) { - break; - } - - availableStreams = currentMetadata.getMaybeQuality().get().getSortedVideoStreams(); - selectedStreamIndex = - currentMetadata.getMaybeQuality().get().getSelectedVideoStreamIndex(); - buildQualityMenu(); - - binding.qualityTextView.setVisibility(View.VISIBLE); - binding.surfaceView.setVisibility(View.VISIBLE); - default: - binding.endScreen.setVisibility(View.GONE); - binding.playbackEndTime.setVisibility(View.VISIBLE); - break; - } - - buildPlaybackSpeedMenu(); - binding.playbackSpeed.setVisibility(View.VISIBLE); - } - - private void updateQueueTime(final int currentTime) { - final int currentStream = playQueue.getIndex(); - int before = 0; - int after = 0; - - final List streams = playQueue.getStreams(); - final int nStreams = streams.size(); - - for (int i = 0; i < nStreams; i++) { - if (i < currentStream) { - before += streams.get(i).getDuration(); - } else { - after += streams.get(i).getDuration(); - } - } - - before *= 1000; - after *= 1000; - - binding.itemsListHeaderDuration.setText( - String.format("%s/%s", - getTimeString(currentTime + before), - getTimeString(before + after) - )); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Popup menus ("popup" means that they pop up, not that they belong to the popup player) - //////////////////////////////////////////////////////////////////////////*/ - //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) - - private void buildQualityMenu() { - if (qualityPopupMenu == null) { - return; - } - qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); - - for (int i = 0; i < availableStreams.size(); i++) { - final VideoStream videoStream = availableStreams.get(i); - qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat - .getNameById(videoStream.getFormatId()) + " " + videoStream.getResolution()); - } - if (getSelectedVideoStream() != null) { - binding.qualityTextView.setText(getSelectedVideoStream().getResolution()); - } - qualityPopupMenu.setOnMenuItemClickListener(this); - qualityPopupMenu.setOnDismissListener(this); - } - - private void buildPlaybackSpeedMenu() { - if (playbackSpeedPopupMenu == null) { - return; - } - playbackSpeedPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_PLAYBACK_SPEED); - - for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { - playbackSpeedPopupMenu.getMenu().add(POPUP_MENU_ID_PLAYBACK_SPEED, i, Menu.NONE, - formatSpeed(PLAYBACK_SPEEDS[i])); - } - binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); - playbackSpeedPopupMenu.setOnMenuItemClickListener(this); - playbackSpeedPopupMenu.setOnDismissListener(this); - } - - private void buildCaptionMenu(@NonNull final List availableLanguages) { - if (captionPopupMenu == null) { - return; - } - captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); - captionPopupMenu.setOnDismissListener(this); - - // Add option for turning off caption - final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, - 0, Menu.NONE, R.string.caption_none); - captionOffItem.setOnMenuItemClickListener(menuItem -> { - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex != RENDERER_UNAVAILABLE) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setRendererDisabled(textRendererIndex, true)); - } - prefs.edit().remove(context.getString(R.string.caption_user_set_key)).apply(); - return true; - }); - - // Add all available captions - for (int i = 0; i < availableLanguages.size(); i++) { - final String captionLanguage = availableLanguages.get(i); - final MenuItem captionItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, - i + 1, Menu.NONE, captionLanguage); - captionItem.setOnMenuItemClickListener(menuItem -> { - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex != RENDERER_UNAVAILABLE) { - // DefaultTrackSelector will select for text tracks in the following order. - // When multiple tracks share the same rank, a random track will be chosen. - // 1. ANY track exactly matching preferred language name - // 2. ANY track exactly matching preferred language stem - // 3. ROLE_FLAG_CAPTION track matching preferred language stem - // 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem - // This means if a caption track of preferred language is not available, - // then an auto-generated track of that language will be chosen automatically. - trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguages(captionLanguage, - PlayerHelper.captionLanguageStemOf(captionLanguage)) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - .setRendererDisabled(textRendererIndex, false)); - prefs.edit().putString(context.getString(R.string.caption_user_set_key), - captionLanguage).apply(); - } - return true; - }); - } - - // apply caption language from previous user preference - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex == RENDERER_UNAVAILABLE) { - return; - } - - // If user prefers to show no caption, then disable the renderer. - // Otherwise, DefaultTrackSelector may automatically find an available caption - // and display that. - final String userPreferredLanguage = - prefs.getString(context.getString(R.string.caption_user_set_key), null); - if (userPreferredLanguage == null) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setRendererDisabled(textRendererIndex, true)); - return; - } - - // Only set preferred language if it does not match the user preference, - // otherwise there might be an infinite cycle at onTextTracksChanged. - final List selectedPreferredLanguages = - trackSelector.getParameters().preferredTextLanguages; - if (!selectedPreferredLanguages.contains(userPreferredLanguage)) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguages(userPreferredLanguage, - PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - .setRendererDisabled(textRendererIndex, false)); - } - } - - /** - * Called when an item of the quality selector or the playback speed selector is selected. - */ - @Override - public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { - if (DEBUG) { - Log.d(TAG, "onMenuItemClick() called with: " - + "menuItem = [" + menuItem + "], " - + "menuItem.getItemId = [" + menuItem.getItemId() + "]"); - } - - if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { - final int menuItemIndex = menuItem.getItemId(); - if (selectedStreamIndex == menuItemIndex || availableStreams == null - || availableStreams.size() <= menuItemIndex) { - return true; - } - - saveStreamProgressState(); //TODO added, check if good - final String newResolution = availableStreams.get(menuItemIndex).getResolution(); - setRecovery(); - setPlaybackQuality(newResolution); - reloadPlayQueueManager(); - - binding.qualityTextView.setText(menuItem.getTitle()); - return true; - } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { - final int speedIndex = menuItem.getItemId(); - final float speed = PLAYBACK_SPEEDS[speedIndex]; - - setPlaybackSpeed(speed); - binding.playbackSpeed.setText(formatSpeed(speed)); + @Nullable final MediaItemTag.Quality quality = Optional.ofNullable(currentMetadata) + .flatMap(MediaItemTag::getMaybeQuality) + .orElse(null); + if (quality == null) { + return null; } - return false; - } - - /** - * Called when some popup menu is dismissed. - */ - @Override - public void onDismiss(@Nullable final PopupMenu menu) { - if (DEBUG) { - Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); - } - isSomePopupMenuVisible = false; //TODO check if this works - if (getSelectedVideoStream() != null) { - binding.qualityTextView.setText(getSelectedVideoStream().getResolution()); - } - if (isPlaying()) { - hideControls(DEFAULT_CONTROLS_DURATION, 0); - hideSystemUIIfNeeded(); - } - } + final List availableStreams = quality.getSortedVideoStreams(); + final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); - private void onCaptionClicked() { - if (DEBUG) { - Log.d(TAG, "onCaptionClicked() called"); + if (selectedStreamIndex >= 0 && availableStreams.size() > selectedStreamIndex) { + return availableStreams.get(selectedStreamIndex); + } else { + return null; } - captionPopupMenu.show(); - isSomePopupMenuVisible = true; - } - - private void setPlaybackQuality(@Nullable final String quality) { - videoResolver.setPlaybackQuality(quality); } //endregion @@ -3655,68 +1981,7 @@ private void setPlaybackQuality(@Nullable final String quality) { //////////////////////////////////////////////////////////////////////////*/ //region Captions (text tracks) - private void setupSubtitleView() { - final float captionScale = PlayerHelper.getCaptionScale(context); - final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); - if (popupPlayerSelected()) { - final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f; - binding.subtitleView.setFractionalTextSize( - SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); - } else { - final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); - final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); - binding.subtitleView.setFixedTextSize( - TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); - } - binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT); - binding.subtitleView.setStyle(captionStyle); - } - - private void onTextTracksChanged(@NonNull final Tracks currentTrack) { - if (binding == null) { - return; - } - - final boolean trackTypeTextSupported = !currentTrack.containsType(C.TRACK_TYPE_TEXT) - || currentTrack.isTypeSupported(C.TRACK_TYPE_TEXT, false); - if (trackSelector.getCurrentMappedTrackInfo() == null || !trackTypeTextSupported) { - binding.captionTextView.setVisibility(View.GONE); - return; - } - - // Extract all loaded languages - final List textTracks = currentTrack - .getGroups() - .stream() - .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) - .collect(Collectors.toList()); - final List availableLanguages = textTracks.stream() - .map(Tracks.Group::getMediaTrackGroup) - .filter(textTrack -> textTrack.length > 0) - .map(textTrack -> textTrack.getFormat(0).language) - .collect(Collectors.toList()); - - // Find selected text track - final Optional selectedTracks = textTracks.stream() - .filter(Tracks.Group::isSelected) - .filter(info -> info.getMediaTrackGroup().length >= 1) - .map(info -> info.getMediaTrackGroup().getFormat(0)) - .findFirst(); - - // Build UI - buildCaptionMenu(availableLanguages); - if (trackSelector.getParameters().getRendererDisabled(getCaptionRendererIndex()) - || !selectedTracks.isPresent()) { - binding.captionTextView.setText(R.string.caption_none); - } else { - binding.captionTextView.setText(selectedTracks.get().language); - } - binding.captionTextView.setVisibility( - availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); - } - - private int getCaptionRendererIndex() { + public int getCaptionRendererIndex() { if (exoPlayerIsNull()) { return RENDERER_UNAVAILABLE; } @@ -3732,218 +1997,10 @@ private int getCaptionRendererIndex() { //endregion - - /*////////////////////////////////////////////////////////////////////////// - // Click listeners - //////////////////////////////////////////////////////////////////////////*/ - //region Click listeners - - @Override - public void onClick(final View v) { - if (DEBUG) { - Log.d(TAG, "onClick() called with: v = [" + v + "]"); - } - if (v.getId() == binding.resizeTextView.getId()) { - onResizeClicked(); - } else if (v.getId() == binding.captionTextView.getId()) { - onCaptionClicked(); - } else if (v.getId() == binding.playbackLiveSync.getId()) { - seekToDefault(); - } else if (v.getId() == binding.playPauseButton.getId()) { - playPause(); - } else if (v.getId() == binding.playPreviousButton.getId()) { - playPrevious(); - } else if (v.getId() == binding.playNextButton.getId()) { - playNext(); - } else if (v.getId() == binding.moreOptionsButton.getId()) { - onMoreOptionsClicked(); - } else if (v.getId() == binding.share.getId()) { - ShareUtils.shareText(context, getVideoTitle(), getVideoUrlAtCurrentTime(), - currentItem.getThumbnailUrl()); - } else if (v.getId() == binding.playWithKodi.getId()) { - onPlayWithKodiClicked(); - } else if (v.getId() == binding.openInBrowser.getId()) { - onOpenInBrowserClicked(); - } else if (v.getId() == binding.fullScreenButton.getId()) { - setRecovery(); - NavigationHelper.playOnMainPlayer(context, playQueue, true); - return; - } else if (v.getId() == binding.screenRotationButton.getId()) { - // Only if it's not a vertical video or vertical video but in landscape with locked - // orientation a screen orientation can be changed automatically - if (!isVerticalVideo - || (service.isLandscape() && globalScreenOrientationLocked(context))) { - fragmentListener.onScreenRotationButtonClicked(); - } else { - toggleFullscreen(); - } - } else if (v.getId() == binding.switchMute.getId()) { - onMuteUnmuteButtonClicked(); - } else if (v.getId() == binding.playerCloseButton.getId()) { - context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); - } - - manageControlsAfterOnClick(v); - } - - /** - * Manages the controls after a click occurred on the player UI. - * @param v – The view that was clicked - */ - public void manageControlsAfterOnClick(@NonNull final View v) { - if (currentState == STATE_COMPLETED) { - return; - } - - controlsVisibilityHandler.removeCallbacksAndMessages(null); - showHideShadow(true, DEFAULT_CONTROLS_DURATION); - animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, - AnimationType.ALPHA, 0, () -> { - if (currentState == STATE_PLAYING && !isSomePopupMenuVisible) { - if (v.getId() == binding.playPauseButton.getId() - // Hide controls in fullscreen immediately - || (v.getId() == binding.screenRotationButton.getId() - && isFullscreen)) { - hideControls(0, 0); - } else { - hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); - } - } - }); - } - - @Override - public boolean onLongClick(final View v) { - if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen) { - fragmentListener.onMoreOptionsLongClicked(); - hideControls(0, 0); - hideSystemUIIfNeeded(); - } else if (v.getId() == binding.share.getId()) { - ShareUtils.copyToClipboard(context, getVideoUrlAtCurrentTime()); - } - return true; - } - - public boolean onKeyDown(final int keyCode) { - switch (keyCode) { - default: - break; - case KeyEvent.KEYCODE_SPACE: - if (isFullscreen) { - playPause(); - if (isPlaying()) { - hideControls(0, 0); - } - return true; - } - break; - case KeyEvent.KEYCODE_BACK: - if (DeviceUtils.isTv(context) && isControlsVisible()) { - hideControls(0, 0); - return true; - } - break; - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_CENTER: - if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) - || isQueueVisible) { - // do not interfere with focus in playlist and play queue etc. - return false; - } - - if (currentState == Player.STATE_BLOCKED) { - return true; - } - - if (isControlsVisible()) { - hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME); - } else { - binding.playPauseButton.requestFocus(); - showControlsThenHide(); - showSystemUIPartially(); - return true; - } - break; - } - - return false; - } - - private void onMoreOptionsClicked() { - if (DEBUG) { - Log.d(TAG, "onMoreOptionsClicked() called"); - } - - final boolean isMoreControlsVisible = - binding.secondaryControls.getVisibility() == View.VISIBLE; - - animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, - isMoreControlsVisible ? 0 : 180); - animate(binding.secondaryControls, !isMoreControlsVisible, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { - // Fix for a ripple effect on background drawable. - // When view returns from GONE state it takes more milliseconds than returning - // from INVISIBLE state. And the delay makes ripple background end to fast - if (isMoreControlsVisible) { - binding.secondaryControls.setVisibility(View.INVISIBLE); - } - }); - showControls(DEFAULT_CONTROLS_DURATION); - } - - private void onPlayWithKodiClicked() { - if (currentMetadata != null) { - pause(); - try { - NavigationHelper.playWithKore(context, Uri.parse(getVideoUrl())); - } catch (final Exception e) { - if (DEBUG) { - Log.i(TAG, "Failed to start kore", e); - } - KoreUtils.showInstallKoreDialog(getParentActivity()); - } - } - } - - private void onOpenInBrowserClicked() { - getCurrentStreamInfo() - .map(Info::getOriginalUrl) - .ifPresent(originalUrl -> ShareUtils.openUrlInBrowser( - Objects.requireNonNull(getParentActivity()), originalUrl)); - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Video size, resize, orientation, fullscreen //////////////////////////////////////////////////////////////////////////*/ //region Video size, resize, orientation, fullscreen - - private void setupScreenRotationButton() { - binding.screenRotationButton.setVisibility(videoPlayerSelected() - && (globalScreenOrientationLocked(context) || isVerticalVideo - || DeviceUtils.isTablet(context)) - ? View.VISIBLE : View.GONE); - binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context, - isFullscreen ? R.drawable.ic_fullscreen_exit - : R.drawable.ic_fullscreen)); - } - - private void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { - binding.surfaceView.setResizeMode(resizeMode); - binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); - } - - void onResizeClicked() { - if (binding != null) { - setResizeMode(nextResizeModeAndSaveToPrefs(this, binding.surfaceView.getResizeMode())); - } - } - @Override // exoplayer listener public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { if (DEBUG) { @@ -3954,137 +2011,11 @@ public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { + "pixelWidthHeightRatio = [" + videoSize.pixelWidthHeightRatio + "]"); } - binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); - isVerticalVideo = videoSize.width < videoSize.height; - - if (globalScreenOrientationLocked(context) - && isFullscreen - && service.isLandscape() == isVerticalVideo - && !DeviceUtils.isTv(context) - && !DeviceUtils.isTablet(context) - && fragmentListener != null) { - // set correct orientation - fragmentListener.onScreenRotationButtonClicked(); - } - - setupScreenRotationButton(); - } - - public void toggleFullscreen() { - if (DEBUG) { - Log.d(TAG, "toggleFullscreen() called"); - } - if (popupPlayerSelected() || exoPlayerIsNull() || fragmentListener == null) { - return; - } - - isFullscreen = !isFullscreen; - if (!isFullscreen) { - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait (open vertical video to reproduce) - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } else { - // Android needs tens milliseconds to send new insets but a user is able to see - // how controls changes it's position from `0` to `nav bar height` padding. - // So just hide the controls to hide this visual inconsistency - hideControls(0, 0); - } - fragmentListener.onFullscreenStateChanged(isFullscreen); - - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(View.GONE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility( - videoPlayerSelected() ? View.VISIBLE : View.GONE); - } - setupScreenRotationButton(); - } - - public void checkLandscape() { - final AppCompatActivity parent = getParentActivity(); - final boolean videoInLandscapeButNotInFullscreen = - service.isLandscape() && !isFullscreen && videoPlayerSelected() && !isAudioOnly; - - final boolean notPaused = currentState != STATE_COMPLETED && currentState != STATE_PAUSED; - if (parent != null - && videoInLandscapeButNotInFullscreen - && notPaused - && !DeviceUtils.isTablet(context)) { - toggleFullscreen(); - } - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Gestures - //////////////////////////////////////////////////////////////////////////*/ - //region Gestures - - @SuppressWarnings("checkstyle:ParameterNumber") - private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, - final int ol, final int ot, final int or, final int ob) { - if (l != ol || t != ot || r != or || b != ob) { - // Use smaller value to be consistent between screen orientations - // (and to make usage easier) - final int width = r - l; - final int height = b - t; - final int min = Math.min(width, height); - maxGestureLength = (int) (min * MAX_GESTURE_LENGTH); - - if (DEBUG) { - Log.d(TAG, "maxGestureLength = " + maxGestureLength); - } - - binding.volumeProgressBar.setMax(maxGestureLength); - binding.brightnessProgressBar.setMax(maxGestureLength); - - setInitialGestureValues(); - binding.itemsListPanel.getLayoutParams().height - = height - binding.itemsListPanel.getTop(); - } - } - - private void setInitialGestureValues() { - if (audioReactor != null) { - final float currentVolumeNormalized = - (float) audioReactor.getVolume() / audioReactor.getMaxVolume(); - binding.volumeProgressBar.setProgress( - (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); - } - } - - private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { - final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() - + closeOverlayBinding.closeButton.getWidth() / 2; - final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop() - + closeOverlayBinding.closeButton.getHeight() / 2; - - final float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); - final float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); - - return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) - + Math.pow(closeOverlayButtonY - fingerY, 2)); - } - - private float getClosingRadius() { - final int buttonRadius = closeOverlayBinding.closeButton.getWidth() / 2; - // 20% wider than the button itself - return buttonRadius * 1.2f; - } - - public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) { - return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); + UIs.call(playerUi -> playerUi.onVideoSizeChanged(videoSize)); } //endregion - /*////////////////////////////////////////////////////////////////////////// // Activity / fragment binding //////////////////////////////////////////////////////////////////////////*/ @@ -4092,13 +2023,7 @@ public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent public void setFragmentListener(final PlayerServiceEventListener listener) { fragmentListener = listener; - fragmentIsVisible = true; - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait - if (!isFullscreen) { - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } - binding.itemsListPanel.setPadding(0, 0, 0, 0); + UIs.call(PlayerUi::onFragmentListenerSet); notifyQueueUpdateToListeners(); notifyMetadataUpdateToListeners(); notifyPlaybackUpdateToListeners(); @@ -4136,28 +2061,6 @@ void stopActivityBinding() { } } - /** - * This will be called when a user goes to another app/activity, turns off a screen. - * We don't want to interrupt playback and don't want to see notification so - * next lines of code will enable audio-only playback only if needed - */ - private void onFragmentStopped() { - if (videoPlayerSelected() && (isPlaying() || isLoading())) { - switch (getMinimizeOnExitAction(context)) { - case MINIMIZE_ON_EXIT_MODE_BACKGROUND: - useVideoSource(false); - break; - case MINIMIZE_ON_EXIT_MODE_POPUP: - setRecovery(); - NavigationHelper.playOnPopupPlayer(getParentActivity(), playQueue, true); - break; - case MINIMIZE_ON_EXIT_MODE_NONE: default: - pause(); - break; - } - } - } - private void notifyQueueUpdateToListeners() { if (fragmentListener != null && playQueue != null) { fragmentListener.onQueueUpdate(playQueue); @@ -4200,27 +2103,12 @@ private void notifyProgressUpdateToListeners(final int currentProgress, } } - @Nullable - public AppCompatActivity getParentActivity() { - // ! instanceof ViewGroup means that view was added via windowManager for Popup - if (binding == null || !(binding.getRoot().getParent() instanceof ViewGroup)) { - return null; - } - - return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); - } - - private void useVideoSource(final boolean videoEnabled) { + public void useVideoSource(final boolean videoEnabled) { if (playQueue == null || isAudioOnly == !videoEnabled || audioPlayerSelected()) { return; } isAudioOnly = !videoEnabled; - // When a user returns from background, controls could be hidden but SystemUI will be shown - // 100%. Hide it. - if (!isAudioOnly && !isControlsVisible()) { - hideSystemUIIfNeeded(); - } // The current metadata may be null sometimes (for e.g. when using an unstable connection // in livestreams) so we will be not able to execute the block below. @@ -4332,7 +2220,7 @@ && isNullOrEmpty(streamInfo.getAudioStreams()))) { //////////////////////////////////////////////////////////////////////////*/ //region Getters - private Optional getCurrentStreamInfo() { + public Optional getCurrentStreamInfo() { return Optional.ofNullable(currentMetadata).flatMap(MediaItemTag::getMaybeStreamInfo); } @@ -4344,6 +2232,10 @@ public boolean exoPlayerIsNull() { return simpleExoPlayer == null; } + public ExoPlayer getExoPlayer() { + return simpleExoPlayer; + } + public boolean isStopped() { return exoPlayerIsNull() || simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_IDLE; } @@ -4356,7 +2248,7 @@ public boolean getPlayWhenReady() { return !exoPlayerIsNull() && simpleExoPlayer.getPlayWhenReady(); } - private boolean isLoading() { + public boolean isLoading() { return !exoPlayerIsNull() && simpleExoPlayer.isLoading(); } @@ -4372,6 +2264,10 @@ private boolean isLive() { } } + public void setPlaybackQuality(@Nullable final String quality) { + videoResolver.setPlaybackQuality(quality); + } + @NonNull public Context getContext() { @@ -4397,7 +2293,7 @@ public boolean audioPlayerSelected() { } public boolean videoPlayerSelected() { - return playerType == PlayerType.VIDEO; + return playerType == PlayerType.MAIN; } public boolean popupPlayerSelected() { @@ -4414,156 +2310,39 @@ public AudioReactor getAudioReactor() { return audioReactor; } - public GestureDetector getGestureDetector() { - return gestureDetector; + public PlayerService getService() { + return service; } - public boolean isFullscreen() { - return isFullscreen; + public boolean isAudioOnly() { + return isAudioOnly; } - public boolean isVerticalVideo() { - return isVerticalVideo; - } - - public boolean isPopupClosing() { - return isPopupClosing; - } - - - public boolean isSomePopupMenuVisible() { - return isSomePopupMenuVisible; - } - - public void setSomePopupMenuVisible(final boolean somePopupMenuVisible) { - isSomePopupMenuVisible = somePopupMenuVisible; - } - - public ImageButton getPlayPauseButton() { - return binding.playPauseButton; - } - - public View getClosingOverlayView() { - return binding.closingOverlay; - } - - public ProgressBar getVolumeProgressBar() { - return binding.volumeProgressBar; - } - - public ProgressBar getBrightnessProgressBar() { - return binding.brightnessProgressBar; - } - - public int getMaxGestureLength() { - return maxGestureLength; - } - - public ImageView getVolumeImageView() { - return binding.volumeImageView; - } - - public RelativeLayout getVolumeRelativeLayout() { - return binding.volumeRelativeLayout; - } - - public ImageView getBrightnessImageView() { - return binding.brightnessImageView; - } - - public RelativeLayout getBrightnessRelativeLayout() { - return binding.brightnessRelativeLayout; - } - - public FloatingActionButton getCloseOverlayButton() { - return closeOverlayBinding.closeButton; - } - - public View getLoadingPanel() { - return binding.loadingPanel; - } - - public TextView getCurrentDisplaySeek() { - return binding.currentDisplaySeek; - } - - public PlayerFastSeekOverlay getFastSeekOverlay() { - return binding.fastSeekOverlay; + @NonNull + public DefaultTrackSelector getTrackSelector() { + return trackSelector; } @Nullable - public WindowManager.LayoutParams getPopupLayoutParams() { - return popupLayoutParams; + public MediaItemTag getCurrentMetadata() { + return currentMetadata; } @Nullable - public WindowManager getWindowManager() { - return windowManager; - } - - public float getScreenWidth() { - return screenWidth; + public PlayQueueItem getCurrentItem() { + return currentItem; } - public float getScreenHeight() { - return screenHeight; + public Optional getFragmentListener() { + return Optional.ofNullable(fragmentListener); } - public View getRootView() { - return binding.getRoot(); - } - - public ExpandableSurfaceView getSurfaceView() { - return binding.surfaceView; - } - - public PlayQueueAdapter getPlayQueueAdapter() { - return playQueueAdapter; - } - - public PlayerBinding getBinding() { - return binding; - } - - //endregion - - - /*////////////////////////////////////////////////////////////////////////// - // SurfaceHolderCallback helpers - //////////////////////////////////////////////////////////////////////////*/ - //region SurfaceHolderCallback helpers - - private void setupVideoSurface() { - // make sure there is nothing left over from previous calls - cleanupVideoSurface(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - surfaceHolderCallback = new SurfaceHolderCallback(context, simpleExoPlayer); - binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); - final Surface surface = binding.surfaceView.getHolder().getSurface(); - // ensure player is using an unreleased surface, which the surfaceView might not be - // when starting playback on background or during player switching - if (surface.isValid()) { - // initially set the surface manually otherwise - // onRenderedFirstFrame() will not be called - simpleExoPlayer.setVideoSurface(surface); - } - } else { - simpleExoPlayer.setVideoSurfaceView(binding.surfaceView); - } - } - - private void cleanupVideoSurface() { - // Only for API >= 23 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && surfaceHolderCallback != null) { - if (binding != null) { - binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); - } - surfaceHolderCallback.release(); - surfaceHolderCallback = null; - } + /** + * @return the user interfaces connected with the player + */ + public PlayerUiList UIs() { + return UIs; } - //endregion /** * Get the video renderer index of the current playing stream. @@ -4592,4 +2371,5 @@ private int getVideoRendererIndex() { // No video renderer index with at least one track found: return unavailable index .orElse(RENDERER_UNAVAILABLE); } + //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java similarity index 63% rename from app/src/main/java/org/schabi/newpipe/player/MainPlayer.java rename to app/src/main/java/org/schabi/newpipe/player/PlayerService.java index a9b9f4c8762..cf83dc5c277 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -19,44 +19,35 @@ package org.schabi.newpipe.player; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; - -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import org.schabi.newpipe.App; -import org.schabi.newpipe.databinding.PlayerBinding; -import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.ThemeHelper; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - /** * One service for all players. * * @author mauriciocolli */ -public final class MainPlayer extends Service { - private static final String TAG = "MainPlayer"; +public final class PlayerService extends Service { + private static final String TAG = PlayerService.class.getSimpleName(); private static final boolean DEBUG = Player.DEBUG; private Player player; - private WindowManager windowManager; - private final IBinder mBinder = new MainPlayer.LocalBinder(); + private final IBinder mBinder = new PlayerService.LocalBinder(); public enum PlayerType { - VIDEO, + MAIN, AUDIO, POPUP } @@ -67,7 +58,7 @@ public enum PlayerType { static final String ACTION_CLOSE = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - static final String ACTION_PLAY_PAUSE + public static final String ACTION_PLAY_PAUSE = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; static final String ACTION_REPEAT = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; @@ -94,19 +85,12 @@ public void onCreate() { Log.d(TAG, "onCreate() called"); } assureCorrectAppLanguage(this); - windowManager = ContextCompat.getSystemService(this, WindowManager.class); - ThemeHelper.setTheme(this); - createView(); - } - - private void createView() { - final PlayerBinding binding = PlayerBinding.inflate(LayoutInflater.from(this)); player = new Player(this); - player.setupFromView(binding); - - NotificationUtil.getInstance().createNotificationAndStartForeground(player, this); + /*final MainPlayerUi mainPlayerUi = new MainPlayerUi(player, + PlayerBinding.inflate(LayoutInflater.from(this))); + player.UIs().add(mainPlayerUi);*/ } @Override @@ -121,11 +105,6 @@ public int onStartCommand(final Intent intent, final int flags, final int startI return START_NOT_STICKY; } - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - || intent.getStringExtra(Player.PLAY_QUEUE_KEY) != null) { - NotificationUtil.getInstance().createNotificationAndStartForeground(player, this); - } - player.handleIntent(intent); if (player.getMediaSessionManager() != null) { player.getMediaSessionManager().handleMediaButtonIntent(intent); @@ -144,13 +123,7 @@ public void stopForImmediateReusing() { // Releases wifi & cpu, disables keepScreenOn, etc. // We can't just pause the player here because it will make transition // from one stream to a new stream not smooth - player.smoothStopPlayer(); - player.setRecovery(); - - // Android TV will handle back button in case controls will be visible - // (one more additional unneeded click while the player is hidden) - player.hideControls(0, 0); - player.closeItemsList(); + player.smoothStopForImmediateReusing(); // Notification shows information about old stream but if a user selects // a stream from backStack it's not actual anymore @@ -180,18 +153,7 @@ public void onDestroy() { private void cleanup() { if (player != null) { - // Exit from fullscreen when user closes the player via notification - if (player.isFullscreen()) { - player.toggleFullscreen(); - } - removeViewFromParent(); - - player.saveStreamProgressState(); - player.setRecovery(); - player.stopActivityBinding(); - player.removePopupFromView(); player.destroy(); - player = null; } } @@ -212,48 +174,14 @@ public IBinder onBind(final Intent intent) { return mBinder; } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - - boolean isLandscape() { - // DisplayMetrics from activity context knows about MultiWindow feature - // while DisplayMetrics from app context doesn't - return DeviceUtils.isLandscape(player != null && player.getParentActivity() != null - ? player.getParentActivity() : this); - } - - @Nullable - public View getView() { - if (player == null) { - return null; - } - - return player.getRootView(); - } - - public void removeViewFromParent() { - if (getView() != null && getView().getParent() != null) { - if (player.getParentActivity() != null) { - // This means view was added to fragment - final ViewGroup parent = (ViewGroup) getView().getParent(); - parent.removeView(getView()); - } else { - // This means view was added by windowManager for popup player - windowManager.removeViewImmediate(getView()); - } - } - } - - public class LocalBinder extends Binder { - public MainPlayer getService() { - return MainPlayer.this; + public PlayerService getService() { + return PlayerService.this; } public Player getPlayer() { - return MainPlayer.this.player; + return PlayerService.this.player; } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt deleted file mode 100644 index c89eabb4785..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt +++ /dev/null @@ -1,520 +0,0 @@ -package org.schabi.newpipe.player.event - -import android.content.Context -import android.os.Handler -import android.util.Log -import android.view.GestureDetector -import android.view.MotionEvent -import android.view.View -import android.view.ViewConfiguration -import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.player.MainPlayer -import org.schabi.newpipe.player.Player -import org.schabi.newpipe.player.helper.PlayerHelper -import org.schabi.newpipe.player.helper.PlayerHelper.savePopupPositionAndSizeToPrefs -import kotlin.math.abs -import kotlin.math.hypot -import kotlin.math.max -import kotlin.math.min - -/** - * Base gesture handling for [Player] - * - * This class contains the logic for the player gestures like View preparations - * and provides some abstract methods to make it easier separating the logic from the UI. - */ -abstract class BasePlayerGestureListener( - @JvmField - protected val player: Player, - @JvmField - protected val service: MainPlayer -) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { - - // /////////////////////////////////////////////////////////////////// - // Abstract methods for VIDEO and POPUP - // /////////////////////////////////////////////////////////////////// - - abstract fun onDoubleTap(event: MotionEvent, portion: DisplayPortion) - - abstract fun onSingleTap(playerType: MainPlayer.PlayerType) - - abstract fun onScroll( - playerType: MainPlayer.PlayerType, - portion: DisplayPortion, - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ) - - abstract fun onScrollEnd(playerType: MainPlayer.PlayerType, event: MotionEvent) - - // /////////////////////////////////////////////////////////////////// - // Abstract methods for POPUP (exclusive) - // /////////////////////////////////////////////////////////////////// - - abstract fun onPopupResizingStart() - - abstract fun onPopupResizingEnd() - - private var initialPopupX: Int = -1 - private var initialPopupY: Int = -1 - - private var isMovingInMain = false - private var isMovingInPopup = false - private var isResizing = false - - private val tossFlingVelocity = PlayerHelper.getTossFlingVelocity() - - // [popup] initial coordinates and distance between fingers - private var initPointerDistance = -1.0 - private var initFirstPointerX = -1f - private var initFirstPointerY = -1f - private var initSecPointerX = -1f - private var initSecPointerY = -1f - - // /////////////////////////////////////////////////////////////////// - // onTouch implementation - // /////////////////////////////////////////////////////////////////// - - override fun onTouch(v: View, event: MotionEvent): Boolean { - return if (player.popupPlayerSelected()) { - onTouchInPopup(v, event) - } else { - onTouchInMain(v, event) - } - } - - private fun onTouchInMain(v: View, event: MotionEvent): Boolean { - player.gestureDetector.onTouchEvent(event) - if (event.action == MotionEvent.ACTION_UP && isMovingInMain) { - isMovingInMain = false - onScrollEnd(MainPlayer.PlayerType.VIDEO, event) - } - return when (event.action) { - MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { - v.parent.requestDisallowInterceptTouchEvent(player.isFullscreen) - true - } - MotionEvent.ACTION_UP -> { - v.parent.requestDisallowInterceptTouchEvent(false) - false - } - else -> true - } - } - - private fun onTouchInPopup(v: View, event: MotionEvent): Boolean { - player.gestureDetector.onTouchEvent(event) - if (event.pointerCount == 2 && !isMovingInPopup && !isResizing) { - if (DEBUG) { - Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.") - } - onPopupResizingStart() - - // record coordinates of fingers - initFirstPointerX = event.getX(0) - initFirstPointerY = event.getY(0) - initSecPointerX = event.getX(1) - initSecPointerY = event.getY(1) - // record distance between fingers - initPointerDistance = hypot( - initFirstPointerX - initSecPointerX.toDouble(), - initFirstPointerY - initSecPointerY.toDouble() - ) - - isResizing = true - } - if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) { - if (DEBUG) { - Log.d( - TAG, - "onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" + - "[${event.rawX}, ${event.rawY}]" - ) - } - return handleMultiDrag(event) - } - if (event.action == MotionEvent.ACTION_UP) { - if (DEBUG) { - Log.d( - TAG, - "onTouch() ACTION_UP > v = [$v], e1.getRaw =" + - " [${event.rawX}, ${event.rawY}]" - ) - } - if (isMovingInPopup) { - isMovingInPopup = false - onScrollEnd(MainPlayer.PlayerType.POPUP, event) - } - if (isResizing) { - isResizing = false - - initPointerDistance = (-1).toDouble() - initFirstPointerX = (-1).toFloat() - initFirstPointerY = (-1).toFloat() - initSecPointerX = (-1).toFloat() - initSecPointerY = (-1).toFloat() - - onPopupResizingEnd() - player.changeState(player.currentState) - } - if (!player.isPopupClosing) { - savePopupPositionAndSizeToPrefs(player) - } - } - - v.performClick() - return true - } - - private fun handleMultiDrag(event: MotionEvent): Boolean { - if (initPointerDistance != -1.0 && event.pointerCount == 2) { - // get the movements of the fingers - val firstPointerMove = hypot( - event.getX(0) - initFirstPointerX.toDouble(), - event.getY(0) - initFirstPointerY.toDouble() - ) - val secPointerMove = hypot( - event.getX(1) - initSecPointerX.toDouble(), - event.getY(1) - initSecPointerY.toDouble() - ) - - // minimum threshold beyond which pinch gesture will work - val minimumMove = ViewConfiguration.get(service).scaledTouchSlop - - if (max(firstPointerMove, secPointerMove) > minimumMove) { - // calculate current distance between the pointers - val currentPointerDistance = hypot( - event.getX(0) - event.getX(1).toDouble(), - event.getY(0) - event.getY(1).toDouble() - ) - - val popupWidth = player.popupLayoutParams!!.width.toDouble() - // change co-ordinates of popup so the center stays at the same position - val newWidth = popupWidth * currentPointerDistance / initPointerDistance - initPointerDistance = currentPointerDistance - player.popupLayoutParams!!.x += ((popupWidth - newWidth) / 2.0).toInt() - - player.checkPopupPositionBounds() - player.updateScreenSize() - player.changePopupSize(min(player.screenWidth.toDouble(), newWidth).toInt()) - return true - } - } - return false - } - - // /////////////////////////////////////////////////////////////////// - // Simple gestures - // /////////////////////////////////////////////////////////////////// - - override fun onDown(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onDown called with e = [$e]") - - if (isDoubleTapping && isDoubleTapEnabled) { - doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) - return true - } - - return if (player.popupPlayerSelected()) - onDownInPopup(e) - else - true - } - - private fun onDownInPopup(e: MotionEvent): Boolean { - // Fix popup position when the user touch it, it may have the wrong one - // because the soft input is visible (the draggable area is currently resized). - player.updateScreenSize() - player.checkPopupPositionBounds() - player.popupLayoutParams?.let { - initialPopupX = it.x - initialPopupY = it.y - } - return super.onDown(e) - } - - override fun onDoubleTap(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onDoubleTap called with e = [$e]") - - onDoubleTap(e, getDisplayPortion(e)) - return true - } - - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") - - if (isDoubleTapping) - return true - - if (player.popupPlayerSelected()) { - if (player.exoPlayerIsNull()) - return false - - onSingleTap(MainPlayer.PlayerType.POPUP) - return true - } else { - super.onSingleTapConfirmed(e) - if (player.currentState == Player.STATE_BLOCKED) - return true - - onSingleTap(MainPlayer.PlayerType.VIDEO) - } - return true - } - - override fun onLongPress(e: MotionEvent?) { - if (player.popupPlayerSelected()) { - player.updateScreenSize() - player.checkPopupPositionBounds() - player.changePopupSize(player.screenWidth.toInt()) - } - } - - override fun onScroll( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - return if (player.popupPlayerSelected()) { - onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY) - } else { - onScrollInMain(initialEvent, movingEvent, distanceX, distanceY) - } - } - - override fun onFling( - e1: MotionEvent?, - e2: MotionEvent?, - velocityX: Float, - velocityY: Float - ): Boolean { - return if (player.popupPlayerSelected()) { - val absVelocityX = abs(velocityX) - val absVelocityY = abs(velocityY) - if (absVelocityX.coerceAtLeast(absVelocityY) > tossFlingVelocity) { - if (absVelocityX > tossFlingVelocity) { - player.popupLayoutParams!!.x = velocityX.toInt() - } - if (absVelocityY > tossFlingVelocity) { - player.popupLayoutParams!!.y = velocityY.toInt() - } - player.checkPopupPositionBounds() - player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams) - return true - } - return false - } else { - true - } - } - - private fun onScrollInMain( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - - if (!player.isFullscreen) { - return false - } - - val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service) - val isTouchingNavigationBar: Boolean = - initialEvent.y > (player.rootView.height - getNavigationBarHeight(service)) - if (isTouchingStatusBar || isTouchingNavigationBar) { - return false - } - - val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD - if ( - !isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) || - player.currentState == Player.STATE_COMPLETED - ) { - return false - } - - isMovingInMain = true - - onScroll( - MainPlayer.PlayerType.VIDEO, - getDisplayHalfPortion(initialEvent), - initialEvent, - movingEvent, - distanceX, - distanceY - ) - - return true - } - - private fun onScrollInPopup( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - - if (isResizing) { - return super.onScroll(initialEvent, movingEvent, distanceX, distanceY) - } - - if (!isMovingInPopup) { - player.closeOverlayButton.animate(true, 200) - } - - isMovingInPopup = true - - val diffX: Float = (movingEvent.rawX - initialEvent.rawX) - var posX: Float = (initialPopupX + diffX) - val diffY: Float = (movingEvent.rawY - initialEvent.rawY) - var posY: Float = (initialPopupY + diffY) - - if (posX > player.screenWidth - player.popupLayoutParams!!.width) { - posX = (player.screenWidth - player.popupLayoutParams!!.width) - } else if (posX < 0) { - posX = 0f - } - - if (posY > player.screenHeight - player.popupLayoutParams!!.height) { - posY = (player.screenHeight - player.popupLayoutParams!!.height) - } else if (posY < 0) { - posY = 0f - } - - player.popupLayoutParams!!.x = posX.toInt() - player.popupLayoutParams!!.y = posY.toInt() - - onScroll( - MainPlayer.PlayerType.POPUP, - getDisplayHalfPortion(initialEvent), - initialEvent, - movingEvent, - distanceX, - distanceY - ) - - player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams) - return true - } - - // /////////////////////////////////////////////////////////////////// - // Multi double tapping - // /////////////////////////////////////////////////////////////////// - - var doubleTapControls: DoubleTapListener? = null - private set - - private val isDoubleTapEnabled: Boolean - get() = doubleTapDelay > 0 - - var isDoubleTapping = false - private set - - fun doubleTapControls(listener: DoubleTapListener) = apply { - doubleTapControls = listener - } - - private var doubleTapDelay = DOUBLE_TAP_DELAY - private val doubleTapHandler: Handler = Handler() - private val doubleTapRunnable = Runnable { - if (DEBUG) - Log.d(TAG, "doubleTapRunnable called") - - isDoubleTapping = false - doubleTapControls?.onDoubleTapFinished() - } - - fun startMultiDoubleTap(e: MotionEvent) { - if (!isDoubleTapping) { - if (DEBUG) - Log.d(TAG, "startMultiDoubleTap called with e = [$e]") - - keepInDoubleTapMode() - doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) - } - } - - fun keepInDoubleTapMode() { - if (DEBUG) - Log.d(TAG, "keepInDoubleTapMode called") - - isDoubleTapping = true - doubleTapHandler.removeCallbacks(doubleTapRunnable) - doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay) - } - - fun endMultiDoubleTap() { - if (DEBUG) - Log.d(TAG, "endMultiDoubleTap called") - - isDoubleTapping = false - doubleTapHandler.removeCallbacks(doubleTapRunnable) - doubleTapControls?.onDoubleTapFinished() - } - - // /////////////////////////////////////////////////////////////////// - // Utils - // /////////////////////////////////////////////////////////////////// - - private fun getDisplayPortion(e: MotionEvent): DisplayPortion { - return if (player.playerType == MainPlayer.PlayerType.POPUP && player.popupLayoutParams != null) { - when { - e.x < player.popupLayoutParams!!.width / 3.0 -> DisplayPortion.LEFT - e.x > player.popupLayoutParams!!.width * 2.0 / 3.0 -> DisplayPortion.RIGHT - else -> DisplayPortion.MIDDLE - } - } else /* MainPlayer.PlayerType.VIDEO */ { - when { - e.x < player.rootView.width / 3.0 -> DisplayPortion.LEFT - e.x > player.rootView.width * 2.0 / 3.0 -> DisplayPortion.RIGHT - else -> DisplayPortion.MIDDLE - } - } - } - - // Currently needed for scrolling since there is no action more the middle portion - private fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { - return if (player.playerType == MainPlayer.PlayerType.POPUP) { - when { - e.x < player.popupLayoutParams!!.width / 2.0 -> DisplayPortion.LEFT_HALF - else -> DisplayPortion.RIGHT_HALF - } - } else /* MainPlayer.PlayerType.VIDEO */ { - when { - e.x < player.rootView.width / 2.0 -> DisplayPortion.LEFT_HALF - else -> DisplayPortion.RIGHT_HALF - } - } - } - - private fun getNavigationBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("navigation_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - private fun getStatusBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("status_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - companion object { - private const val TAG = "BasePlayerGestListener" - private val DEBUG = Player.DEBUG - - private const val DOUBLE_TAP_DELAY = 550L - private const val MOVEMENT_THRESHOLD = 40 - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java index b5520e8bee7..84bd9d277b3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.player.event; - import com.google.android.exoplayer2.PlaybackParameters; import org.schabi.newpipe.extractor.stream.StreamInfo; diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java deleted file mode 100644 index a7fb40c47af..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.schabi.newpipe.player.event; - -import static org.schabi.newpipe.ktx.AnimationType.ALPHA; -import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME; -import static org.schabi.newpipe.player.Player.STATE_PLAYING; - -import android.app.Activity; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.ProgressBar; - -import androidx.annotation.NonNull; -import androidx.appcompat.content.res.AppCompatResources; - -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.helper.PlayerHelper; - -/** - * GestureListener for the player - * - * While {@link BasePlayerGestureListener} contains the logic behind the single gestures - * this class focuses on the visual aspect like hiding and showing the controls or changing - * volume/brightness during scrolling for specific events. - */ -public class PlayerGestureListener - extends BasePlayerGestureListener - implements View.OnTouchListener { - private static final String TAG = PlayerGestureListener.class.getSimpleName(); - private static final boolean DEBUG = MainActivity.DEBUG; - - private final int maxVolume; - - public PlayerGestureListener(final Player player, final MainPlayer service) { - super(player, service); - maxVolume = player.getAudioReactor().getMaxVolume(); - } - - @Override - public void onDoubleTap(@NonNull final MotionEvent event, - @NonNull final DisplayPortion portion) { - if (DEBUG) { - Log.d(TAG, "onDoubleTap called with playerType = [" - + player.getPlayerType() + "], portion = [" + portion + "]"); - } - if (player.isSomePopupMenuVisible()) { - player.hideControls(0, 0); - } - - if (portion == DisplayPortion.LEFT || portion == DisplayPortion.RIGHT) { - startMultiDoubleTap(event); - } else if (portion == DisplayPortion.MIDDLE) { - player.playPause(); - } - } - - @Override - public void onSingleTap(@NonNull final MainPlayer.PlayerType playerType) { - if (DEBUG) { - Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]"); - } - - if (player.isControlsVisible()) { - player.hideControls(150, 0); - return; - } - // -- Controls are not visible -- - - // When player is completed show controls and don't hide them later - if (player.getCurrentState() == Player.STATE_COMPLETED) { - player.showControls(0); - } else { - player.showControlsThenHide(); - } - } - - @Override - public void onScroll(@NonNull final MainPlayer.PlayerType playerType, - @NonNull final DisplayPortion portion, - @NonNull final MotionEvent initialEvent, - @NonNull final MotionEvent movingEvent, - final float distanceX, final float distanceY) { - if (DEBUG) { - Log.d(TAG, "onScroll called with playerType = [" - + player.getPlayerType() + "], portion = [" + portion + "]"); - } - if (playerType == MainPlayer.PlayerType.VIDEO) { - - // -- Brightness and Volume control -- - final boolean isBrightnessGestureEnabled = - PlayerHelper.isBrightnessGestureEnabled(service); - final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service); - - if (isBrightnessGestureEnabled && isVolumeGestureEnabled) { - if (portion == DisplayPortion.LEFT_HALF) { - onScrollMainBrightness(distanceX, distanceY); - - } else /* DisplayPortion.RIGHT_HALF */ { - onScrollMainVolume(distanceX, distanceY); - } - } else if (isBrightnessGestureEnabled) { - onScrollMainBrightness(distanceX, distanceY); - } else if (isVolumeGestureEnabled) { - onScrollMainVolume(distanceX, distanceY); - } - - } else /* MainPlayer.PlayerType.POPUP */ { - - // -- Determine if the ClosingOverlayView (red X) has to be shown or hidden -- - final View closingOverlayView = player.getClosingOverlayView(); - final boolean showClosingOverlayView = player.isInsideClosingRadius(movingEvent); - // Check if an view is in expected state and if not animate it into the correct state - final int expectedVisibility = showClosingOverlayView ? View.VISIBLE : View.GONE; - if (closingOverlayView.getVisibility() != expectedVisibility) { - animate(closingOverlayView, showClosingOverlayView, 200); - } - } - } - - private void onScrollMainVolume(final float distanceX, final float distanceY) { - // If we just started sliding, change the progress bar to match the system volume - if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { - final float volumePercent = player - .getAudioReactor().getVolume() / (float) maxVolume; - player.getVolumeProgressBar().setProgress( - (int) (volumePercent * player.getMaxGestureLength())); - } - - player.getVolumeProgressBar().incrementProgressBy((int) distanceY); - final float currentProgressPercent = (float) player - .getVolumeProgressBar().getProgress() / player.getMaxGestureLength(); - final int currentVolume = (int) (maxVolume * currentProgressPercent); - player.getAudioReactor().setVolume(currentVolume); - - if (DEBUG) { - Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); - } - - player.getVolumeImageView().setImageDrawable( - AppCompatResources.getDrawable(service, currentProgressPercent <= 0 - ? R.drawable.ic_volume_off - : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute - : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down - : R.drawable.ic_volume_up) - ); - - if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { - animate(player.getVolumeRelativeLayout(), true, 200, SCALE_AND_ALPHA); - } - if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { - player.getBrightnessRelativeLayout().setVisibility(View.GONE); - } - } - - private void onScrollMainBrightness(final float distanceX, final float distanceY) { - final Activity parent = player.getParentActivity(); - if (parent == null) { - return; - } - - final Window window = parent.getWindow(); - final WindowManager.LayoutParams layoutParams = window.getAttributes(); - final ProgressBar bar = player.getBrightnessProgressBar(); - final float oldBrightness = layoutParams.screenBrightness; - bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness)))); - bar.incrementProgressBy((int) distanceY); - - final float currentProgressPercent = (float) bar.getProgress() / bar.getMax(); - layoutParams.screenBrightness = currentProgressPercent; - window.setAttributes(layoutParams); - - // Save current brightness level - PlayerHelper.setScreenBrightness(parent, currentProgressPercent); - - if (DEBUG) { - Log.d(TAG, "onScroll().brightnessControl, " - + "currentBrightness = " + currentProgressPercent); - } - - player.getBrightnessImageView().setImageDrawable( - AppCompatResources.getDrawable(service, - currentProgressPercent < 0.25 - ? R.drawable.ic_brightness_low - : currentProgressPercent < 0.75 - ? R.drawable.ic_brightness_medium - : R.drawable.ic_brightness_high) - ); - - if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) { - animate(player.getBrightnessRelativeLayout(), true, 200, SCALE_AND_ALPHA); - } - if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { - player.getVolumeRelativeLayout().setVisibility(View.GONE); - } - } - - @Override - public void onScrollEnd(@NonNull final MainPlayer.PlayerType playerType, - @NonNull final MotionEvent event) { - if (DEBUG) { - Log.d(TAG, "onScrollEnd called with playerType = [" - + player.getPlayerType() + "]"); - } - - if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) { - player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); - } - - if (playerType == MainPlayer.PlayerType.VIDEO) { - if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { - animate(player.getVolumeRelativeLayout(), false, 200, SCALE_AND_ALPHA, - 200); - } - if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { - animate(player.getBrightnessRelativeLayout(), false, 200, SCALE_AND_ALPHA, - 200); - } - } else /* Popup-Player */ { - if (player.isInsideClosingRadius(event)) { - player.closePopup(); - } else if (!player.isPopupClosing()) { - animate(player.getCloseOverlayButton(), false, 200); - animate(player.getClosingOverlayView(), false, 200); - } - } - } - - @Override - public void onPopupResizingStart() { - if (DEBUG) { - Log.d(TAG, "onPopupResizingStart called"); - } - player.getLoadingPanel().setVisibility(View.GONE); - - player.hideControls(0, 0); - animate(player.getFastSeekOverlay(), false, 0); - animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0); - } - - @Override - public void onPopupResizingEnd() { - if (DEBUG) { - Log.d(TAG, "onPopupResizingEnd called"); - } - } -} - - diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java index 359eab8b28e..8c18fd2ad1c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java @@ -3,6 +3,8 @@ import com.google.android.exoplayer2.PlaybackException; public interface PlayerServiceEventListener extends PlayerEventListener { + void onViewCreated(); + void onFullscreenStateChanged(boolean fullscreen); void onScreenRotationButtonClicked(); diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java index f774c90a0e7..8effe2f0e93 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java @@ -1,11 +1,11 @@ package org.schabi.newpipe.player.event; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener { void onServiceConnected(Player player, - MainPlayer playerService, + PlayerService playerService, boolean playAfterConnect); void onServiceDisconnected(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt new file mode 100644 index 00000000000..bd5d6f1c5f5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -0,0 +1,182 @@ +package org.schabi.newpipe.player.gesture + +import android.os.Handler +import android.util.Log +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import org.schabi.newpipe.databinding.PlayerBinding +import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.ui.VideoPlayerUi + +/** + * Base gesture handling for [Player] + * + * This class contains the logic for the player gestures like View preparations + * and provides some abstract methods to make it easier separating the logic from the UI. + */ +abstract class BasePlayerGestureListener( + private val playerUi: VideoPlayerUi, +) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { + + protected val player: Player = playerUi.player + protected val binding: PlayerBinding = playerUi.binding + + override fun onTouch(v: View, event: MotionEvent): Boolean { + playerUi.gestureDetector.onTouchEvent(event) + return false + } + + private fun onDoubleTap( + event: MotionEvent, + portion: DisplayPortion + ) { + if (DEBUG) { + Log.d( + TAG, + "onDoubleTap called with playerType = [" + + player.playerType + "], portion = [" + portion + "]" + ) + } + if (playerUi.isSomePopupMenuVisible) { + playerUi.hideControls(0, 0) + } + if (portion === DisplayPortion.LEFT || portion === DisplayPortion.RIGHT) { + startMultiDoubleTap(event) + } else if (portion === DisplayPortion.MIDDLE) { + player.playPause() + } + } + + protected fun onSingleTap() { + if (playerUi.isControlsVisible) { + playerUi.hideControls(150, 0) + return + } + // -- Controls are not visible -- + + // When player is completed show controls and don't hide them later + if (player.currentState == Player.STATE_COMPLETED) { + playerUi.showControls(0) + } else { + playerUi.showControlsThenHide() + } + } + + open fun onScrollEnd(event: MotionEvent) { + if (DEBUG) { + Log.d( + TAG, + "onScrollEnd called with playerType = [" + + player.playerType + "]" + ) + } + if (playerUi.isControlsVisible && player.currentState == Player.STATE_PLAYING) { + playerUi.hideControls( + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, + VideoPlayerUi.DEFAULT_CONTROLS_HIDE_TIME + ) + } + } + + // /////////////////////////////////////////////////////////////////// + // Simple gestures + // /////////////////////////////////////////////////////////////////// + + override fun onDown(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onDown called with e = [$e]") + + if (isDoubleTapping && isDoubleTapEnabled) { + doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) + return true + } + + return if (onDownNotDoubleTapping(e)) super.onDown(e) else true + } + + /** + * @return true if `super.onDown(e)` should be called, false otherwise + */ + open fun onDownNotDoubleTapping(e: MotionEvent): Boolean { + return false // do not call super.onDown(e) by default, overridden for popup player + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onDoubleTap called with e = [$e]") + + onDoubleTap(e, getDisplayPortion(e)) + return true + } + + // /////////////////////////////////////////////////////////////////// + // Multi double tapping + // /////////////////////////////////////////////////////////////////// + + private var doubleTapControls: DoubleTapListener? = null + + private val isDoubleTapEnabled: Boolean + get() = doubleTapDelay > 0 + + var isDoubleTapping = false + private set + + fun doubleTapControls(listener: DoubleTapListener) = apply { + doubleTapControls = listener + } + + private var doubleTapDelay = DOUBLE_TAP_DELAY + private val doubleTapHandler: Handler = Handler() + private val doubleTapRunnable = Runnable { + if (DEBUG) + Log.d(TAG, "doubleTapRunnable called") + + isDoubleTapping = false + doubleTapControls?.onDoubleTapFinished() + } + + private fun startMultiDoubleTap(e: MotionEvent) { + if (!isDoubleTapping) { + if (DEBUG) + Log.d(TAG, "startMultiDoubleTap called with e = [$e]") + + keepInDoubleTapMode() + doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) + } + } + + fun keepInDoubleTapMode() { + if (DEBUG) + Log.d(TAG, "keepInDoubleTapMode called") + + isDoubleTapping = true + doubleTapHandler.removeCallbacks(doubleTapRunnable) + doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay) + } + + fun endMultiDoubleTap() { + if (DEBUG) + Log.d(TAG, "endMultiDoubleTap called") + + isDoubleTapping = false + doubleTapHandler.removeCallbacks(doubleTapRunnable) + doubleTapControls?.onDoubleTapFinished() + } + + // /////////////////////////////////////////////////////////////////// + // Utils + // /////////////////////////////////////////////////////////////////// + + abstract fun getDisplayPortion(e: MotionEvent): DisplayPortion + + // Currently needed for scrolling since there is no action more the middle portion + abstract fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion + + companion object { + private const val TAG = "BasePlayerGestListener" + private val DEBUG = Player.DEBUG + + private const val DOUBLE_TAP_DELAY = 550L + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java similarity index 98% rename from app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java rename to app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index a5de56e7569..2400091057a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event; +package org.schabi.newpipe.player.gesture; import android.content.Context; import android.graphics.Rect; diff --git a/app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt similarity index 65% rename from app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt rename to app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt index f15e42897ca..684f6d326f3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event +package org.schabi.newpipe.player.gesture enum class DisplayPortion { LEFT, MIDDLE, RIGHT, LEFT_HALF, RIGHT_HALF diff --git a/app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt similarity index 81% rename from app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt rename to app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt index 84cfb9b8d5a..1a0b141e648 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event +package org.schabi.newpipe.player.gesture interface DoubleTapListener { fun onDoubleTapStarted(portion: DisplayPortion) {} diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt new file mode 100644 index 00000000000..17205fb9ae5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -0,0 +1,232 @@ +package org.schabi.newpipe.player.gesture + +import android.app.Activity +import android.content.Context +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.View.OnTouchListener +import android.widget.ProgressBar +import androidx.appcompat.content.res.AppCompatResources +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.ktx.AnimationType +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.helper.PlayerHelper +import org.schabi.newpipe.player.ui.MainPlayerUi +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * GestureListener for the player + * + * While [BasePlayerGestureListener] contains the logic behind the single gestures + * this class focuses on the visual aspect like hiding and showing the controls or changing + * volume/brightness during scrolling for specific events. + */ +class MainPlayerGestureListener( + private val playerUi: MainPlayerUi +) : BasePlayerGestureListener(playerUi), OnTouchListener { + private val maxVolume: Int = player.audioReactor.maxVolume + + private var isMoving = false + + override fun onTouch(v: View, event: MotionEvent): Boolean { + super.onTouch(v, event) + if (event.action == MotionEvent.ACTION_UP && isMoving) { + isMoving = false + onScrollEnd(event) + } + return when (event.action) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { + v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) + true + } + MotionEvent.ACTION_UP -> { + v.parent.requestDisallowInterceptTouchEvent(false) + false + } + else -> true + } + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + + if (isDoubleTapping) + return true + super.onSingleTapConfirmed(e) + + if (player.currentState != Player.STATE_BLOCKED) + onSingleTap() + return true + } + + private fun onScrollVolume(distanceY: Float) { + // If we just started sliding, change the progress bar to match the system volume + if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + val volumePercent: Float = player.audioReactor.volume / maxVolume.toFloat() + binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() + } + + binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) + val currentProgressPercent: Float = + binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH + val currentVolume = (maxVolume * currentProgressPercent).toInt() + player.audioReactor.volume = currentVolume + if (DEBUG) { + Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") + } + + binding.volumeImageView.setImageDrawable( + AppCompatResources.getDrawable( + player.context, + when { + currentProgressPercent <= 0 -> R.drawable.ic_volume_off + currentProgressPercent < 0.25 -> R.drawable.ic_volume_mute + currentProgressPercent < 0.75 -> R.drawable.ic_volume_down + else -> R.drawable.ic_volume_up + } + ) + ) + + if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + binding.volumeRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) + } + if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.visibility = View.GONE + } + } + + private fun onScrollBrightness(distanceY: Float) { + val parent: Activity = playerUi.parentActivity + val window = parent.window + val layoutParams = window.attributes + val bar: ProgressBar = binding.brightnessProgressBar + val oldBrightness = layoutParams.screenBrightness + bar.progress = (bar.max * max(0f, min(1f, oldBrightness))).toInt() + bar.incrementProgressBy(distanceY.toInt()) + val currentProgressPercent = bar.progress.toFloat() / bar.max + layoutParams.screenBrightness = currentProgressPercent + window.attributes = layoutParams + + // Save current brightness level + PlayerHelper.setScreenBrightness(parent, currentProgressPercent) + if (DEBUG) { + Log.d( + TAG, + "onScroll().brightnessControl, " + + "currentBrightness = " + currentProgressPercent + ) + } + binding.brightnessImageView.setImageDrawable( + AppCompatResources.getDrawable( + player.context, + if (currentProgressPercent < 0.25) R.drawable.ic_brightness_low else if (currentProgressPercent < 0.75) R.drawable.ic_brightness_medium else R.drawable.ic_brightness_high + ) + ) + if (binding.brightnessRelativeLayout.visibility != View.VISIBLE) { + binding.brightnessRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) + } + if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.visibility = View.GONE + } + } + + override fun onScrollEnd(event: MotionEvent) { + super.onScrollEnd(event) + if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) + } + if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + binding.brightnessRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) + } + } + + override fun onScroll( + initialEvent: MotionEvent, + movingEvent: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + + if (!playerUi.isFullscreen) { + return false + } + + val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(player.context) + val isTouchingNavigationBar: Boolean = + initialEvent.y > (binding.root.height - getNavigationBarHeight(player.context)) + if (isTouchingStatusBar || isTouchingNavigationBar) { + return false + } + + val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD + if ( + !isMoving && (insideThreshold || abs(distanceX) > abs(distanceY)) || + player.currentState == Player.STATE_COMPLETED + ) { + return false + } + + isMoving = true + + // -- Brightness and Volume control -- + val isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(player.context) + val isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(player.context) + if (isBrightnessGestureEnabled && isVolumeGestureEnabled) { + if (getDisplayHalfPortion(initialEvent) === DisplayPortion.LEFT_HALF) { + onScrollBrightness(distanceY) + } else /* DisplayPortion.RIGHT_HALF */ { + onScrollVolume(distanceY) + } + } else if (isBrightnessGestureEnabled) { + onScrollBrightness(distanceY) + } else if (isVolumeGestureEnabled) { + onScrollVolume(distanceY) + } + + return true + } + + override fun getDisplayPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < binding.root.width / 3.0 -> DisplayPortion.LEFT + e.x > binding.root.width * 2.0 / 3.0 -> DisplayPortion.RIGHT + else -> DisplayPortion.MIDDLE + } + } + + override fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < binding.root.width / 2.0 -> DisplayPortion.LEFT_HALF + else -> DisplayPortion.RIGHT_HALF + } + } + + companion object { + private val TAG = MainPlayerGestureListener::class.java.simpleName + private val DEBUG = MainActivity.DEBUG + private const val MOVEMENT_THRESHOLD = 40 + const val MAX_GESTURE_LENGTH = 0.75f + + private fun getNavigationBarHeight(context: Context): Int { + val resId = context.resources + .getIdentifier("navigation_bar_height", "dimen", "android") + return if (resId > 0) { + context.resources.getDimensionPixelSize(resId) + } else 0 + } + + private fun getStatusBarHeight(context: Context): Int { + val resId = context.resources + .getIdentifier("status_bar_height", "dimen", "android") + return if (resId > 0) { + context.resources.getDimensionPixelSize(resId) + } else 0 + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt new file mode 100644 index 00000000000..b8c1bc54c5f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -0,0 +1,287 @@ +package org.schabi.newpipe.player.gesture + +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.ktx.AnimationType +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.player.helper.PlayerHelper +import org.schabi.newpipe.player.ui.PopupPlayerUi +import kotlin.math.abs +import kotlin.math.hypot +import kotlin.math.max +import kotlin.math.min + +class PopupPlayerGestureListener( + private val playerUi: PopupPlayerUi, +) : BasePlayerGestureListener(playerUi) { + + private var isMoving = false + + private var initialPopupX: Int = -1 + private var initialPopupY: Int = -1 + private var isResizing = false + + // initial coordinates and distance between fingers + private var initPointerDistance = -1.0 + private var initFirstPointerX = -1f + private var initFirstPointerY = -1f + private var initSecPointerX = -1f + private var initSecPointerY = -1f + + override fun onTouch(v: View, event: MotionEvent): Boolean { + super.onTouch(v, event) + if (event.pointerCount == 2 && !isMoving && !isResizing) { + if (DEBUG) { + Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.") + } + onPopupResizingStart() + + // record coordinates of fingers + initFirstPointerX = event.getX(0) + initFirstPointerY = event.getY(0) + initSecPointerX = event.getX(1) + initSecPointerY = event.getY(1) + // record distance between fingers + initPointerDistance = hypot( + initFirstPointerX - initSecPointerX.toDouble(), + initFirstPointerY - initSecPointerY.toDouble() + ) + + isResizing = true + } + if (event.action == MotionEvent.ACTION_MOVE && !isMoving && isResizing) { + if (DEBUG) { + Log.d( + TAG, + "onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" + + "[${event.rawX}, ${event.rawY}]" + ) + } + return handleMultiDrag(event) + } + if (event.action == MotionEvent.ACTION_UP) { + if (DEBUG) { + Log.d( + TAG, + "onTouch() ACTION_UP > v = [$v], e1.getRaw =" + + " [${event.rawX}, ${event.rawY}]" + ) + } + if (isMoving) { + isMoving = false + onScrollEnd(event) + } + if (isResizing) { + isResizing = false + + initPointerDistance = (-1).toDouble() + initFirstPointerX = (-1).toFloat() + initFirstPointerY = (-1).toFloat() + initSecPointerX = (-1).toFloat() + initSecPointerY = (-1).toFloat() + + onPopupResizingEnd() + player.changeState(player.currentState) + } + if (!playerUi.isPopupClosing) { + PlayerHelper.savePopupPositionAndSizeToPrefs(playerUi) + } + } + + v.performClick() + return true + } + + override fun onScrollEnd(event: MotionEvent) { + super.onScrollEnd(event) + if (playerUi.isInsideClosingRadius(event)) { + playerUi.closePopup() + } else if (!playerUi.isPopupClosing) { + playerUi.closeOverlayBinding.closeButton.animate(false, 200) + binding.closingOverlay.animate(false, 200) + } + } + + private fun handleMultiDrag(event: MotionEvent): Boolean { + if (initPointerDistance != -1.0 && event.pointerCount == 2) { + // get the movements of the fingers + val firstPointerMove = hypot( + event.getX(0) - initFirstPointerX.toDouble(), + event.getY(0) - initFirstPointerY.toDouble() + ) + val secPointerMove = hypot( + event.getX(1) - initSecPointerX.toDouble(), + event.getY(1) - initSecPointerY.toDouble() + ) + + // minimum threshold beyond which pinch gesture will work + val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop + + if (max(firstPointerMove, secPointerMove) > minimumMove) { + // calculate current distance between the pointers + val currentPointerDistance = hypot( + event.getX(0) - event.getX(1).toDouble(), + event.getY(0) - event.getY(1).toDouble() + ) + + val popupWidth = playerUi.popupLayoutParams.width.toDouble() + // change co-ordinates of popup so the center stays at the same position + val newWidth = popupWidth * currentPointerDistance / initPointerDistance + initPointerDistance = currentPointerDistance + playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() + + playerUi.checkPopupPositionBounds() + playerUi.updateScreenSize() + playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) + return true + } + } + return false + } + + private fun onPopupResizingStart() { + if (DEBUG) { + Log.d(TAG, "onPopupResizingStart called") + } + binding.loadingPanel.visibility = View.GONE + playerUi.hideControls(0, 0) + binding.fastSeekOverlay.animate(false, 0) + binding.currentDisplaySeek.animate(false, 0, AnimationType.ALPHA, 0) + } + + private fun onPopupResizingEnd() { + if (DEBUG) { + Log.d(TAG, "onPopupResizingEnd called") + } + } + + override fun onLongPress(e: MotionEvent?) { + playerUi.updateScreenSize() + playerUi.checkPopupPositionBounds() + playerUi.changePopupSize(playerUi.screenWidth) + } + + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent?, + velocityX: Float, + velocityY: Float + ): Boolean { + return if (player.popupPlayerSelected()) { + val absVelocityX = abs(velocityX) + val absVelocityY = abs(velocityY) + if (absVelocityX.coerceAtLeast(absVelocityY) > TOSS_FLING_VELOCITY) { + if (absVelocityX > TOSS_FLING_VELOCITY) { + playerUi.popupLayoutParams.x = velocityX.toInt() + } + if (absVelocityY > TOSS_FLING_VELOCITY) { + playerUi.popupLayoutParams.y = velocityY.toInt() + } + playerUi.checkPopupPositionBounds() + playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams) + return true + } + return false + } else { + true + } + } + + override fun onDownNotDoubleTapping(e: MotionEvent): Boolean { + // Fix popup position when the user touch it, it may have the wrong one + // because the soft input is visible (the draggable area is currently resized). + playerUi.updateScreenSize() + playerUi.checkPopupPositionBounds() + playerUi.popupLayoutParams.let { + initialPopupX = it.x + initialPopupY = it.y + } + return true // we want `super.onDown(e)` to be called + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + + if (isDoubleTapping) + return true + if (player.exoPlayerIsNull()) + return false + + onSingleTap() + return true + } + + override fun onScroll( + initialEvent: MotionEvent, + movingEvent: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + + if (isResizing) { + return super.onScroll(initialEvent, movingEvent, distanceX, distanceY) + } + + if (!isMoving) { + playerUi.closeOverlayBinding.closeButton.animate(true, 200) + } + + isMoving = true + + val diffX: Float = (movingEvent.rawX - initialEvent.rawX) + var posX: Float = (initialPopupX + diffX) + val diffY: Float = (movingEvent.rawY - initialEvent.rawY) + var posY: Float = (initialPopupY + diffY) + + if (posX > playerUi.screenWidth - playerUi.popupLayoutParams.width) { + posX = (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() + } else if (posX < 0) { + posX = 0f + } + + if (posY > playerUi.screenHeight - playerUi.popupLayoutParams.height) { + posY = (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() + } else if (posY < 0) { + posY = 0f + } + + playerUi.popupLayoutParams.x = posX.toInt() + playerUi.popupLayoutParams.y = posY.toInt() + + // -- Determine if the ClosingOverlayView (red X) has to be shown or hidden -- + val showClosingOverlayView: Boolean = playerUi.isInsideClosingRadius(movingEvent) + // Check if an view is in expected state and if not animate it into the correct state + val expectedVisibility = if (showClosingOverlayView) View.VISIBLE else View.GONE + if (binding.closingOverlay.visibility != expectedVisibility) { + binding.closingOverlay.animate(showClosingOverlayView, 200) + } + + playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams) + return true + } + + override fun getDisplayPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < playerUi.popupLayoutParams.width / 3.0 -> DisplayPortion.LEFT + e.x > playerUi.popupLayoutParams.width * 2.0 / 3.0 -> DisplayPortion.RIGHT + else -> DisplayPortion.MIDDLE + } + } + + override fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < playerUi.popupLayoutParams.width / 2.0 -> DisplayPortion.LEFT_HALF + else -> DisplayPortion.RIGHT_HALF + } + } + + companion object { + private val TAG = PopupPlayerGestureListener::class.java.simpleName + private val DEBUG = MainActivity.DEBUG + private const val TOSS_FLING_VELOCITY = 2500 + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 19a5a645bbe..8a5a4f8d204 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -26,7 +26,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; -import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; @@ -207,7 +207,7 @@ private void initUI() { ? View.VISIBLE : View.GONE); animateRotation(binding.pitchToogleControlModes, - Player.DEFAULT_CONTROLS_DURATION, + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, isCurrentlyVisible ? 180 : 0); }); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 2131861bff6..ec4cf8602a5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -3,7 +3,6 @@ import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.Player.IDLE_WINDOW_FLAGS; import static org.schabi.newpipe.player.Player.PLAYER_TYPE; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER; @@ -11,6 +10,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; +import static org.schabi.newpipe.player.ui.PopupPlayerUi.IDLE_WINDOW_FLAGS; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; @@ -49,11 +49,12 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.player.ui.PopupPlayerUi; import org.schabi.newpipe.util.ListHelper; import java.lang.annotation.Retention; @@ -339,10 +340,6 @@ public static boolean isUsingDSP() { return true; } - public static int getTossFlingVelocity() { - return 2500; - } - @NonNull public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) { final CaptioningManager captioningManager = ContextCompat.getSystemService(context, @@ -452,10 +449,10 @@ private static SinglePlayQueue getAutoQueuedSinglePlayQueue( // Utils used by player //////////////////////////////////////////////////////////////////////////// - public static MainPlayer.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { + public static PlayerService.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra - return MainPlayer.PlayerType.values()[ - intent.getIntExtra(PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal())]; + return PlayerService.PlayerType.values()[ + intent.getIntExtra(PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal())]; } public static boolean isPlaybackResumeEnabled(final Player player) { @@ -529,19 +526,20 @@ public static void savePlaybackParametersToPrefs(final Player player, } /** - * @param player {@code screenWidth} and {@code screenHeight} must have been initialized + * @param playerUi {@code screenWidth} and {@code screenHeight} must have been initialized * @return the popup starting layout params */ @SuppressLint("RtlHardcoded") public static WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs( - final Player player) { - final boolean popupRememberSizeAndPos = player.getPrefs().getBoolean( - player.getContext().getString(R.string.popup_remember_size_pos_key), true); - final float defaultSize = - player.getContext().getResources().getDimension(R.dimen.popup_default_width); + final PopupPlayerUi playerUi) { + final SharedPreferences prefs = playerUi.getPlayer().getPrefs(); + final Context context = playerUi.getPlayer().getContext(); + + final boolean popupRememberSizeAndPos = prefs.getBoolean( + context.getString(R.string.popup_remember_size_pos_key), true); + final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); final float popupWidth = popupRememberSizeAndPos - ? player.getPrefs().getFloat(player.getContext().getString( - R.string.popup_saved_width_key), defaultSize) + ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) : defaultSize; final float popupHeight = getMinimumVideoHeight(popupWidth); @@ -553,27 +551,26 @@ public static WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs( popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - final int centerX = (int) (player.getScreenWidth() / 2f - popupWidth / 2f); - final int centerY = (int) (player.getScreenHeight() / 2f - popupHeight / 2f); + final int centerX = (int) (playerUi.getScreenWidth() / 2f - popupWidth / 2f); + final int centerY = (int) (playerUi.getScreenHeight() / 2f - popupHeight / 2f); popupLayoutParams.x = popupRememberSizeAndPos - ? player.getPrefs().getInt(player.getContext().getString( - R.string.popup_saved_x_key), centerX) : centerX; + ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; popupLayoutParams.y = popupRememberSizeAndPos - ? player.getPrefs().getInt(player.getContext().getString( - R.string.popup_saved_y_key), centerY) : centerY; + ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; return popupLayoutParams; } - public static void savePopupPositionAndSizeToPrefs(final Player player) { - if (player.getPopupLayoutParams() != null) { - player.getPrefs().edit() - .putFloat(player.getContext().getString(R.string.popup_saved_width_key), - player.getPopupLayoutParams().width) - .putInt(player.getContext().getString(R.string.popup_saved_x_key), - player.getPopupLayoutParams().x) - .putInt(player.getContext().getString(R.string.popup_saved_y_key), - player.getPopupLayoutParams().y) + public static void savePopupPositionAndSizeToPrefs(final PopupPlayerUi playerUi) { + if (playerUi.getPopupLayoutParams() != null) { + final Context context = playerUi.getPlayer().getContext(); + playerUi.getPlayer().getPrefs().edit() + .putFloat(context.getString(R.string.popup_saved_width_key), + playerUi.getPopupLayoutParams().width) + .putInt(context.getString(R.string.popup_saved_x_key), + playerUi.getPopupLayoutParams().x) + .putInt(context.getString(R.string.popup_saved_y_key), + playerUi.getPopupLayoutParams().y) .apply(); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index 4c09ed3c19a..cb613f8541e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -16,7 +16,7 @@ import org.schabi.newpipe.App; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; @@ -42,17 +42,17 @@ public static synchronized PlayerHolder getInstance() { private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection(); private boolean bound; - @Nullable private MainPlayer playerService; + @Nullable private PlayerService playerService; @Nullable private Player player; /** - * Returns the current {@link MainPlayer.PlayerType} of the {@link MainPlayer} service, + * Returns the current {@link PlayerService.PlayerType} of the {@link PlayerService} service, * otherwise `null` if no service running. * * @return Current PlayerType */ @Nullable - public MainPlayer.PlayerType getType() { + public PlayerService.PlayerType getType() { if (player == null) { return null; } @@ -122,7 +122,7 @@ public void startService(final boolean playAfterConnect, // and NullPointerExceptions inside the service because the service will be // bound twice. Prevent it with unbinding first unbind(context); - ContextCompat.startForegroundService(context, new Intent(context, MainPlayer.class)); + ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class)); serviceConnection.doPlayAfterConnect(playAfterConnect); bind(context); } @@ -130,7 +130,7 @@ public void startService(final boolean playAfterConnect, public void stopService() { final Context context = getCommonContext(); unbind(context); - context.stopService(new Intent(context, MainPlayer.class)); + context.stopService(new Intent(context, PlayerService.class)); } class PlayerServiceConnection implements ServiceConnection { @@ -156,7 +156,7 @@ public void onServiceConnected(final ComponentName compName, final IBinder servi if (DEBUG) { Log.d(TAG, "Player service is connected"); } - final MainPlayer.LocalBinder localBinder = (MainPlayer.LocalBinder) service; + final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service; playerService = localBinder.getService(); player = localBinder.getPlayer(); @@ -172,7 +172,7 @@ private void bind(final Context context) { Log.d(TAG, "bind() called"); } - final Intent serviceIntent = new Intent(context, MainPlayer.class); + final Intent serviceIntent = new Intent(context, PlayerService.class); bound = context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); if (!bound) { @@ -211,6 +211,13 @@ private void stopPlayerListener() { private final PlayerServiceEventListener internalListener = new PlayerServiceEventListener() { + @Override + public void onViewCreated() { + if (listener != null) { + listener.onViewCreated(); + } + } + @Override public void onFullscreenStateChanged(final boolean fullscreen) { if (listener != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt b/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt deleted file mode 100644 index 52eff5a1cce..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.schabi.newpipe.player.listeners.view - -import android.util.Log -import android.view.View -import androidx.appcompat.widget.PopupMenu -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.player.Player -import org.schabi.newpipe.player.helper.PlaybackParameterDialog - -/** - * Click listener for the playbackSpeed textview of the player - */ -class PlaybackSpeedClickListener( - private val player: Player, - private val playbackSpeedPopupMenu: PopupMenu -) : View.OnClickListener { - - companion object { - private const val TAG: String = "PlaybSpeedClickListener" - } - - override fun onClick(v: View) { - if (MainActivity.DEBUG) { - Log.d(TAG, "onPlaybackSpeedClicked() called") - } - - if (player.videoPlayerSelected()) { - PlaybackParameterDialog.newInstance( - player.playbackSpeed.toDouble(), - player.playbackPitch.toDouble(), - player.playbackSkipSilence - ) { speed: Float, pitch: Float, skipSilence: Boolean -> - player.setPlaybackParameters( - speed, - pitch, - skipSilence - ) - } - .show(player.parentActivity!!.supportFragmentManager, null) - } else { - playbackSpeedPopupMenu.show() - player.isSomePopupMenuVisible = true - } - - player.manageControlsAfterOnClick(v) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt b/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt deleted file mode 100644 index 43e8288e605..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.schabi.newpipe.player.listeners.view - -import android.annotation.SuppressLint -import android.util.Log -import android.view.View -import androidx.appcompat.widget.PopupMenu -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.extractor.MediaFormat -import org.schabi.newpipe.player.Player - -/** - * Click listener for the qualityTextView of the player - */ -class QualityClickListener( - private val player: Player, - private val qualityPopupMenu: PopupMenu -) : View.OnClickListener { - - companion object { - private const val TAG: String = "QualityClickListener" - } - - @SuppressLint("SetTextI18n") // we don't need I18N because of a " " - override fun onClick(v: View) { - if (MainActivity.DEBUG) { - Log.d(TAG, "onQualitySelectorClicked() called") - } - - qualityPopupMenu.show() - player.isSomePopupMenuVisible = true - - val videoStream = player.selectedVideoStream - if (videoStream != null) { - player.binding.qualityTextView.text = - MediaFormat.getNameById(videoStream.formatId) + " " + videoStream.getResolution() - } - - player.saveWasPlaying() - player.manageControlsAfterOnClick(v) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index ee0a6f11819..2f261a0fa11 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -8,6 +8,9 @@ import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.mediasession.MediaSessionCallback; import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.ui.VideoPlayerUi; + +import java.util.Optional; public class PlayerMediaSession implements MediaSessionCallback { private final Player player; @@ -89,7 +92,7 @@ public MediaDescriptionCompat getQueueMetadata(final int index) { public void play() { player.play(); // hide the player controls even if the play command came from the media session - player.hideControls(0, 0); + player.UIs().get(VideoPlayerUi.class).ifPresent(playerUi -> playerUi.hideControls(0, 0)); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java new file mode 100644 index 00000000000..10ed424bab8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -0,0 +1,937 @@ +package org.schabi.newpipe.player.ui; + +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.player.Player.STATE_COMPLETED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; +import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; + +import android.content.Intent; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Build; +import android.os.Handler; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.StreamSegment; +import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.fragments.detail.VideoDetailFragment; +import org.schabi.newpipe.info_list.StreamSegmentAdapter; +import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.event.PlayerServiceEventListener; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.MainPlayerGestureListener; +import org.schabi.newpipe.player.helper.PlaybackParameterDialog; +import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; +import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; +import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; +import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.external_communication.KoreUtils; + +import java.util.List; +import java.util.Objects; + +public final class MainPlayerUi extends VideoPlayerUi { + private static final String TAG = MainPlayerUi.class.getSimpleName(); + + private boolean isFullscreen = false; + private boolean isVerticalVideo = false; + private boolean fragmentIsVisible = false; + + private ContentObserver settingsContentObserver; + + private PlayQueueAdapter playQueueAdapter; + private StreamSegmentAdapter segmentAdapter; + private boolean isQueueVisible = false; + private boolean areSegmentsVisible = false; + + // fullscreen player + private ItemTouchHelper itemTouchHelper; + + public MainPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player, playerBinding); + } + + /** + * Open fullscreen on tablets where the option to have the main player start automatically in + * fullscreen mode is on. Rotating the device to landscape is already done in {@link + * VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's + * enough for phones, but not for tablets since the mini player can be also shown in landscape. + */ + private void directlyOpenFullscreenIfNeeded() { + if (PlayerHelper.isStartMainPlayerFullscreenEnabled(player.getService()) + && DeviceUtils.isTablet(player.getService()) + && PlayerHelper.globalScreenOrientationLocked(player.getService())) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } + } + + @Override + public void setupAfterIntent() { + // needed for tablets, check the function for a better explanation + directlyOpenFullscreenIfNeeded(); + + super.setupAfterIntent(); + + binding.getRoot().setVisibility(View.VISIBLE); + initVideoPlayer(); + // Android TV: without it focus will frame the whole player + binding.playPauseButton.requestFocus(); + + // Note: This is for automatically playing (when "Resume playback" is off), see #6179 + if (player.getPlayWhenReady()) { + player.play(); + } else { + player.pause(); + } + } + + @Override + BasePlayerGestureListener buildGestureListener() { + return new MainPlayerGestureListener(this); + } + + @Override + protected void initListeners() { + super.initListeners(); + + binding.queueButton.setOnClickListener(v -> onQueueClicked()); + binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); + + binding.addToPlaylistButton.setOnClickListener(v -> + player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager())); + + settingsContentObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(final boolean selfChange) { + setupScreenRotationButton(); + } + }; + context.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, + settingsContentObserver); + + binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); + } + + @Override + public void initPlayback() { + super.initPlayback(); + + if (playQueueAdapter != null) { + playQueueAdapter.dispose(); + } + playQueueAdapter = new PlayQueueAdapter(context, + Objects.requireNonNull(player.getPlayQueue())); + segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); + } + + @Override + public void removeViewFromParent() { + // view was added to fragment + final ViewParent parent = binding.getRoot().getParent(); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(binding.getRoot()); + } + } + + @Override + public void destroy() { + super.destroy(); + context.getContentResolver().unregisterContentObserver(settingsContentObserver); + + // Exit from fullscreen when user closes the player via notification + if (isFullscreen) { + toggleFullscreen(); + } + + removeViewFromParent(); + } + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + + if (playQueueAdapter != null) { + playQueueAdapter.unsetSelectedListener(); + playQueueAdapter.dispose(); + } + } + + @Override + public void smoothStopForImmediateReusing() { + super.smoothStopForImmediateReusing(); + // Android TV will handle back button in case controls will be visible + // (one more additional unneeded click while the player is hidden) + hideControls(0, 0); + closeItemsList(); + } + + private void initVideoPlayer() { + // restore last resize mode + setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(player)); + binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + } + + @Override + protected void setupElementsVisibility() { + super.setupElementsVisibility(); + + closeItemsList(); + showHideKodiButton(); + binding.fullScreenButton.setVisibility(View.GONE); + setupScreenRotationButton(); + binding.resizeTextView.setVisibility(View.VISIBLE); + binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); + binding.moreOptionsButton.setVisibility(View.VISIBLE); + binding.topControls.setOrientation(LinearLayout.VERTICAL); + binding.primaryControls.getLayoutParams().width + = LinearLayout.LayoutParams.MATCH_PARENT; + binding.secondaryControls.setVisibility(View.INVISIBLE); + binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, + R.drawable.ic_expand_more)); + binding.share.setVisibility(View.VISIBLE); + binding.openInBrowser.setVisibility(View.VISIBLE); + binding.switchMute.setVisibility(View.VISIBLE); + binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); + // Top controls have a large minHeight which is allows to drag the player + // down in fullscreen mode (just larger area to make easy to locate by finger) + binding.topControls.setClickable(true); + binding.topControls.setFocusable(true); + + if (isFullscreen) { + binding.titleTextView.setVisibility(View.VISIBLE); + binding.channelTextView.setVisibility(View.VISIBLE); + } else { + binding.titleTextView.setVisibility(View.GONE); + binding.channelTextView.setVisibility(View.GONE); + } + } + + @Override + protected void setupElementsSize(final Resources resources) { + setupElementsSize( + resources.getDimensionPixelSize(R.dimen.player_main_buttons_min_width), + resources.getDimensionPixelSize(R.dimen.player_main_top_padding), + resources.getDimensionPixelSize(R.dimen.player_main_controls_padding), + resources.getDimensionPixelSize(R.dimen.player_main_buttons_padding) + ); + } + + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + // Close it because when changing orientation from portrait + // (in fullscreen mode) the size of queue layout can be larger than the screen size + closeItemsList(); + } else if (ACTION_PLAY_PAUSE.equals(intent.getAction())) { + // Ensure that we have audio-only stream playing when a user + // started to play from notification's play button from outside of the app + if (!fragmentIsVisible) { + onFragmentStopped(); + } + } else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED.equals(intent.getAction())) { + fragmentIsVisible = false; + onFragmentStopped(); + } else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED.equals(intent.getAction())) { + // Restore video source when user returns to the fragment + fragmentIsVisible = true; + player.useVideoSource(true); + + // When a user returns from background, the system UI will always be shown even if + // controls are invisible: hide it in that case + if (!isControlsVisible()) { + hideSystemUIIfNeeded(); + } + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Fragment binding + //////////////////////////////////////////////////////////////////////////*/ + //region Fragment binding + @Override + public void onFragmentListenerSet() { + super.onFragmentListenerSet(); + fragmentIsVisible = true; + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait + if (!isFullscreen) { + binding.playbackControlRoot.setPadding(0, 0, 0, 0); + } + binding.itemsListPanel.setPadding(0, 0, 0, 0); + player.getFragmentListener().ifPresent(PlayerServiceEventListener::onViewCreated); + } + + /** + * This will be called when a user goes to another app/activity, turns off a screen. + * We don't want to interrupt playback and don't want to see notification so + * next lines of code will enable audio-only playback only if needed + */ + private void onFragmentStopped() { + if (player.isPlaying() || player.isLoading()) { + switch (getMinimizeOnExitAction(context)) { + case MINIMIZE_ON_EXIT_MODE_BACKGROUND: + player.useVideoSource(false); + break; + case MINIMIZE_ON_EXIT_MODE_POPUP: + player.setRecovery(); + NavigationHelper.playOnPopupPlayer(getParentActivity(), + player.getPlayQueue(), true); + break; + case MINIMIZE_ON_EXIT_MODE_NONE: default: + player.pause(); + break; + } + } + } + //endregion + + private void showHideKodiButton() { + // show kodi button if it supports the current service and it is enabled in settings + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null + && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) + ? View.VISIBLE : View.GONE); + } + + @Override + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + super.onUpdateProgress(currentProgress, duration, bufferPercent); + + if (areSegmentsVisible) { + segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); + } + if (isQueueVisible) { + updateQueueTime(currentProgress); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Controls showing / hiding + //////////////////////////////////////////////////////////////////////////*/ + //region Controls showing / hiding + + protected void showOrHideButtons() { + super.showOrHideButtons(); + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final boolean showQueue = playQueue.getStreams().size() > 1; + final boolean showSegment = !player.getCurrentStreamInfo() + .map(StreamInfo::getStreamSegments) + .map(List::isEmpty) + .orElse(/*no stream info=*/true); + + binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); + binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f); + binding.segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE); + binding.segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f); + } + + @Override + public void showSystemUIPartially() { + if (isFullscreen) { + final Window window = getParentActivity().getWindow(); + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + window.getDecorView().setSystemUiVisibility(visibility); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + @Override + public void hideSystemUIIfNeeded() { + player.getFragmentListener().ifPresent(PlayerServiceEventListener::hideSystemUiIfNeeded); + } + + /** + * Calculate the maximum allowed height for the {@link R.id.endScreen} + * to prevent it from enlarging the player. + *

+ * The calculating follows these rules: + *

    + *
  • + * Show at least stream title and content creator on TVs and tablets + * when in landscape (always the case for TVs) and not in fullscreen mode. + * This requires to have at least 85dp free space for {@link R.id.detail_root} + * and additional space for the stream title text size + * ({@link R.id.detail_title_root_layout}). + * The text size is 15sp on tablets and 16sp on TVs, + * see {@link R.id.titleTextView}. + *
  • + *
  • + * Otherwise, the max thumbnail height is the screen height. + * TODO investigate why this is done on popup player, too + *
  • + *
+ * + * @param bitmap the bitmap that needs to be resized to fit the end screen + * @return the maximum height for the end screen thumbnail + */ + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + final int screenHeight = context.getResources().getDisplayMetrics().heightPixels; + + if (DeviceUtils.isTv(context) && !isFullscreen()) { + final int videoInfoHeight = + DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); + return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); + } else if (DeviceUtils.isTablet(context) && isLandscape() && !isFullscreen()) { + final int videoInfoHeight = + DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); + return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); + } else { // fullscreen player: max height is the device height + return Math.min(bitmap.getHeight(), screenHeight); + } + } + //endregion + + @Override + public void onPlaying() { + super.onPlaying(); + checkLandscape(); + } + + @Override + public void onCompleted() { + super.onCompleted(); + if (isFullscreen) { + toggleFullscreen(); + } + } + + + @Override + protected void setupSubtitleView(float captionScale) { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); + final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); + binding.subtitleView.setFixedTextSize( + TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); + } + + + + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + + @SuppressWarnings("checkstyle:ParameterNumber") + private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, + final int ol, final int ot, final int or, final int ob) { + if (l != ol || t != ot || r != or || b != ob) { + // Use smaller value to be consistent between screen orientations + // (and to make usage easier) + final int width = r - l; + final int height = b - t; + final int min = Math.min(width, height); + final int maxGestureLength = (int) (min * MainPlayerGestureListener.MAX_GESTURE_LENGTH); + + if (DEBUG) { + Log.d(TAG, "maxGestureLength = " + maxGestureLength); + } + + binding.volumeProgressBar.setMax(maxGestureLength); + binding.brightnessProgressBar.setMax(maxGestureLength); + + setInitialGestureValues(); + binding.itemsListPanel.getLayoutParams().height + = height - binding.itemsListPanel.getTop(); + } + } + + private void setInitialGestureValues() { + if (player.getAudioReactor() != null) { + final float currentVolumeNormalized = + (float) player.getAudioReactor().getVolume() + / player.getAudioReactor().getMaxVolume(); + binding.volumeProgressBar.setProgress( + (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Play queue, segments and streams + //////////////////////////////////////////////////////////////////////////*/ + //region Play queue, segments and streams + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + showHideKodiButton(); + if (areSegmentsVisible) { + if (segmentAdapter.setItems(info)) { + final int adapterPosition = getNearestStreamSegmentPosition( + player.getExoPlayer().getCurrentPosition()); + segmentAdapter.selectSegmentAt(adapterPosition); + binding.itemsList.scrollToPosition(adapterPosition); + } else { + closeItemsList(); + } + } + } + + @Override + public void onPlayQueueEdited() { + super.onPlayQueueEdited(); + showOrHideButtons(); + } + + private void onQueueClicked() { + isQueueVisible = true; + + hideSystemUIIfNeeded(); + buildQueue(); + + binding.itemsListHeaderTitle.setVisibility(View.GONE); + binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); + binding.shuffleButton.setVisibility(View.VISIBLE); + binding.repeatButton.setVisibility(View.VISIBLE); + binding.addToPlaylistButton.setVisibility(View.VISIBLE); + + hideControls(0, 0); + binding.itemsListPanel.requestFocus(); + animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA); + + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null) { + binding.itemsList.scrollToPosition(playQueue.getIndex()); + } + + updateQueueTime((int) player.getExoPlayer().getCurrentPosition()); + } + + private void buildQueue() { + binding.itemsList.setAdapter(playQueueAdapter); + binding.itemsList.setClickable(true); + binding.itemsList.setLongClickable(true); + + binding.itemsList.clearOnScrollListeners(); + binding.itemsList.addOnScrollListener(getQueueScrollListener()); + + itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); + itemTouchHelper.attachToRecyclerView(binding.itemsList); + + playQueueAdapter.setSelectedListener(getOnSelectedListener()); + + binding.itemsListClose.setOnClickListener(view -> closeItemsList()); + } + + private void onSegmentsClicked() { + areSegmentsVisible = true; + + hideSystemUIIfNeeded(); + buildSegments(); + + binding.itemsListHeaderTitle.setVisibility(View.VISIBLE); + binding.itemsListHeaderDuration.setVisibility(View.GONE); + binding.shuffleButton.setVisibility(View.GONE); + binding.repeatButton.setVisibility(View.GONE); + binding.addToPlaylistButton.setVisibility(View.GONE); + + hideControls(0, 0); + binding.itemsListPanel.requestFocus(); + animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA); + + final int adapterPosition = getNearestStreamSegmentPosition( + player.getExoPlayer().getCurrentPosition()); + segmentAdapter.selectSegmentAt(adapterPosition); + binding.itemsList.scrollToPosition(adapterPosition); + } + + private void buildSegments() { + binding.itemsList.setAdapter(segmentAdapter); + binding.itemsList.setClickable(true); + binding.itemsList.setLongClickable(false); + + binding.itemsList.clearOnScrollListeners(); + if (itemTouchHelper != null) { + itemTouchHelper.attachToRecyclerView(null); + } + + player.getCurrentStreamInfo().ifPresent(segmentAdapter::setItems); + + binding.shuffleButton.setVisibility(View.GONE); + binding.repeatButton.setVisibility(View.GONE); + binding.addToPlaylistButton.setVisibility(View.GONE); + binding.itemsListClose.setOnClickListener(view -> closeItemsList()); + } + + public void closeItemsList() { + if (isQueueVisible || areSegmentsVisible) { + isQueueVisible = false; + areSegmentsVisible = false; + + if (itemTouchHelper != null) { + itemTouchHelper.attachToRecyclerView(null); + } + + animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA, 0, () -> { + // Even when queueLayout is GONE it receives touch events + // and ruins normal behavior of the app. This line fixes it + binding.itemsListPanel.setTranslationY( + -binding.itemsListPanel.getHeight() * 5); + }); + + // clear focus, otherwise a white rectangle remains on top of the player + binding.itemsListClose.clearFocus(); + binding.playPauseButton.requestFocus(); + } + } + + private OnScrollBelowItemsListener getQueueScrollListener() { + return new OnScrollBelowItemsListener() { + @Override + public void onScrolledDown(final RecyclerView recyclerView) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null && !playQueue.isComplete()) { + playQueue.fetch(); + } else if (binding != null) { + binding.itemsList.clearOnScrollListeners(); + } + } + }; + } + + private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() { + return (item, seconds) -> { + segmentAdapter.selectSegment(item); + player.seekTo(seconds * 1000L); + player.triggerProgressUpdate(); + }; + } + + private int getNearestStreamSegmentPosition(final long playbackPosition) { + //noinspection SimplifyOptionalCallChains + if (!player.getCurrentStreamInfo().isPresent()) { + return 0; + } + + int nearestPosition = 0; + final List segments + = player.getCurrentStreamInfo().get().getStreamSegments(); + + for (int i = 0; i < segments.size(); i++) { + if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { + break; + } + nearestPosition++; + } + return Math.max(0, nearestPosition - 1); + } + + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new PlayQueueItemTouchCallback() { + @Override + public void onMove(final int sourceIndex, final int targetIndex) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null) { + playQueue.move(sourceIndex, targetIndex); + } + } + + @Override + public void onSwiped(final int index) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null && index != -1) { + playQueue.remove(index); + } + } + }; + } + + private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { + return new PlayQueueItemBuilder.OnSelectedListener() { + @Override + public void selected(final PlayQueueItem item, final View view) { + player.selectQueueItem(item); + } + + @Override + public void held(final PlayQueueItem item, final View view) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + @Nullable final AppCompatActivity parentActivity = getParentActivity(); + if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) { + openPopupMenu(player.getPlayQueue(), item, view, true, + parentActivity.getSupportFragmentManager(), context); + } + } + + @Override + public void onStartDrag(final PlayQueueItemHolder viewHolder) { + if (itemTouchHelper != null) { + itemTouchHelper.startDrag(viewHolder); + } + } + }; + } + + private void updateQueueTime(final int currentTime) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final int currentStream = playQueue.getIndex(); + int before = 0; + int after = 0; + + final List streams = playQueue.getStreams(); + final int nStreams = streams.size(); + + for (int i = 0; i < nStreams; i++) { + if (i < currentStream) { + before += streams.get(i).getDuration(); + } else { + after += streams.get(i).getDuration(); + } + } + + before *= 1000; + after *= 1000; + + binding.itemsListHeaderDuration.setText( + String.format("%s/%s", + getTimeString(currentTime + before), + getTimeString(before + after) + )); + } + + @Override + protected boolean isAnyListViewOpen() { + return isQueueVisible || areSegmentsVisible; + } + + @Override + public boolean isFullscreen() { + return isFullscreen; + } + + public boolean isVerticalVideo() { + return isVerticalVideo; + } + + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Click listeners + //////////////////////////////////////////////////////////////////////////*/ + //region Click listeners + @Override + public void onClick(final View v) { + if (v.getId() == binding.screenRotationButton.getId()) { + // Only if it's not a vertical video or vertical video but in landscape with locked + // orientation a screen orientation can be changed automatically + if (!isVerticalVideo || (isLandscape() && globalScreenOrientationLocked(context))) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } else { + toggleFullscreen(); + } + } + + // call it later since it calls manageControlsAfterOnClick at the end + super.onClick(v); + } + + @Override + protected void onPlaybackSpeedClicked() { + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), + player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) + -> player.setPlaybackParameters(speed, pitch, skipSilence)) + .show(getParentActivity().getSupportFragmentManager(), null); + } + + @Override + public boolean onLongClick(final View v) { + if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onMoreOptionsLongClicked); + hideControls(0, 0); + hideSystemUIIfNeeded(); + return true; + } + return super.onLongClick(v); + } + + @Override + public boolean onKeyDown(final int keyCode) { + if (keyCode == KeyEvent.KEYCODE_SPACE && isFullscreen) { + player.playPause(); + if (player.isPlaying()) { + hideControls(0, 0); + } + return true; + } + return super.onKeyDown(keyCode); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Video size, resize, orientation, fullscreen + //////////////////////////////////////////////////////////////////////////*/ + //region Video size, resize, orientation, fullscreen + + private void setupScreenRotationButton() { + binding.screenRotationButton.setVisibility(globalScreenOrientationLocked(context) + || isVerticalVideo || DeviceUtils.isTablet(context) + ? View.VISIBLE : View.GONE); + binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context, + isFullscreen ? R.drawable.ic_fullscreen_exit + : R.drawable.ic_fullscreen)); + } + + @Override + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { + super.onVideoSizeChanged(videoSize); + isVerticalVideo = videoSize.width < videoSize.height; + + if (globalScreenOrientationLocked(context) + && isFullscreen + && isLandscape() == isVerticalVideo + && !DeviceUtils.isTv(context) + && !DeviceUtils.isTablet(context)) { + // set correct orientation + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } + + setupScreenRotationButton(); + } + + public void toggleFullscreen() { + if (DEBUG) { + Log.d(TAG, "toggleFullscreen() called"); + } + final PlayerServiceEventListener fragmentListener + = player.getFragmentListener().orElse(null); + if (fragmentListener == null || player.exoPlayerIsNull()) { + return; + } + + isFullscreen = !isFullscreen; + if (!isFullscreen) { + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait (open vertical video to reproduce) + binding.playbackControlRoot.setPadding(0, 0, 0, 0); + } else { + // Android needs tens milliseconds to send new insets but a user is able to see + // how controls changes it's position from `0` to `nav bar height` padding. + // So just hide the controls to hide this visual inconsistency + hideControls(0, 0); + } + fragmentListener.onFullscreenStateChanged(isFullscreen); + + if (isFullscreen) { + binding.titleTextView.setVisibility(View.VISIBLE); + binding.channelTextView.setVisibility(View.VISIBLE); + binding.playerCloseButton.setVisibility(View.GONE); + } else { + binding.titleTextView.setVisibility(View.GONE); + binding.channelTextView.setVisibility(View.GONE); + binding.playerCloseButton.setVisibility(View.VISIBLE); + } + setupScreenRotationButton(); + } + + public void checkLandscape() { + // check if landscape is correct + final boolean videoInLandscapeButNotInFullscreen + = isLandscape() && !isFullscreen && !player.isAudioOnly(); + final boolean notPaused = player.getCurrentState() != STATE_COMPLETED + && player.getCurrentState() != STATE_PAUSED; + + if (videoInLandscapeButNotInFullscreen + && notPaused + && !DeviceUtils.isTablet(context)) { + toggleFullscreen(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Getters + public PlayerBinding getBinding() { + return binding; + } + + public AppCompatActivity getParentActivity() { + return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); + } + + public boolean isLandscape() { + // DisplayMetrics from activity context knows about MultiWindow feature + // while DisplayMetrics from app context doesn't + return DeviceUtils.isLandscape(getParentActivity()); + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java new file mode 100644 index 00000000000..40c83c6c779 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java @@ -0,0 +1,26 @@ +package org.schabi.newpipe.player.ui; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.player.NotificationUtil; +import org.schabi.newpipe.player.Player; + +public final class NotificationPlayerUi extends PlayerUi { + boolean foregroundNotificationAlreadyCreated = false; + + public NotificationPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayer() { + super.initPlayer(); + if (!foregroundNotificationAlreadyCreated) { + NotificationUtil.getInstance() + .createNotificationAndStartForeground(player, player.getService()); + foregroundNotificationAlreadyCreated = true; + } + } + + // TODO TODO on destroy remove foreground +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java new file mode 100644 index 00000000000..fd63790d6f7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -0,0 +1,120 @@ +package org.schabi.newpipe.player.ui; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player.RepeatMode; +import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.Player; + +import java.util.List; + +public abstract class PlayerUi { + private static final String TAG = PlayerUi.class.getSimpleName(); + + @NonNull protected Context context; + @NonNull protected Player player; + + public PlayerUi(@NonNull final Player player) { + this.context = player.getContext(); + this.player = player; + } + + @NonNull + public Player getPlayer() { + return player; + } + + + public void setupAfterIntent() { + } + + public void initPlayer() { + } + + public void initPlayback() { + } + + public void destroyPlayer() { + } + + public void destroy() { + } + + public void smoothStopForImmediateReusing() { + } + + public void onFragmentListenerSet() { + } + + public void onBroadcastReceived(final Intent intent) { + } + + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + } + + public void onPrepared() { + } + + public void onBlocked() { + } + + public void onPlaying() { + } + + public void onBuffering() { + } + + public void onPaused() { + } + + public void onPausedSeek() { + } + + public void onCompleted() { + } + + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + } + + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + } + + public void onMuteUnmuteChanged(final boolean isMuted) { + } + + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + } + + public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { + } + + public void onRenderedFirstFrame() { + } + + public void onCues(@NonNull final List cues) { + } + + public void onMetadataChanged(@NonNull final StreamInfo info) { + } + + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + } + + public void onPlayQueueEdited() { + } + + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java new file mode 100644 index 00000000000..8c5c0dbfabd --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -0,0 +1,36 @@ +package org.schabi.newpipe.player.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public final class PlayerUiList { + final List playerUis = new ArrayList<>(); + + public void add(final PlayerUi playerUi) { + playerUis.add(playerUi); + } + + public void destroyAll(final Class playerUiType) { + playerUis.stream() + .filter(playerUiType::isInstance) + .forEach(playerUi -> { + playerUi.destroyPlayer(); + playerUi.destroy(); + }); + playerUis.removeIf(playerUiType::isInstance); + } + + public Optional get(final Class playerUiType) { + return playerUis.stream() + .filter(playerUiType::isInstance) + .map(playerUiType::cast) + .findFirst(); + } + + public void call(final Consumer consumer) { + //noinspection SimplifyStreamApiCallChains + playerUis.stream().forEach(consumer); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java new file mode 100644 index 00000000000..b8a26a23314 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -0,0 +1,460 @@ +package org.schabi.newpipe.player.ui; + +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AnticipateInterpolator; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.SubtitleView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.PopupPlayerGestureListener; +import org.schabi.newpipe.player.helper.PlayerHelper; + +public final class PopupPlayerUi extends VideoPlayerUi { + private static final String TAG = PopupPlayerUi.class.getSimpleName(); + + /*////////////////////////////////////////////////////////////////////////// + // Popup player + //////////////////////////////////////////////////////////////////////////*/ + + private PlayerPopupCloseOverlayBinding closeOverlayBinding; + + private boolean isPopupClosing = false; + + private int screenWidth; + private int screenHeight; + + /*////////////////////////////////////////////////////////////////////////// + // Popup player window manager + //////////////////////////////////////////////////////////////////////////*/ + + public static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + public static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + + private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup + private final WindowManager windowManager; + + public PopupPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player, playerBinding); + windowManager = ContextCompat.getSystemService(context, WindowManager.class); + } + + @Override + public void setupAfterIntent() { + setupElementsVisibility(); + binding.getRoot().setVisibility(View.VISIBLE); + initPopup(); + initPopupCloseOverlay(); + binding.playPauseButton.requestFocus(); + } + + @Override + BasePlayerGestureListener buildGestureListener() { + return new PopupPlayerGestureListener(this); + } + + @SuppressLint("RtlHardcoded") + private void initPopup() { + if (DEBUG) { + Log.d(TAG, "initPopup() called"); + } + + // Popup is already added to windowManager + if (popupHasParent()) { + return; + } + + updateScreenSize(); + + popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); + binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); + + checkPopupPositionBounds(); + + binding.loadingPanel.setMinimumWidth(popupLayoutParams.width); + binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); + + windowManager.addView(binding.getRoot(), popupLayoutParams); + + // Popup doesn't have aspectRatio selector, using FIT automatically + setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); + } + + @SuppressLint("RtlHardcoded") + private void initPopupCloseOverlay() { + if (DEBUG) { + Log.d(TAG, "initPopupCloseOverlay() called"); + } + + // closeOverlayView is already added to windowManager + if (closeOverlayBinding != null) { + return; + } + + closeOverlayBinding = PlayerPopupCloseOverlayBinding.inflate(LayoutInflater.from(context)); + + final WindowManager.LayoutParams closeOverlayLayoutParams = buildCloseOverlayLayoutParams(); + closeOverlayBinding.closeButton.setVisibility(View.GONE); + windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams); + } + + @Override + protected void setupElementsVisibility() { + binding.fullScreenButton.setVisibility(View.VISIBLE); + binding.screenRotationButton.setVisibility(View.GONE); + binding.resizeTextView.setVisibility(View.GONE); + binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); + binding.queueButton.setVisibility(View.GONE); + binding.segmentsButton.setVisibility(View.GONE); + binding.moreOptionsButton.setVisibility(View.GONE); + binding.topControls.setOrientation(LinearLayout.HORIZONTAL); + binding.primaryControls.getLayoutParams().width + = LinearLayout.LayoutParams.WRAP_CONTENT; + binding.secondaryControls.setAlpha(1.0f); + binding.secondaryControls.setVisibility(View.VISIBLE); + binding.secondaryControls.setTranslationY(0); + binding.share.setVisibility(View.GONE); + binding.playWithKodi.setVisibility(View.GONE); + binding.openInBrowser.setVisibility(View.GONE); + binding.switchMute.setVisibility(View.GONE); + binding.playerCloseButton.setVisibility(View.GONE); + binding.topControls.bringToFront(); + binding.topControls.setClickable(false); + binding.topControls.setFocusable(false); + binding.bottomControls.bringToFront(); + super.setupElementsVisibility(); + } + + @Override + protected void setupElementsSize(final Resources resources) { + setupElementsSize( + 0, + 0, + resources.getDimensionPixelSize(R.dimen.player_popup_controls_padding), + resources.getDimensionPixelSize(R.dimen.player_popup_buttons_padding) + ); + } + + @Override + public void removeViewFromParent() { + // view was added by windowManager for popup player + windowManager.removeViewImmediate(binding.getRoot()); + } + + @Override + public void destroy() { + super.destroy(); + removePopupFromView(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + updateScreenSize(); + changePopupSize(popupLayoutParams.width); + checkPopupPositionBounds(); + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Use only audio source when screen turns off while popup player is playing + if (player.isPlaying() || player.isLoading()) { + player.useVideoSource(false); + } + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Restore video source when screen turns on and user is watching video in popup player + if (player.isPlaying() || player.isLoading()) { + player.useVideoSource(true); + } + } + } + //endregion + + + /** + * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary + * that goes from (0, 0) to (screenWidth, screenHeight). + *

+ * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed + * and {@code true} is returned to represent this change. + *

+ */ + public void checkPopupPositionBounds() { + if (DEBUG) { + Log.d(TAG, "checkPopupPositionBounds() called with: " + + "screenWidth = [" + screenWidth + "], " + + "screenHeight = [" + screenHeight + "]"); + } + if (popupLayoutParams == null) { + return; + } + + if (popupLayoutParams.x < 0) { + popupLayoutParams.x = 0; + } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { + popupLayoutParams.x = screenWidth - popupLayoutParams.width; + } + + if (popupLayoutParams.y < 0) { + popupLayoutParams.y = 0; + } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { + popupLayoutParams.y = screenHeight - popupLayoutParams.height; + } + } + + public void updateScreenSize() { + final DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(metrics); + + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + if (DEBUG) { + Log.d(TAG, "updateScreenSize() called: screenWidth = [" + + screenWidth + "], screenHeight = [" + screenHeight + "]"); + } + } + + /** + * Changes the size of the popup based on the width. + * @param width the new width, height is calculated with + * {@link PlayerHelper#getMinimumVideoHeight(float)} + */ + public void changePopupSize(final int width) { + if (DEBUG) { + Log.d(TAG, "changePopupSize() called with: width = [" + width + "]"); + } + + if (anyPopupViewIsNull()) { + return; + } + + final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); + final int actualWidth = (int) (width > screenWidth ? screenWidth + : (width < minimumWidth ? minimumWidth : width)); + final int actualHeight = (int) getMinimumVideoHeight(width); + if (DEBUG) { + Log.d(TAG, "updatePopupSize() updated values:" + + " width = [" + actualWidth + "], height = [" + actualHeight + "]"); + } + + popupLayoutParams.width = actualWidth; + popupLayoutParams.height = actualHeight; + binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } + + private void changePopupWindowFlags(final int flags) { + if (DEBUG) { + Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); + } + + if (!anyPopupViewIsNull()) { + popupLayoutParams.flags = flags; + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } + } + + public void closePopup() { + if (DEBUG) { + Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); + } + if (isPopupClosing) { + return; + } + isPopupClosing = true; + + player.saveStreamProgressState(); + windowManager.removeView(binding.getRoot()); + + animatePopupOverlayAndFinishService(); + } + + public boolean isPopupClosing() { + return isPopupClosing; + } + + public void removePopupFromView() { + if (windowManager != null) { + // wrap in try-catch since it could sometimes generate errors randomly + try { + if (popupHasParent()) { + windowManager.removeView(binding.getRoot()); + } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup from window manager", e); + } + + try { + final boolean closeOverlayHasParent = closeOverlayBinding != null + && closeOverlayBinding.getRoot().getParent() != null; + if (closeOverlayHasParent) { + windowManager.removeView(closeOverlayBinding.getRoot()); + } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup overlay from window manager", e); + } + } + } + + private void animatePopupOverlayAndFinishService() { + final int targetTranslationY = + (int) (closeOverlayBinding.closeButton.getRootView().getHeight() + - closeOverlayBinding.closeButton.getY()); + + closeOverlayBinding.closeButton.animate().setListener(null).cancel(); + closeOverlayBinding.closeButton.animate() + .setInterpolator(new AnticipateInterpolator()) + .translationY(targetTranslationY) + .setDuration(400) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(final Animator animation) { + end(); + } + + @Override + public void onAnimationEnd(final Animator animation) { + end(); + } + + private void end() { + windowManager.removeView(closeOverlayBinding.getRoot()); + closeOverlayBinding = null; + player.getService().stopService(); + } + }).start(); + } + + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + // no need for the end screen thumbnail to be resized on popup player: it's only needed + // for the main player so that it is enlarged correctly inside the fragment + return bitmap.getHeight(); + } + + private boolean popupHasParent() { + return binding != null + && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams + && binding.getRoot().getParent() != null; + } + + private boolean anyPopupViewIsNull() { + return popupLayoutParams == null || windowManager == null + || binding.getRoot().getParent() == null; + } + + @Override + public void onPlaying() { + super.onPlaying(); + changePopupWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); + } + + @Override + public void onPaused() { + super.onPaused(); + changePopupWindowFlags(IDLE_WINDOW_FLAGS); + } + + @Override + public void onCompleted() { + super.onCompleted(); + changePopupWindowFlags(IDLE_WINDOW_FLAGS); + } + + @Override + protected void setupSubtitleView(final float captionScale) { + final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f; + binding.subtitleView.setFractionalTextSize( + SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); + } + + @Override + protected void onPlaybackSpeedClicked() { + playbackSpeedPopupMenu.show(); + isSomePopupMenuVisible = true; + } + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { + final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() + + closeOverlayBinding.closeButton.getWidth() / 2; + final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop() + + closeOverlayBinding.closeButton.getHeight() / 2; + + final float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); + final float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); + + return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + + Math.pow(closeOverlayButtonY - fingerY, 2)); + } + + private float getClosingRadius() { + final int buttonRadius = closeOverlayBinding.closeButton.getWidth() / 2; + // 20% wider than the button itself + return buttonRadius * 1.2f; + } + + public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) { + return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + public PlayerPopupCloseOverlayBinding getCloseOverlayBinding() { + return closeOverlayBinding; + } + + public WindowManager.LayoutParams getPopupLayoutParams() { + return popupLayoutParams; + } + + public WindowManager getWindowManager() { + return windowManager; + } + + public int getScreenHeight() { + return screenHeight; + } + + public int getScreenWidth() { + return screenWidth; + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java new file mode 100644 index 00000000000..99ecb5540a1 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -0,0 +1,1523 @@ +package org.schabi.newpipe.player.ui; + +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; +import static org.schabi.newpipe.player.Player.RENDERER_UNAVAILABLE; +import static org.schabi.newpipe.player.Player.STATE_BUFFERING; +import static org.schabi.newpipe.player.Player.STATE_COMPLETED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED_SEEK; +import static org.schabi.newpipe.player.Player.STATE_PLAYING; +import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; +import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; +import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; + +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.util.Log; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Surface; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player.RepeatMode; +import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.CaptionStyleCompat; +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.fragments.detail.VideoDetailFragment; +import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.DisplayPortion; +import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.playback.SurfaceHolderCallback; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; +import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; +import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.external_communication.KoreUtils; +import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public abstract class VideoPlayerUi extends PlayerUi + implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { + private static final String TAG = VideoPlayerUi.class.getSimpleName(); + + // time constants + public static final long DEFAULT_CONTROLS_DURATION = 300; // 300 millis + public static final long DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds + public static final long DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds + public static final int SEEK_OVERLAY_DURATION = 450; // 450 millis + + // other constants (TODO remove playback speeds and use normal menu for popup, too) + private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f}; + + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + + protected PlayerBinding binding; + private final Handler controlsVisibilityHandler = new Handler(); + @Nullable private SurfaceHolderCallback surfaceHolderCallback; + @Nullable private Bitmap thumbnail = null; + + + /*////////////////////////////////////////////////////////////////////////// + // Popup menus ("popup" means that they pop up, not that they belong to the popup player) + //////////////////////////////////////////////////////////////////////////*/ + + private static final int POPUP_MENU_ID_QUALITY = 69; + private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79; + private static final int POPUP_MENU_ID_CAPTION = 89; + + protected boolean isSomePopupMenuVisible = false; + private PopupMenu qualityPopupMenu; + protected PopupMenu playbackSpeedPopupMenu; + private PopupMenu captionPopupMenu; + + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + + private GestureDetector gestureDetector; + private BasePlayerGestureListener playerGestureListener; + + @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = + new SeekbarPreviewThumbnailHolder(); + + public VideoPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player); + binding = playerBinding; + } + + + /*////////////////////////////////////////////////////////////////////////// + // Setup + //////////////////////////////////////////////////////////////////////////*/ + //region Setup + public void setupFromView() { + initViews(); + initListeners(); + setupPlayerSeekOverlay(); + } + + private void initViews() { + setupSubtitleView(); + + binding.resizeTextView + .setText(PlayerHelper.resizeTypeOf(context, binding.surfaceView.getResizeMode())); + + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + binding.playbackSeekBar.getProgressDrawable() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); + + final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, + R.style.DarkPopupMenu); + + qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); + playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); + captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); + + binding.progressBarLoadingPanel.getIndeterminateDrawable() + .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); + + binding.titleTextView.setSelected(true); + binding.channelTextView.setSelected(true); + + // Prevent hiding of bottom sheet via swipe inside queue + binding.itemsList.setNestedScrollingEnabled(false); + } + + abstract BasePlayerGestureListener buildGestureListener(); + + protected void initListeners() { + binding.qualityTextView.setOnClickListener(this); + binding.playbackSpeed.setOnClickListener(this); + + binding.playbackSeekBar.setOnSeekBarChangeListener(this); + binding.captionTextView.setOnClickListener(this); + binding.resizeTextView.setOnClickListener(this); + binding.playbackLiveSync.setOnClickListener(this); + + playerGestureListener = buildGestureListener(); + gestureDetector = new GestureDetector(context, playerGestureListener); + binding.getRoot().setOnTouchListener(playerGestureListener); + + binding.repeatButton.setOnClickListener(v -> onRepeatClicked()); + binding.shuffleButton.setOnClickListener(v -> onShuffleClicked()); + + binding.playPauseButton.setOnClickListener(this); + binding.playPreviousButton.setOnClickListener(this); + binding.playNextButton.setOnClickListener(this); + + binding.moreOptionsButton.setOnClickListener(this); + binding.moreOptionsButton.setOnLongClickListener(this); + binding.share.setOnClickListener(this); + binding.share.setOnLongClickListener(this); + binding.fullScreenButton.setOnClickListener(this); + binding.screenRotationButton.setOnClickListener(this); + binding.playWithKodi.setOnClickListener(this); + binding.openInBrowser.setOnClickListener(this); + binding.playerCloseButton.setOnClickListener(this); + binding.switchMute.setOnClickListener(this); + + ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> { + final Insets cutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()); + if (!cutout.equals(Insets.NONE)) { + view.setPadding(cutout.left, cutout.top, cutout.right, cutout.bottom); + } + return windowInsets; + }); + + // PlaybackControlRoot already consumed window insets but we should pass them to + // player_overlays and fast_seek_overlay too. Without it they will be off-centered. + binding.playbackControlRoot.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + binding.playerOverlays.setPadding( + v.getPaddingLeft(), + v.getPaddingTop(), + v.getPaddingRight(), + v.getPaddingBottom()); + + // If we added padding to the fast seek overlay, too, it would not go under the + // system ui. Instead we apply negative margins equal to the window insets of + // the opposite side, so that the view covers all of the player (overflowing on + // some sides) and its center coincides with the center of other controls. + final RelativeLayout.LayoutParams fastSeekParams = (RelativeLayout.LayoutParams) + binding.fastSeekOverlay.getLayoutParams(); + fastSeekParams.leftMargin = -v.getPaddingRight(); + fastSeekParams.topMargin = -v.getPaddingBottom(); + fastSeekParams.rightMargin = -v.getPaddingLeft(); + fastSeekParams.bottomMargin = -v.getPaddingTop(); + }); + } + + /** + * Initializes the Fast-For/Backward overlay. + */ + private void setupPlayerSeekOverlay() { + binding.fastSeekOverlay + .seekSecondsSupplier(() -> retrieveSeekDurationFromPreferences(player) / 1000) + .performListener(new PlayerFastSeekOverlay.PerformListener() { + + @Override + public void onDoubleTap() { + animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION); + } + + @Override + public void onDoubleTapEnd() { + animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION); + } + + @NonNull + @Override + public FastSeekDirection getFastSeekDirection( + @NonNull final DisplayPortion portion + ) { + if (player.exoPlayerIsNull()) { + // Abort seeking + playerGestureListener.endMultiDoubleTap(); + return FastSeekDirection.NONE; + } + if (portion == DisplayPortion.LEFT) { + // Check if it's possible to rewind + // Small puffer to eliminate infinite rewind seeking + if (player.getExoPlayer().getCurrentPosition() < 500L) { + return FastSeekDirection.NONE; + } + return FastSeekDirection.BACKWARD; + } else if (portion == DisplayPortion.RIGHT) { + // Check if it's possible to fast-forward + if (player.getCurrentState() == STATE_COMPLETED + || player.getExoPlayer().getCurrentPosition() + >= player.getExoPlayer().getDuration()) { + return FastSeekDirection.NONE; + } + return FastSeekDirection.FORWARD; + } + /* portion == DisplayPortion.MIDDLE */ + return FastSeekDirection.NONE; + } + + @Override + public void seek(final boolean forward) { + playerGestureListener.keepInDoubleTapMode(); + if (forward) { + player.fastForward(); + } else { + player.fastRewind(); + } + } + }); + playerGestureListener.doubleTapControls(binding.fastSeekOverlay); + } + + @Override + public void setupAfterIntent() { + super.setupAfterIntent(); + setupElementsVisibility(); + setupElementsSize(context.getResources()); + } + + @Override + public void initPlayer() { + super.initPlayer(); + setupVideoSurface(); + setupFromView(); + } + + @Override + public void initPlayback() { + super.initPlayback(); + + // #6825 - Ensure that the shuffle-button is in the correct state on the UI + setShuffleButton(player.getExoPlayer().getShuffleModeEnabled()); + } + + public abstract void removeViewFromParent(); + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + cleanupVideoSurface(); + } + + @Override + public void destroy() { + super.destroy(); + if (binding != null) { + binding.endScreen.setImageBitmap(null); + } + } + + protected void setupElementsVisibility() { + setMuteButton(player.isMuted()); + animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0); + } + + protected abstract void setupElementsSize(Resources resources); + + protected void setupElementsSize(final int buttonsMinWidth, + final int playerTopPad, + final int controlsPad, + final int buttonsPad) { + binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); + binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); + binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); + binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + // When the orientation changed, the screen height might be smaller. + // If the end screen thumbnail is not re-scaled, + // it can be larger than the current screen height + // and thus enlarging the whole player. + // This causes the seekbar to be ouf the visible area. + updateEndScreenThumbnail(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Thumbnail + //////////////////////////////////////////////////////////////////////////*/ + //region Thumbnail + /** + * Scale the player audio / end screen thumbnail down if necessary. + *

+ * This is necessary when the thumbnail's height is larger than the device's height + * and thus is enlarging the player's height + * causing the bottom playback controls to be out of the visible screen. + *

+ */ + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + thumbnail = bitmap; + updateEndScreenThumbnail(); + } + + private void updateEndScreenThumbnail() { + if (thumbnail == null) { + // remove end screen thumbnail + binding.endScreen.setImageDrawable(null); + return; + } + + final float endScreenHeight = calculateMaxEndScreenThumbnailHeight(thumbnail); + final Bitmap endScreenBitmap = Bitmap.createScaledBitmap( + thumbnail, + (int) (thumbnail.getWidth() / (thumbnail.getHeight() / endScreenHeight)), + (int) endScreenHeight, + true); + + if (DEBUG) { + Log.d(TAG, "Thumbnail - onThumbnailLoaded() called with: " + + "currentThumbnail = [" + thumbnail + "], " + + thumbnail.getWidth() + "x" + thumbnail.getHeight() + + ", scaled end screen height = " + endScreenHeight + + ", scaled end screen width = " + endScreenBitmap.getWidth()); + } + + binding.endScreen.setImageBitmap(endScreenBitmap); + } + + protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap); + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Progress loop and updates + //////////////////////////////////////////////////////////////////////////*/ + //region Progress loop and updates + @Override + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + + if (duration != binding.playbackSeekBar.getMax()) { + setVideoDurationToControls(duration); + } + if (player.getCurrentState() != STATE_PAUSED) { + updatePlayBackElementsCurrentDuration(currentProgress); + } + if (player.isLoading() || bufferPercent > 90) { + binding.playbackSeekBar.setSecondaryProgress( + (int) (binding.playbackSeekBar.getMax() * ((float) bufferPercent / 100))); + } + if (DEBUG && bufferPercent % 20 == 0) { //Limit log + Log.d(TAG, "notifyProgressUpdateToListeners() called with: " + + "isVisible = " + isControlsVisible() + ", " + + "currentProgress = [" + currentProgress + "], " + + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); + } + binding.playbackLiveSync.setClickable(!player.isLiveEdge()); + } + + /** + * Sets the current duration into the corresponding elements. + */ + private void updatePlayBackElementsCurrentDuration(final int currentProgress) { + // Don't set seekbar progress while user is seeking + if (player.getCurrentState() != STATE_PAUSED_SEEK) { + binding.playbackSeekBar.setProgress(currentProgress); + } + binding.playbackCurrentTime.setText(getTimeString(currentProgress)); + } + + /** + * Sets the video duration time into all control components (e.g. seekbar). + */ + private void setVideoDurationToControls(final int duration) { + binding.playbackEndTime.setText(getTimeString(duration)); + + binding.playbackSeekBar.setMax(duration); + // This is important for Android TVs otherwise it would apply the default from + // setMax/Min methods which is (max - min) / 20 + binding.playbackSeekBar.setKeyProgressIncrement( + PlayerHelper.retrieveSeekDurationFromPreferences(player)); + } + + @Override // seekbar listener + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { + // Currently we don't need method execution when fromUser is false + if (!fromUser) { + return; + } + if (DEBUG) { + Log.d(TAG, "onProgressChanged() called with: " + + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); + } + + binding.currentDisplaySeek.setText(getTimeString(progress)); + + // Seekbar Preview Thumbnail + SeekbarPreviewThumbnailHelper + .tryResizeAndSetSeekbarPreviewThumbnail( + player.getContext(), + seekbarPreviewThumbnailHolder.getBitmapAt(progress), + binding.currentSeekbarPreviewThumbnail, + binding.subtitleView::getWidth); + + adjustSeekbarPreviewContainer(); + } + + + private void adjustSeekbarPreviewContainer() { + try { + // Should only be required when an error occurred before + // and the layout was positioned in the center + binding.bottomSeekbarPreviewLayout.setGravity(Gravity.NO_GRAVITY); + + // Calculate the current left position of seekbar progress in px + // More info: https://stackoverflow.com/q/20493577 + final int currentSeekbarLeft = + binding.playbackSeekBar.getLeft() + + binding.playbackSeekBar.getPaddingLeft() + + binding.playbackSeekBar.getThumb().getBounds().left; + + // Calculate the (unchecked) left position of the container + final int uncheckedContainerLeft = + currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); + + // Fix the position so it's within the boundaries + final int checkedContainerLeft = + Math.max( + Math.min( + uncheckedContainerLeft, + // Max left + binding.playbackWindowRoot.getWidth() + - binding.seekbarPreviewContainer.getWidth() + ), + 0 // Min left + ); + + // See also: https://stackoverflow.com/a/23249734 + final LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams( + binding.seekbarPreviewContainer.getLayoutParams()); + params.setMarginStart(checkedContainerLeft); + binding.seekbarPreviewContainer.setLayoutParams(params); + } catch (final Exception ex) { + Log.e(TAG, "Failed to adjust seekbarPreviewContainer", ex); + // Fallback - position in the middle + binding.bottomSeekbarPreviewLayout.setGravity(Gravity.CENTER); + } + } + + @Override // seekbar listener + public void onStartTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } + if (player.getCurrentState() != STATE_PAUSED_SEEK) { + player.changeState(STATE_PAUSED_SEEK); + } + + player.saveWasPlaying(); + if (player.isPlaying()) { + player.getExoPlayer().pause(); + } + + showControls(0); + animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SCALE_AND_ALPHA); + animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SCALE_AND_ALPHA); + } + + @Override // seekbar listener + public void onStopTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } + + player.seekTo(seekBar.getProgress()); + if (player.wasPlaying() || player.getExoPlayer().getDuration() == seekBar.getProgress()) { + player.getExoPlayer().play(); + } + + binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA); + + if (player.getCurrentState() == STATE_PAUSED_SEEK) { + player.changeState(STATE_BUFFERING); + } + if (!player.isProgressLoopRunning()) { + player.startProgressLoop(); + } + if (player.wasPlaying()) { + showControlsThenHide(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Controls showing / hiding + //////////////////////////////////////////////////////////////////////////*/ + //region Controls showing / hiding + + public boolean isControlsVisible() { + return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE; + } + + public void showControlsThenHide() { + if (DEBUG) { + Log.d(TAG, "showControlsThenHide() called"); + } + + showOrHideButtons(); + showSystemUIPartially(); + + final long hideTime = binding.playbackControlRoot.isInTouchMode() + ? DEFAULT_CONTROLS_HIDE_TIME + : DPAD_CONTROLS_HIDE_TIME; + + showHideShadow(true, DEFAULT_CONTROLS_DURATION); + animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, + AnimationType.ALPHA, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); + } + + public void showControls(final long duration) { + if (DEBUG) { + Log.d(TAG, "showControls() called"); + } + showOrHideButtons(); + showSystemUIPartially(); + controlsVisibilityHandler.removeCallbacksAndMessages(null); + showHideShadow(true, duration); + animate(binding.playbackControlRoot, true, duration); + } + + public void hideControls(final long duration, final long delay) { + if (DEBUG) { + Log.d(TAG, "hideControls() called with: duration = [" + duration + + "], delay = [" + delay + "]"); + } + + showOrHideButtons(); + + controlsVisibilityHandler.removeCallbacksAndMessages(null); + controlsVisibilityHandler.postDelayed(() -> { + showHideShadow(false, duration); + animate(binding.playbackControlRoot, false, duration, AnimationType.ALPHA, + 0, this::hideSystemUIIfNeeded); + }, delay); + } + + public void showHideShadow(final boolean show, final long duration) { + animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null); + animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null); + animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null); + } + + protected void showOrHideButtons() { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final boolean showPrev = playQueue.getIndex() != 0; + final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); + + binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); + binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); + binding.playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE); + binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); + } + + protected void showSystemUIPartially() { + // system UI is really changed only by MainPlayerUi, so overridden there + } + + protected void hideSystemUIIfNeeded() { + // system UI is really changed only by MainPlayerUi, so overridden there + } + + protected boolean isAnyListViewOpen() { + // only MainPlayerUi has list views for the queue and for segments, so overridden there + return false; + } + + public boolean isFullscreen() { + // only MainPlayerUi can be in fullscreen, so overridden there + return false; + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states + @Override + public void onPrepared() { + super.onPrepared(); + setVideoDurationToControls((int) player.getExoPlayer().getDuration()); + binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + } + + @Override + public void onBlocked() { + super.onBlocked(); + + // if we are e.g. switching players, hide controls + hideControls(DEFAULT_CONTROLS_DURATION, 0); + + binding.playbackSeekBar.setEnabled(false); + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + + binding.loadingPanel.setBackgroundColor(Color.BLACK); + animate(binding.loadingPanel, true, 0); + animate(binding.surfaceForeground, true, 100); + + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); + animatePlayButtons(false, 100); + binding.getRoot().setKeepScreenOn(false); + } + + @Override + public void onPlaying() { + super.onPlaying(); + + updateStreamRelatedViews(); + + binding.playbackSeekBar.setEnabled(true); + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + + binding.loadingPanel.setVisibility(View.GONE); + + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + + animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_pause); + animatePlayButtons(true, 200); + if (!isAnyListViewOpen()) { + binding.playPauseButton.requestFocus(); + } + }); + + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onBuffering() { + super.onBuffering(); + binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT); + binding.loadingPanel.setVisibility(View.VISIBLE); + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onPaused() { + super.onPaused(); + + // Don't let UI elements popup during double tap seeking. This state is entered sometimes + // during seeking/loading. This if-else check ensures that the controls aren't popping up. + if (!playerGestureListener.isDoubleTapping()) { + showControls(400); + binding.loadingPanel.setVisibility(View.GONE); + + animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); + animatePlayButtons(true, 200); + if (!isAnyListViewOpen()) { + binding.playPauseButton.requestFocus(); + } + }); + } + + binding.getRoot().setKeepScreenOn(false); + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + animatePlayButtons(false, 100); + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onCompleted() { + super.onCompleted(); + + animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_replay); + animatePlayButtons(true, DEFAULT_CONTROLS_DURATION); + }); + + binding.getRoot().setKeepScreenOn(false); + + // When a (short) video ends the elements have to display the correct values - see #6180 + updatePlayBackElementsCurrentDuration(binding.playbackSeekBar.getMax()); + + showControls(500); + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + binding.loadingPanel.setVisibility(View.GONE); + animate(binding.surfaceForeground, true, 100); + } + + private void animatePlayButtons(final boolean show, final long duration) { + animate(binding.playPauseButton, show, duration, AnimationType.SCALE_AND_ALPHA); + + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + if (!show || playQueue.getIndex() > 0) { + animate( + binding.playPreviousButton, + show, + duration, + AnimationType.SCALE_AND_ALPHA); + } + if (!show || playQueue.getIndex() + 1 < playQueue.getStreams().size()) { + animate( + binding.playNextButton, + show, + duration, + AnimationType.SCALE_AND_ALPHA); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Repeat, shuffle, mute + //////////////////////////////////////////////////////////////////////////*/ + //region Repeat and shuffle + public void onRepeatClicked() { + if (DEBUG) { + Log.d(TAG, "onRepeatClicked() called"); + } + player.cycleNextRepeatMode(); + } + + public void onShuffleClicked() { + if (DEBUG) { + Log.d(TAG, "onShuffleClicked() called"); + } + player.toggleShuffleModeEnabled(); + } + + @Override + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + super.onRepeatModeChanged(repeatMode); + setRepeatModeButton(binding.repeatButton, repeatMode); + } + + @Override + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + super.onShuffleModeEnabledChanged(shuffleModeEnabled); + setShuffleButton(shuffleModeEnabled); + } + + @Override + public void onMuteUnmuteChanged(final boolean isMuted) { + super.onMuteUnmuteChanged(isMuted); + setMuteButton(isMuted); + } + + private void setRepeatModeButton(final AppCompatImageButton imageButton, + @RepeatMode final int repeatMode) { + switch (repeatMode) { + case REPEAT_MODE_OFF: + imageButton.setImageResource(R.drawable.exo_controls_repeat_off); + break; + case REPEAT_MODE_ONE: + imageButton.setImageResource(R.drawable.exo_controls_repeat_one); + break; + case REPEAT_MODE_ALL: + imageButton.setImageResource(R.drawable.exo_controls_repeat_all); + break; + } + } + + private void setMuteButton(final boolean isMuted) { + binding.switchMute.setImageDrawable(AppCompatResources.getDrawable(context, isMuted + ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); + } + + private void setShuffleButton(final boolean shuffled) { + binding.shuffleButton.setImageAlpha(shuffled ? 255 : 77); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer listeners (that didn't fit in other categories) + //////////////////////////////////////////////////////////////////////////*/ + //region ExoPlayer listeners (that didn't fit in other categories) + @Override + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + super.onTextTracksChanged(currentTracks); + + final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) + || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); + if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null + || !trackTypeTextSupported) { + binding.captionTextView.setVisibility(View.GONE); + return; + } + + // Extract all loaded languages + final List textTracks = currentTracks + .getGroups() + .stream() + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) + .collect(Collectors.toList()); + final List availableLanguages = textTracks.stream() + .map(Tracks.Group::getMediaTrackGroup) + .filter(textTrack -> textTrack.length > 0) + .map(textTrack -> textTrack.getFormat(0).language) + .collect(Collectors.toList()); + + // Find selected text track + final Optional selectedTracks = textTracks.stream() + .filter(Tracks.Group::isSelected) + .filter(info -> info.getMediaTrackGroup().length >= 1) + .map(info -> info.getMediaTrackGroup().getFormat(0)) + .findFirst(); + + // Build UI + buildCaptionMenu(availableLanguages); + //noinspection SimplifyOptionalCallChains + if (player.getTrackSelector().getParameters().getRendererDisabled( + player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { + binding.captionTextView.setText(R.string.caption_none); + } else { + binding.captionTextView.setText(selectedTracks.get().language); + } + binding.captionTextView.setVisibility( + availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); + } + + @Override + public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { + super.onPlaybackParametersChanged(playbackParameters); + binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed)); + } + + @Override + public void onRenderedFirstFrame() { + super.onRenderedFirstFrame(); + //TODO check if this causes black screen when switching to fullscreen + animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); + } + + @Override + public void onCues(@NonNull List cues) { + super.onCues(cues); + binding.subtitleView.setCues(cues); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Metadata & stream related views + //////////////////////////////////////////////////////////////////////////*/ + //region Metadata & stream related views + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + + updateStreamRelatedViews(); + + binding.titleTextView.setText(info.getName()); + binding.channelTextView.setText(info.getUploaderName()); + + this.seekbarPreviewThumbnailHolder.resetFrom(player.getContext(), info.getPreviewFrames()); + } + + private void updateStreamRelatedViews() { + //noinspection SimplifyOptionalCallChains + if (!player.getCurrentStreamInfo().isPresent()) { + return; + } + final StreamInfo info = player.getCurrentStreamInfo().get(); + + binding.qualityTextView.setVisibility(View.GONE); + binding.playbackSpeed.setVisibility(View.GONE); + + binding.playbackEndTime.setVisibility(View.GONE); + binding.playbackLiveSync.setVisibility(View.GONE); + + switch (info.getStreamType()) { + case AUDIO_STREAM: + case POST_LIVE_AUDIO_STREAM: + binding.surfaceView.setVisibility(View.GONE); + binding.endScreen.setVisibility(View.VISIBLE); + binding.playbackEndTime.setVisibility(View.VISIBLE); + break; + + case AUDIO_LIVE_STREAM: + binding.surfaceView.setVisibility(View.GONE); + binding.endScreen.setVisibility(View.VISIBLE); + binding.playbackLiveSync.setVisibility(View.VISIBLE); + break; + + case LIVE_STREAM: + binding.surfaceView.setVisibility(View.VISIBLE); + binding.endScreen.setVisibility(View.GONE); + binding.playbackLiveSync.setVisibility(View.VISIBLE); + break; + + case VIDEO_STREAM: + case POST_LIVE_STREAM: + //noinspection SimplifyOptionalCallChains + if (player.getCurrentMetadata() != null + && !player.getCurrentMetadata().getMaybeQuality().isPresent() + || (info.getVideoStreams().isEmpty() + && info.getVideoOnlyStreams().isEmpty())) { + break; + } + + buildQualityMenu(); + + binding.qualityTextView.setVisibility(View.VISIBLE); + binding.surfaceView.setVisibility(View.VISIBLE); + default: + binding.endScreen.setVisibility(View.GONE); + binding.playbackEndTime.setVisibility(View.VISIBLE); + break; + } + + buildPlaybackSpeedMenu(); + binding.playbackSpeed.setVisibility(View.VISIBLE); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Popup menus ("popup" means that they pop up, not that they belong to the popup player) + //////////////////////////////////////////////////////////////////////////*/ + //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) + private void buildQualityMenu() { + if (qualityPopupMenu == null) { + return; + } + qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); + + @Nullable final List availableStreams + = Optional.ofNullable(player.getCurrentMetadata()) + .flatMap(MediaItemTag::getMaybeQuality) + .map(MediaItemTag.Quality::getSortedVideoStreams) + .orElse(null); + if (availableStreams == null) { + return; + } + + for (int i = 0; i < availableStreams.size(); i++) { + final VideoStream videoStream = availableStreams.get(i); + qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat + .getNameById(videoStream.getFormatId()) + " " + videoStream.getResolution()); + } + final VideoStream selectedVideoStream = player.getSelectedVideoStream(); + if (selectedVideoStream != null) { + binding.qualityTextView.setText(selectedVideoStream.getResolution()); + } + qualityPopupMenu.setOnMenuItemClickListener(this); + qualityPopupMenu.setOnDismissListener(this); + } + + private void buildPlaybackSpeedMenu() { + if (playbackSpeedPopupMenu == null) { + return; + } + playbackSpeedPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_PLAYBACK_SPEED); + + for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { + playbackSpeedPopupMenu.getMenu().add(POPUP_MENU_ID_PLAYBACK_SPEED, i, Menu.NONE, + formatSpeed(PLAYBACK_SPEEDS[i])); + } + binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + playbackSpeedPopupMenu.setOnMenuItemClickListener(this); + playbackSpeedPopupMenu.setOnDismissListener(this); + } + + private void buildCaptionMenu(@NonNull final List availableLanguages) { + if (captionPopupMenu == null) { + return; + } + captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); + + captionPopupMenu.setOnDismissListener(this); + + // Add option for turning off caption + final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, + 0, Menu.NONE, R.string.caption_none); + captionOffItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex != RENDERER_UNAVAILABLE) { + player.getTrackSelector().setParameters(player.getTrackSelector() + .buildUponParameters().setRendererDisabled(textRendererIndex, true)); + } + player.getPrefs().edit() + .remove(context.getString(R.string.caption_user_set_key)).apply(); + return true; + }); + + // Add all available captions + for (int i = 0; i < availableLanguages.size(); i++) { + final String captionLanguage = availableLanguages.get(i); + final MenuItem captionItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, + i + 1, Menu.NONE, captionLanguage); + captionItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex != RENDERER_UNAVAILABLE) { + // DefaultTrackSelector will select for text tracks in the following order. + // When multiple tracks share the same rank, a random track will be chosen. + // 1. ANY track exactly matching preferred language name + // 2. ANY track exactly matching preferred language stem + // 3. ROLE_FLAG_CAPTION track matching preferred language stem + // 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem + // This means if a caption track of preferred language is not available, + // then an auto-generated track of that language will be chosen automatically. + player.getTrackSelector().setParameters(player.getTrackSelector() + .buildUponParameters() + .setPreferredTextLanguages(captionLanguage, + PlayerHelper.captionLanguageStemOf(captionLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + .setRendererDisabled(textRendererIndex, false)); + player.getPrefs().edit().putString(context.getString( + R.string.caption_user_set_key), captionLanguage).apply(); + } + return true; + }); + } + captionPopupMenu.setOnDismissListener(this); + + // apply caption language from previous user preference + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex == RENDERER_UNAVAILABLE) { + return; + } + + // If user prefers to show no caption, then disable the renderer. + // Otherwise, DefaultTrackSelector may automatically find an available caption + // and display that. + final String userPreferredLanguage = + player.getPrefs().getString(context.getString(R.string.caption_user_set_key), null); + if (userPreferredLanguage == null) { + player.getTrackSelector().setParameters(player.getTrackSelector().buildUponParameters() + .setRendererDisabled(textRendererIndex, true)); + return; + } + + // Only set preferred language if it does not match the user preference, + // otherwise there might be an infinite cycle at onTextTracksChanged. + final List selectedPreferredLanguages = + player.getTrackSelector().getParameters().preferredTextLanguages; + if (!selectedPreferredLanguages.contains(userPreferredLanguage)) { + player.getTrackSelector().setParameters(player.getTrackSelector().buildUponParameters() + .setPreferredTextLanguages(userPreferredLanguage, + PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + .setRendererDisabled(textRendererIndex, false)); + } + } + + protected abstract void onPlaybackSpeedClicked(); + + private void onQualityClicked() { + qualityPopupMenu.show(); + isSomePopupMenuVisible = true; + + final VideoStream videoStream = player.getSelectedVideoStream(); + if (videoStream != null) { + //noinspection SetTextI18n + binding.qualityTextView.setText(MediaFormat.getNameById(videoStream.getFormatId()) + + " " + videoStream.getResolution()); + } + + player.saveWasPlaying(); + } + + /** + * Called when an item of the quality selector or the playback speed selector is selected. + */ + @Override + public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { + if (DEBUG) { + Log.d(TAG, "onMenuItemClick() called with: " + + "menuItem = [" + menuItem + "], " + + "menuItem.getItemId = [" + menuItem.getItemId() + "]"); + } + + if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { + final int menuItemIndex = menuItem.getItemId(); + @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); + //noinspection SimplifyOptionalCallChains + if (currentMetadata == null || !currentMetadata.getMaybeQuality().isPresent()) { + return true; + } + + final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get(); + final List availableStreams = quality.getSortedVideoStreams(); + final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); + if (selectedStreamIndex == menuItemIndex|| availableStreams.size() <= menuItemIndex) { + return true; + } + + player.saveStreamProgressState(); //TODO added, check if good + final String newResolution = availableStreams.get(menuItemIndex).getResolution(); + player.setRecovery(); + player.setPlaybackQuality(newResolution); + player.reloadPlayQueueManager(); + + binding.qualityTextView.setText(menuItem.getTitle()); + return true; + } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { + final int speedIndex = menuItem.getItemId(); + final float speed = PLAYBACK_SPEEDS[speedIndex]; + + player.setPlaybackSpeed(speed); + binding.playbackSpeed.setText(formatSpeed(speed)); + } + + return false; + } + + /** + * Called when some popup menu is dismissed. + */ + @Override + public void onDismiss(@Nullable final PopupMenu menu) { + if (DEBUG) { + Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); + } + isSomePopupMenuVisible = false; //TODO check if this works + final VideoStream selectedVideoStream = player.getSelectedVideoStream(); + if (selectedVideoStream != null) { + binding.qualityTextView.setText(selectedVideoStream.getResolution()); + } + if (player.isPlaying()) { + hideControls(DEFAULT_CONTROLS_DURATION, 0); + hideSystemUIIfNeeded(); + } + } + + private void onCaptionClicked() { + if (DEBUG) { + Log.d(TAG, "onCaptionClicked() called"); + } + captionPopupMenu.show(); + isSomePopupMenuVisible = true; + } + + public boolean isSomePopupMenuVisible() { + return isSomePopupMenuVisible; + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Captions (text tracks) + //////////////////////////////////////////////////////////////////////////*/ + //region Captions (text tracks) + private void setupSubtitleView() { + setupSubtitleView(PlayerHelper.getCaptionScale(context)); + final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); + binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT); + binding.subtitleView.setStyle(captionStyle); + } + + protected abstract void setupSubtitleView(final float captionScale); + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Click listeners + //////////////////////////////////////////////////////////////////////////*/ + //region Click listeners + @Override + public void onClick(final View v) { + if (DEBUG) { + Log.d(TAG, "onClick() called with: v = [" + v + "]"); + } + if (v.getId() == binding.resizeTextView.getId()) { + onResizeClicked(); + } else if (v.getId() == binding.captionTextView.getId()) { + onCaptionClicked(); + } else if (v.getId() == binding.playbackLiveSync.getId()) { + player.seekToDefault(); + } else if (v.getId() == binding.playPauseButton.getId()) { + player.playPause(); + } else if (v.getId() == binding.playPreviousButton.getId()) { + player.playPrevious(); + } else if (v.getId() == binding.playNextButton.getId()) { + player.playNext(); + } else if (v.getId() == binding.moreOptionsButton.getId()) { + onMoreOptionsClicked(); + } else if (v.getId() == binding.share.getId()) { + final PlayQueueItem currentItem = player.getCurrentItem(); + if (currentItem != null) { + ShareUtils.shareText(context, currentItem.getTitle(), + player.getVideoUrlAtCurrentTime(), currentItem.getThumbnailUrl()); + } + } else if (v.getId() == binding.playWithKodi.getId()) { + onPlayWithKodiClicked(); + } else if (v.getId() == binding.openInBrowser.getId()) { + onOpenInBrowserClicked(); + } else if (v.getId() == binding.fullScreenButton.getId()) { + player.setRecovery(); + NavigationHelper.playOnMainPlayer(context, player.getPlayQueue(), true); + return; + } else if (v.getId() == binding.switchMute.getId()) { + player.toggleMute(); + } else if (v.getId() == binding.playerCloseButton.getId()) { + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); + } else if (v.getId() == binding.playbackSpeed.getId()) { + onPlaybackSpeedClicked(); + } else if (v.getId() == binding.qualityTextView.getId()) { + onQualityClicked(); + } + + manageControlsAfterOnClick(v); + } + + /** + * Manages the controls after a click occurred on the player UI. + * @param v – The view that was clicked + */ + public void manageControlsAfterOnClick(@NonNull final View v) { + if (player.getCurrentState() == STATE_COMPLETED) { + return; + } + + controlsVisibilityHandler.removeCallbacksAndMessages(null); + showHideShadow(true, DEFAULT_CONTROLS_DURATION); + animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, + AnimationType.ALPHA, 0, () -> { + if (player.getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible) { + if (v.getId() == binding.playPauseButton.getId() + // Hide controls in fullscreen immediately + || (v.getId() == binding.screenRotationButton.getId() + && isFullscreen())) { + hideControls(0, 0); + } else { + hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); + } + } + }); + } + + @Override + public boolean onLongClick(final View v) { + if (v.getId() == binding.share.getId()) { + ShareUtils.copyToClipboard(context, player.getVideoUrlAtCurrentTime()); + } + return true; + } + + public boolean onKeyDown(final int keyCode) { + switch (keyCode) { + default: + break; + case KeyEvent.KEYCODE_BACK: + if (DeviceUtils.isTv(context) && isControlsVisible()) { + hideControls(0, 0); + return true; + } + break; + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) + || isAnyListViewOpen()) { + // do not interfere with focus in playlist and play queue etc. + return false; + } + + if (player.getCurrentState() == org.schabi.newpipe.player.Player.STATE_BLOCKED) { + return true; + } + + if (isControlsVisible()) { + hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME); + } else { + binding.playPauseButton.requestFocus(); + showControlsThenHide(); + showSystemUIPartially(); + return true; + } + break; + } + + return false; + } + + private void onMoreOptionsClicked() { + if (DEBUG) { + Log.d(TAG, "onMoreOptionsClicked() called"); + } + + final boolean isMoreControlsVisible = + binding.secondaryControls.getVisibility() == View.VISIBLE; + + animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, + isMoreControlsVisible ? 0 : 180); + animate(binding.secondaryControls, !isMoreControlsVisible, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA, 0, () -> { + // Fix for a ripple effect on background drawable. + // When view returns from GONE state it takes more milliseconds than returning + // from INVISIBLE state. And the delay makes ripple background end to fast + if (isMoreControlsVisible) { + binding.secondaryControls.setVisibility(View.INVISIBLE); + } + }); + showControls(DEFAULT_CONTROLS_DURATION); + } + + private void onPlayWithKodiClicked() { + if (player.getCurrentMetadata() != null) { + player.pause(); + try { + NavigationHelper.playWithKore(context, Uri.parse(player.getVideoUrl())); + } catch (final Exception e) { + if (DEBUG) { + Log.i(TAG, "Failed to start kore", e); + } + KoreUtils.showInstallKoreDialog(player.getContext()); + } + } + } + + private void onOpenInBrowserClicked() { + player.getCurrentStreamInfo().ifPresent(streamInfo -> + ShareUtils.openUrlInBrowser(player.getContext(), streamInfo.getOriginalUrl())); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Video size, resize, orientation, fullscreen + //////////////////////////////////////////////////////////////////////////*/ + //region Video size, resize, orientation, fullscreen + protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { + binding.surfaceView.setResizeMode(resizeMode); + binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); + } + + void onResizeClicked() { + setResizeMode(nextResizeModeAndSaveToPrefs(player, binding.surfaceView.getResizeMode())); + } + + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + super.onVideoSizeChanged(videoSize); + binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // SurfaceHolderCallback helpers + //////////////////////////////////////////////////////////////////////////*/ + //region SurfaceHolderCallback helpers + private void setupVideoSurface() { + // make sure there is nothing left over from previous calls + cleanupVideoSurface(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); + binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); + final Surface surface = binding.surfaceView.getHolder().getSurface(); + + // ensure player is using an unreleased surface, which the surfaceView might not be + // when starting playback on background or during player switching + if (surface.isValid()) { + // initially set the surface manually otherwise + // onRenderedFirstFrame() will not be called + player.getExoPlayer().setVideoSurface(surface); + } + + } else { + player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); + } + } + + private void cleanupVideoSurface() { + final Optional exoPlayer = Optional.ofNullable(player.getExoPlayer()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + if (surfaceHolderCallback != null) { + binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); + surfaceHolderCallback.release(); + surfaceHolderCallback = null; + } + exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null)); + } else { + exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null)); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Getters + public PlayerBinding getBinding() { + return binding; + } + + public GestureDetector getGestureDetector() { + return gestureDetector; + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 849574171c1..0eb58f7a9a4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -26,7 +26,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; @@ -61,7 +61,7 @@ public void onBindViewHolder(@NonNull final PreferenceViewHolder holder) { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(MainPlayer.ACTION_RECREATE_NOTIFICATION)); + getContext().sendBroadcast(new Intent(PlayerService.ACTION_RECREATE_NOTIFICATION)); } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index c40b1a43081..36b2bd46d4f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -50,8 +50,8 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.PlayQueueActivity; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -91,7 +91,7 @@ public static Intent getPlayerIntent(@NonNull final Context context, intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey); } } - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal()); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); return intent; @@ -163,8 +163,8 @@ public static void playOnPopupPlayer(final Context context, Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.POPUP.ordinal()); + final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.POPUP.ordinal()); ContextCompat.startForegroundService(context, intent); } @@ -174,8 +174,8 @@ public static void playOnBackgroundPlayer(final Context context, Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT) .show(); - final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal()); + final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.AUDIO.ordinal()); ContextCompat.startForegroundService(context, intent); } @@ -184,7 +184,7 @@ public static void enqueueOnPlayer(final Context context, final PlayQueue queue, final PlayerType playerType) { Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerEnqueueIntent(context, MainPlayer.class, queue); + final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue); intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); ContextCompat.startForegroundService(context, intent); @@ -194,7 +194,7 @@ public static void enqueueOnPlayer(final Context context, final PlayQueue queue) PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); - playerType = MainPlayer.PlayerType.AUDIO; + playerType = PlayerService.PlayerType.AUDIO; } enqueueOnPlayer(context, queue, playerType); @@ -205,10 +205,10 @@ public static void enqueueNextOnPlayer(final Context context, final PlayQueue qu PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); - playerType = MainPlayer.PlayerType.AUDIO; + playerType = PlayerService.PlayerType.AUDIO; } Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerEnqueueNextIntent(context, MainPlayer.class, queue); + final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue); intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); ContextCompat.startForegroundService(context, intent); @@ -414,14 +414,14 @@ public static void openVideoDetailFragment(@NonNull final Context context, final boolean switchingPlayers) { final boolean autoPlay; - @Nullable final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); + @Nullable final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { // no player open autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else if (switchingPlayers) { // switching player to main player autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state - } else if (playerType == MainPlayer.PlayerType.VIDEO) { + } else if (playerType == PlayerService.PlayerType.MAIN) { // opening new stream while already playing in main player autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else { @@ -436,7 +436,7 @@ public static void openVideoDetailFragment(@NonNull final Context context, // Situation when user switches from players to main player. All needed data is // here, we can start watching (assuming newQueue equals playQueue). // Starting directly in fullscreen if the previous player type was popup. - detailFragment.openVideoPlayer(playerType == MainPlayer.PlayerType.POPUP + detailFragment.openVideoPlayer(playerType == PlayerService.PlayerType.POPUP || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)); } else { detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue); diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt index 649b604943a..cbba0a75b54 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt @@ -12,8 +12,8 @@ import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.START import androidx.constraintlayout.widget.ConstraintSet import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R -import org.schabi.newpipe.player.event.DisplayPortion -import org.schabi.newpipe.player.event.DoubleTapListener +import org.schabi.newpipe.player.gesture.DisplayPortion +import org.schabi.newpipe.player.gesture.DoubleTapListener class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs), DoubleTapListener { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 97ccd199ecb..01d84281237 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -25,7 +25,7 @@ android:layout_gravity="center_horizontal" app:behavior_hideable="true" app:behavior_peekHeight="0dp" - app:layout_behavior="org.schabi.newpipe.player.event.CustomBottomSheetBehavior" /> + app:layout_behavior="org.schabi.newpipe.player.gesture.CustomBottomSheetBehavior" /> From b3f99645a39005ddfad19f61b98b272c81414470 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Apr 2022 10:48:34 +0200 Subject: [PATCH 122/240] Fix some crashes / issues after player refactor --- .../fragments/detail/VideoDetailFragment.java | 28 ++-- .../org/schabi/newpipe/player/Player.java | 8 +- .../gesture/MainPlayerGestureListener.kt | 15 +-- .../newpipe/player/ui/MainPlayerUi.java | 95 ++++++++----- .../schabi/newpipe/player/ui/PlayerUi.java | 1 - .../newpipe/player/ui/PopupPlayerUi.java | 35 +++-- .../newpipe/player/ui/VideoPlayerUi.java | 127 +++++++++++++----- .../views/player/PlayerFastSeekOverlay.kt | 6 +- 8 files changed, 194 insertions(+), 121 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 5ecc35034dc..cb8f0961f5d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -240,10 +240,6 @@ public void onServiceConnected(final Player connectedPlayer, playerUi.ifPresent(MainPlayerUi::toggleFullscreen); } - if (playerIsNotStopped() && player.videoPlayerSelected()) { - addVideoPlayerView(); - } - //noinspection SimplifyOptionalCallChains if (playAfterConnect || (currentInfo != null @@ -335,6 +331,9 @@ public void onPause() { @Override public void onResume() { super.onResume(); + if (DEBUG) { + Log.d(TAG, "onResume() called"); + } activity.sendBroadcast(new Intent(ACTION_VIDEO_FRAGMENT_RESUMED)); @@ -1310,22 +1309,14 @@ private void addVideoPlayerView() { if (!isPlayerAvailable()) { return; } - - final Optional root = player.UIs().get(VideoPlayerUi.class) - .map(VideoPlayerUi::getBinding) - .map(ViewBinding::getRoot); - - // Check if viewHolder already contains a child TODO TODO whaat - /*if (playerService != null - && root.map(View::getParent).orElse(null) != binding.playerPlaceholder) { - playerService.removeViewFromParent(); - }*/ setHeightThumbnail(); // Prevent from re-adding a view multiple times - if (root.isPresent() && root.get().getParent() == null) { - binding.playerPlaceholder.addView(root.get()); - } + new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + })); } private void removeVideoPlayerView() { @@ -1793,9 +1784,6 @@ private void showPlaybackProgress(final long progress, final long duration) { @Override public void onViewCreated() { - // Video view can have elements visible from popup, - // We hide it here but once it ready the view will be shown in handleIntent() - getRoot().ifPresent(view -> view.setVisibility(View.GONE)); addVideoPlayerView(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 284ab74d8c3..78e93970c70 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -485,6 +485,10 @@ private void initUIsForCurrentPlayerType() { // make sure UIs know whether a service is connected or not UIs.call(PlayerUi::onFragmentListenerSet); } + if (!exoPlayerIsNull()) { + UIs.call(PlayerUi::initPlayer); + UIs.call(PlayerUi::initPlayback); + } } private void initPlayback(@NonNull final PlayQueue queue, @@ -599,7 +603,7 @@ public void destroy() { progressUpdateDisposable.set(null); PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading - UIs.call(PlayerUi::destroy); + UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } public void setRecovery() { @@ -737,7 +741,7 @@ private void onBroadcastReceived(final Intent intent) { case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { - Log.d(TAG, "onConfigurationChanged() called"); + Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); } break; case Intent.ACTION_HEADSET_PLUG: //FIXME diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 17205fb9ae5..81e216006c9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -1,12 +1,12 @@ package org.schabi.newpipe.player.gesture -import android.app.Activity import android.content.Context import android.util.Log import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener import android.widget.ProgressBar +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R @@ -29,8 +29,6 @@ import kotlin.math.min class MainPlayerGestureListener( private val playerUi: MainPlayerUi ) : BasePlayerGestureListener(playerUi), OnTouchListener { - private val maxVolume: Int = player.audioReactor.maxVolume - private var isMoving = false override fun onTouch(v: View, event: MotionEvent): Boolean { @@ -41,11 +39,11 @@ class MainPlayerGestureListener( } return when (event.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { - v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) + v.parent?.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) true } MotionEvent.ACTION_UP -> { - v.parent.requestDisallowInterceptTouchEvent(false) + v.parent?.requestDisallowInterceptTouchEvent(false) false } else -> true @@ -68,14 +66,15 @@ class MainPlayerGestureListener( private fun onScrollVolume(distanceY: Float) { // If we just started sliding, change the progress bar to match the system volume if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { - val volumePercent: Float = player.audioReactor.volume / maxVolume.toFloat() + val volumePercent: Float = + player.audioReactor.volume / player.audioReactor.maxVolume.toFloat() binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() } binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) val currentProgressPercent: Float = binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH - val currentVolume = (maxVolume * currentProgressPercent).toInt() + val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt() player.audioReactor.volume = currentVolume if (DEBUG) { Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") @@ -102,7 +101,7 @@ class MainPlayerGestureListener( } private fun onScrollBrightness(distanceY: Float) { - val parent: Activity = playerUi.parentActivity + val parent: AppCompatActivity = playerUi.parentActivity.orElse(null) ?: return val window = parent.window val layoutParams = window.attributes val bar: ProgressBar = binding.brightnessProgressBar diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 10ed424bab8..7c60671dd75 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -13,12 +13,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Color; -import android.os.Build; import android.os.Handler; import android.provider.Settings; import android.util.DisplayMetrics; @@ -28,7 +29,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; -import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -37,6 +37,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -68,8 +69,9 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; -public final class MainPlayerUi extends VideoPlayerUi { +public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutChangeListener { private static final String TAG = MainPlayerUi.class.getSimpleName(); private boolean isFullscreen = false; @@ -113,7 +115,6 @@ public void setupAfterIntent() { super.setupAfterIntent(); - binding.getRoot().setVisibility(View.VISIBLE); initVideoPlayer(); // Android TV: without it focus will frame the whole player binding.playPauseButton.requestFocus(); @@ -139,7 +140,8 @@ protected void initListeners() { binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); binding.addToPlaylistButton.setOnClickListener(v -> - player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager())); + getParentActivity().map(FragmentActivity::getSupportFragmentManager) + .ifPresent(player::onAddToPlaylistClicked)); settingsContentObserver = new ContentObserver(new Handler()) { @Override @@ -151,7 +153,20 @@ public void onChange(final boolean selfChange) { Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, settingsContentObserver); - binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); + binding.getRoot().addOnLayoutChangeListener(this); + } + + @Override + protected void deinitListeners() { + super.deinitListeners(); + + binding.queueButton.setOnClickListener(null); + binding.segmentsButton.setOnClickListener(null); + binding.addToPlaylistButton.setOnClickListener(null); + + context.getContentResolver().unregisterContentObserver(settingsContentObserver); + + binding.getRoot().removeOnLayoutChangeListener(this); } @Override @@ -178,7 +193,6 @@ public void removeViewFromParent() { @Override public void destroy() { super.destroy(); - context.getContentResolver().unregisterContentObserver(settingsContentObserver); // Exit from fullscreen when user closes the player via notification if (isFullscreen) { @@ -324,9 +338,10 @@ private void onFragmentStopped() { player.useVideoSource(false); break; case MINIMIZE_ON_EXIT_MODE_POPUP: - player.setRecovery(); - NavigationHelper.playOnPopupPlayer(getParentActivity(), - player.getPlayQueue(), true); + getParentActivity().ifPresent(activity -> { + player.setRecovery(); + NavigationHelper.playOnPopupPlayer(activity, player.getPlayQueue(), true); + }); break; case MINIMIZE_ON_EXIT_MODE_NONE: default: player.pause(); @@ -385,14 +400,15 @@ protected void showOrHideButtons() { @Override public void showSystemUIPartially() { if (isFullscreen) { - final Window window = getParentActivity().getWindow(); - window.setStatusBarColor(Color.TRANSPARENT); - window.setNavigationBarColor(Color.TRANSPARENT); - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - window.getDecorView().setSystemUiVisibility(visibility); - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getParentActivity().map(Activity::getWindow).ifPresent(window -> { + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + window.getDecorView().setSystemUiVisibility(visibility); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + }); } } @@ -476,8 +492,9 @@ protected void setupSubtitleView(float captionScale) { //region Gestures @SuppressWarnings("checkstyle:ParameterNumber") - private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, - final int ol, final int ot, final int or, final int ob) { + @Override + public void onLayoutChange(final View view, final int l, final int t, final int r, final int b, + final int ol, final int ot, final int or, final int ob) { if (l != ol || t != ot || r != or || b != ob) { // Use smaller value to be consistent between screen orientations // (and to make usage easier) @@ -501,9 +518,8 @@ private void onLayoutChange(final View view, final int l, final int t, final int private void setInitialGestureValues() { if (player.getAudioReactor() != null) { - final float currentVolumeNormalized = - (float) player.getAudioReactor().getVolume() - / player.getAudioReactor().getMaxVolume(); + final float currentVolumeNormalized = (float) player.getAudioReactor().getVolume() + / player.getAudioReactor().getMaxVolume(); binding.volumeProgressBar.setProgress( (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); } @@ -714,7 +730,7 @@ public void selected(final PlayQueueItem item, final View view) { @Override public void held(final PlayQueueItem item, final View view) { @Nullable final PlayQueue playQueue = player.getPlayQueue(); - @Nullable final AppCompatActivity parentActivity = getParentActivity(); + @Nullable final AppCompatActivity parentActivity = getParentActivity().orElse(null); if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) { openPopupMenu(player.getPlayQueue(), item, view, true, parentActivity.getSupportFragmentManager(), context); @@ -801,10 +817,15 @@ public void onClick(final View v) { @Override protected void onPlaybackSpeedClicked() { + final AppCompatActivity activity = getParentActivity().orElse(null); + if (activity == null) { + return; + } + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) -> player.setPlaybackParameters(speed, pitch, skipSilence)) - .show(getParentActivity().getSupportFragmentManager(), null); + .show(activity.getSupportFragmentManager(), null); } @Override @@ -876,15 +897,15 @@ public void toggleFullscreen() { } isFullscreen = !isFullscreen; - if (!isFullscreen) { - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait (open vertical video to reproduce) - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } else { + if (isFullscreen) { // Android needs tens milliseconds to send new insets but a user is able to see // how controls changes it's position from `0` to `nav bar height` padding. // So just hide the controls to hide this visual inconsistency hideControls(0, 0); + } else { + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait (open vertical video to reproduce) + binding.playbackControlRoot.setPadding(0, 0, 0, 0); } fragmentListener.onFullscreenStateChanged(isFullscreen); @@ -924,14 +945,22 @@ public PlayerBinding getBinding() { return binding; } - public AppCompatActivity getParentActivity() { - return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); + public Optional getParentActivity() { + final ViewParent rootParent = binding.getRoot().getParent(); + if (rootParent instanceof ViewGroup) { + final Context activity = ((ViewGroup) rootParent).getContext(); + if (activity instanceof AppCompatActivity) { + return Optional.of((AppCompatActivity) activity); + } + } + return Optional.empty(); } public boolean isLandscape() { // DisplayMetrics from activity context knows about MultiWindow feature // while DisplayMetrics from app context doesn't - return DeviceUtils.isLandscape(getParentActivity()); + return DeviceUtils.isLandscape( + getParentActivity().map(Context.class::cast).orElse(player.getService())); } //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index fd63790d6f7..15b468fb715 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -19,7 +19,6 @@ import java.util.List; public abstract class PlayerUi { - private static final String TAG = PlayerUi.class.getSimpleName(); @NonNull protected Context context; @NonNull protected Player player; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index b8a26a23314..7df9102b75d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -69,11 +69,9 @@ public PopupPlayerUi(@NonNull final Player player, @Override public void setupAfterIntent() { - setupElementsVisibility(); - binding.getRoot().setVisibility(View.VISIBLE); + super.setupAfterIntent(); initPopup(); initPopupCloseOverlay(); - binding.playPauseButton.requestFocus(); } @Override @@ -103,6 +101,7 @@ private void initPopup() { binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); windowManager.addView(binding.getRoot(), popupLayoutParams); + setupVideoSurfaceIfNeeded(); // now there is a parent, we can setup video surface // Popup doesn't have aspectRatio selector, using FIT automatically setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); @@ -304,25 +303,23 @@ public boolean isPopupClosing() { } public void removePopupFromView() { - if (windowManager != null) { - // wrap in try-catch since it could sometimes generate errors randomly - try { - if (popupHasParent()) { - windowManager.removeView(binding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup from window manager", e); + // wrap in try-catch since it could sometimes generate errors randomly + try { + if (popupHasParent()) { + windowManager.removeView(binding.getRoot()); } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup from window manager", e); + } - try { - final boolean closeOverlayHasParent = closeOverlayBinding != null - && closeOverlayBinding.getRoot().getParent() != null; - if (closeOverlayHasParent) { - windowManager.removeView(closeOverlayBinding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup overlay from window manager", e); + try { + final boolean closeOverlayHasParent = closeOverlayBinding != null + && closeOverlayBinding.getRoot().getParent() != null; + if (closeOverlayHasParent) { + windowManager.removeView(closeOverlayBinding.getRoot()); } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup overlay from window manager", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 99ecb5540a1..24cdb8908bb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -32,7 +32,6 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; -import android.view.Surface; import android.view.View; import android.widget.LinearLayout; import android.widget.RelativeLayout; @@ -107,6 +106,7 @@ public abstract class VideoPlayerUi extends PlayerUi protected PlayerBinding binding; private final Handler controlsVisibilityHandler = new Handler(); @Nullable private SurfaceHolderCallback surfaceHolderCallback; + boolean surfaceIsSetup = false; @Nullable private Bitmap thumbnail = null; @@ -130,6 +130,7 @@ public abstract class VideoPlayerUi extends PlayerUi private GestureDetector gestureDetector; private BasePlayerGestureListener playerGestureListener; + @Nullable private View.OnLayoutChangeListener onLayoutChangeListener = null; @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = new SeekbarPreviewThumbnailHolder(); @@ -138,6 +139,7 @@ public VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; + setupFromView(); } @@ -222,8 +224,8 @@ protected void initListeners() { // PlaybackControlRoot already consumed window insets but we should pass them to // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - binding.playbackControlRoot.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + onLayoutChangeListener + = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { binding.playerOverlays.setPadding( v.getPaddingLeft(), v.getPaddingTop(), @@ -240,7 +242,43 @@ protected void initListeners() { fastSeekParams.topMargin = -v.getPaddingBottom(); fastSeekParams.rightMargin = -v.getPaddingLeft(); fastSeekParams.bottomMargin = -v.getPaddingTop(); - }); + }; + binding.playbackControlRoot.addOnLayoutChangeListener(onLayoutChangeListener); + } + + protected void deinitListeners() { + binding.qualityTextView.setOnClickListener(null); + binding.playbackSpeed.setOnClickListener(null); + binding.playbackSeekBar.setOnSeekBarChangeListener(null); + binding.captionTextView.setOnClickListener(null); + binding.resizeTextView.setOnClickListener(null); + binding.playbackLiveSync.setOnClickListener(null); + + binding.getRoot().setOnTouchListener(null); + playerGestureListener = null; + gestureDetector = null; + + binding.repeatButton.setOnClickListener(null); + binding.shuffleButton.setOnClickListener(null); + + binding.playPauseButton.setOnClickListener(null); + binding.playPreviousButton.setOnClickListener(null); + binding.playNextButton.setOnClickListener(null); + + binding.moreOptionsButton.setOnClickListener(null); + binding.moreOptionsButton.setOnLongClickListener(null); + binding.share.setOnClickListener(null); + binding.share.setOnLongClickListener(null); + binding.fullScreenButton.setOnClickListener(null); + binding.screenRotationButton.setOnClickListener(null); + binding.playWithKodi.setOnClickListener(null); + binding.openInBrowser.setOnClickListener(null); + binding.playerCloseButton.setOnClickListener(null); + binding.switchMute.setOnClickListener(null); + + ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, null); + + binding.playbackControlRoot.removeOnLayoutChangeListener(onLayoutChangeListener); } /** @@ -304,18 +342,25 @@ public void seek(final boolean forward) { playerGestureListener.doubleTapControls(binding.fastSeekOverlay); } + public void deinitPlayerSeekOverlay() { + binding.fastSeekOverlay + .seekSecondsSupplier(null) + .performListener(null); + } + @Override public void setupAfterIntent() { super.setupAfterIntent(); setupElementsVisibility(); setupElementsSize(context.getResources()); + binding.getRoot().setVisibility(View.VISIBLE); + binding.playPauseButton.requestFocus(); } @Override public void initPlayer() { super.initPlayer(); - setupVideoSurface(); - setupFromView(); + setupVideoSurfaceIfNeeded(); } @Override @@ -331,7 +376,7 @@ public void initPlayback() { @Override public void destroyPlayer() { super.destroyPlayer(); - cleanupVideoSurface(); + clearVideoSurface(); } @Override @@ -340,6 +385,8 @@ public void destroy() { if (binding != null) { binding.endScreen.setImageBitmap(null); } + deinitPlayerSeekOverlay(); + deinitListeners(); } protected void setupElementsVisibility() { @@ -1470,40 +1517,50 @@ public void onVideoSizeChanged(@NonNull VideoSize videoSize) { // SurfaceHolderCallback helpers //////////////////////////////////////////////////////////////////////////*/ //region SurfaceHolderCallback helpers - private void setupVideoSurface() { - // make sure there is nothing left over from previous calls - cleanupVideoSurface(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); - binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); - final Surface surface = binding.surfaceView.getHolder().getSurface(); - - // ensure player is using an unreleased surface, which the surfaceView might not be - // when starting playback on background or during player switching - if (surface.isValid()) { - // initially set the surface manually otherwise - // onRenderedFirstFrame() will not be called - player.getExoPlayer().setVideoSurface(surface); + + /** + * Connects the video surface to the exo player. This can be called anytime without the risk for + * issues to occur, since the player will run just fine when no surface is connected. Therefore + * the video surface will be setup only when all of these conditions are true: it is not already + * setup (this just prevents wasting resources to setup the surface again), there is an exo + * player, the root view is attached to a parent and the surface view is valid/unreleased (the + * latter two conditions prevent "The surface has been released" errors). So this function can + * be called many times and even while the UI is in unready states. + */ + public void setupVideoSurfaceIfNeeded() { + if (!surfaceIsSetup && player.getExoPlayer() != null + && binding.getRoot().getParent() != null) { + // make sure there is nothing left over from previous calls + clearVideoSurface(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); + binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); + + // ensure player is using an unreleased surface, which the surfaceView might not be + // when starting playback on background or during player switching + if (binding.surfaceView.getHolder().getSurface().isValid()) { + // initially set the surface manually otherwise + // onRenderedFirstFrame() will not be called + player.getExoPlayer().setVideoSurfaceHolder(binding.surfaceView.getHolder()); + } + } else { + player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); } - } else { - player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); + surfaceIsSetup = true; } } - private void cleanupVideoSurface() { - final Optional exoPlayer = Optional.ofNullable(player.getExoPlayer()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - if (surfaceHolderCallback != null) { - binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); - surfaceHolderCallback.release(); - surfaceHolderCallback = null; - } - exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null)); - } else { - exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null)); + private void clearVideoSurface() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M // >=API23 + && surfaceHolderCallback != null) { + binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); + surfaceHolderCallback.release(); + surfaceHolderCallback = null; } + Optional.ofNullable(player.getExoPlayer()).ifPresent(ExoPlayer::clearVideoSurface); + surfaceIsSetup = false; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt index cbba0a75b54..d0782e1a18d 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt @@ -38,14 +38,14 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : private var performListener: PerformListener? = null - fun performListener(listener: PerformListener) = apply { + fun performListener(listener: PerformListener?) = apply { performListener = listener } private var seekSecondsSupplier: () -> Int = { 0 } - fun seekSecondsSupplier(supplier: () -> Int) = apply { - seekSecondsSupplier = supplier + fun seekSecondsSupplier(supplier: (() -> Int)?) = apply { + seekSecondsSupplier = supplier ?: { 0 } } // Indicates whether this (double) tap is the first of a series From 0bba1d95dee627843bc97224e6e04260931fa9a8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:14:28 +0200 Subject: [PATCH 123/240] Move all notification-related calls to NotificationPlayerUi --- .../newpipe/player/NotificationUtil.java | 93 +++++++-------- .../org/schabi/newpipe/player/Player.java | 60 ++-------- .../schabi/newpipe/player/PlayerService.java | 4 - .../player/ui/NotificationPlayerUi.java | 107 +++++++++++++++++- 4 files changed, 154 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index f5caf2c7974..e88defe7f42 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -45,22 +45,16 @@ public final class NotificationUtil { private static final boolean DEBUG = Player.DEBUG; private static final int NOTIFICATION_ID = 123789; - @Nullable private static NotificationUtil instance = null; - @NotificationConstants.Action private final int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone(); private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; - private NotificationUtil() { - } + private Player player; - public static NotificationUtil getInstance() { - if (instance == null) { - instance = new NotificationUtil(); - } - return instance; + public NotificationUtil(final Player player) { + this.player = player; } @@ -71,20 +65,18 @@ public static NotificationUtil getInstance() { /** * Creates the notification if it does not exist already and recreates it if forceRecreate is * true. Updates the notification with the data in the player. - * @param player the player currently open, to take data from * @param forceRecreate whether to force the recreation of the notification even if it already * exists */ - synchronized void createNotificationIfNeededAndUpdate(final Player player, - final boolean forceRecreate) { + public synchronized void createNotificationIfNeededAndUpdate(final boolean forceRecreate) { if (forceRecreate || notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } - private synchronized NotificationCompat.Builder createNotification(final Player player) { + private synchronized NotificationCompat.Builder createNotification() { if (DEBUG) { Log.d(TAG, "createNotification()"); } @@ -93,7 +85,7 @@ private synchronized NotificationCompat.Builder createNotification(final Player new NotificationCompat.Builder(player.getContext(), player.getContext().getString(R.string.notification_channel_id)); - initializeNotificationSlots(player); + initializeNotificationSlots(); // count the number of real slots, to make sure compact slots indices are not out of bound int nonNothingSlotCount = 5; @@ -132,9 +124,8 @@ private synchronized NotificationCompat.Builder createNotification(final Player /** * Updates the notification builder and the button icons depending on the playback state. - * @param player the player currently open, to take data from */ - private synchronized void updateNotification(final Player player) { + private synchronized void updateNotification() { if (DEBUG) { Log.d(TAG, "updateNotification()"); } @@ -145,17 +136,17 @@ private synchronized void updateNotification(final Player player) { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); - updateActions(notificationBuilder, player); + updateActions(notificationBuilder); final boolean showThumbnail = player.getPrefs().getBoolean( player.getContext().getString(R.string.show_thumbnail_key), true); if (showThumbnail) { - setLargeIcon(notificationBuilder, player); + setLargeIcon(notificationBuilder); } } @SuppressLint("RestrictedApi") - boolean shouldUpdateBufferingSlot() { + public boolean shouldUpdateBufferingSlot() { if (notificationBuilder == null) { // if there is no notification active, there is no point in updating it return false; @@ -173,22 +164,22 @@ boolean shouldUpdateBufferingSlot() { } - public void createNotificationAndStartForeground(final Player player, final Service service) { + public void createNotificationAndStartForeground() { if (notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build(), + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); } else { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build()); + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build()); } } - void cancelNotificationAndStopForeground(final Service service) { - ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE); + public void cancelNotificationAndStopForeground() { + ServiceCompat.stopForeground(player.getService(), ServiceCompat.STOP_FOREGROUND_REMOVE); if (notificationManager != null) { notificationManager.cancel(NOTIFICATION_ID); @@ -202,7 +193,7 @@ void cancelNotificationAndStopForeground(final Service service) { // ACTIONS ///////////////////////////////////////////////////// - private void initializeNotificationSlots(final Player player) { + private void initializeNotificationSlots() { for (int i = 0; i < 5; ++i) { notificationSlots[i] = player.getPrefs().getInt( player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]), @@ -211,7 +202,7 @@ private void initializeNotificationSlots(final Player player) { } @SuppressLint("RestrictedApi") - private void updateActions(final NotificationCompat.Builder builder, final Player player) { + private void updateActions(final NotificationCompat.Builder builder) { builder.mActions.clear(); for (int i = 0; i < 5; ++i) { addAction(builder, player, notificationSlots[i]); @@ -221,7 +212,7 @@ private void updateActions(final NotificationCompat.Builder builder, final Playe private void addAction(final NotificationCompat.Builder builder, final Player player, @NotificationConstants.Action final int slot) { - final NotificationCompat.Action action = getAction(player, slot); + final NotificationCompat.Action action = getAction(slot); if (action != null) { builder.addAction(action); } @@ -229,41 +220,40 @@ private void addAction(final NotificationCompat.Builder builder, @Nullable private NotificationCompat.Action getAction( - final Player player, @NotificationConstants.Action final int selectedAction) { final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction]; switch (selectedAction) { case NotificationConstants.PREVIOUS: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); case NotificationConstants.NEXT: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); case NotificationConstants.REWIND: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); case NotificationConstants.FORWARD: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); case NotificationConstants.SMART_REWIND_PREVIOUS: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_previous, + return getAction(R.drawable.exo_notification_previous, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); } else { - return getAction(player, R.drawable.exo_controls_rewind, + return getAction(R.drawable.exo_controls_rewind, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); } case NotificationConstants.SMART_FORWARD_NEXT: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_next, + return getAction(R.drawable.exo_notification_next, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); } else { - return getAction(player, R.drawable.exo_controls_fastforward, + return getAction(R.drawable.exo_controls_fastforward, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); } @@ -279,42 +269,42 @@ private NotificationCompat.Action getAction( case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { - return getAction(player, R.drawable.ic_replay, + return getAction(R.drawable.ic_replay, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else if (player.isPlaying() || player.getCurrentState() == Player.STATE_PREFLIGHT || player.getCurrentState() == Player.STATE_BLOCKED || player.getCurrentState() == Player.STATE_BUFFERING) { - return getAction(player, R.drawable.exo_notification_pause, + return getAction(R.drawable.exo_notification_pause, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else { - return getAction(player, R.drawable.exo_notification_play, + return getAction(R.drawable.exo_notification_play, R.string.exo_controls_play_description, ACTION_PLAY_PAUSE); } case NotificationConstants.REPEAT: if (player.getRepeatMode() == REPEAT_MODE_ALL) { - return getAction(player, R.drawable.exo_media_action_repeat_all, + return getAction(R.drawable.exo_media_action_repeat_all, R.string.exo_controls_repeat_all_description, ACTION_REPEAT); } else if (player.getRepeatMode() == REPEAT_MODE_ONE) { - return getAction(player, R.drawable.exo_media_action_repeat_one, + return getAction(R.drawable.exo_media_action_repeat_one, R.string.exo_controls_repeat_one_description, ACTION_REPEAT); } else /* player.getRepeatMode() == REPEAT_MODE_OFF */ { - return getAction(player, R.drawable.exo_media_action_repeat_off, + return getAction(R.drawable.exo_media_action_repeat_off, R.string.exo_controls_repeat_off_description, ACTION_REPEAT); } case NotificationConstants.SHUFFLE: if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) { - return getAction(player, R.drawable.exo_controls_shuffle_on, + return getAction(R.drawable.exo_controls_shuffle_on, R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE); } else { - return getAction(player, R.drawable.exo_controls_shuffle_off, + return getAction(R.drawable.exo_controls_shuffle_off, R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE); } case NotificationConstants.CLOSE: - return getAction(player, R.drawable.ic_close, + return getAction(R.drawable.ic_close, R.string.close, ACTION_CLOSE); case NotificationConstants.NOTHING: @@ -324,8 +314,7 @@ private NotificationCompat.Action getAction( } } - private NotificationCompat.Action getAction(final Player player, - @DrawableRes final int drawable, + private NotificationCompat.Action getAction(@DrawableRes final int drawable, @StringRes final int title, final String intentAction) { return new NotificationCompat.Action(drawable, player.getContext().getString(title), @@ -353,7 +342,7 @@ private Intent getIntentForNotification(final Player player) { // BITMAP ///////////////////////////////////////////////////// - private void setLargeIcon(final NotificationCompat.Builder builder, final Player player) { + private void setLargeIcon(final NotificationCompat.Builder builder) { final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 78e93970c70..e2732f4d01f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -38,7 +38,6 @@ import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; @@ -620,7 +619,7 @@ public void setRecovery() { } private void setRecovery(final int queuePos, final long windowPos) { - if (playQueue.size() <= queuePos) { + if (playQueue == null || playQueue.size() <= queuePos) { return; } @@ -735,9 +734,6 @@ private void onBroadcastReceived(final Intent intent) { case ACTION_SHUFFLE: toggleShuffleModeEnabled(); break; - case ACTION_RECREATE_NOTIFICATION: - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); - break; case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { @@ -797,8 +793,6 @@ public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { } currentThumbnail = bitmap; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); // there is a new thumbnail, so changed the end screen thumbnail, too. UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @@ -807,8 +801,7 @@ public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); currentThumbnail = null; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); + UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); } @Override @@ -1082,8 +1075,6 @@ private void onBlocked() { } UIs.call(PlayerUi::onBlocked); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onPlaying() { @@ -1095,8 +1086,6 @@ private void onPlaying() { } UIs.call(PlayerUi::onPlaying); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onBuffering() { @@ -1105,10 +1094,6 @@ private void onBuffering() { } UIs.call(PlayerUi::onBuffering); - - if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPaused() { @@ -1121,24 +1106,13 @@ private void onPaused() { } UIs.call(PlayerUi::onPaused); - - // Remove running notification when user does not want minimization to background or popup - if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE - && videoPlayerSelected()) { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(service); - } else { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPausedSeek() { if (DEBUG) { Log.d(TAG, "onPausedSeek() called"); } - UIs.call(PlayerUi::onPausedSeek); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onCompleted() { @@ -1150,7 +1124,6 @@ private void onCompleted() { } UIs.call(PlayerUi::onCompleted); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); if (playQueue.getIndex() < playQueue.size() - 1) { playQueue.offsetIndex(+1); @@ -1190,7 +1163,7 @@ public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + "repeatMode = [" + repeatMode + "]"); } UIs.call(playerUi -> playerUi.onRepeatModeChanged(repeatMode)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } @Override @@ -1209,7 +1182,7 @@ public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { } UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } public void toggleShuffleModeEnabled() { @@ -1217,11 +1190,6 @@ public void toggleShuffleModeEnabled() { simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); } } - - private void onShuffleOrRepeatModeChanged() { - notifyPlaybackUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } //endregion @@ -1806,12 +1774,15 @@ public void saveStreamProgressStateCompleted() { //////////////////////////////////////////////////////////////////////////*/ //region Metadata - private void onMetadataChanged(@NonNull final StreamInfo info) { + private void updateMetadataWith(@NonNull final StreamInfo info) { if (DEBUG) { Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); } + if (exoPlayerIsNull()) { + return; + } - UIs.call(playerUi -> playerUi.onMetadataChanged(info)); + maybeAutoQueueNextStream(info); initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); @@ -1826,17 +1797,7 @@ private void onMetadataChanged(@NonNull final StreamInfo info) { ); notifyMetadataUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } - - private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { - if (exoPlayerIsNull()) { - return; - } - - maybeAutoQueueNextStream(streamInfo); - onMetadataChanged(streamInfo); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); + UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @NonNull @@ -1925,7 +1886,6 @@ public void selectQueueItem(final PlayQueueItem item) { public void onPlayQueueEdited() { notifyPlaybackUpdateToListeners(); UIs.call(PlayerUi::onPlayQueueEdited); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @Override // own playback listener diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index cf83dc5c277..7bf918c7308 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -88,9 +88,6 @@ public void onCreate() { ThemeHelper.setTheme(this); player = new Player(this); - /*final MainPlayerUi mainPlayerUi = new MainPlayerUi(player, - PlayerBinding.inflate(LayoutInflater.from(this))); - player.UIs().add(mainPlayerUi);*/ } @Override @@ -159,7 +156,6 @@ private void cleanup() { } public void stopService() { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(this); cleanup(); stopSelf(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java index 40c83c6c779..5736eca3bbd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java @@ -1,26 +1,125 @@ package org.schabi.newpipe.player.ui; +import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; + +import android.content.Intent; +import android.graphics.Bitmap; + import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.Player.RepeatMode; +import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.helper.PlayerHelper; public final class NotificationPlayerUi extends PlayerUi { - boolean foregroundNotificationAlreadyCreated = false; + private boolean foregroundNotificationAlreadyCreated = false; + private final NotificationUtil notificationUtil; public NotificationPlayerUi(@NonNull final Player player) { super(player); + notificationUtil = new NotificationUtil(player); } @Override public void initPlayer() { super.initPlayer(); if (!foregroundNotificationAlreadyCreated) { - NotificationUtil.getInstance() - .createNotificationAndStartForeground(player, player.getService()); + notificationUtil.createNotificationAndStartForeground(); foregroundNotificationAlreadyCreated = true; } } - // TODO TODO on destroy remove foreground + @Override + public void destroy() { + super.destroy(); + notificationUtil.cancelNotificationAndStopForeground(); + } + + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBlocked() { + super.onBlocked(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onPlaying() { + super.onPlaying(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBuffering() { + super.onBuffering(); + if (notificationUtil.shouldUpdateBufferingSlot()) { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPaused() { + super.onPaused(); + + // Remove running notification when user does not want minimization to background or popup + if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE + && player.videoPlayerSelected()) { + notificationUtil.cancelNotificationAndStopForeground(); + } else { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onCompleted() { + super.onCompleted(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + super.onRepeatModeChanged(repeatMode); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + super.onShuffleModeEnabledChanged(shuffleModeEnabled); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) { + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + + @Override + public void onPlayQueueEdited() { + super.onPlayQueueEdited(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } } From 90a89f8ca555433b1e8f9f9d2713d0f7667060be Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:40:55 +0200 Subject: [PATCH 124/240] Move player-notification files into their package --- .../org/schabi/newpipe/player/Player.java | 20 ++++++------ .../schabi/newpipe/player/PlayerService.java | 25 --------------- .../NotificationConstants.java | 32 +++++++++++++++++-- .../NotificationPlayerUi.java | 6 ++-- .../{ => notification}/NotificationUtil.java | 25 +++++++-------- .../newpipe/player/ui/MainPlayerUi.java | 2 +- .../custom/NotificationActionsPreference.java | 6 ++-- 7 files changed, 60 insertions(+), 56 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{ => notification}/NotificationConstants.java (82%) rename app/src/main/java/org/schabi/newpipe/player/{ui => notification}/NotificationPlayerUi.java (94%) rename app/src/main/java/org/schabi/newpipe/player/{ => notification}/NotificationUtil.java (94%) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index e2732f4d01f..55600b95698 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -29,21 +29,21 @@ import static com.google.android.exoplayer2.Player.RepeatMode; import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; -import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; -import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; @@ -125,7 +125,7 @@ import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; import org.schabi.newpipe.player.ui.MainPlayerUi; -import org.schabi.newpipe.player.ui.NotificationPlayerUi; +import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; import org.schabi.newpipe.player.ui.PopupPlayerUi; diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 7bf918c7308..b5014eeed09 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -28,8 +28,6 @@ import android.os.IBinder; import android.util.Log; -import org.schabi.newpipe.App; -import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -52,29 +50,6 @@ public enum PlayerType { POPUP } - /*////////////////////////////////////////////////////////////////////////// - // Notification - //////////////////////////////////////////////////////////////////////////*/ - - static final String ACTION_CLOSE - = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE - = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; - static final String ACTION_REPEAT - = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; - static final String ACTION_PLAY_NEXT - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; - static final String ACTION_PLAY_PREVIOUS - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; - static final String ACTION_FAST_REWIND - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; - static final String ACTION_FAST_FORWARD - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; - static final String ACTION_SHUFFLE - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; - public static final String ACTION_RECREATE_NOTIFICATION - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; - /*////////////////////////////////////////////////////////////////////////// // Service's LifeCycle //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java similarity index 82% rename from app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 6c9858d1bdf..53ef752bd8a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player; +package org.schabi.newpipe.player.notification; import android.content.Context; import android.content.SharedPreferences; @@ -7,6 +7,7 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.util.Localization; @@ -20,7 +21,34 @@ public final class NotificationConstants { - private NotificationConstants() { } + private NotificationConstants() { + } + + + + /*////////////////////////////////////////////////////////////////////////// + // Intent actions + //////////////////////////////////////////////////////////////////////////*/ + + public static final String ACTION_CLOSE + = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; + public static final String ACTION_PLAY_PAUSE + = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT + = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; + public static final String ACTION_PLAY_NEXT + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; + public static final String ACTION_PLAY_PREVIOUS + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; + public static final String ACTION_SHUFFLE + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; + public static final String ACTION_RECREATE_NOTIFICATION + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; + public static final int NOTHING = 0; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java similarity index 94% rename from app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java index 5736eca3bbd..ed678a18c09 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java @@ -1,7 +1,7 @@ -package org.schabi.newpipe.player.ui; +package org.schabi.newpipe.player.notification; -import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; import android.content.Intent; import android.graphics.Bitmap; @@ -12,9 +12,9 @@ import com.google.android.exoplayer2.Player.RepeatMode; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.ui.PlayerUi; public final class NotificationPlayerUi extends PlayerUi { private boolean foregroundNotificationAlreadyCreated = false; diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java similarity index 94% rename from app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index e88defe7f42..5f005245343 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -1,8 +1,7 @@ -package org.schabi.newpipe.player; +package org.schabi.newpipe.player.notification; import android.annotation.SuppressLint; import android.app.PendingIntent; -import android.app.Service; import android.content.Intent; import android.content.pm.ServiceInfo; import android.graphics.Bitmap; @@ -19,6 +18,7 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; @@ -26,14 +26,14 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; -import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE; /** * This is a utility class for player notifications. @@ -51,7 +51,7 @@ public final class NotificationUtil { private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; - private Player player; + private final Player player; public NotificationUtil(final Player player) { this.player = player; @@ -205,12 +205,11 @@ private void initializeNotificationSlots() { private void updateActions(final NotificationCompat.Builder builder) { builder.mActions.clear(); for (int i = 0; i < 5; ++i) { - addAction(builder, player, notificationSlots[i]); + addAction(builder, notificationSlots[i]); } } private void addAction(final NotificationCompat.Builder builder, - final Player player, @NotificationConstants.Action final int slot) { final NotificationCompat.Action action = getAction(slot); if (action != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 7c60671dd75..80230d0f77a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -5,13 +5,13 @@ import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.player.Player.STATE_COMPLETED; import static org.schabi.newpipe.player.Player.STATE_PAUSED; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; import android.app.Activity; import android.content.Context; diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 0eb58f7a9a4..dfcf2e5974e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings.custom; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -27,7 +29,7 @@ import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.NotificationConstants; +import org.schabi.newpipe.player.notification.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; @@ -61,7 +63,7 @@ public void onBindViewHolder(@NonNull final PreferenceViewHolder holder) { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(PlayerService.ACTION_RECREATE_NOTIFICATION)); + getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION)); } From 8c26403e91de46631fffceeb1216bb20c630033f Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:42:22 +0200 Subject: [PATCH 125/240] Remove unused PlayerState --- .../schabi/newpipe/player/PlayerState.java | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerState.java diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java b/app/src/main/java/org/schabi/newpipe/player/PlayerState.java deleted file mode 100644 index af875a32ba8..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.schabi.newpipe.player; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.schabi.newpipe.player.playqueue.PlayQueue; - -import java.io.Serializable; - -public class PlayerState implements Serializable { - - @NonNull - private final PlayQueue playQueue; - private final int repeatMode; - private final float playbackSpeed; - private final float playbackPitch; - @Nullable - private final String playbackQuality; - private final boolean playbackSkipSilence; - private final boolean wasPlaying; - - PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode, - final float playbackSpeed, final float playbackPitch, - final boolean playbackSkipSilence, final boolean wasPlaying) { - this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, - playbackSkipSilence, wasPlaying); - } - - PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode, - final float playbackSpeed, final float playbackPitch, - @Nullable final String playbackQuality, final boolean playbackSkipSilence, - final boolean wasPlaying) { - this.playQueue = playQueue; - this.repeatMode = repeatMode; - this.playbackSpeed = playbackSpeed; - this.playbackPitch = playbackPitch; - this.playbackQuality = playbackQuality; - this.playbackSkipSilence = playbackSkipSilence; - this.wasPlaying = wasPlaying; - } - - /*////////////////////////////////////////////////////////////////////////// - // Serdes - //////////////////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////////////////// - // Getters - //////////////////////////////////////////////////////////////////////////*/ - - @NonNull - public PlayQueue getPlayQueue() { - return playQueue; - } - - public int getRepeatMode() { - return repeatMode; - } - - public float getPlaybackSpeed() { - return playbackSpeed; - } - - public float getPlaybackPitch() { - return playbackPitch; - } - - @Nullable - public String getPlaybackQuality() { - return playbackQuality; - } - - public boolean isPlaybackSkipSilence() { - return playbackSkipSilence; - } - - public boolean wasPlaying() { - return wasPlaying; - } -} From 6fb02569978c8126cb24376419980a8321843b33 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:43:54 +0200 Subject: [PATCH 126/240] Remove unused PlayerServiceBinder --- .../newpipe/player/PlayQueueActivity.java | 4 +--- .../newpipe/player/PlayerServiceBinder.java | 17 ----------------- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index d00e6265e0c..cdba900f9bf 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -207,9 +207,7 @@ public void onServiceDisconnected(final ComponentName name) { public void onServiceConnected(final ComponentName name, final IBinder service) { Log.d(TAG, "Player service is connected"); - if (service instanceof PlayerServiceBinder) { - player = ((PlayerServiceBinder) service).getPlayerInstance(); - } else if (service instanceof PlayerService.LocalBinder) { + if (service instanceof PlayerService.LocalBinder) { player = ((PlayerService.LocalBinder) service).getPlayer(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java b/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java deleted file mode 100644 index 5c28c6c7b1b..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.schabi.newpipe.player; - -import android.os.Binder; - -import androidx.annotation.NonNull; - -class PlayerServiceBinder extends Binder { - private final Player player; - - PlayerServiceBinder(@NonNull final Player player) { - this.player = player; - } - - Player getPlayerInstance() { - return player; - } -} From fa25ecf52143a3cb8b695db7c25799de320b1e5f Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:55:23 +0200 Subject: [PATCH 127/240] Add comment about broadcast receiver --- app/src/main/java/org/schabi/newpipe/player/Player.java | 6 ++++++ .../main/java/org/schabi/newpipe/player/ui/PlayerUi.java | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 55600b95698..b0fed3d7d70 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -663,6 +663,12 @@ public void smoothStopForImmediateReusing() { //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + /** + * This function prepares the broadcast receiver and is called only in the constructor. + * Therefore if you want any PlayerUi to receive a broadcast action, you should add it here, + * even if that player ui might never be added to the player. In that case the received + * broadcast would not do anything. + */ private void setupBroadcastReceiver() { if (DEBUG) { Log.d(TAG, "setupBroadcastReceiver() called"); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 15b468fb715..81e93ca238f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -55,6 +55,10 @@ public void smoothStopForImmediateReusing() { public void onFragmentListenerSet() { } + /** + * If you want to register new broadcast actions to receive here, add them to + * {@link Player#setupBroadcastReceiver()}. + */ public void onBroadcastReceived(final Intent intent) { } From 6559416bd8e0857a32b00c097638be9dc2eec88b Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 23:07:29 +0200 Subject: [PATCH 128/240] Improve //region comments in player UIs --- .../newpipe/player/ui/MainPlayerUi.java | 71 ++++++---- .../newpipe/player/ui/PopupPlayerUi.java | 80 +++++++---- .../newpipe/player/ui/VideoPlayerUi.java | 131 ++++++++++-------- 3 files changed, 172 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 80230d0f77a..c62382782e3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -88,6 +88,12 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // fullscreen player private ItemTouchHelper itemTouchHelper; + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public MainPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player, playerBinding); @@ -272,12 +278,14 @@ protected void setupElementsSize(final Resources resources) { resources.getDimensionPixelSize(R.dimen.player_main_buttons_padding) ); } + //endregion /*////////////////////////////////////////////////////////////////////////// // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -313,6 +321,7 @@ public void onBroadcastReceived(final Intent intent) { // Fragment binding //////////////////////////////////////////////////////////////////////////*/ //region Fragment binding + @Override public void onFragmentListenerSet() { super.onFragmentListenerSet(); @@ -351,13 +360,11 @@ private void onFragmentStopped() { } //endregion - private void showHideKodiButton() { - // show kodi button if it supports the current service and it is enabled in settings - @Nullable final PlayQueue playQueue = player.getPlayQueue(); - binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null - && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) - ? View.VISIBLE : View.GONE); - } + + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states @Override public void onUpdateProgress(final int currentProgress, @@ -373,6 +380,22 @@ public void onUpdateProgress(final int currentProgress, } } + @Override + public void onPlaying() { + super.onPlaying(); + checkLandscape(); + } + + @Override + public void onCompleted() { + super.onCompleted(); + if (isFullscreen) { + toggleFullscreen(); + } + } + //endregion + + /*////////////////////////////////////////////////////////////////////////// // Controls showing / hiding //////////////////////////////////////////////////////////////////////////*/ @@ -457,22 +480,21 @@ protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitma return Math.min(bitmap.getHeight(), screenHeight); } } - //endregion - @Override - public void onPlaying() { - super.onPlaying(); - checkLandscape(); + private void showHideKodiButton() { + // show kodi button if it supports the current service and it is enabled in settings + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null + && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) + ? View.VISIBLE : View.GONE); } + //endregion - @Override - public void onCompleted() { - super.onCompleted(); - if (isFullscreen) { - toggleFullscreen(); - } - } + /*////////////////////////////////////////////////////////////////////////// + // Captions (text tracks) + //////////////////////////////////////////////////////////////////////////*/ + //region Captions (text tracks) @Override protected void setupSubtitleView(float captionScale) { @@ -482,8 +504,7 @@ protected void setupSubtitleView(float captionScale) { binding.subtitleView.setFixedTextSize( TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); } - - + //endregion /*////////////////////////////////////////////////////////////////////////// @@ -798,6 +819,7 @@ public boolean isVerticalVideo() { // Click listeners //////////////////////////////////////////////////////////////////////////*/ //region Click listeners + @Override public void onClick(final View v) { if (v.getId() == binding.screenRotationButton.getId()) { @@ -855,9 +877,9 @@ public boolean onKeyDown(final int keyCode) { /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size, orientation, fullscreen //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size, orientation, fullscreen private void setupScreenRotationButton() { binding.screenRotationButton.setVisibility(globalScreenOrientationLocked(context) @@ -941,9 +963,6 @@ public void checkLandscape() { // Getters //////////////////////////////////////////////////////////////////////////*/ //region Getters - public PlayerBinding getBinding() { - return binding; - } public Optional getParentActivity() { final ViewParent rootParent = binding.getRoot().getParent(); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 7df9102b75d..43440b87359 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -8,7 +8,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; -import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; @@ -61,6 +60,12 @@ public final class PopupPlayerUi extends VideoPlayerUi { private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup private final WindowManager windowManager; + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public PopupPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player, playerBinding); @@ -173,11 +178,14 @@ public void destroy() { super.destroy(); removePopupFromView(); } + //endregion + /*////////////////////////////////////////////////////////////////////////// // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -200,6 +208,11 @@ public void onBroadcastReceived(final Intent intent) { //endregion + /*////////////////////////////////////////////////////////////////////////// + // Popup position and size + //////////////////////////////////////////////////////////////////////////*/ + //region Popup position and size + /** * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary * that goes from (0, 0) to (screenWidth, screenHeight). @@ -272,16 +285,19 @@ public void changePopupSize(final int width) { windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); } - private void changePopupWindowFlags(final int flags) { - if (DEBUG) { - Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); - } - - if (!anyPopupViewIsNull()) { - popupLayoutParams.flags = flags; - windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); - } + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + // no need for the end screen thumbnail to be resized on popup player: it's only needed + // for the main player so that it is enlarged correctly inside the fragment + return bitmap.getHeight(); } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Popup closing + //////////////////////////////////////////////////////////////////////////*/ + //region Popup closing public void closePopup() { if (DEBUG) { @@ -351,23 +367,22 @@ private void end() { } }).start(); } + //endregion - @Override - protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { - // no need for the end screen thumbnail to be resized on popup player: it's only needed - // for the main player so that it is enlarged correctly inside the fragment - return bitmap.getHeight(); - } + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states - private boolean popupHasParent() { - return binding != null - && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams - && binding.getRoot().getParent() != null; - } + private void changePopupWindowFlags(final int flags) { + if (DEBUG) { + Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); + } - private boolean anyPopupViewIsNull() { - return popupLayoutParams == null || windowManager == null - || binding.getRoot().getParent() == null; + if (!anyPopupViewIsNull()) { + popupLayoutParams.flags = flags; + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } } @Override @@ -400,11 +415,14 @@ protected void onPlaybackSpeedClicked() { playbackSpeedPopupMenu.show(); isSomePopupMenuVisible = true; } + //endregion + /*////////////////////////////////////////////////////////////////////////// // Gestures //////////////////////////////////////////////////////////////////////////*/ //region Gestures + private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() + closeOverlayBinding.closeButton.getWidth() / 2; @@ -433,7 +451,19 @@ public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent /*////////////////////////////////////////////////////////////////////////// // Getters //////////////////////////////////////////////////////////////////////////*/ - //region Gestures + //region Getters + + private boolean popupHasParent() { + return binding != null + && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams + && binding.getRoot().getParent() != null; + } + + private boolean anyPopupViewIsNull() { + return popupLayoutParams == null || windowManager == null + || binding.getRoot().getParent() == null; + } + public PlayerPopupCloseOverlayBinding getCloseOverlayBinding() { return closeOverlayBinding; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 24cdb8908bb..f4ebc3304b5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -135,6 +135,12 @@ public abstract class VideoPlayerUi extends PlayerUi @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = new SeekbarPreviewThumbnailHolder(); + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); @@ -142,11 +148,6 @@ public VideoPlayerUi(@NonNull final Player player, setupFromView(); } - - /*////////////////////////////////////////////////////////////////////////// - // Setup - //////////////////////////////////////////////////////////////////////////*/ - //region Setup public void setupFromView() { initViews(); initListeners(); @@ -414,6 +415,7 @@ protected void setupElementsSize(final int buttonsMinWidth, // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -433,6 +435,7 @@ public void onBroadcastReceived(final Intent intent) { // Thumbnail //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail + /** * Scale the player audio / end screen thumbnail down if necessary. *

@@ -481,6 +484,7 @@ private void updateEndScreenThumbnail() { // Progress loop and updates //////////////////////////////////////////////////////////////////////////*/ //region Progress loop and updates + @Override public void onUpdateProgress(final int currentProgress, final int duration, @@ -744,6 +748,7 @@ public boolean isFullscreen() { // Playback states //////////////////////////////////////////////////////////////////////////*/ //region Playback states + @Override public void onPrepared() { super.onPrepared(); @@ -885,7 +890,8 @@ private void animatePlayButtons(final boolean show, final long duration) { /*////////////////////////////////////////////////////////////////////////// // Repeat, shuffle, mute //////////////////////////////////////////////////////////////////////////*/ - //region Repeat and shuffle + //region Repeat, shuffle, mute + public void onRepeatClicked() { if (DEBUG) { Log.d(TAG, "onRepeatClicked() called"); @@ -945,52 +951,9 @@ private void setShuffleButton(final boolean shuffled) { /*////////////////////////////////////////////////////////////////////////// - // ExoPlayer listeners (that didn't fit in other categories) + // Other player listeners //////////////////////////////////////////////////////////////////////////*/ - //region ExoPlayer listeners (that didn't fit in other categories) - @Override - public void onTextTracksChanged(@NonNull final Tracks currentTracks) { - super.onTextTracksChanged(currentTracks); - - final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) - || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); - if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null - || !trackTypeTextSupported) { - binding.captionTextView.setVisibility(View.GONE); - return; - } - - // Extract all loaded languages - final List textTracks = currentTracks - .getGroups() - .stream() - .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) - .collect(Collectors.toList()); - final List availableLanguages = textTracks.stream() - .map(Tracks.Group::getMediaTrackGroup) - .filter(textTrack -> textTrack.length > 0) - .map(textTrack -> textTrack.getFormat(0).language) - .collect(Collectors.toList()); - - // Find selected text track - final Optional selectedTracks = textTracks.stream() - .filter(Tracks.Group::isSelected) - .filter(info -> info.getMediaTrackGroup().length >= 1) - .map(info -> info.getMediaTrackGroup().getFormat(0)) - .findFirst(); - - // Build UI - buildCaptionMenu(availableLanguages); - //noinspection SimplifyOptionalCallChains - if (player.getTrackSelector().getParameters().getRendererDisabled( - player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { - binding.captionTextView.setText(R.string.caption_none); - } else { - binding.captionTextView.setText(selectedTracks.get().language); - } - binding.captionTextView.setVisibility( - availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); - } + //region Other player listeners @Override public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { @@ -1004,12 +967,6 @@ public void onRenderedFirstFrame() { //TODO check if this causes black screen when switching to fullscreen animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); } - - @Override - public void onCues(@NonNull List cues) { - super.onCues(cues); - binding.subtitleView.setCues(cues); - } //endregion @@ -1017,6 +974,7 @@ public void onCues(@NonNull List cues) { // Metadata & stream related views //////////////////////////////////////////////////////////////////////////*/ //region Metadata & stream related views + @Override public void onMetadataChanged(@NonNull final StreamInfo info) { super.onMetadataChanged(info); @@ -1092,6 +1050,7 @@ private void updateStreamRelatedViews() { // Popup menus ("popup" means that they pop up, not that they belong to the popup player) //////////////////////////////////////////////////////////////////////////*/ //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) + private void buildQualityMenu() { if (qualityPopupMenu == null) { return; @@ -1315,6 +1274,57 @@ public boolean isSomePopupMenuVisible() { // Captions (text tracks) //////////////////////////////////////////////////////////////////////////*/ //region Captions (text tracks) + + @Override + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + super.onTextTracksChanged(currentTracks); + + final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) + || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); + if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null + || !trackTypeTextSupported) { + binding.captionTextView.setVisibility(View.GONE); + return; + } + + // Extract all loaded languages + final List textTracks = currentTracks + .getGroups() + .stream() + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) + .collect(Collectors.toList()); + final List availableLanguages = textTracks.stream() + .map(Tracks.Group::getMediaTrackGroup) + .filter(textTrack -> textTrack.length > 0) + .map(textTrack -> textTrack.getFormat(0).language) + .collect(Collectors.toList()); + + // Find selected text track + final Optional selectedTracks = textTracks.stream() + .filter(Tracks.Group::isSelected) + .filter(info -> info.getMediaTrackGroup().length >= 1) + .map(info -> info.getMediaTrackGroup().getFormat(0)) + .findFirst(); + + // Build UI + buildCaptionMenu(availableLanguages); + //noinspection SimplifyOptionalCallChains + if (player.getTrackSelector().getParameters().getRendererDisabled( + player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { + binding.captionTextView.setText(R.string.caption_none); + } else { + binding.captionTextView.setText(selectedTracks.get().language); + } + binding.captionTextView.setVisibility( + availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); + } + + @Override + public void onCues(@NonNull List cues) { + super.onCues(cues); + binding.subtitleView.setCues(cues); + } + private void setupSubtitleView() { setupSubtitleView(PlayerHelper.getCaptionScale(context)); final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); @@ -1330,6 +1340,7 @@ private void setupSubtitleView() { // Click listeners //////////////////////////////////////////////////////////////////////////*/ //region Click listeners + @Override public void onClick(final View v) { if (DEBUG) { @@ -1493,9 +1504,10 @@ private void onOpenInBrowserClicked() { /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size + protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { binding.surfaceView.setResizeMode(resizeMode); binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); @@ -1569,6 +1581,7 @@ private void clearVideoSurface() { // Getters //////////////////////////////////////////////////////////////////////////*/ //region Getters + public PlayerBinding getBinding() { return binding; } From 1b39b5376f518ee570258da82fbe4fb09d11c231 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Apr 2022 00:01:59 +0200 Subject: [PATCH 129/240] Add some javadocs; move preparing player uis to PlayerUiList --- .../org/schabi/newpipe/player/Player.java | 25 ++--- .../schabi/newpipe/player/ui/PlayerUi.java | 92 ++++++++++++++++++- .../newpipe/player/ui/PlayerUiList.java | 43 ++++++++- 3 files changed, 138 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index b0fed3d7d70..f8ea7bc90cc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -447,7 +447,7 @@ && isPlaybackResumeEnabled(this) private void initUIsForCurrentPlayerType() { //noinspection SimplifyOptionalCallChains if (!UIs.get(NotificationPlayerUi.class).isPresent()) { - UIs.add(new NotificationPlayerUi(this)); + UIs.addAndPrepare(new NotificationPlayerUi(this)); } if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) @@ -469,24 +469,15 @@ private void initUIsForCurrentPlayerType() { switch (playerType) { case MAIN: UIs.destroyAll(PopupPlayerUi.class); - UIs.add(new MainPlayerUi(this, binding)); - break; - case AUDIO: - UIs.destroyAll(VideoPlayerUi.class); + UIs.addAndPrepare(new MainPlayerUi(this, binding)); break; case POPUP: UIs.destroyAll(MainPlayerUi.class); - UIs.add(new PopupPlayerUi(this, binding)); + UIs.addAndPrepare(new PopupPlayerUi(this, binding)); + break; + case AUDIO: + UIs.destroyAll(VideoPlayerUi.class); break; - } - - if (fragmentListener != null) { - // make sure UIs know whether a service is connected or not - UIs.call(PlayerUi::onFragmentListenerSet); - } - if (!exoPlayerIsNull()) { - UIs.call(PlayerUi::initPlayer); - UIs.call(PlayerUi::initPlayback); } } @@ -1968,9 +1959,9 @@ public int getCaptionRendererIndex() { /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size @Override // exoplayer listener public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { if (DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 81e93ca238f..c4db1f33426 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -18,50 +18,105 @@ import java.util.List; +/** + * A player UI is a component that can seamlessly connect and disconnect from the {@link Player} and + * provide a user interface of some sort. Try to extend this class instead of adding more code to + * {@link Player}! + */ public abstract class PlayerUi { - @NonNull protected Context context; - @NonNull protected Player player; + @NonNull protected final Context context; + @NonNull protected final Player player; + /** + * @param player the player instance that will be usable throughout the lifetime of this UI + */ public PlayerUi(@NonNull final Player player) { this.context = player.getContext(); this.player = player; } + /** + * @return the player instance this UI was constructed with + */ @NonNull public Player getPlayer() { return player; } + /** + * Called after the player received an intent and processed it + */ public void setupAfterIntent() { } + /** + * Called right after the exoplayer instance is constructed, or right after this UI is + * constructed if the exoplayer is already available then. Note that the exoplayer instance + * could be built and destroyed multiple times during the lifetime of the player, so this method + * might be called multiple times. + */ public void initPlayer() { } + /** + * Called when playback in the exoplayer is about to start, or right after this UI is + * constructed if the exoplayer and the play queue are already available then. The play queue + * will therefore always be not null. + */ public void initPlayback() { } + /** + * Called when the exoplayer instance is about to be destroyed. Note that the exoplayer instance + * could be built and destroyed multiple times during the lifetime of the player, so this method + * might be called multiple times. Be sure to unset any video surface view or play queue + * listeners! This will also be called when this UI is being discarded, just before {@link + * #destroy()}. + */ public void destroyPlayer() { } + /** + * Called when this UI is being discarded, either because the player is switching to a different + * UI or because the player is shutting down completely + */ public void destroy() { } + /** + * Called when the player is smooth-stopping, that is, transitioning smoothly to a new play + * queue after the user tapped on a new video stream while a stream was playing in the video + * detail fragment + */ public void smoothStopForImmediateReusing() { } + /** + * Called when the video detail fragment listener is connected with the player, or right after + * this UI is constructed if the listener is already connected then + */ public void onFragmentListenerSet() { } /** - * If you want to register new broadcast actions to receive here, add them to - * {@link Player#setupBroadcastReceiver()}. + * Broadcasts that the player receives will also be notified to UIs here. If you want to + * register new broadcast actions to receive here, add them to {@link + * Player#setupBroadcastReceiver()}. */ public void onBroadcastReceived(final Intent intent) { } + /** + * Called when stream progress (i.e. the current time in the seekbar) or stream duration change. + * Will surely be called every {@link Player#PROGRESS_LOOP_INTERVAL_MILLIS} while a stream is + * playing. + * @param currentProgress the current progress in milliseconds + * @param duration the duration of the stream being played + * @param bufferPercent the percentage of stream already buffered, see {@link + * com.google.android.exoplayer2.BasePlayer#getBufferedPercentage()} + */ public void onUpdateProgress(final int currentProgress, final int duration, final int bufferPercent) { @@ -97,27 +152,56 @@ public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { public void onMuteUnmuteChanged(final boolean isMuted) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onTracksChanged(Tracks) + */ public void onTextTracksChanged(@NonNull final Tracks currentTracks) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onPlaybackParametersChanged + */ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onRenderedFirstFrame + */ public void onRenderedFirstFrame() { } + /** + * @see com.google.android.exoplayer2.text.TextOutput#onCues + */ public void onCues(@NonNull final List cues) { } + /** + * Called when the stream being played changes + * @param info the {@link StreamInfo} metadata object, along with data about the selected and + * available video streams (to be used to build the resolution menus, for example) + */ public void onMetadataChanged(@NonNull final StreamInfo info) { } + /** + * Called when the thumbnail for the current metadata was loaded + * @param bitmap the thumbnail to process, or null if there is no thumbnail or there was an + * error when loading the thumbnail + */ public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { } + /** + * Called when the play queue was edited: a stream was appended, moved or removed. + */ public void onPlayQueueEdited() { } + /** + * @param videoSize the new video size, useful to set the surface aspect ratio + * @see com.google.android.exoplayer2.Player.Listener#onVideoSizeChanged + */ public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 8c5c0dbfabd..749cda02c61 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -8,10 +8,39 @@ public final class PlayerUiList { final List playerUis = new ArrayList<>(); - public void add(final PlayerUi playerUi) { + /** + * Adds the provided player ui to the list and calls on it the initialization functions that + * apply based on the current player state. The preparation step needs to be done since when UIs + * are removed and re-added, the player will not call e.g. initPlayer again since the exoplayer + * is already initialized, but we need to notify the newly built UI that the player is ready + * nonetheless. + * @param playerUi the player ui to prepare and add to the list; its {@link + * PlayerUi#getPlayer()} will be used to query information about the player + * state + */ + public void addAndPrepare(final PlayerUi playerUi) { + if (playerUi.getPlayer().getFragmentListener().isPresent()) { + // make sure UIs know whether a service is connected or not + playerUi.onFragmentListenerSet(); + } + + if (!playerUi.getPlayer().exoPlayerIsNull()) { + playerUi.initPlayer(); + if (playerUi.getPlayer().getPlayQueue() != null) { + playerUi.initPlayback(); + } + } + playerUis.add(playerUi); } + /** + * Destroys all matching player UIs and removes them from the list + * @param playerUiType the class of the player UI to destroy; the {@link + * Class#isInstance(Object)} method will be used, so even subclasses will be + * destroyed and removed + * @param the class type parameter + */ public void destroyAll(final Class playerUiType) { playerUis.stream() .filter(playerUiType::isInstance) @@ -22,6 +51,14 @@ public void destroyAll(final Class playerUiType) { playerUis.removeIf(playerUiType::isInstance); } + /** + * @param playerUiType the class of the player UI to return; the {@link + * Class#isInstance(Object)} method will be used, so even subclasses could + * be returned + * @param the class type parameter + * @return the first player UI of the required type found in the list, or an empty {@link + * Optional} otherwise + */ public Optional get(final Class playerUiType) { return playerUis.stream() .filter(playerUiType::isInstance) @@ -29,6 +66,10 @@ public Optional get(final Class playerUiType) { .findFirst(); } + /** + * Calls the provided consumer on all player UIs in the list + * @param consumer the consumer to call with player UIs + */ public void call(final Consumer consumer) { //noinspection SimplifyStreamApiCallChains playerUis.stream().forEach(consumer); From a19073ec011e7c314ccab2e9d84d466d235fd24a Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 12:03:59 +0200 Subject: [PATCH 130/240] Restore checkstyle and solve its errors --- app/build.gradle | 2 +- .../fragments/detail/VideoDetailFragment.java | 25 +++++++++---------- .../org/schabi/newpipe/player/Player.java | 6 +++-- .../player/notification/NotificationUtil.java | 4 +-- .../player/playback/PlayerMediaSession.java | 2 -- .../newpipe/player/ui/MainPlayerUi.java | 2 +- .../schabi/newpipe/player/ui/PlayerUi.java | 16 +++++++----- .../newpipe/player/ui/PlayerUiList.java | 4 +-- .../newpipe/player/ui/VideoPlayerUi.java | 15 ++++++----- .../custom/NotificationActionsPreference.java | 1 - 10 files changed, 41 insertions(+), 36 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 46eee8d00c6..9867037e6a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,7 +166,7 @@ afterEvaluate { if (!System.properties.containsKey('skipFormatKtlint')) { preDebugBuild.dependsOn formatKtlint } - //preDebugBuild.dependsOn runCheckstyle, runKtlint + preDebugBuild.dependsOn runCheckstyle, runKtlint } sonarqube { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index cb8f0961f5d..8ffff2f9ef1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1,5 +1,16 @@ package org.schabi.newpipe.fragments.detail; +import static android.text.TextUtils.isEmpty; +import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; +import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; +import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; +import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; +import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams; + import android.animation.ValueAnimator; import android.app.Activity; import android.content.BroadcastReceiver; @@ -43,7 +54,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; -import androidx.viewbinding.ViewBinding; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -78,9 +88,9 @@ import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.PlayerService.PlayerType; -import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -118,17 +128,6 @@ import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; -import static android.text.TextUtils.isEmpty; -import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; -import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; -import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; -import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; -import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; -import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; -import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams; - public final class VideoDetailFragment extends BaseStateFragment implements BackPressable, diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index f8ea7bc90cc..0755f9b4de0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -239,6 +239,7 @@ public final class Player implements PlaybackListener, Listener { // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ + @SuppressWarnings("MemberName") // keep the unusual member name private final PlayerUiList UIs = new PlayerUiList(); private BroadcastReceiver broadcastReceiver; @@ -1148,7 +1149,7 @@ public void setRepeatMode(@RepeatMode final int repeatMode) { simpleExoPlayer.setRepeatMode(repeatMode); } } - + public void cycleNextRepeatMode() { setRepeatMode(nextRepeatMode(getRepeatMode())); } @@ -1181,7 +1182,7 @@ public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); notifyPlaybackUpdateToListeners(); } - + public void toggleShuffleModeEnabled() { if (!exoPlayerIsNull()) { simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); @@ -2301,6 +2302,7 @@ public Optional getFragmentListener() { /** * @return the user interfaces connected with the player */ + @SuppressWarnings("MethodName") // keep the unusual method name public PlayerUiList UIs() { return UIs; } diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 5f005245343..28c3b3655ad 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -132,7 +132,7 @@ private synchronized void updateNotification() { // also update content intent, in case the user switched players notificationBuilder.setContentIntent(PendingIntent.getActivity(player.getContext(), - NOTIFICATION_ID, getIntentForNotification(player), FLAG_UPDATE_CURRENT)); + NOTIFICATION_ID, getIntentForNotification(), FLAG_UPDATE_CURRENT)); notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); @@ -321,7 +321,7 @@ private NotificationCompat.Action getAction(@DrawableRes final int drawable, new Intent(intentAction), FLAG_UPDATE_CURRENT)); } - private Intent getIntentForNotification(final Player player) { + private Intent getIntentForNotification() { if (player.audioPlayerSelected() || player.popupPlayerSelected()) { // Means we play in popup or audio only. Let's show the play queue return NavigationHelper.getPlayQueueActivityIntent(player.getContext()); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index 2f261a0fa11..3be9b61734f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -10,8 +10,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.ui.VideoPlayerUi; -import java.util.Optional; - public class PlayerMediaSession implements MediaSessionCallback { private final Player player; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index c62382782e3..3bdda0029ae 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -497,7 +497,7 @@ private void showHideKodiButton() { //region Captions (text tracks) @Override - protected void setupSubtitleView(float captionScale) { + protected void setupSubtitleView(final float captionScale) { final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index c4db1f33426..49980069035 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -46,7 +46,7 @@ public Player getPlayer() { /** - * Called after the player received an intent and processed it + * Called after the player received an intent and processed it. */ public void setupAfterIntent() { } @@ -80,7 +80,7 @@ public void destroyPlayer() { /** * Called when this UI is being discarded, either because the player is switching to a different - * UI or because the player is shutting down completely + * UI or because the player is shutting down completely. */ public void destroy() { } @@ -88,14 +88,14 @@ public void destroy() { /** * Called when the player is smooth-stopping, that is, transitioning smoothly to a new play * queue after the user tapped on a new video stream while a stream was playing in the video - * detail fragment + * detail fragment. */ public void smoothStopForImmediateReusing() { } /** * Called when the video detail fragment listener is connected with the player, or right after - * this UI is constructed if the listener is already connected then + * this UI is constructed if the listener is already connected then. */ public void onFragmentListenerSet() { } @@ -104,6 +104,7 @@ public void onFragmentListenerSet() { * Broadcasts that the player receives will also be notified to UIs here. If you want to * register new broadcast actions to receive here, add them to {@link * Player#setupBroadcastReceiver()}. + * @param intent the broadcast intent received by the player */ public void onBroadcastReceived(final Intent intent) { } @@ -154,12 +155,14 @@ public void onMuteUnmuteChanged(final boolean isMuted) { /** * @see com.google.android.exoplayer2.Player.Listener#onTracksChanged(Tracks) + * @param currentTracks the available tracks information */ public void onTextTracksChanged(@NonNull final Tracks currentTracks) { } /** * @see com.google.android.exoplayer2.Player.Listener#onPlaybackParametersChanged + * @param playbackParameters the new playback parameters */ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { } @@ -172,12 +175,13 @@ public void onRenderedFirstFrame() { /** * @see com.google.android.exoplayer2.text.TextOutput#onCues + * @param cues the cues to pass to the subtitle view */ public void onCues(@NonNull final List cues) { } /** - * Called when the stream being played changes + * Called when the stream being played changes. * @param info the {@link StreamInfo} metadata object, along with data about the selected and * available video streams (to be used to build the resolution menus, for example) */ @@ -185,7 +189,7 @@ public void onMetadataChanged(@NonNull final StreamInfo info) { } /** - * Called when the thumbnail for the current metadata was loaded + * Called when the thumbnail for the current metadata was loaded. * @param bitmap the thumbnail to process, or null if there is no thumbnail or there was an * error when loading the thumbnail */ diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 749cda02c61..05c0ed5b3cc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -35,7 +35,7 @@ public void addAndPrepare(final PlayerUi playerUi) { } /** - * Destroys all matching player UIs and removes them from the list + * Destroys all matching player UIs and removes them from the list. * @param playerUiType the class of the player UI to destroy; the {@link * Class#isInstance(Object)} method will be used, so even subclasses will be * destroyed and removed @@ -67,7 +67,7 @@ public Optional get(final Class playerUiType) { } /** - * Calls the provided consumer on all player UIs in the list + * Calls the provided consumer on all player UIs in the list. * @param consumer the consumer to call with player UIs */ public void call(final Consumer consumer) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index f4ebc3304b5..393bf141bf4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -86,7 +86,8 @@ import java.util.stream.Collectors; public abstract class VideoPlayerUi extends PlayerUi - implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { + implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, + PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { private static final String TAG = VideoPlayerUi.class.getSimpleName(); // time constants @@ -476,7 +477,7 @@ private void updateEndScreenThumbnail() { binding.endScreen.setImageBitmap(endScreenBitmap); } - protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap); + protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull Bitmap bitmap); //endregion @@ -511,6 +512,7 @@ public void onUpdateProgress(final int currentProgress, /** * Sets the current duration into the corresponding elements. + * @param currentProgress the current progress, in milliseconds */ private void updatePlayBackElementsCurrentDuration(final int currentProgress) { // Don't set seekbar progress while user is seeking @@ -522,6 +524,7 @@ private void updatePlayBackElementsCurrentDuration(final int currentProgress) { /** * Sets the video duration time into all control components (e.g. seekbar). + * @param duration the video duration, in milliseconds */ private void setVideoDurationToControls(final int duration) { binding.playbackEndTime.setText(getTimeString(duration)); @@ -1214,7 +1217,7 @@ public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get(); final List availableStreams = quality.getSortedVideoStreams(); final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); - if (selectedStreamIndex == menuItemIndex|| availableStreams.size() <= menuItemIndex) { + if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { return true; } @@ -1320,7 +1323,7 @@ public void onTextTracksChanged(@NonNull final Tracks currentTracks) { } @Override - public void onCues(@NonNull List cues) { + public void onCues(@NonNull final List cues) { super.onCues(cues); binding.subtitleView.setCues(cues); } @@ -1332,7 +1335,7 @@ private void setupSubtitleView() { binding.subtitleView.setStyle(captionStyle); } - protected abstract void setupSubtitleView(final float captionScale); + protected abstract void setupSubtitleView(float captionScale); //endregion @@ -1518,7 +1521,7 @@ void onResizeClicked() { } @Override - public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { super.onVideoSizeChanged(videoSize); binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index dfcf2e5974e..b4f6d598a43 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -28,7 +28,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.notification.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; From 4979f84e4116d114dca851a31d706bec90a93450 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 16:01:23 +0200 Subject: [PATCH 131/240] Solve some Sonarlint warnings --- .../newpipe/local/dialog/PlaylistDialog.java | 37 ++++++++++++++-- .../newpipe/player/PlayQueueActivity.java | 8 ++-- .../org/schabi/newpipe/player/Player.java | 42 +------------------ .../schabi/newpipe/player/PlayerService.java | 17 ++++---- .../gesture/BasePlayerGestureListener.kt | 5 ++- .../player/notification/NotificationUtil.java | 1 + .../newpipe/player/ui/MainPlayerUi.java | 13 +++--- .../schabi/newpipe/player/ui/PlayerUi.java | 2 +- .../newpipe/player/ui/PopupPlayerUi.java | 16 ++++--- .../newpipe/player/ui/VideoPlayerUi.java | 40 +++++++++--------- 10 files changed, 86 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index f568ef81a03..dec8b05b230 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -9,15 +9,20 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.StateSaver; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.Disposable; @@ -131,13 +136,13 @@ protected void setStreamEntities(final List streamEntities) { * @param context context used for accessing the database * @param streamEntities used for crating the dialog * @param onExec execution that should occur after a dialog got created, e.g. showing it - * @return Disposable + * @return the disposable that was created */ public static Disposable createCorrespondingDialog( final Context context, final List streamEntities, - final Consumer onExec - ) { + final Consumer onExec) { + return new LocalPlaylistManager(NewPipeDatabase.getInstance(context)) .hasPlaylists() .observeOn(AndroidSchedulers.mainThread()) @@ -147,4 +152,30 @@ public static Disposable createCorrespondingDialog( : PlaylistCreationDialog.newInstance(streamEntities)) ); } + + /** + * Creates a {@link PlaylistAppendDialog} when playlists exists, + * otherwise a {@link PlaylistCreationDialog}. If the player's play queue is null or empty, no + * dialog will be created. + * + * @param player the player from which to extract the context and the play queue + * @param fragmentManager the fragment manager to use to show the dialog + * @return the disposable that was created + */ + public static Disposable showForPlayQueue( + final Player player, + @NonNull final FragmentManager fragmentManager) { + + final List streamEntities = Stream.of(player.getPlayQueue()) + .filter(Objects::nonNull) + .flatMap(playQueue -> Objects.requireNonNull(playQueue).getStreams().stream()) + .map(StreamEntity::new) + .collect(Collectors.toList()); + if (streamEntities.isEmpty()) { + return Disposable.empty(); + } + + return PlaylistDialog.createCorrespondingDialog(player.getContext(), streamEntities, + dialog -> dialog.show(fragmentManager, "PlaylistDialog")); + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index cdba900f9bf..c18a7f4874d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -29,6 +29,7 @@ import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -53,8 +54,6 @@ public final class PlayQueueActivity extends AppCompatActivity private Player player; - private PlayQueueAdapter adapter = null; - private boolean serviceBound; private ServiceConnection serviceConnection; @@ -128,7 +127,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { NavigationHelper.openSettings(this); return true; case R.id.action_append_playlist: - player.onAddToPlaylistClicked(getSupportFragmentManager()); + PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager()); return true; case R.id.action_playback_speed: openPlaybackParameterDialog(); @@ -441,10 +440,9 @@ public void onStopTrackingTouch(final SeekBar seekBar) { @Override public void onQueueUpdate(@Nullable final PlayQueue queue) { if (queue == null) { - adapter = null; queueControlBinding.playQueue.setAdapter(null); } else { - adapter = new PlayQueueAdapter(this, queue); + final PlayQueueAdapter adapter = new PlayQueueAdapter(this, queue); adapter.setSelectedListener(getOnSelectedListener()); queueControlBinding.playQueue.setAdapter(adapter); } diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 0755f9b4de0..2d44c644918 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -63,16 +63,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; @@ -96,7 +86,6 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; @@ -105,7 +94,6 @@ import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; -import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; @@ -116,6 +104,7 @@ import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; @@ -125,7 +114,6 @@ import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; import org.schabi.newpipe.player.ui.MainPlayerUi; -import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; import org.schabi.newpipe.player.ui.PopupPlayerUi; @@ -137,10 +125,8 @@ import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.IntStream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -1192,32 +1178,6 @@ public void toggleShuffleModeEnabled() { - /*////////////////////////////////////////////////////////////////////////// - // Playlist append TODO this does not make sense here - //////////////////////////////////////////////////////////////////////////*/ - //region Playlist append - - public void onAddToPlaylistClicked(@NonNull final FragmentManager fragmentManager) { - if (DEBUG) { - Log.d(TAG, "onAddToPlaylistClicked() called"); - } - - if (getPlayQueue() != null) { - PlaylistDialog.createCorrespondingDialog( - getContext(), - getPlayQueue() - .getStreams() - .stream() - .map(StreamEntity::new) - .collect(Collectors.toList()), - dialog -> dialog.show(fragmentManager, TAG) - ); - } - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Mute / Unmute //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index b5014eeed09..326b0159006 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -71,16 +71,17 @@ public int onStartCommand(final Intent intent, final int flags, final int startI Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]"); } - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - && player.getPlayQueue() == null) { - // Player is not working, no need to process media button's action - return START_NOT_STICKY; - } - player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); + if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) + || player.getPlayQueue() != null) { + // ^ no need to process media button's action if player is not working + + player.handleIntent(intent); + if (player.getMediaSessionManager() != null) { + player.getMediaSessionManager().handleMediaButtonIntent(intent); + } } + return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index bd5d6f1c5f5..b006e73aac7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -92,7 +92,10 @@ abstract class BasePlayerGestureListener( return true } - return if (onDownNotDoubleTapping(e)) super.onDown(e) else true + if (onDownNotDoubleTapping(e)) { + return super.onDown(e) + } + return true } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 28c3b3655ad..2ba754500ff 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -266,6 +266,7 @@ private NotificationCompat.Action getAction( null); } + // fallthrough case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { return getAction(R.drawable.ic_replay, diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 3bdda0029ae..eebcc81c464 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -51,6 +51,7 @@ import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.info_list.StreamSegmentAdapter; import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; @@ -147,7 +148,8 @@ protected void initListeners() { binding.addToPlaylistButton.setOnClickListener(v -> getParentActivity().map(FragmentActivity::getSupportFragmentManager) - .ifPresent(player::onAddToPlaylistClicked)); + .ifPresent(fragmentManager -> + PlaylistDialog.showForPlayQueue(player, fragmentManager))); settingsContentObserver = new ContentObserver(new Handler()) { @Override @@ -401,6 +403,7 @@ public void onCompleted() { //////////////////////////////////////////////////////////////////////////*/ //region Controls showing / hiding + @Override protected void showOrHideButtons() { super.showOrHideButtons(); @Nullable final PlayQueue playQueue = player.getPlayQueue(); @@ -667,12 +670,11 @@ public void closeItemsList() { } animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { + AnimationType.SLIDE_AND_ALPHA, 0, () -> // Even when queueLayout is GONE it receives touch events // and ruins normal behavior of the app. This line fixes it binding.itemsListPanel.setTranslationY( - -binding.itemsListPanel.getHeight() * 5); - }); + -binding.itemsListPanel.getHeight() * 5.0f)); // clear focus, otherwise a white rectangle remains on top of the player binding.itemsListClose.clearFocus(); @@ -845,8 +847,7 @@ protected void onPlaybackSpeedClicked() { } PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), - player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) - -> player.setPlaybackParameters(speed, pitch, skipSilence)) + player.getPlaybackSkipSilence(), player::setPlaybackParameters) .show(activity.getSupportFragmentManager(), null); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 49980069035..9ce04bfd5c9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -31,7 +31,7 @@ public abstract class PlayerUi { /** * @param player the player instance that will be usable throughout the lifetime of this UI */ - public PlayerUi(@NonNull final Player player) { + protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); this.player = player; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 43440b87359..8283437f88e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.ui; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; @@ -140,8 +141,7 @@ protected void setupElementsVisibility() { binding.segmentsButton.setVisibility(View.GONE); binding.moreOptionsButton.setVisibility(View.GONE); binding.topControls.setOrientation(LinearLayout.HORIZONTAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.WRAP_CONTENT; + binding.primaryControls.getLayoutParams().width = WRAP_CONTENT; binding.secondaryControls.setAlpha(1.0f); binding.secondaryControls.setVisibility(View.VISIBLE); binding.secondaryControls.setTranslationY(0); @@ -193,14 +193,12 @@ public void onBroadcastReceived(final Intent intent) { updateScreenSize(); changePopupSize(popupLayoutParams.width); checkPopupPositionBounds(); - } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - // Use only audio source when screen turns off while popup player is playing - if (player.isPlaying() || player.isLoading()) { + } else if (player.isPlaying() || player.isLoading()) { + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Use only audio source when screen turns off while popup player is playing player.useVideoSource(false); - } - } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { - // Restore video source when screen turns on and user is watching video in popup player - if (player.isPlaying() || player.isLoading()) { + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Restore video source when screen turns on and user was watching video in popup player.useVideoSource(true); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 393bf141bf4..5b0be6f648c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -41,7 +41,6 @@ import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.PopupMenu; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -142,7 +141,7 @@ public abstract class VideoPlayerUi extends PlayerUi //////////////////////////////////////////////////////////////////////////*/ //region Constructor, setup, destroy - public VideoPlayerUi(@NonNull final Player player, + protected VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; @@ -912,7 +911,20 @@ public void onShuffleClicked() { @Override public void onRepeatModeChanged(@RepeatMode final int repeatMode) { super.onRepeatModeChanged(repeatMode); - setRepeatModeButton(binding.repeatButton, repeatMode); + + switch (repeatMode) { + case REPEAT_MODE_OFF: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); + break; + case REPEAT_MODE_ONE: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); + break; + case REPEAT_MODE_ALL: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); + break; + default: + break; // unreachable + } } @Override @@ -927,21 +939,6 @@ public void onMuteUnmuteChanged(final boolean isMuted) { setMuteButton(isMuted); } - private void setRepeatModeButton(final AppCompatImageButton imageButton, - @RepeatMode final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - } - } - private void setMuteButton(final boolean isMuted) { binding.switchMute.setImageDrawable(AppCompatResources.getDrawable(context, isMuted ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); @@ -1037,6 +1034,7 @@ private void updateStreamRelatedViews() { binding.qualityTextView.setVisibility(View.VISIBLE); binding.surfaceView.setVisibility(View.VISIBLE); + // fallthrough default: binding.endScreen.setVisibility(View.GONE); binding.playbackEndTime.setVisibility(View.VISIBLE); @@ -1426,8 +1424,6 @@ public boolean onLongClick(final View v) { public boolean onKeyDown(final int keyCode) { switch (keyCode) { - default: - break; case KeyEvent.KEYCODE_BACK: if (DeviceUtils.isTv(context) && isControlsVisible()) { hideControls(0, 0); @@ -1442,7 +1438,7 @@ public boolean onKeyDown(final int keyCode) { if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) || isAnyListViewOpen()) { // do not interfere with focus in playlist and play queue etc. - return false; + break; } if (player.getCurrentState() == org.schabi.newpipe.player.Player.STATE_BLOCKED) { @@ -1458,6 +1454,8 @@ public boolean onKeyDown(final int keyCode) { return true; } break; + default: + break; // ignore other keys } return false; From 1cf746f7216c173194e17b52abb7bac85763cb23 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 7 Jul 2022 11:09:07 +0200 Subject: [PATCH 132/240] Fix volume gestures not working anymore --- .../gesture/MainPlayerGestureListener.kt | 55 ++++++++++++------- .../newpipe/player/ui/MainPlayerUi.java | 7 ++- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 81e216006c9..fd7b4ecf03f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -8,11 +8,13 @@ import android.view.View.OnTouchListener import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.isVisible import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.helper.AudioReactor import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi import kotlin.math.abs @@ -64,22 +66,27 @@ class MainPlayerGestureListener( } private fun onScrollVolume(distanceY: Float) { + val bar: ProgressBar = binding.volumeProgressBar + val audioReactor: AudioReactor = player.audioReactor + // If we just started sliding, change the progress bar to match the system volume - if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { - val volumePercent: Float = - player.audioReactor.volume / player.audioReactor.maxVolume.toFloat() - binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() + if (!binding.volumeRelativeLayout.isVisible) { + val volumePercent: Float = audioReactor.volume / audioReactor.maxVolume.toFloat() + bar.progress = (volumePercent * bar.max).toInt() } + // Update progress bar binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) - val currentProgressPercent: Float = - binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH - val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt() - player.audioReactor.volume = currentVolume + + // Update volume + val currentProgressPercent: Float = bar.progress / bar.max.toFloat() + val currentVolume = (audioReactor.maxVolume * currentProgressPercent).toInt() + audioReactor.volume = currentVolume if (DEBUG) { Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") } + // Update player center image binding.volumeImageView.setImageDrawable( AppCompatResources.getDrawable( player.context, @@ -92,12 +99,11 @@ class MainPlayerGestureListener( ) ) - if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + // Make sure the correct layout is visible + if (!binding.volumeRelativeLayout.isVisible) { binding.volumeRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) } - if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { - binding.volumeRelativeLayout.visibility = View.GONE - } + binding.brightnessRelativeLayout.isVisible = false } private fun onScrollBrightness(distanceY: Float) { @@ -105,9 +111,13 @@ class MainPlayerGestureListener( val window = parent.window val layoutParams = window.attributes val bar: ProgressBar = binding.brightnessProgressBar + + // Update progress bar val oldBrightness = layoutParams.screenBrightness bar.progress = (bar.max * max(0f, min(1f, oldBrightness))).toInt() bar.incrementProgressBy(distanceY.toInt()) + + // Update brightness val currentProgressPercent = bar.progress.toFloat() / bar.max layoutParams.screenBrightness = currentProgressPercent window.attributes = layoutParams @@ -121,26 +131,32 @@ class MainPlayerGestureListener( "currentBrightness = " + currentProgressPercent ) } + + // Update player center image binding.brightnessImageView.setImageDrawable( AppCompatResources.getDrawable( player.context, - if (currentProgressPercent < 0.25) R.drawable.ic_brightness_low else if (currentProgressPercent < 0.75) R.drawable.ic_brightness_medium else R.drawable.ic_brightness_high + when { + currentProgressPercent < 0.25 -> R.drawable.ic_brightness_low + currentProgressPercent < 0.75 -> R.drawable.ic_brightness_medium + else -> R.drawable.ic_brightness_high + } ) ) - if (binding.brightnessRelativeLayout.visibility != View.VISIBLE) { + + // Make sure the correct layout is visible + if (!binding.brightnessRelativeLayout.isVisible) { binding.brightnessRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) } - if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { - binding.volumeRelativeLayout.visibility = View.GONE - } + binding.volumeRelativeLayout.isVisible = false } override fun onScrollEnd(event: MotionEvent) { super.onScrollEnd(event) - if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + if (binding.volumeRelativeLayout.isVisible) { binding.volumeRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) } - if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + if (binding.brightnessRelativeLayout.isVisible) { binding.brightnessRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) } } @@ -210,7 +226,6 @@ class MainPlayerGestureListener( private val TAG = MainPlayerGestureListener::class.java.simpleName private val DEBUG = MainActivity.DEBUG private const val MOVEMENT_THRESHOLD = 40 - const val MAX_GESTURE_LENGTH = 0.75f private fun getNavigationBarHeight(context: Context): Int { val resId = context.resources diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index eebcc81c464..d9f5ea7f43c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -520,12 +520,13 @@ protected void setupSubtitleView(final float captionScale) { public void onLayoutChange(final View view, final int l, final int t, final int r, final int b, final int ol, final int ot, final int or, final int ob) { if (l != ol || t != ot || r != or || b != ob) { - // Use smaller value to be consistent between screen orientations - // (and to make usage easier) + // Use a smaller value to be consistent across screen orientations, and to make usage + // easier. Multiply by 3/4 to ensure the user does not need to move the finger up to the + // screen border, in order to reach the maximum volume/brightness. final int width = r - l; final int height = b - t; final int min = Math.min(width, height); - final int maxGestureLength = (int) (min * MainPlayerGestureListener.MAX_GESTURE_LENGTH); + final int maxGestureLength = (int) (min * 0.75); if (DEBUG) { Log.d(TAG, "maxGestureLength = " + maxGestureLength); From 9c51fc3adeaac4670da0bead156315b286f8b5c3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 7 Jul 2022 11:59:00 +0200 Subject: [PATCH 133/240] Move functions to get Android dimen to ThemeHelper --- .../gesture/MainPlayerGestureListener.kt | 28 ++++++------------- .../org/schabi/newpipe/util/ThemeHelper.java | 16 +++++++++++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index fd7b4ecf03f..095b3ccdb77 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -1,6 +1,5 @@ package org.schabi.newpipe.player.gesture -import android.content.Context import android.util.Log import android.view.MotionEvent import android.view.View @@ -17,6 +16,7 @@ import org.schabi.newpipe.player.Player import org.schabi.newpipe.player.helper.AudioReactor import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi +import org.schabi.newpipe.util.ThemeHelper.getAndroidDimenPx import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -172,9 +172,13 @@ class MainPlayerGestureListener( return false } - val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(player.context) - val isTouchingNavigationBar: Boolean = - initialEvent.y > (binding.root.height - getNavigationBarHeight(player.context)) + // Calculate heights of status and navigation bars + val statusBarHeight = getAndroidDimenPx(player.context, "status_bar_height") + val navigationBarHeight = getAndroidDimenPx(player.context, "navigation_bar_height") + + // Do not handle this event if initially it started from status or navigation bars + val isTouchingStatusBar = initialEvent.y < statusBarHeight + val isTouchingNavigationBar = initialEvent.y > (binding.root.height - navigationBarHeight) if (isTouchingStatusBar || isTouchingNavigationBar) { return false } @@ -226,21 +230,5 @@ class MainPlayerGestureListener( private val TAG = MainPlayerGestureListener::class.java.simpleName private val DEBUG = MainActivity.DEBUG private const val MOVEMENT_THRESHOLD = 40 - - private fun getNavigationBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("navigation_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - private fun getStatusBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("status_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index b8e3a86ed38..389af80eefe 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -244,6 +244,22 @@ public static Drawable resolveDrawable(@NonNull final Context context, return AppCompatResources.getDrawable(context, typedValue.resourceId); } + /** + * Gets a runtime dimen from the {@code android} package. Should be used for dimens for which + * normal accessing with {@code R.dimen.} is not available. + * + * @param context context + * @param name dimen resource name (e.g. navigation_bar_height) + * @return the obtained dimension, in pixels, or 0 if the resource could not be resolved + */ + public static int getAndroidDimenPx(@NonNull final Context context, final String name) { + final int resId = context.getResources().getIdentifier(name, "dimen", "android"); + if (resId <= 0) { + return 0; + } + return context.getResources().getDimensionPixelSize(resId); + } + private static String getSelectedThemeKey(final Context context) { final String themeKey = context.getString(R.string.theme_key); final String defaultTheme = context.getResources().getString(R.string.default_theme_value); From 3692858a3d10fb33d0386149d630264ca93eca23 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 8 Jul 2022 22:33:35 +0200 Subject: [PATCH 134/240] Move popup layout param to PopupPlayerUi --- .../gesture/PopupPlayerGestureListener.kt | 65 ++++++----- .../newpipe/player/helper/PlayerHelper.java | 103 +--------------- .../newpipe/player/ui/PopupPlayerUi.java | 110 +++++++++++++++++- 3 files changed, 141 insertions(+), 137 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index b8c1bc54c5f..bda6ee8d10f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -7,7 +7,6 @@ import android.view.ViewConfiguration import org.schabi.newpipe.MainActivity import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.PopupPlayerUi import kotlin.math.abs import kotlin.math.hypot @@ -87,7 +86,7 @@ class PopupPlayerGestureListener( player.changeState(player.currentState) } if (!playerUi.isPopupClosing) { - PlayerHelper.savePopupPositionAndSizeToPrefs(playerUi) + playerUi.savePopupPositionAndSizeToPrefs() } } @@ -106,40 +105,42 @@ class PopupPlayerGestureListener( } private fun handleMultiDrag(event: MotionEvent): Boolean { - if (initPointerDistance != -1.0 && event.pointerCount == 2) { - // get the movements of the fingers - val firstPointerMove = hypot( - event.getX(0) - initFirstPointerX.toDouble(), - event.getY(0) - initFirstPointerY.toDouble() - ) - val secPointerMove = hypot( - event.getX(1) - initSecPointerX.toDouble(), - event.getY(1) - initSecPointerY.toDouble() - ) + if (initPointerDistance == -1.0 || event.pointerCount != 2) { + return false + } - // minimum threshold beyond which pinch gesture will work - val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop + // get the movements of the fingers + val firstPointerMove = hypot( + event.getX(0) - initFirstPointerX.toDouble(), + event.getY(0) - initFirstPointerY.toDouble() + ) + val secPointerMove = hypot( + event.getX(1) - initSecPointerX.toDouble(), + event.getY(1) - initSecPointerY.toDouble() + ) + + // minimum threshold beyond which pinch gesture will work + val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop + if (max(firstPointerMove, secPointerMove) <= minimumMove) { + return false + } - if (max(firstPointerMove, secPointerMove) > minimumMove) { - // calculate current distance between the pointers - val currentPointerDistance = hypot( - event.getX(0) - event.getX(1).toDouble(), - event.getY(0) - event.getY(1).toDouble() - ) + // calculate current distance between the pointers + val currentPointerDistance = hypot( + event.getX(0) - event.getX(1).toDouble(), + event.getY(0) - event.getY(1).toDouble() + ) - val popupWidth = playerUi.popupLayoutParams.width.toDouble() - // change co-ordinates of popup so the center stays at the same position - val newWidth = popupWidth * currentPointerDistance / initPointerDistance - initPointerDistance = currentPointerDistance - playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() + val popupWidth = playerUi.popupLayoutParams.width.toDouble() + // change co-ordinates of popup so the center stays at the same position + val newWidth = popupWidth * currentPointerDistance / initPointerDistance + initPointerDistance = currentPointerDistance + playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() - playerUi.checkPopupPositionBounds() - playerUi.updateScreenSize() - playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) - return true - } - } - return false + playerUi.checkPopupPositionBounds() + playerUi.updateScreenSize() + playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) + return true } private fun onPopupResizingStart() { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index ec4cf8602a5..d1d29dd71a1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -10,19 +10,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -import static org.schabi.newpipe.player.ui.PopupPlayerUi.IDLE_WINDOW_FLAGS; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.graphics.PixelFormat; -import android.os.Build; import android.provider.Settings; -import android.view.Gravity; -import android.view.ViewGroup; -import android.view.WindowManager; import android.view.accessibility.CaptioningManager; import androidx.annotation.IntDef; @@ -49,12 +43,11 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.player.ui.PopupPlayerUi; import org.schabi.newpipe.util.ListHelper; import java.lang.annotation.Retention; @@ -77,20 +70,6 @@ public final class PlayerHelper { private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); - /** - * Maximum opacity allowed for Android 12 and higher to allow touches on other apps when using - * NewPipe's popup player. - * - *

- * This value is hardcoded instead of being get dynamically with the method linked of the - * constant documentation below, because it is not static and popup player layout parameters - * are generated with static methods. - *

- * - * @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE - */ - private static final float MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER = 0.8f; - @Retention(SOURCE) @IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI, AUTOPLAY_TYPE_NEVER}) @@ -525,90 +504,10 @@ public static void savePlaybackParametersToPrefs(final Player player, .apply(); } - /** - * @param playerUi {@code screenWidth} and {@code screenHeight} must have been initialized - * @return the popup starting layout params - */ - @SuppressLint("RtlHardcoded") - public static WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs( - final PopupPlayerUi playerUi) { - final SharedPreferences prefs = playerUi.getPlayer().getPrefs(); - final Context context = playerUi.getPlayer().getContext(); - - final boolean popupRememberSizeAndPos = prefs.getBoolean( - context.getString(R.string.popup_remember_size_pos_key), true); - final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); - final float popupWidth = popupRememberSizeAndPos - ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) - : defaultSize; - final float popupHeight = getMinimumVideoHeight(popupWidth); - - final WindowManager.LayoutParams popupLayoutParams = new WindowManager.LayoutParams( - (int) popupWidth, (int) popupHeight, - popupLayoutParamType(), - IDLE_WINDOW_FLAGS, - PixelFormat.TRANSLUCENT); - popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - - final int centerX = (int) (playerUi.getScreenWidth() / 2f - popupWidth / 2f); - final int centerY = (int) (playerUi.getScreenHeight() / 2f - popupHeight / 2f); - popupLayoutParams.x = popupRememberSizeAndPos - ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; - popupLayoutParams.y = popupRememberSizeAndPos - ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; - - return popupLayoutParams; - } - - public static void savePopupPositionAndSizeToPrefs(final PopupPlayerUi playerUi) { - if (playerUi.getPopupLayoutParams() != null) { - final Context context = playerUi.getPlayer().getContext(); - playerUi.getPlayer().getPrefs().edit() - .putFloat(context.getString(R.string.popup_saved_width_key), - playerUi.getPopupLayoutParams().width) - .putInt(context.getString(R.string.popup_saved_x_key), - playerUi.getPopupLayoutParams().x) - .putInt(context.getString(R.string.popup_saved_y_key), - playerUi.getPopupLayoutParams().y) - .apply(); - } - } - public static float getMinimumVideoHeight(final float width) { return width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have } - @SuppressLint("RtlHardcoded") - public static WindowManager.LayoutParams buildCloseOverlayLayoutParams() { - final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - - final WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - popupLayoutParamType(), - flags, - PixelFormat.TRANSLUCENT); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Setting maximum opacity allowed for touch events to other apps for Android 12 and - // higher to prevent non interaction when using other apps with the popup player - closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER; - } - - closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - closeOverlayLayoutParams.softInputMode = - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - return closeOverlayLayoutParams; - } - - public static int popupLayoutParamType() { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.O - ? WindowManager.LayoutParams.TYPE_PHONE - : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - } - public static int retrieveSeekDurationFromPreferences(final Player player) { return Integer.parseInt(Objects.requireNonNull(player.getPrefs().getString( player.getContext().getString(R.string.seek_duration_key), diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 8283437f88e..46396a84036 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -2,21 +2,25 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.schabi.newpipe.MainActivity.DEBUG; -import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.os.Build; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AnticipateInterpolator; import android.widget.LinearLayout; @@ -38,6 +42,20 @@ public final class PopupPlayerUi extends VideoPlayerUi { private static final String TAG = PopupPlayerUi.class.getSimpleName(); + /** + * Maximum opacity allowed for Android 12 and higher to allow touches on other apps when using + * NewPipe's popup player. + * + *

+ * This value is hardcoded instead of being get dynamically with the method linked of the + * constant documentation below, because it is not static and popup player layout parameters + * are generated with static methods. + *

+ * + * @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE + */ + private static final float MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER = 0.8f; + /*////////////////////////////////////////////////////////////////////////// // Popup player //////////////////////////////////////////////////////////////////////////*/ @@ -98,7 +116,7 @@ private void initPopup() { updateScreenSize(); - popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); + popupLayoutParams = retrievePopupLayoutParamsFromPrefs(); binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); checkPopupPositionBounds(); @@ -446,6 +464,92 @@ public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent //endregion + /*////////////////////////////////////////////////////////////////////////// + // Popup & closing overlay layout params + saving popup position and size + //////////////////////////////////////////////////////////////////////////*/ + //region Popup & closing overlay layout params + saving popup position and size + + /** + * {@code screenWidth} and {@code screenHeight} must have been initialized. + * @return the popup starting layout params + */ + @SuppressLint("RtlHardcoded") + public WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs() { + final SharedPreferences prefs = getPlayer().getPrefs(); + final Context context = getPlayer().getContext(); + + final boolean popupRememberSizeAndPos = prefs.getBoolean( + context.getString(R.string.popup_remember_size_pos_key), true); + final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); + final float popupWidth = popupRememberSizeAndPos + ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) + : defaultSize; + final float popupHeight = getMinimumVideoHeight(popupWidth); + + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + (int) popupWidth, (int) popupHeight, + popupLayoutParamType(), + IDLE_WINDOW_FLAGS, + PixelFormat.TRANSLUCENT); + params.gravity = Gravity.LEFT | Gravity.TOP; + params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + + final int centerX = (int) (screenWidth / 2f - popupWidth / 2f); + final int centerY = (int) (screenHeight / 2f - popupHeight / 2f); + params.x = popupRememberSizeAndPos + ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; + params.y = popupRememberSizeAndPos + ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; + + return params; + } + + public void savePopupPositionAndSizeToPrefs() { + if (getPopupLayoutParams() != null) { + final Context context = getPlayer().getContext(); + getPlayer().getPrefs().edit() + .putFloat(context.getString(R.string.popup_saved_width_key), + popupLayoutParams.width) + .putInt(context.getString(R.string.popup_saved_x_key), + popupLayoutParams.x) + .putInt(context.getString(R.string.popup_saved_y_key), + popupLayoutParams.y) + .apply(); + } + } + + @SuppressLint("RtlHardcoded") + public static WindowManager.LayoutParams buildCloseOverlayLayoutParams() { + final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + + final WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, + popupLayoutParamType(), + flags, + PixelFormat.TRANSLUCENT); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Setting maximum opacity allowed for touch events to other apps for Android 12 and + // higher to prevent non interaction when using other apps with the popup player + closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER; + } + + closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + closeOverlayLayoutParams.softInputMode = + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + return closeOverlayLayoutParams; + } + + public static int popupLayoutParamType() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.O + ? WindowManager.LayoutParams.TYPE_PHONE + : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } + //endregion + + /*////////////////////////////////////////////////////////////////////////// // Getters //////////////////////////////////////////////////////////////////////////*/ From 61c1da144e4b0a824d4816d9ecad6a59b53d9cf3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:17:30 +0200 Subject: [PATCH 135/240] Some refactorings after review comments --- .../fragments/detail/VideoDetailFragment.java | 11 +++-- .../newpipe/local/dialog/PlaylistDialog.java | 2 +- .../schabi/newpipe/player/PlayerService.java | 23 ++++----- .../newpipe/player/ui/MainPlayerUi.java | 47 ++++++++----------- .../newpipe/player/ui/VideoPlayerUi.java | 19 +++----- 5 files changed, 42 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 8ffff2f9ef1..5dc6bb436de 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1311,11 +1311,12 @@ private void addVideoPlayerView() { setHeightThumbnail(); // Prevent from re-adding a view multiple times - new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); - })); + new Handler(Looper.getMainLooper()).post(() -> + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + })); } private void removeVideoPlayerView() { diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index dec8b05b230..612c3818187 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -168,7 +168,7 @@ public static Disposable showForPlayQueue( final List streamEntities = Stream.of(player.getPlayQueue()) .filter(Objects::nonNull) - .flatMap(playQueue -> Objects.requireNonNull(playQueue).getStreams().stream()) + .flatMap(playQueue -> playQueue.getStreams().stream()) .map(StreamEntity::new) .collect(Collectors.toList()); if (streamEntities.isEmpty()) { diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 326b0159006..14e8262d6fb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -33,8 +33,6 @@ /** * One service for all players. - * - * @author mauriciocolli */ public final class PlayerService extends Service { private static final String TAG = PlayerService.class.getSimpleName(); @@ -72,14 +70,16 @@ public int onStartCommand(final Intent intent, final int flags, final int startI + "], flags = [" + flags + "], startId = [" + startId + "]"); } - if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - || player.getPlayQueue() != null) { - // ^ no need to process media button's action if player is not working + if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) + && player.getPlayQueue() == null) { + // No need to process media button's actions if the player is not working, otherwise the + // player service would strangely start with nothing to play + return START_NOT_STICKY; + } - player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } + player.handleIntent(intent); + if (player.getMediaSessionManager() != null) { + player.getMediaSessionManager().handleMediaButtonIntent(intent); } return START_NOT_STICKY; @@ -97,11 +97,6 @@ public void stopForImmediateReusing() { // We can't just pause the player here because it will make transition // from one stream to a new stream not smooth player.smoothStopForImmediateReusing(); - - // Notification shows information about old stream but if a user selects - // a stream from backStack it's not actual anymore - // So we should hide the notification at all. - // When autoplay enabled such notification flashing is annoying so skip this case } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index d9f5ea7f43c..278e4f1fffa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -75,6 +75,11 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutChangeListener { private static final String TAG = MainPlayerUi.class.getSimpleName(); + // see the Javadoc of calculateMaxEndScreenThumbnailHeight for information + private static final int DETAIL_ROOT_MINIMUM_HEIGHT = 85; // dp + private static final int DETAIL_TITLE_TEXT_SIZE_TV = 16; // sp + private static final int DETAIL_TITLE_TEXT_SIZE_TABLET = 15; // sp + private boolean isFullscreen = false; private boolean isVerticalVideo = false; private boolean fragmentIsVisible = false; @@ -262,13 +267,8 @@ protected void setupElementsVisibility() { binding.topControls.setClickable(true); binding.topControls.setFocusable(true); - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - } + binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); } @Override @@ -450,13 +450,12 @@ public void hideSystemUIIfNeeded() { * The calculating follows these rules: *
    *
  • - * Show at least stream title and content creator on TVs and tablets - * when in landscape (always the case for TVs) and not in fullscreen mode. - * This requires to have at least 85dp free space for {@link R.id.detail_root} - * and additional space for the stream title text size - * ({@link R.id.detail_title_root_layout}). - * The text size is 15sp on tablets and 16sp on TVs, - * see {@link R.id.titleTextView}. + * Show at least stream title and content creator on TVs and tablets when in landscape + * (always the case for TVs) and not in fullscreen mode. This requires to have at least + * {@link #DETAIL_ROOT_MINIMUM_HEIGHT} free space for {@link R.id.detail_root} and + * additional space for the stream title text size ({@link R.id.detail_title_root_layout}). + * The text size is {@link #DETAIL_TITLE_TEXT_SIZE_TABLET} on tablets and + * {@link #DETAIL_TITLE_TEXT_SIZE_TV} on TVs, see {@link R.id.titleTextView}. *
  • *
  • * Otherwise, the max thumbnail height is the screen height. @@ -472,12 +471,12 @@ protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitma final int screenHeight = context.getResources().getDisplayMetrics().heightPixels; if (DeviceUtils.isTv(context) && !isFullscreen()) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); + final int videoInfoHeight = DeviceUtils.dpToPx(DETAIL_ROOT_MINIMUM_HEIGHT, context) + + DeviceUtils.spToPx(DETAIL_TITLE_TEXT_SIZE_TV, context); return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); } else if (DeviceUtils.isTablet(context) && isLandscape() && !isFullscreen()) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); + final int videoInfoHeight = DeviceUtils.dpToPx(DETAIL_ROOT_MINIMUM_HEIGHT, context) + + DeviceUtils.spToPx(DETAIL_TITLE_TEXT_SIZE_TABLET, context); return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); } else { // fullscreen player: max height is the device height return Math.min(bitmap.getHeight(), screenHeight); @@ -933,15 +932,9 @@ public void toggleFullscreen() { } fragmentListener.onFullscreenStateChanged(isFullscreen); - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(View.GONE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility(View.VISIBLE); - } + binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); setupScreenRotationButton(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 5b0be6f648c..4d106511251 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.player.ui; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -912,18 +911,12 @@ public void onShuffleClicked() { public void onRepeatModeChanged(@RepeatMode final int repeatMode) { super.onRepeatModeChanged(repeatMode); - switch (repeatMode) { - case REPEAT_MODE_OFF: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - default: - break; // unreachable + if (repeatMode == REPEAT_MODE_ALL) { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); + } else if (repeatMode == REPEAT_MODE_ONE) { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); + } else /* repeatMode == REPEAT_MODE_OFF */ { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); } } From c03eac1dc99e9e61b863b5984444407dc8ee13a2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:50:12 +0200 Subject: [PATCH 136/240] Some SonarLint refactors --- .../player/gesture/BasePlayerGestureListener.kt | 3 ++- .../newpipe/player/gesture/DoubleTapListener.kt | 6 +++--- .../org/schabi/newpipe/player/ui/MainPlayerUi.java | 11 +++++------ .../org/schabi/newpipe/player/ui/PopupPlayerUi.java | 3 +-- .../org/schabi/newpipe/player/ui/VideoPlayerUi.java | 3 ++- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index b006e73aac7..555c34f9632 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -1,6 +1,7 @@ package org.schabi.newpipe.player.gesture import android.os.Handler +import android.os.Looper import android.util.Log import android.view.GestureDetector import android.view.MotionEvent @@ -130,7 +131,7 @@ abstract class BasePlayerGestureListener( } private var doubleTapDelay = DOUBLE_TAP_DELAY - private val doubleTapHandler: Handler = Handler() + private val doubleTapHandler: Handler = Handler(Looper.getMainLooper()) private val doubleTapRunnable = Runnable { if (DEBUG) Log.d(TAG, "doubleTapRunnable called") diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt index 1a0b141e648..fc026abd9b2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt @@ -1,7 +1,7 @@ package org.schabi.newpipe.player.gesture interface DoubleTapListener { - fun onDoubleTapStarted(portion: DisplayPortion) {} - fun onDoubleTapProgressDown(portion: DisplayPortion) {} - fun onDoubleTapFinished() {} + fun onDoubleTapStarted(portion: DisplayPortion) + fun onDoubleTapProgressDown(portion: DisplayPortion) + fun onDoubleTapFinished() } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 278e4f1fffa..52a486addc6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.ui; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -21,6 +22,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.os.Handler; +import android.os.Looper; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; @@ -156,7 +158,7 @@ protected void initListeners() { .ifPresent(fragmentManager -> PlaylistDialog.showForPlayQueue(player, fragmentManager))); - settingsContentObserver = new ContentObserver(new Handler()) { + settingsContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { @Override public void onChange(final boolean selfChange) { setupScreenRotationButton(); @@ -237,8 +239,7 @@ public void smoothStopForImmediateReusing() { private void initVideoPlayer() { // restore last resize mode setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(player)); - binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } @Override @@ -253,8 +254,7 @@ protected void setupElementsVisibility() { binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); binding.moreOptionsButton.setVisibility(View.VISIBLE); binding.topControls.setOrientation(LinearLayout.VERTICAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.MATCH_PARENT; + binding.primaryControls.getLayoutParams().width = MATCH_PARENT; binding.secondaryControls.setVisibility(View.INVISIBLE); binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_expand_more)); @@ -459,7 +459,6 @@ public void hideSystemUIIfNeeded() { *
  • *
  • * Otherwise, the max thumbnail height is the screen height. - * TODO investigate why this is done on popup player, too *
  • *
* diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 46396a84036..bb810f86bc1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -287,8 +287,7 @@ public void changePopupSize(final int width) { } final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); - final int actualWidth = (int) (width > screenWidth ? screenWidth - : (width < minimumWidth ? minimumWidth : width)); + final int actualWidth = Math.min((int) Math.max(width, minimumWidth), screenWidth); final int actualHeight = (int) getMinimumVideoHeight(width); if (DEBUG) { Log.d(TAG, "updatePopupSize() updated values:" diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 4d106511251..bdb327df186 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -25,6 +25,7 @@ import android.net.Uri; import android.os.Build; import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; @@ -103,7 +104,7 @@ public abstract class VideoPlayerUi extends PlayerUi //////////////////////////////////////////////////////////////////////////*/ protected PlayerBinding binding; - private final Handler controlsVisibilityHandler = new Handler(); + private final Handler controlsVisibilityHandler = new Handler(Looper.getMainLooper()); @Nullable private SurfaceHolderCallback surfaceHolderCallback; boolean surfaceIsSetup = false; @Nullable private Bitmap thumbnail = null; From 4443c908cb203e97df5e2624c34681aea2cfe129 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:58:03 +0200 Subject: [PATCH 137/240] Fix SonarLint java:S5320, restrict broadcasts to app package --- .../java/org/schabi/newpipe/player/ui/VideoPlayerUi.java | 5 ++++- .../settings/custom/NotificationActionsPreference.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index bdb327df186..d38c8cfe4bd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -57,6 +57,7 @@ import com.google.android.exoplayer2.ui.CaptionStyleCompat; import com.google.android.exoplayer2.video.VideoSize; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.extractor.MediaFormat; @@ -1372,7 +1373,9 @@ public void onClick(final View v) { } else if (v.getId() == binding.switchMute.getId()) { player.toggleMute(); } else if (v.getId() == binding.playerCloseButton.getId()) { - context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); + // set package to this app's package to prevent the intent from being seen outside + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER) + .setPackage(App.PACKAGE_NAME)); } else if (v.getId() == binding.playbackSpeed.getId()) { onPlaybackSpeedClicked(); } else if (v.getId() == binding.qualityTextView.getId()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index b4f6d598a43..03b5a5a9507 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -25,6 +25,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; @@ -62,7 +63,9 @@ public void onBindViewHolder(@NonNull final PreferenceViewHolder holder) { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION)); + // set package to this app's package to prevent the intent from being seen outside + getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION) + .setPackage(App.PACKAGE_NAME)); } From 8187a3bc04704e8b1cc380ccbf0c0274a4514494 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 10 Jul 2022 23:05:37 +0200 Subject: [PATCH 138/240] Move PlayerType into its own class and add documentation Also replace some `isPlayerOpen` with direct `playerType == null` checks. --- .../org/schabi/newpipe/RouterActivity.java | 6 ++-- .../fragments/detail/VideoDetailFragment.java | 2 +- .../list/channel/ChannelFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../org/schabi/newpipe/player/Player.java | 4 +-- .../schabi/newpipe/player/PlayerService.java | 5 --- .../org/schabi/newpipe/player/PlayerType.java | 32 +++++++++++++++++++ .../newpipe/player/helper/PlayerHelper.java | 9 ------ .../newpipe/player/helper/PlayerHolder.java | 7 ++-- .../schabi/newpipe/util/NavigationHelper.java | 28 ++++++++-------- 11 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerType.java diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 1194b406850..d055da1e8f6 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -60,7 +60,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.dialog.PlaylistDialog; -import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; @@ -630,8 +630,8 @@ private boolean canHandleChoiceLikeShowInfo(final String selectedChoiceKey) { } // ...the player is not running or in normal Video-mode/type - final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); - return playerType == null || playerType == PlayerService.PlayerType.MAIN; + final PlayerType playerType = PlayerHolder.getInstance().getType(); + return playerType == null || playerType == PlayerType.MAIN; } private void openAddToPlaylistDialog() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 5dc6bb436de..92e7e4f160b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -90,7 +90,7 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.helper.PlayerHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index aabd64744c0..e44048473aa 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.local.feed.notifications.NotificationHelper; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 65fd8ada1c6..e3caeb522b0 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 3bec07dcc59..7fc72e06418 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.Localization; diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 2d44c644918..17b5a1985c0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -32,7 +32,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; @@ -95,7 +94,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; @@ -308,7 +306,7 @@ public void handleIntent(@NonNull final Intent intent) { } final PlayerType oldPlayerType = playerType; - playerType = retrievePlayerTypeFromIntent(intent); + playerType = PlayerType.retrieveFromIntent(intent); initUIsForCurrentPlayerType(); // We need to setup audioOnly before super(), see "sourceOf" isAudioOnly = audioPlayerSelected(); diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 14e8262d6fb..8d982617a64 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -42,11 +42,6 @@ public final class PlayerService extends Service { private final IBinder mBinder = new PlayerService.LocalBinder(); - public enum PlayerType { - MAIN, - AUDIO, - POPUP - } /*////////////////////////////////////////////////////////////////////////// // Service's LifeCycle diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerType.java b/app/src/main/java/org/schabi/newpipe/player/PlayerType.java new file mode 100644 index 00000000000..171a703953c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerType.java @@ -0,0 +1,32 @@ +package org.schabi.newpipe.player; + +import static org.schabi.newpipe.player.Player.PLAYER_TYPE; + +import android.content.Intent; + +public enum PlayerType { + MAIN, + AUDIO, + POPUP; + + /** + * @return an integer representing this {@link PlayerType}, to be used to save it in intents + * @see #retrieveFromIntent(Intent) Use retrieveFromIntent() to retrieve and convert player type + * integers from an intent + */ + public int valueForIntent() { + return ordinal(); + } + + /** + * @param intent the intent to retrieve a player type from + * @return the player type integer retrieved from the intent, converted back into a {@link + * PlayerType}, or {@link PlayerType#MAIN} if there is no player type extra in the + * intent + * @throws ArrayIndexOutOfBoundsException if the intent contains an invalid player type integer + * @see #valueForIntent() Use valueForIntent() to obtain valid player type integers + */ + public static PlayerType retrieveFromIntent(final Intent intent) { + return values()[intent.getIntExtra(PLAYER_TYPE, MAIN.valueForIntent())]; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index d1d29dd71a1..fb346f5ba05 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -3,7 +3,6 @@ import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.Player.PLAYER_TYPE; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI; @@ -14,7 +13,6 @@ import android.annotation.SuppressLint; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.provider.Settings; import android.view.accessibility.CaptioningManager; @@ -44,7 +42,6 @@ import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; @@ -428,12 +425,6 @@ private static SinglePlayQueue getAutoQueuedSinglePlayQueue( // Utils used by player //////////////////////////////////////////////////////////////////////////// - public static PlayerService.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { - // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra - return PlayerService.PlayerType.values()[ - intent.getIntExtra(PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal())]; - } - public static boolean isPlaybackResumeEnabled(final Player player) { return player.getPrefs().getBoolean( player.getContext().getString(R.string.enable_watch_history_key), true) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index cb613f8541e..5eaecd48dec 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -18,6 +18,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -46,13 +47,13 @@ public static synchronized PlayerHolder getInstance() { @Nullable private Player player; /** - * Returns the current {@link PlayerService.PlayerType} of the {@link PlayerService} service, - * otherwise `null` if no service running. + * Returns the current {@link PlayerType} of the {@link PlayerService} service, + * otherwise `null` if no service is running. * * @return Current PlayerType */ @Nullable - public PlayerService.PlayerType getType() { + public PlayerType getType() { if (player == null) { return null; } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 36b2bd46d4f..3b2c52691c5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -51,9 +51,9 @@ import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.PlayQueueActivity; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -91,7 +91,7 @@ public static Intent getPlayerIntent(@NonNull final Context context, intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey); } } - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent()); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); return intent; @@ -164,7 +164,7 @@ public static void playOnPopupPlayer(final Context context, Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.POPUP.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -175,7 +175,7 @@ public static void playOnBackgroundPlayer(final Context context, .show(); final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.AUDIO.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -186,15 +186,15 @@ public static void enqueueOnPlayer(final Context context, Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue); - intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent()); ContextCompat.startForegroundService(context, intent); } public static void enqueueOnPlayer(final Context context, final PlayQueue queue) { PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + if (playerType == null) { Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); - playerType = PlayerService.PlayerType.AUDIO; + playerType = PlayerType.AUDIO; } enqueueOnPlayer(context, queue, playerType); @@ -203,14 +203,14 @@ public static void enqueueOnPlayer(final Context context, final PlayQueue queue) /* ENQUEUE NEXT */ public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) { PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + if (playerType == null) { Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); - playerType = PlayerService.PlayerType.AUDIO; + playerType = PlayerType.AUDIO; } Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue); - intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -414,14 +414,14 @@ public static void openVideoDetailFragment(@NonNull final Context context, final boolean switchingPlayers) { final boolean autoPlay; - @Nullable final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + @Nullable final PlayerType playerType = PlayerHolder.getInstance().getType(); + if (playerType == null) { // no player open autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else if (switchingPlayers) { // switching player to main player autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state - } else if (playerType == PlayerService.PlayerType.MAIN) { + } else if (playerType == PlayerType.MAIN) { // opening new stream while already playing in main player autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else { @@ -436,7 +436,7 @@ public static void openVideoDetailFragment(@NonNull final Context context, // Situation when user switches from players to main player. All needed data is // here, we can start watching (assuming newQueue equals playQueue). // Starting directly in fullscreen if the previous player type was popup. - detailFragment.openVideoPlayer(playerType == PlayerService.PlayerType.POPUP + detailFragment.openVideoPlayer(playerType == PlayerType.POPUP || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)); } else { detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue); From 4536e8b55b59502471cd0b409f8b68e8cff600d2 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Jul 2022 01:48:52 -0400 Subject: [PATCH 139/240] Update some miscellaneous libraries --- app/build.gradle | 18 +++++++++--------- build.gradle | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9867037e6a9..787dd7833f4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,7 +95,7 @@ android { } ext { - checkstyleVersion = '10.0' + checkstyleVersion = '10.3.1' androidxLifecycleVersion = '2.3.1' androidxRoomVersion = '2.4.2' @@ -110,7 +110,7 @@ ext { leakCanaryVersion = '2.5' stethoVersion = '1.6.0' mockitoVersion = '4.0.0' - assertJVersion = '3.22.0' + assertJVersion = '3.23.1' } configurations { @@ -179,7 +179,7 @@ sonarqube { dependencies { /** Desugaring **/ - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6' /** NewPipe libraries **/ // You can use a local version by uncommenting a few lines in settings.gradle @@ -191,7 +191,7 @@ dependencies { /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" - ktlint 'com.pinterest:ktlint:0.44.0' + ktlint 'com.pinterest:ktlint:0.45.2' /** Kotlin **/ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" @@ -199,14 +199,14 @@ dependencies { /** AndroidX **/ implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'androidx.core:core-ktx:1.6.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' - implementation 'androidx.media:media:1.5.0' + implementation 'androidx.media:media:1.6.0' implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.room:room-runtime:${androidxRoomVersion}" @@ -226,7 +226,7 @@ dependencies { kapt "frankiesardo:icepick-processor:${icepickVersion}" // HTML parser - implementation "org.jsoup:jsoup:1.14.3" + implementation "org.jsoup:jsoup:1.15.2" // HTTP client //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users @@ -274,7 +274,7 @@ dependencies { implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" // Date and time formatting - implementation "org.ocpsoft.prettytime:prettytime:5.0.2.Final" + implementation "org.ocpsoft.prettytime:prettytime:5.0.3.Final" /** Debugging **/ // Memory leak detection diff --git a/build.gradle b/build.gradle index bea444fab92..322a47a6f8e 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong From c1e78cf55bf6e4178ac3ef3b4f7bae52c7f992c0 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Jul 2022 03:23:45 -0400 Subject: [PATCH 140/240] Update OkHttp to 4.x --- app/build.gradle | 3 +-- app/src/main/java/org/schabi/newpipe/DownloaderImpl.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9867037e6a9..b5e26e8fc15 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -229,8 +229,7 @@ dependencies { implementation "org.jsoup:jsoup:1.14.3" // HTTP client - //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users - implementation "com.squareup.okhttp3:okhttp:3.12.13" + implementation "com.squareup.okhttp3:okhttp:4.10.0" // Media player implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index f2803dc2fb1..79d2ad7b7dd 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -135,7 +135,7 @@ public Response execute(@NonNull final Request request) RequestBody requestBody = null; if (dataToSend != null) { - requestBody = RequestBody.create(null, dataToSend); + requestBody = RequestBody.create(dataToSend); } final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder() From cc7a8fb1a611299bab6b574b75b602b38a23b2d9 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Tue, 21 Jun 2022 20:22:39 +0300 Subject: [PATCH 141/240] Improve image placeholders - Show placeholders until the image is loaded because timeout can be very long and missing profile pictures and video thumbnails make app inconvenient to use - Adapt profile picture and video thumbnail placeholders to light theme - Replace profile picture and video thumbnail placeholders with vector graphics --- .../fragments/detail/VideoDetailFragment.java | 4 +- .../schabi/newpipe/util/PicassoHelper.java | 17 +++- app/src/main/res/drawable-nodpi/buddy.png | Bin 1168 -> 0 bytes .../res/drawable-nodpi/buddy_channel_item.png | Bin 1051 -> 0 bytes .../res/drawable-nodpi/dummy_thumbnail.png | Bin 956 -> 0 bytes .../drawable-nodpi/dummy_thumbnail_dark.png | Bin 105 -> 134 bytes .../dummy_thumbnail_playlist.png | Bin 1225 -> 0 bytes app/src/main/res/drawable/buddy.xml | 31 ++++++++ app/src/main/res/drawable/dummy_thumbnail.xml | 22 ++++++ .../res/drawable/dummy_thumbnail_playlist.xml | 30 +++++++ .../res/layout/list_channel_grid_item.xml | 2 +- .../res/layout/list_channel_mini_item.xml | 2 +- .../res/layout/list_comments_mini_item.xml | 2 +- .../res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/values-night/colors.xml | 3 + app/src/main/res/values/colors.xml | 3 + assets/buddy_channel_item.svg | 73 ------------------ assets/dummy_thumbnail.svg | 7 ++ assets/dummy_thumbnail_playlist.svg | 8 ++ 19 files changed, 125 insertions(+), 81 deletions(-) delete mode 100644 app/src/main/res/drawable-nodpi/buddy.png delete mode 100644 app/src/main/res/drawable-nodpi/buddy_channel_item.png delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail.png delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_playlist.png create mode 100644 app/src/main/res/drawable/buddy.xml create mode 100644 app/src/main/res/drawable/dummy_thumbnail.xml create mode 100644 app/src/main/res/drawable/dummy_thumbnail_playlist.xml delete mode 100644 assets/buddy_channel_item.svg create mode 100644 assets/dummy_thumbnail.svg create mode 100644 assets/dummy_thumbnail_playlist.svg diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 278d472d4c6..78f3772e904 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -714,7 +714,7 @@ private View.OnTouchListener getOnControlsTouchListener() { } private void initThumbnailViews(@NonNull final StreamInfo info) { - PicassoHelper.loadThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) + PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.detailThumbnailImageView, new Callback() { @Override public void onSuccess() { @@ -2361,7 +2361,7 @@ private void updateOverlayData(@Nullable final String overlayTitle, binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark); - PicassoHelper.loadThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) + PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.overlayThumbnail); } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index aabc459d015..223e8ac9caa 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -104,6 +104,10 @@ public static RequestCreator loadThumbnail(final String url) { return loadImageDefault(url, R.drawable.dummy_thumbnail); } + public static RequestCreator loadDetailsThumbnail(final String url) { + return loadImageDefault(url, R.drawable.dummy_thumbnail, false); + } + public static RequestCreator loadBanner(final String url) { return loadImageDefault(url, R.drawable.channel_banner); } @@ -189,15 +193,24 @@ public void onPrepareLoad(final Drawable placeHolderDrawable) { private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { + return loadImageDefault(url, placeholderResId, true); + } + + private static RequestCreator loadImageDefault(final String url, final int placeholderResId, + final boolean showPlaceholderWhileLoading) { if (!shouldLoadImages || isBlank(url)) { return picassoInstance .load((String) null) .placeholder(placeholderResId) // show placeholder when no image should load .error(placeholderResId); } else { - return picassoInstance + final RequestCreator requestCreator = picassoInstance .load(url) - .error(placeholderResId); // don't show placeholder while loading, only on error + .error(placeholderResId); + if (showPlaceholderWhileLoading) { + requestCreator.placeholder(placeholderResId); + } + return requestCreator; } } } diff --git a/app/src/main/res/drawable-nodpi/buddy.png b/app/src/main/res/drawable-nodpi/buddy.png deleted file mode 100644 index 8713ee02bfa8cd492bf79ed4730ca4060ceeb631..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1168 zcmV;B1aJF^P)b~j1!&(iVkGA*yRLsUHxctK)V zbrwUKLgK~AqOlZmKgKJ|YYQQFIo?ozuk3;}eY~-(u?X@o$J-OLt0fRsC3shIYX#(- zNuCL1%5yBL~_YZH(P`V$pD zqy|Nz=7uyeXe13Uq?I5_ww#bl3bN7*A4KIKEK_Wd1xQxb3@jDYkVb%~h8CjwU@E7D)L^S7h2&r>r-a;rv3o+u0E`Xk zAPrb+$RGt+E2toy1Zz?eLH1y7PXrl(xiJmoD!`n$l0ZtZSCT*`U~fzTQ3=?K6#?V~ z12qJY9SpP~fJ|VZ2?3-B1NHPn6pYkqhqN%#Njs#1k@nglYZz&x9WsTHrrIF`7-^^- z(ua{U?T{2kO0`2Ij3l)~1dJ5>0ucdX86@`ppFO<%W-wA;J7j3tN0jiPaRVc*wL|tW z(m^}q2qU%LA3lWRpvN%`H1XxgbV~qfVW4BUR(0a6bI$P>d_(L(AznCe$_5LNcT*v+jXfvEir znCq2FKcw!%K>b=XJ&yxYb}t60>+zM-w&x0SWt3!Cq&I* zNwe-fkfsM~>K)$>DFtk5^F~NFgJn&-w?NJrtSh^`9@3_;u;ln!NFBq<;`()vQozzg z`5MShz}m$2<&X`mZuwHk3YNEi8DtCV+q?vFD`0)1d>(Ru3yJzU$T5Z!#m&=@OA1Fy zFHb?b49=9RCm;nJYW{yn1*fY12RX-Zs`&IDNDjx!)juH{IM?Q1kXFFCMEf^H4d7q{ z^%vw8PPYFO(j{=RwEF|Hgrlu~hg@SgT73NtGKaG*euZ2GoK0MRfvn(g>z|NL!r^51 z1G0nDl|LYTI9>J~a)jfx-ym~1-r_4nC2+jtE94%|*L;C2;C#z3kRF_`_Zf1A2VFiv zDtJ)!2~xm==AR%p@Sy%`|M z$uorG8CUd71_tH>o-U3d6}R4;Zxmo);9%JJhx3$K|NgTH#ezx%!PgEjI8j$!K5O|Z zzLvy2;(yc}-&{ENO`)%0bIaX(=gaM!R6~nC-9PwPV)Ci;kG~avnkTi?=a=2jTl4N{ zx83>Fc`SZLVO{lFf$g#n=dZlDu5RW8EBOcZD);-Ra}@48_}^gXWceW8kKC7nDm?av z{7}t$snUMO=#NqLMCqU`n}bt=oWDu=2UgEil(xxLHhi++!=AZ&CIl^H4fW#QnYATp z-6p3wuHM%=xgr;BUAkm~*Fw`!POgnF5?&l>dGbPKx=@gGNm7=gqISuY(;O>>OQu)| zDq5Fp6BaabN>AogG;e*E$K+Hb5W&qcv*ExL`y-M927R_qIE2){P2ly&x#?`@_^8`9 zg-Iy*n*g`aG+Pzc&MCG>8e9&^Epl)=I^oA9p|5Gv|9_O;FYs6>^>zA1iRm9dwuc(C zHD=zanXpxM$-BblK7*9o({(RbU6}0qM(zE{jZVSpOW18czUVz*{WmegW?q`K#E&;8 zIozMM@LrYpw@^xI>NEM`BpI9A=8v3S9eR{`I96BZiOuzlqdMoBoK|%@RYxi+A9<^5 zwW<2Je9uCyzi!yp4gH%GgXh|nsmIJ$}Rs%;$ZVd<&qPs zt>P~&b376sblSD>L>`vk$|pWayFmy*8bpJ zDRAea_X(MUx-~5p%@2A$3j{SdK2%I$V>9Mg0=K=XbS{$IP4O z{n?Q9EWx0EHvgt=%|hblFXA#zo8%>#r^fe*&f;-Q{rKYTc1xbzcb$J8XBILuTkltv z5oR&^XcH35FX-~?gUqFvqkWdPs{=l)e{oXdf!djZXKw!r)ap6^?$Eit+B>((C@Ahr z%8CD%wr7ifRPy0|&a>oA>)UI$``IM7zB>BV)^x8?{0f`8h6Y=%9gnIuywI@r*jZu| znvoyGwTrDTQesQewD3gn&#@o>{EqW2>-&>r*LXZ_(Z|zYQa)$A*kiinPNr$lkNR(` zm;6sR%zC#cI!)?-n#K9myA;1)`|)ty({I&=+su4<5++@__3=@4bX@G?s;5ox STg?c}*bJVoelF{r5}E*aKG6LD diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail.png b/app/src/main/res/drawable-nodpi/dummy_thumbnail.png deleted file mode 100644 index 86f454186e1f39cf9ca3ac28dd95502fdbce4655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 956 zcmeAS@N?(olHy`uVBq!ia0y~yU|a=cf8YQT3?*&#{6LDa*vT`5;~7`oJ0;^v zbkO&@i;A40S_@}sggCW&t!Uwz8UW%7X@xkchOB7uS`olGH2}z62;_nkpmA}jU-kOz z?QT77ynJ~dP~_Fwoytz3TYq1XT)67y-s}+O@=*J%Z%?i;Mux_3 zttx%jux8czTeWX`0zzf=t(8Met1B)GE?gC5W4~Bx?de}%la}pR;k0Z=9mDFTWjn$` zFNj=Ub!z6LsL;URn2^Ojg@LhG){9#l-MR`adqqS%^n}QD{)96?Wl{-U-LH!OR=otO zRE(Y`e_)B`y3nto-Qsg60IAvE!BqKrE0CQfuM(X^i>I|_E!nISGXK@})Aa@JqL$g8 zm}h)>kQeHDc2>Ysn;nZ+MeTdl5Wos#o!Z^fmi4Rp@(R0a^3&@*dbEBXlnuRl__lyn z$kvL&a|?hT{wv|os`YerO&ch7mWLgHnG*aK9*E?oBd!%lXN&;yEws~T?4U2%!8 zGZ1W|y^5RZ-?x4t?_ckKX>j0(Z0M6x$B_N^avJup4{*IV!SU;B-T3be3%P7WzHRvT zT2`;>NC4{}%cpy^(mWcq62G*+x*om%eZ6_e{GzzjHa))zrV$y6Uoi3%n%#7VP>pe=l=;*57T*`9qKWzO`aqa+~mqO>CQ2 z)THPwm^~vpdBvu@-i2HhD|K63m`>kT3RDwn0E0NDPwL>%ybA~*haoIVPItuU|?H)Z&(<0e9y%;t!Ks7D}w%g6j_y9 zd%x&~zu$seOMqsVMa~vk_4YR(N9eKgi2-l3#XkMwT{Y!;egH2oIOXC?*T@N(n0)X> Ym3dv9k!XF~Gf=klboFyt=akR{0K-zPssI20 diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png index 02f698918fff753ac9d5bfe6091ac8349435243b..0e73416edeb6371b992c6c150b42d46a1c22f156 100644 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*yMny-N{YhX3vTXZ8bmyq+$OArj%q2?+^5_!$^k V7#Ru_Qn`T=44$rjF6*2UngBC(9*6({ literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^?m(=;2qYLEakt!I&(;+6**`aRLM5QNh-iK2A&*Qo$V9E1zaSW-r_2yzD4^RoyhCjTU&MyAT<3;rZUGZhj37hA-&Z3Ve?Q;Lp*yG@)pezZZRsB|P*hnYQrnlAje`Hk3S|LIAbp7KR{)xjUW9eGv}CnXjKosifj&vP{) z{F(dzylTf|5>uUyZFrlof^F{pzEkUxCV%AMNDSI2zNq~zhh%cdvoxM%iL(y|@LKVM zj8;tU@ls6AYRP4AHcj8~jpa7OykiS~Yq zIX!PnLS(|NYz7z!bj^~ zZ=+f1Q{rrYJ0Hu6Q=7bT>b}GJ{6=YkH`g|%JekfenO*pyt7mzUT|+Nz(U+W(+CO@x_M8ooTwdrYDaq@nHhE&a9MCF8Dyo_L9U1(RTviPS^Ur$ZX;p6|W zncGadSpV0}?OJ}ynuom&F)JPwd-F;Dy`G0IbQBJ(j>R>r@XP{JfC-?%^>7C`(3r<)-(T2l)gh!>>E3+i$JRmUbd(d z&UHSqL)-N>gOT2n`cUKPZ@Ls7e+b?3S0Q<$p6hKEBfb6QnRhuZ9yV*~X%@c-B)fw^ zl8=Knyfu5660>x{f0bKp6AJ4Da_vvtun-5DwxZ_gUcb8=?kYU4`lJSw`}nit+wpHN v9Drp2gTsXVUlpc1f%7!MjLuFbP0l+XkK#Ah!m diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/buddy.xml new file mode 100644 index 00000000000..b7d23c4b108 --- /dev/null +++ b/app/src/main/res/drawable/buddy.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml new file mode 100644 index 00000000000..5114fda3283 --- /dev/null +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -0,0 +1,22 @@ + + + + diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml new file mode 100644 index 00000000000..683b814c9a6 --- /dev/null +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/layout/list_channel_grid_item.xml b/app/src/main/res/layout/list_channel_grid_item.xml index d9084bbe97d..9f92c35a70e 100644 --- a/app/src/main/res/layout/list_channel_grid_item.xml +++ b/app/src/main/res/layout/list_channel_grid_item.xml @@ -18,7 +18,7 @@ android:layout_centerHorizontal="true" android:layout_margin="2dp" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index b66e07a12c5..e03f7c2fa83 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index 6bd36331101..ba984e0ff94 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index c858ccc4e1a..f6e5f358799 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -22,7 +22,7 @@ android:layout_width="48dp" android:layout_height="48dp" app:shapeAppearance="@style/CircularImageView" - tools:src="@drawable/buddy_channel_item" /> + tools:src="@drawable/buddy" /> @color/white @color/white + + #6C6C6C + #999999
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 281fd9ce7dc..bbcc5d4bb78 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,6 +6,9 @@ #CD201F + #F6F6F6 + #6E6E6E + #EEEEEE #EEEEEE diff --git a/assets/buddy_channel_item.svg b/assets/buddy_channel_item.svg deleted file mode 100644 index 4dec41f9d4a..00000000000 --- a/assets/buddy_channel_item.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/assets/dummy_thumbnail.svg b/assets/dummy_thumbnail.svg new file mode 100644 index 00000000000..bdea80b55ba --- /dev/null +++ b/assets/dummy_thumbnail.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/dummy_thumbnail_playlist.svg b/assets/dummy_thumbnail_playlist.svg new file mode 100644 index 00000000000..bd4b190aae8 --- /dev/null +++ b/assets/dummy_thumbnail_playlist.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 7b41acb781a994943318adb513fd9ccc9bc0b9f0 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Thu, 7 Jul 2022 22:56:40 +0300 Subject: [PATCH 142/240] Use corresponding material icon in user profile thumbnail --- app/src/main/res/drawable/buddy.xml | 43 +++++++++++------------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/buddy.xml index b7d23c4b108..b1d8be868a3 100644 --- a/app/src/main/res/drawable/buddy.xml +++ b/app/src/main/res/drawable/buddy.xml @@ -1,31 +1,20 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> - - + android:pathData="M0,0 L24,0 L24,24 L0,24 z" + android:fillColor="@color/placeholder_foreground" /> + + + + + From 429f2536af73c1ac94626e5e32425e9036135bcf Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Thu, 7 Jul 2022 23:07:09 +0300 Subject: [PATCH 143/240] Optimize thumbnail placeholder drawables --- app/src/main/res/drawable/dummy_thumbnail.xml | 26 +++++--------- .../res/drawable/dummy_thumbnail_playlist.xml | 35 ++++++------------- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index 5114fda3283..bfa2a59cc7c 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -1,22 +1,12 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M0,0h24v24h-24z" + android:fillColor="@color/placeholder_background" /> + android:pathData="M15,11.9023L12.2188,13.3828L9.0898,15L9.0273,12.1641L9,9L11.8477,10.3555ZM15,11.9023" + android:fillColor="@color/placeholder_foreground" /> diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml index 683b814c9a6..2e74c16e2d0 100644 --- a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -1,30 +1,15 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M0,0h24v24h-24z" + android:fillColor="@color/placeholder_background" /> + android:pathData="M13.8008,11.9023L11.0156,13.3828L7.8906,15L7.8281,12.1641L7.8008,9L10.6484,10.3555ZM13.8008,11.9023" + android:fillColor="@color/placeholder_foreground" /> + android:pathData="M16.8008,11.9023L14.0156,13.3828L10.8906,15L10.8281,12.1641L10.8008,9L13.6484,10.3555ZM16.8008,11.9023" + android:fillColor="@color/placeholder_foreground" /> From 35eeccd45a374caf3de21e90fd874151cb7f5cfe Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Wed, 13 Jul 2022 23:13:17 +0300 Subject: [PATCH 144/240] Rename buddy.xml to dummy_person.xml --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 3 ++- app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java | 2 +- app/src/main/res/drawable/{buddy.xml => dummy_person.xml} | 0 app/src/main/res/layout-large-land/fragment_video_detail.xml | 4 ++-- app/src/main/res/layout/channel_header.xml | 4 ++-- app/src/main/res/layout/fragment_video_detail.xml | 4 ++-- app/src/main/res/layout/list_channel_grid_item.xml | 2 +- app/src/main/res/layout/list_channel_item.xml | 2 +- app/src/main/res/layout/list_channel_mini_item.xml | 2 +- app/src/main/res/layout/list_comments_item.xml | 2 +- app/src/main/res/layout/list_comments_mini_item.xml | 2 +- app/src/main/res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/layout/playlist_header.xml | 2 +- app/src/main/res/layout/select_channel_item.xml | 2 +- 14 files changed, 17 insertions(+), 16 deletions(-) rename app/src/main/res/drawable/{buddy.xml => dummy_person.xml} (100%) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 78f3772e904..56e8f6d921b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1551,7 +1551,8 @@ public void handleResult(@NonNull final StreamInfo info) { binding.detailUploaderThumbnailView.setVisibility(View.GONE); } - final Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy); + final Drawable buddyDrawable + = AppCompatResources.getDrawable(activity, R.drawable.dummy_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 223e8ac9caa..04135702bfd 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -97,7 +97,7 @@ public static boolean getShouldLoadImages() { public static RequestCreator loadAvatar(final String url) { - return loadImageDefault(url, R.drawable.buddy); + return loadImageDefault(url, R.drawable.dummy_person); } public static RequestCreator loadThumbnail(final String url) { diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/dummy_person.xml similarity index 100% rename from app/src/main/res/drawable/buddy.xml rename to app/src/main/res/drawable/dummy_person.xml diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index d8e52b35427..1fecf93a504 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -271,7 +271,7 @@ android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" /> diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml index 3365e66e2d8..c2a70af022f 100644 --- a/app/src/main/res/layout/channel_header.xml +++ b/app/src/main/res/layout/channel_header.xml @@ -33,7 +33,7 @@ android:layout_width="@dimen/channel_avatar_size" android:layout_height="@dimen/channel_avatar_size" android:padding="1dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" app:strokeWidth="2dp" /> @@ -44,7 +44,7 @@ android:layout_height="@dimen/sub_channel_avatar_size" android:layout_gravity="bottom|right" android:padding="1dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" android:visibility="gone" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 18655283e37..49b6a807c48 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -258,7 +258,7 @@ android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" /> diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index c09c17ead38..67bdac5f7b4 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -64,7 +64,7 @@ android:layout_height="@dimen/video_item_search_avatar_image_height" android:layout_marginLeft="@dimen/video_item_search_avatar_left_margin" android:layout_marginRight="@dimen/video_item_search_avatar_right_margin" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/itemTitleView" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index e03f7c2fa83..747d22f5242 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 4358c438f1a..7cd7e066fa0 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -20,7 +20,7 @@ android:layout_marginLeft="3dp" android:layout_marginRight="@dimen/comment_item_avatar_right_margin" android:focusable="false" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index ba984e0ff94..e000b55c1fc 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index f6e5f358799..237ed107a3f 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -22,7 +22,7 @@ android:layout_width="48dp" android:layout_height="48dp" app:shapeAppearance="@style/CircularImageView" - tools:src="@drawable/buddy" /> + tools:src="@drawable/dummy_person" /> diff --git a/app/src/main/res/layout/select_channel_item.xml b/app/src/main/res/layout/select_channel_item.xml index cfaaf276065..c77ff64fd42 100644 --- a/app/src/main/res/layout/select_channel_item.xml +++ b/app/src/main/res/layout/select_channel_item.xml @@ -19,7 +19,7 @@ android:layout_alignParentTop="true" android:layout_marginStart="3dp" android:layout_marginRight="8dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> From dff1adb1adf7c8b82f657eab189ef49f447d23b2 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Wed, 13 Jul 2022 23:44:21 +0300 Subject: [PATCH 145/240] Fix swapped colors in video and playlist thumbnails --- .../main/res/drawable-night/dummy_person.xml | 20 ++++++++++++++++++ .../drawable-nodpi/dummy_thumbnail_dark.bmp | Bin 0 -> 62 bytes .../drawable-nodpi/dummy_thumbnail_dark.png | Bin 134 -> 0 bytes app/src/main/res/drawable/dummy_person.xml | 4 ++-- app/src/main/res/drawable/dummy_thumbnail.xml | 4 ++-- app/src/main/res/values/colors.xml | 4 ++-- 6 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/drawable-night/dummy_person.xml create mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png diff --git a/app/src/main/res/drawable-night/dummy_person.xml b/app/src/main/res/drawable-night/dummy_person.xml new file mode 100644 index 00000000000..b1d8be868a3 --- /dev/null +++ b/app/src/main/res/drawable-night/dummy_person.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f16e2a2bfe14d493e27d6c0686248d9a5223c1e0 GIT binary patch literal 62 jcmZ?rwPSz)DYhX3vTXZ8bmyq+$OArj%q2?+^5_!$^k V7#Ru_Qn`T=44$rjF6*2UngBC(9*6({ diff --git a/app/src/main/res/drawable/dummy_person.xml b/app/src/main/res/drawable/dummy_person.xml index b1d8be868a3..2b3229e8fea 100644 --- a/app/src/main/res/drawable/dummy_person.xml +++ b/app/src/main/res/drawable/dummy_person.xml @@ -6,7 +6,7 @@ + android:fillColor="@color/placeholder_background" /> + android:fillColor="@color/placeholder_foreground" /> diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index bfa2a59cc7c..6a04fc53e19 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="@color/placeholder_foreground" /> + android:fillColor="@color/placeholder_background" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index bbcc5d4bb78..25dd0fc86ce 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,8 +6,8 @@ #CD201F - #F6F6F6 - #6E6E6E + #6E6E6E + #F6F6F6 #EEEEEE From 7b9b9218dc72e9d29beb5bdd4cb08bca3545d36d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 10:55:26 +0200 Subject: [PATCH 146/240] Remove bottom-sheet-thumbnail placeholder, clear the image instead --- .../fragments/detail/VideoDetailFragment.java | 2 +- .../res/drawable-nodpi/dummy_thumbnail_dark.bmp | Bin 62 -> 0 bytes app/src/main/res/layout/fragment_video_detail.xml | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 56e8f6d921b..4e81101dbc8 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -2361,7 +2361,7 @@ private void updateOverlayData(@Nullable final String overlayTitle, @Nullable final String thumbnailUrl) { binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); - binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark); + binding.overlayThumbnail.setImageDrawable(null); PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.overlayThumbnail); } diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp deleted file mode 100644 index f16e2a2bfe14d493e27d6c0686248d9a5223c1e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62 jcmZ?rwPSz)D + android:scaleType="fitCenter" /> Date: Thu, 14 Jul 2022 10:56:28 +0200 Subject: [PATCH 147/240] Improve placeholder thumbnail SVGs and remove theme customization Theme customization does not seem to work well with Picasso: square/picasso#1275 --- .../main/res/drawable-night/dummy_person.xml | 20 ------------------- app/src/main/res/drawable/dummy_thumbnail.xml | 6 +++--- .../res/drawable/dummy_thumbnail_playlist.xml | 4 ++-- app/src/main/res/values-night/colors.xml | 3 --- app/src/main/res/values/colors.xml | 4 ++-- 5 files changed, 7 insertions(+), 30 deletions(-) delete mode 100644 app/src/main/res/drawable-night/dummy_person.xml diff --git a/app/src/main/res/drawable-night/dummy_person.xml b/app/src/main/res/drawable-night/dummy_person.xml deleted file mode 100644 index b1d8be868a3..00000000000 --- a/app/src/main/res/drawable-night/dummy_person.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index 6a04fc53e19..aa81e03c9c8 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml index 2e74c16e2d0..c56d558b8c3 100644 --- a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -7,9 +7,9 @@ android:pathData="M0,0h24v24h-24z" android:fillColor="@color/placeholder_background" /> diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index a0a0e891b3f..99026e70b5f 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -2,7 +2,4 @@ @color/white @color/white - - #6C6C6C - #999999 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 25dd0fc86ce..73e8a0cb185 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,8 +6,8 @@ #CD201F - #6E6E6E - #F6F6F6 + #999999 + #6C6C6C #EEEEEE From 6ea85e6380f35a0123e8b64c847c0eb3b355c454 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 10:58:48 +0200 Subject: [PATCH 148/240] Rename dummy_* and more to placeholder_* --- .../fragments/detail/VideoDetailFragment.java | 2 +- .../newpipe/local/dialog/PlaylistAppendDialog.java | 3 ++- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../main/java/org/schabi/newpipe/player/Player.java | 2 +- .../java/org/schabi/newpipe/util/PicassoHelper.java | 10 +++++----- ...el_banner.png => placeholder_channel_banner.png} | Bin .../{dummy_person.xml => placeholder_person.xml} | 0 ...ylist.xml => placeholder_thumbnail_playlist.xml} | 0 ...humbnail.xml => placeholder_thumbnail_video.xml} | 0 .../res/layout-large-land/fragment_video_detail.xml | 8 ++++---- app/src/main/res/layout/channel_header.xml | 6 +++--- app/src/main/res/layout/fragment_video_detail.xml | 6 +++--- app/src/main/res/layout/item_stream_segment.xml | 2 +- app/src/main/res/layout/list_channel_grid_item.xml | 2 +- app/src/main/res/layout/list_channel_item.xml | 2 +- app/src/main/res/layout/list_channel_mini_item.xml | 2 +- app/src/main/res/layout/list_comments_item.xml | 2 +- app/src/main/res/layout/list_comments_mini_item.xml | 2 +- app/src/main/res/layout/list_playlist_grid_item.xml | 2 +- app/src/main/res/layout/list_playlist_item.xml | 2 +- app/src/main/res/layout/list_playlist_mini_item.xml | 2 +- app/src/main/res/layout/list_stream_grid_item.xml | 2 +- app/src/main/res/layout/list_stream_item.xml | 2 +- app/src/main/res/layout/list_stream_mini_item.xml | 2 +- .../res/layout/list_stream_playlist_grid_item.xml | 2 +- .../main/res/layout/list_stream_playlist_item.xml | 2 +- .../main/res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/layout/play_queue_item.xml | 2 +- app/src/main/res/layout/player.xml | 2 +- app/src/main/res/layout/playlist_header.xml | 2 +- app/src/main/res/layout/select_channel_item.xml | 2 +- 31 files changed, 39 insertions(+), 38 deletions(-) rename app/src/main/res/drawable-nodpi/{channel_banner.png => placeholder_channel_banner.png} (100%) rename app/src/main/res/drawable/{dummy_person.xml => placeholder_person.xml} (100%) rename app/src/main/res/drawable/{dummy_thumbnail_playlist.xml => placeholder_thumbnail_playlist.xml} (100%) rename app/src/main/res/drawable/{dummy_thumbnail.xml => placeholder_thumbnail_video.xml} (100%) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 4e81101dbc8..c8ae0781e4c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1552,7 +1552,7 @@ public void handleResult(@NonNull final StreamInfo info) { } final Drawable buddyDrawable - = AppCompatResources.getDrawable(activity, R.drawable.dummy_person); + = AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index a874cdd621c..a97eb0c1844 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -145,7 +145,8 @@ private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager, final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); - if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) { + if (playlist.thumbnailUrl + .equals("drawable://" + R.drawable.placeholder_thumbnail_playlist)) { playlistDisposables.add(manager .changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 4bffc09839a..b4af73d08b7 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -613,7 +613,7 @@ private void updateThumbnailUrl() { newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0)) .getStreamEntity().getThumbnailUrl(); } else { - newThumbnailUrl = "drawable://" + R.drawable.dummy_thumbnail_playlist; + newThumbnailUrl = "drawable://" + R.drawable.placeholder_thumbnail_playlist; } changeThumbnailUrl(newThumbnailUrl); diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 100563765d4..36da0124a54 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -3108,7 +3108,7 @@ public String getUploaderName() { public Bitmap getThumbnail() { if (currentThumbnail == null) { currentThumbnail = BitmapFactory.decodeResource( - context.getResources(), R.drawable.dummy_thumbnail); + context.getResources(), R.drawable.placeholder_thumbnail_video); } return currentThumbnail; } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 04135702bfd..a104660f480 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -97,23 +97,23 @@ public static boolean getShouldLoadImages() { public static RequestCreator loadAvatar(final String url) { - return loadImageDefault(url, R.drawable.dummy_person); + return loadImageDefault(url, R.drawable.placeholder_person); } public static RequestCreator loadThumbnail(final String url) { - return loadImageDefault(url, R.drawable.dummy_thumbnail); + return loadImageDefault(url, R.drawable.placeholder_thumbnail_video); } public static RequestCreator loadDetailsThumbnail(final String url) { - return loadImageDefault(url, R.drawable.dummy_thumbnail, false); + return loadImageDefault(url, R.drawable.placeholder_thumbnail_video, false); } public static RequestCreator loadBanner(final String url) { - return loadImageDefault(url, R.drawable.channel_banner); + return loadImageDefault(url, R.drawable.placeholder_channel_banner); } public static RequestCreator loadPlaylistThumbnail(final String url) { - return loadImageDefault(url, R.drawable.dummy_thumbnail_playlist); + return loadImageDefault(url, R.drawable.placeholder_thumbnail_playlist); } public static RequestCreator loadSeekbarThumbnailPreview(final String url) { diff --git a/app/src/main/res/drawable-nodpi/channel_banner.png b/app/src/main/res/drawable-nodpi/placeholder_channel_banner.png similarity index 100% rename from app/src/main/res/drawable-nodpi/channel_banner.png rename to app/src/main/res/drawable-nodpi/placeholder_channel_banner.png diff --git a/app/src/main/res/drawable/dummy_person.xml b/app/src/main/res/drawable/placeholder_person.xml similarity index 100% rename from app/src/main/res/drawable/dummy_person.xml rename to app/src/main/res/drawable/placeholder_person.xml diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml similarity index 100% rename from app/src/main/res/drawable/dummy_thumbnail_playlist.xml rename to app/src/main/res/drawable/placeholder_thumbnail_playlist.xml diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/placeholder_thumbnail_video.xml similarity index 100% rename from app/src/main/res/drawable/dummy_thumbnail.xml rename to app/src/main/res/drawable/placeholder_thumbnail_video.xml diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 1fecf93a504..5904724ad20 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -57,7 +57,7 @@ android:scaleType="fitCenter" tools:ignore="RtlHardcoded" tools:layout_height="200dp" - tools:src="@drawable/dummy_thumbnail" /> + tools:src="@drawable/placeholder_thumbnail_video" /> @@ -645,7 +645,7 @@ android:paddingLeft="@dimen/video_item_search_padding" android:paddingRight="@dimen/video_item_search_padding" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" /> + android:src="@drawable/placeholder_thumbnail_video" /> @@ -44,7 +44,7 @@ android:layout_height="@dimen/sub_channel_avatar_size" android:layout_gravity="bottom|right" android:padding="1dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" android:visibility="gone" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 137dafee273..c794c5a5549 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -47,7 +47,7 @@ android:scaleType="fitCenter" tools:ignore="RtlHardcoded" tools:layout_height="200dp" - tools:src="@drawable/dummy_thumbnail" /> + tools:src="@drawable/placeholder_thumbnail_video" /> diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index 67bdac5f7b4..cf4685e508b 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -64,7 +64,7 @@ android:layout_height="@dimen/video_item_search_avatar_image_height" android:layout_marginLeft="@dimen/video_item_search_avatar_left_margin" android:layout_marginRight="@dimen/video_item_search_avatar_right_margin" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/itemTitleView" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index 747d22f5242..473709bbd06 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 7cd7e066fa0..ad73c5ff43e 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -20,7 +20,7 @@ android:layout_marginLeft="3dp" android:layout_marginRight="@dimen/comment_item_avatar_right_margin" android:focusable="false" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index e000b55c1fc..606a237c5fc 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_playlist_grid_item.xml b/app/src/main/res/layout/list_playlist_grid_item.xml index e052e9ac0ab..3ab69448dfe 100644 --- a/app/src/main/res/layout/list_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_playlist_grid_item.xml @@ -17,7 +17,7 @@ android:layout_centerHorizontal="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:scaleType="fitStart" - android:src="@drawable/dummy_thumbnail_playlist" + android:src="@drawable/placeholder_thumbnail_playlist" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_stream_item.xml b/app/src/main/res/layout/list_stream_item.xml index 1117a028e5c..793942568e4 100644 --- a/app/src/main/res/layout/list_stream_item.xml +++ b/app/src/main/res/layout/list_stream_item.xml @@ -15,7 +15,7 @@ android:layout_width="@dimen/video_item_search_thumbnail_image_width" android:layout_height="@dimen/video_item_search_thumbnail_image_height" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" app:layout_constraintBottom_toTopOf="@+id/itemProgressView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/list_stream_mini_item.xml b/app/src/main/res/layout/list_stream_mini_item.xml index d33c9d4ba8e..2eab66eaf63 100644 --- a/app/src/main/res/layout/list_stream_mini_item.xml +++ b/app/src/main/res/layout/list_stream_mini_item.xml @@ -18,7 +18,7 @@ android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" tools:ignore="RtlHardcoded" /> + tools:src="@drawable/placeholder_person" /> diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index b8b60b3f8b3..60cbcf7c46e 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -397,7 +397,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="2dp" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" android:visibility="gone" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml index 39eca2fdaa7..9c038db3a66 100644 --- a/app/src/main/res/layout/playlist_header.xml +++ b/app/src/main/res/layout/playlist_header.xml @@ -45,7 +45,7 @@ android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:padding="0.7dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" app:strokeWidth="1dp" /> diff --git a/app/src/main/res/layout/select_channel_item.xml b/app/src/main/res/layout/select_channel_item.xml index c77ff64fd42..c5fd51bb8a2 100644 --- a/app/src/main/res/layout/select_channel_item.xml +++ b/app/src/main/res/layout/select_channel_item.xml @@ -19,7 +19,7 @@ android:layout_alignParentTop="true" android:layout_marginStart="3dp" android:layout_marginRight="8dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> From 9f993e0c49f2c0cf1f48205b584096621779eece Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 14:47:54 +0200 Subject: [PATCH 149/240] Make video and playlist placeholder thumbnails 16:9 After making the playlist and video thumbnails' scaleType fitCenter, the 24dp*24dp thumbnails would appear as a square, which would be strange, since the image view is 16:9. --- .../main/res/drawable/placeholder_thumbnail_playlist.xml | 8 ++++---- app/src/main/res/drawable/placeholder_thumbnail_video.xml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml b/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml index c56d558b8c3..de286d860fc 100644 --- a/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml @@ -1,15 +1,15 @@ diff --git a/app/src/main/res/drawable/placeholder_thumbnail_video.xml b/app/src/main/res/drawable/placeholder_thumbnail_video.xml index aa81e03c9c8..0b262f923d0 100644 --- a/app/src/main/res/drawable/placeholder_thumbnail_video.xml +++ b/app/src/main/res/drawable/placeholder_thumbnail_video.xml @@ -1,12 +1,12 @@ From 0e8cc72b134bf5755f59cbbfd6e01180509e7c57 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 22:14:03 +0200 Subject: [PATCH 150/240] Fix random NullPointerException when adding video player view --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 92e7e4f160b..0b32a5e2949 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1305,7 +1305,7 @@ private boolean isAutoplayEnabled() { } private void addVideoPlayerView() { - if (!isPlayerAvailable()) { + if (!isPlayerAvailable() || getView() == null) { return; } setHeightThumbnail(); From 25a43b57b20ec686b6a5a9c4eb44241a6b2ab864 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:19:37 +0200 Subject: [PATCH 151/240] Updated checkstyle So that the assign operators are used on the same branch --- checkstyle/checkstyle.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/checkstyle/checkstyle.xml b/checkstyle/checkstyle.xml index 282358f6a6d..ce378a65f90 100644 --- a/checkstyle/checkstyle.xml +++ b/checkstyle/checkstyle.xml @@ -116,6 +116,10 @@ + + + + From 8b209df253fe8e11377fed82b2e45ba87f4c4a02 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:19:58 +0200 Subject: [PATCH 152/240] Changed the code accordingly + Removed some unused code --- .../material/appbar/FlingBehavior.java | 4 +- .../org/schabi/newpipe/DownloaderImpl.java | 10 +- .../java/org/schabi/newpipe/MainActivity.java | 8 +- .../newpipe/download/DownloadDialog.java | 12 +- .../schabi/newpipe/error/ErrorActivity.java | 8 +- .../fragments/detail/DescriptionFragment.java | 16 +- .../newpipe/info_list/InfoItemBuilder.java | 4 +- .../dialog/StreamDialogDefaultEntry.java | 4 +- .../holder/CommentsMiniInfoItemHolder.java | 9 +- .../holder/StreamMiniInfoItemHolder.java | 7 +- .../local/bookmark/BookmarkFragment.java | 4 +- .../local/dialog/PlaylistCreationDialog.java | 4 +- .../local/playlist/LocalPlaylistFragment.java | 10 +- .../services/SubscriptionsExportService.java | 8 +- .../newpipe/player/helper/CacheFactory.java | 4 +- .../player/helper/PlayerDataSource.java | 4 +- .../newpipe/player/helper/PlayerHelper.java | 4 +- .../player/playback/PlayerMediaSession.java | 9 +- .../settings/ContentSettingsFragment.java | 12 +- .../settings/DebugSettingsFragment.java | 30 +- .../settings/DownloadSettingsFragment.java | 8 +- .../PeertubeInstanceListFragment.java | 4 +- .../settings/UpdateSettingsFragment.java | 12 +- .../schabi/newpipe/streams/DataReader.java | 4 +- .../newpipe/streams/Mp4FromDashWriter.java | 4 +- .../org/schabi/newpipe/util/ListHelper.java | 29 +- .../schabi/newpipe/util/PeertubeHelper.java | 4 +- .../schabi/newpipe/util/PermissionHelper.java | 4 +- .../schabi/newpipe/util/PicassoHelper.java | 4 +- .../org/schabi/newpipe/util/StateSaver.java | 8 +- .../InternalUrlsHandler.java | 8 +- .../util/urlfinder/PatternsCompat.java | 274 +----------------- .../newpipe/views/FocusAwareDrawerLayout.java | 8 +- .../services/ImportExportJsonHelperTest.java | 10 +- .../settings/tabs/TabsJsonHelperTest.java | 14 +- 35 files changed, 153 insertions(+), 413 deletions(-) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 3e5f408f754..47c8deb837b 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -132,8 +132,8 @@ private Field getLastNestedScrollingChildRefField() { try { final Class headerBehaviorType = this.getClass().getSuperclass().getSuperclass(); if (headerBehaviorType != null) { - final Field field - = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef"); + final Field field = + headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef"); field.setAccessible(true); return field; } diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index f2803dc2fb1..3579a0bad39 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -1,5 +1,7 @@ package org.schabi.newpipe; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Context; import androidx.annotation.NonNull; @@ -26,10 +28,10 @@ import okhttp3.ResponseBody; public final class DownloaderImpl extends Downloader { - public static final String USER_AGENT - = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; - public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY - = "youtube_restricted_mode_key"; + public static final String USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; + public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY = + "youtube_restricted_mode_key"; public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000"; public static final String YOUTUBE_DOMAIN = "youtube.com"; diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index dd59eeb2535..4a982874c5b 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -473,8 +473,8 @@ protected void onResume() { ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e); } - final SharedPreferences sharedPreferences - = PreferenceManager.getDefaultSharedPreferences(this); + final SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(this); if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) { if (DEBUG) { Log.d(TAG, "Theme has changed, recreating activity..."); @@ -646,8 +646,8 @@ public boolean onCreateOptionsMenu(final Menu menu) { } super.onCreateOptionsMenu(menu); - final Fragment fragment - = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); + final Fragment fragment = + getSupportFragmentManager().findFragmentById(R.id.fragment_holder); if (!(fragment instanceof SearchFragment)) { toolbarLayoutBinding.toolbarSearchContainer.getRoot().setVisibility(View.GONE); } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index e4adddc2a44..0e64e8b4846 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.download; +import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP; +import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -82,10 +86,6 @@ import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; import us.shandian.giga.service.MissionState; -import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP; -import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { private static final String TAG = "DialogFragment"; @@ -205,8 +205,8 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context)); Icepick.restoreInstanceState(this, savedInstanceState); - final SparseArray> secondaryStreams - = new SparseArray<>(4); + final SparseArray> secondaryStreams = + new SparseArray<>(4); final List videoStreams = wrappedVideoStreams.getStreamsList(); for (int i = 0; i < videoStreams.size(); i++) { diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index bd843029687..8b2ac37dcd4 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -65,11 +65,11 @@ public class ErrorActivity extends AppCompatActivity { public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; public static final String ERROR_EMAIL_SUBJECT = "Exception in "; - public static final String ERROR_GITHUB_ISSUE_URL - = "https://github.com/TeamNewPipe/NewPipe/issues"; + public static final String ERROR_GITHUB_ISSUE_URL = + "https://github.com/TeamNewPipe/NewPipe/issues"; - public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER - = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private ErrorInfo errorInfo; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index d57ddb02df6..33fbe5db111 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.fragments.detail; +import static android.text.TextUtils.isEmpty; +import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; + import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -33,10 +37,6 @@ import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; -import static android.text.TextUtils.isEmpty; -import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; -import static org.schabi.newpipe.extractor.utils.Utils.isBlank; - public class DescriptionFragment extends BaseFragment { @State @@ -185,8 +185,8 @@ private void addMetadataItem(final LayoutInflater inflater, return; } - final ItemMetadataBinding itemBinding - = ItemMetadataBinding.inflate(inflater, layout, false); + final ItemMetadataBinding itemBinding = + ItemMetadataBinding.inflate(inflater, layout, false); itemBinding.metadataTypeView.setText(type); itemBinding.metadataTypeView.setOnLongClickListener(v -> { @@ -206,8 +206,8 @@ private void addMetadataItem(final LayoutInflater inflater, private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) { - final ItemMetadataTagsBinding itemBinding - = ItemMetadataTagsBinding.inflate(inflater, layout, false); + final ItemMetadataTagsBinding itemBinding = + ItemMetadataTagsBinding.inflate(inflater, layout, false); final List tags = new ArrayList<>(streamInfo.getTags()); Collections.sort(tags); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index d78bf10769d..68f19ee9714 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -67,8 +67,8 @@ public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem i public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager, final boolean useMiniVariant) { - final InfoItemHolder holder - = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); + final InfoItemHolder holder = + holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); holder.updateFromItem(infoItem, historyRecordManager); return holder.itemView; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java index d035465932e..2b3f73926e1 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java @@ -115,8 +115,8 @@ public enum StreamDialogDefaultEntry { DOWNLOAD(R.string.download, (fragment, item) -> fetchStreamInfoAndSaveToDatabase(fragment.requireContext(), item.getServiceId(), item.getUrl(), info -> { - final DownloadDialog downloadDialog - = new DownloadDialog(fragment.requireContext(), info); + final DownloadDialog downloadDialog = + new DownloadDialog(fragment.requireContext(), info); downloadDialog.show(fragment.getChildFragmentManager(), "downloadDialog"); }) ), diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 6e4773c09d4..b900750a8f3 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -23,9 +23,9 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.external_communication.TimestampExtractor; -import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.external_communication.TimestampExtractor; import java.util.regex.Matcher; @@ -204,8 +204,9 @@ private void ellipsize() { boolean hasEllipsis = false; if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) { - final int endOfLastLine - = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1); + final int endOfLastLine = itemContentView + .getLayout() + .getLineEnd(COMMENT_DEFAULT_LINES - 1); int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2); if (end == -1) { end = Math.max(endOfLastLine - 2, 0); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 54d31ca5735..8d17017d217 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -14,8 +14,8 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.StreamTypeUtil; import org.schabi.newpipe.views.AnimatedProgressBar; @@ -111,8 +111,9 @@ public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { final StreamInfoItem item = (StreamInfoItem) infoItem; - final StreamStateEntity state - = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; + final StreamStateEntity state = historyRecordManager + .loadStreamState(infoItem) + .blockingGet()[0]; if (state != null && item.getDuration() > 0 && !StreamTypeUtil.isLiveStream(item.getStreamType())) { itemProgressView.setMax((int) item.getDuration()); diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index f272a8831f4..ac11d007f49 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -256,8 +256,8 @@ private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) { } private void showLocalDialog(final PlaylistMetadataEntry selectedItem) { - final DialogEditTextBinding dialogBinding - = DialogEditTextBinding.inflate(getLayoutInflater()); + final DialogEditTextBinding dialogBinding = + DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setHint(R.string.name); dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT); dialogBinding.dialogEditText.setText(selectedItem.name); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java index 0c09f3f0dc3..0d5cfac234f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java @@ -45,8 +45,8 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { return super.onCreateDialog(savedInstanceState); } - final DialogEditTextBinding dialogBinding - = DialogEditTextBinding.inflate(getLayoutInflater()); + final DialogEditTextBinding dialogBinding = + DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.getRoot().getContext().setTheme(ThemeHelper.getDialogTheme(requireContext())); dialogBinding.dialogEditText.setHint(R.string.name); dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT); diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index f744d7561e6..fa789d97d8a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -42,6 +42,7 @@ import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.dialog.InfoItemDialog; +import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.PlayerType; @@ -50,7 +51,6 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; -import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; @@ -402,8 +402,8 @@ public void removeWatchedStreams(final boolean removePartiallyWatched) { final Iterator playlistIter = playlist.iterator(); // History data - final HistoryRecordManager recordManager - = new HistoryRecordManager(getContext()); + final HistoryRecordManager recordManager = + new HistoryRecordManager(getContext()); final Iterator historyIter = recordManager .getStreamHistorySortedById().blockingFirst().iterator(); @@ -544,8 +544,8 @@ private void createRenameDialog() { return; } - final DialogEditTextBinding dialogBinding - = DialogEditTextBinding.inflate(getLayoutInflater()); + final DialogEditTextBinding dialogBinding = + DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setHint(R.string.name); dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT); dialogBinding.dialogEditText.setSelection(dialogBinding.dialogEditText.getText().length()); diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java index 06310359706..d56d16f3cc5 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java @@ -19,6 +19,8 @@ package org.schabi.newpipe.local.subscription.services; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Intent; import android.net.Uri; import android.util.Log; @@ -43,8 +45,6 @@ import io.reactivex.rxjava3.functions.Function; import io.reactivex.rxjava3.schedulers.Schedulers; -import static org.schabi.newpipe.MainActivity.DEBUG; - public class SubscriptionsExportService extends BaseImportExportService { public static final String KEY_FILE_PATH = "key_file_path"; @@ -109,8 +109,8 @@ private void startExport() { subscriptionManager.subscriptionTable().getAll().take(1) .map(subscriptionEntities -> { - final List result - = new ArrayList<>(subscriptionEntities.size()); + final List result = + new ArrayList<>(subscriptionEntities.size()); for (final SubscriptionEntity entity : subscriptionEntities) { result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(), entity.getName())); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index d189616d193..41fcc823a7e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -39,8 +39,8 @@ public DataSource createDataSource() { .createDataSource(); final FileDataSource fileSource = new FileDataSource(); - final CacheDataSink dataSink - = new CacheDataSink(cache, PlayerHelper.getPreferredFileSize()); + final CacheDataSink dataSink = + new CacheDataSink(cache, PlayerHelper.getPreferredFileSize()); return new CacheDataSource(cache, dataSource, fileSource, dataSink, CACHE_FLAGS, null); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 88f25e194ef..0530d56e921 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -208,8 +208,8 @@ private static void instantiateCacheIfNeeded(final Context context) { Log.w(TAG, "instantiateCacheIfNeeded: could not create cache dir"); } - final LeastRecentlyUsedCacheEvictor evictor - = new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize()); + final LeastRecentlyUsedCacheEvictor evictor = + new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize()); cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context)); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index fb346f5ba05..abde7c3d12c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -62,8 +62,8 @@ public final class PlayerHelper { private static final StringBuilder STRING_BUILDER = new StringBuilder(); - private static final Formatter STRING_FORMATTER - = new Formatter(STRING_BUILDER, Locale.getDefault()); + private static final Formatter STRING_FORMATTER = + new Formatter(STRING_BUILDER, Locale.getDefault()); private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index 3be9b61734f..3c41acc7596 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -61,8 +61,7 @@ public MediaDescriptionCompat getQueueMetadata(final int index) { return null; } - final MediaDescriptionCompat.Builder descriptionBuilder - = new MediaDescriptionCompat.Builder() + final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() .setMediaId(String.valueOf(index)) .setTitle(item.getTitle()) .setSubtitle(item.getUploader()); @@ -76,14 +75,14 @@ public MediaDescriptionCompat getQueueMetadata(final int index) { additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); - descriptionBuilder.setExtras(additionalMetadata); + descBuilder.setExtras(additionalMetadata); final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); if (thumbnailUri != null) { - descriptionBuilder.setIconUri(thumbnailUri); + descBuilder.setIconUri(thumbnailUri); } - return descriptionBuilder.build(); + return descBuilder.build(); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 47458ad3fcd..37f83057bfd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -43,8 +43,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { private static final String ZIP_MIME_TYPE = "application/zip"; - private final SimpleDateFormat exportDateFormat - = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); + private final SimpleDateFormat exportDateFormat = + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); private ContentSettingsManager manager; @@ -160,8 +160,8 @@ private void requestExportPathResult(final ActivityResult result) { // will be saved only on success final Uri lastExportDataUri = result.getData().getData(); - final StoredFileHelper file - = new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); + final StoredFileHelper file = + new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); exportDatabase(file, lastExportDataUri); } @@ -173,8 +173,8 @@ private void requestImportPathResult(final ActivityResult result) { // will be saved only on success final Uri lastImportDataUri = result.getData().getData(); - final StoredFileHelper file - = new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); + final StoredFileHelper file = + new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); new AlertDialog.Builder(requireActivity()) .setMessage(R.string.override_current_data) diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index dd9f5fb1fe5..0f4c9765e5d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -9,8 +9,8 @@ import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.local.feed.notifications.NotificationWorker; +import org.schabi.newpipe.util.PicassoHelper; import java.util.Optional; @@ -21,20 +21,20 @@ public class DebugSettingsFragment extends BasePreferenceFragment { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResourceRegistry(); - final Preference allowHeapDumpingPreference - = findPreference(getString(R.string.allow_heap_dumping_key)); - final Preference showMemoryLeaksPreference - = findPreference(getString(R.string.show_memory_leaks_key)); - final Preference showImageIndicatorsPreference - = findPreference(getString(R.string.show_image_indicators_key)); - final Preference checkNewStreamsPreference - = findPreference(getString(R.string.check_new_streams_key)); - final Preference crashTheAppPreference - = findPreference(getString(R.string.crash_the_app_key)); - final Preference showErrorSnackbarPreference - = findPreference(getString(R.string.show_error_snackbar_key)); - final Preference createErrorNotificationPreference - = findPreference(getString(R.string.create_error_notification_key)); + final Preference allowHeapDumpingPreference = + findPreference(getString(R.string.allow_heap_dumping_key)); + final Preference showMemoryLeaksPreference = + findPreference(getString(R.string.show_memory_leaks_key)); + final Preference showImageIndicatorsPreference = + findPreference(getString(R.string.show_image_indicators_key)); + final Preference checkNewStreamsPreference = + findPreference(getString(R.string.check_new_streams_key)); + final Preference crashTheAppPreference = + findPreference(getString(R.string.crash_the_app_key)); + final Preference showErrorSnackbarPreference = + findPreference(getString(R.string.show_error_snackbar_key)); + final Preference createErrorNotificationPreference = + findPreference(getString(R.string.create_error_notification_key)); assert allowHeapDumpingPreference != null; assert showMemoryLeaksPreference != null; diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index 74dc9f63e93..5a4300cdd17 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Activity; import android.content.ContentResolver; import android.content.Context; @@ -32,8 +34,6 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - public class DownloadSettingsFragment extends BasePreferenceFragment { public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; private String downloadPathVideoPreference; @@ -255,8 +255,8 @@ private void requestDownloadPathResult(final ActivityResult result, final String context.grantUriPermission(context.getPackageName(), uri, StoredDirectoryHelper.PERMISSION_FLAGS); - final StoredDirectoryHelper mainStorage - = new StoredDirectoryHelper(context, uri, null); + final StoredDirectoryHelper mainStorage = + new StoredDirectoryHelper(context, uri, null); Log.i(TAG, "Acquiring tree success from " + uri.toString()); if (!mainStorage.canWrite()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 1ff7947fd74..92b9a036267 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -199,8 +199,8 @@ private void initButton(final View rootView) { } private void showAddItemDialog(final Context c) { - final DialogEditTextBinding dialogBinding - = DialogEditTextBinding.inflate(getLayoutInflater()); + final DialogEditTextBinding dialogBinding = + DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); dialogBinding.dialogEditText.setHint(R.string.peertube_instance_add_help); diff --git a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java index 1043e88c2e8..f1f63ffdf8d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java @@ -9,19 +9,19 @@ import org.schabi.newpipe.R; public class UpdateSettingsFragment extends BasePreferenceFragment { - private final Preference.OnPreferenceChangeListener updatePreferenceChange - = (preference, checkForUpdates) -> { + private final Preference.OnPreferenceChangeListener updatePreferenceChange = (p, nVal) -> { + final boolean checkForUpdates = (boolean) nVal; defaultPreferences.edit() - .putBoolean(getString(R.string.update_app_key), (boolean) checkForUpdates).apply(); + .putBoolean(getString(R.string.update_app_key), checkForUpdates) + .apply(); - if ((boolean) checkForUpdates) { + if (checkForUpdates) { checkNewVersionNow(); } return true; }; - private final Preference.OnPreferenceClickListener manualUpdateClick - = preference -> { + private final Preference.OnPreferenceClickListener manualUpdateClick = preference -> { Toast.makeText(getContext(), R.string.checking_updates_toast, Toast.LENGTH_SHORT).show(); checkNewVersionNow(); return true; diff --git a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java index dc6e29d7ddf..68225fbab57 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java @@ -82,8 +82,8 @@ public short readShort() throws IOException { public long readLong() throws IOException { primitiveRead(LONG_SIZE); - final long high - = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; + final long high = + primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; final long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7]; return high << 32 | low; } diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 889cc85e629..807f190b4ad 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -307,8 +307,8 @@ public void build(final SharpStream output) throws IOException { outWrite(makeMdat(totalSampleSize, is64)); final int[] sampleIndex = new int[readers.length]; - final int[] sizes - = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; + final int[] sizes = + new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; final int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; int written = readers.length; diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index eabac83304f..b0b4bae74c7 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -176,8 +176,8 @@ public static List getSortedStreamVideosList( @Nullable final List videoOnlyStreams, final boolean ascendingOrder, final boolean preferVideoOnlyStreams) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final boolean showHigherResolutions = preferences.getBoolean( context.getString(R.string.show_higher_resolutions_key), false); @@ -214,8 +214,8 @@ private static List getFilteredStreamList( private static String computeDefaultResolution(final Context context, final int key, final int value) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); // Load the preferred resolution otherwise the best available String resolution = preferences != null @@ -254,8 +254,8 @@ static int getDefaultResolutionIndex(final String defaultResolution, return 0; } - final int defaultStreamIndex - = getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); + final int defaultStreamIndex = + getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); // this is actually an error, // but maybe there is really no stream fitting to the default value. @@ -446,8 +446,9 @@ static int getVideoStreamIndex(@NonNull final String targetResolution, final String targetResolutionNoRefresh = targetResolution.replaceAll("p\\d+$", "p"); for (int idx = 0; idx < videoStreams.size(); idx++) { - final MediaFormat format - = targetFormat == null ? null : videoStreams.get(idx).getFormat(); + final MediaFormat format = targetFormat == null + ? null + : videoStreams.get(idx).getFormat(); final String resolution = videoStreams.get(idx).getResolution(); final String resolutionNoRefresh = resolution.replaceAll("p\\d+$", "p"); @@ -510,8 +511,8 @@ private static int getDefaultResolutionWithDefaultFormat(@NonNull final Context private static MediaFormat getDefaultFormat(@NonNull final Context context, @StringRes final int defaultFormatKey, @StringRes final int defaultFormatValueKey) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final String defaultFormat = context.getString(defaultFormatValueKey); final String defaultFormatString = preferences.getString( @@ -617,8 +618,8 @@ private static boolean isLimitingDataUsage(final Context context) { private static String getResolutionLimit(@NonNull final Context context) { String resolutionLimit = null; if (isMeteredNetwork(context)) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final String defValue = context.getString(R.string.limit_data_usage_none_key); final String value = preferences.getString( context.getString(R.string.limit_mobile_data_usage_key), defValue); @@ -634,8 +635,8 @@ private static String getResolutionLimit(@NonNull final Context context) { * @return {@code true} if connected to a metered network */ public static boolean isMeteredNetwork(@NonNull final Context context) { - final ConnectivityManager manager - = ContextCompat.getSystemService(context, ConnectivityManager.class); + final ConnectivityManager manager = + ContextCompat.getSystemService(context, ConnectivityManager.class); if (manager == null || manager.getActiveNetworkInfo() == null) { return false; } diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index dcc39eccf6d..97e6e756397 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -54,8 +54,8 @@ public static PeertubeInstance selectInstance(final PeertubeInstance instance, final Context context) { final SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(context); - final String selectedInstanceKey - = context.getString(R.string.peertube_selected_instance_key); + final String selectedInstanceKey = + context.getString(R.string.peertube_selected_instance_key); final JsonStringWriter jsonWriter = JsonWriter.string().object(); jsonWriter.value("name", instance.getName()); jsonWriter.value("url", instance.getUrl()); diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java index 1e30613741e..f3151ec8b49 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java @@ -122,8 +122,8 @@ public static boolean isPopupEnabled(final Context context) { } public static void showPopupEnablementToast(final Context context) { - final Toast toast - = Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG); + final Toast toast = + Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG); final TextView messageView = toast.getView().findViewById(android.R.id.message); if (messageView != null) { messageView.setGravity(Gravity.CENTER); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index a104660f480..54140b0fba9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -28,8 +28,8 @@ public final class PicassoHelper { public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; - private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY - = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; + private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = + "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; private PicassoHelper() { } diff --git a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java index 6ebdaee0234..3c901aacb51 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java +++ b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java @@ -46,8 +46,8 @@ */ public final class StateSaver { public static final String KEY_SAVED_STATE = "key_saved_state"; - private static final ConcurrentHashMap> STATE_OBJECTS_HOLDER - = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> STATE_OBJECTS_HOLDER = + new ConcurrentHashMap<>(); private static final String TAG = "StateSaver"; private static final String CACHE_DIR_NAME = "state_cache"; private static String cacheDirPath; @@ -107,8 +107,8 @@ private static SavedState tryToRestore(@NonNull final SavedState savedState, } try { - Queue savedObjects - = STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); + Queue savedObjects = + STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); if (savedObjects != null) { writeRead.readFrom(savedObjects); if (MainActivity.DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java index 240341ab072..c46e6636d1f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java @@ -153,13 +153,13 @@ public static boolean playOnPopup(final Context context, return false; } - final Single single - = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); + final Single single = + ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); disposables.add(single.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(info -> { - final PlayQueue playQueue - = new SinglePlayQueue(info, seconds * 1000L); + final PlayQueue playQueue = + new SinglePlayQueue(info, seconds * 1000L); NavigationHelper.playOnPopupPlayer(context, playQueue, false); }, throwable -> { if (DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java index 5a0dbb003bb..5fededbcb01 100644 --- a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java @@ -18,159 +18,13 @@ package org.schabi.newpipe.util.urlfinder; -import androidx.annotation.RestrictTo; - import java.util.regex.Pattern; -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; - /** * Commonly used regular expression patterns. */ public final class PatternsCompat { - /** - * Regular expression to match all IANA top-level domains. - * - * List accurate as of 2015/11/24. List taken from: - * http://data.iana.org/TLD/tlds-alpha-by-domain.txt - * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py - */ - static final String IANA_TOP_LEVEL_DOMAINS = "(?:" - + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active" - + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica" - + "|amsterdam|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia" - + "|associates|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])" - + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva" - + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz" - + "|black|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots" - + "|boutique|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build" - + "|builders|business|buzz|bzh|b[abdefghijmnorstvwyz])" - + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards" - + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center" - + "|ceo|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani" - + "|cisco|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed" - + "|coach|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec" - + "|condos|construction|consulting|contractors|cooking|cool|coop|corsica|country" - + "|coupons|courses|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc" - + "|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])" - + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta" - + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory" - + "|discount|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])" - + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises" - + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert" - + "|exposed|express|e[cegrstu])" - + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm" - + "|fashion|feedback|ferrero|film|final|finance|financial|firmdale|fish|fishing|fit" - + "|fitness|flights|florist|flowers|flsmidth|fly|foo|football|forex|forsale|forum" - + "|foundation|frl|frogans|fund|furniture|futbol|fyi|f[ijkmor])" - + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving" - + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov" - + "|grainger|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru" - + "|g[abdefghilmnpqrstuwy])" - + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey" - + "|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house" - + "|how|hsbc|hyundai|h[kmnrtu])" - + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink" - + "|institute|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau" - + "|iwc|i[delmnoqrst])" - + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])" - + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto" - + "|k[eghimnprwyz])" - + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease" - + "|leclerc|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde" - + "|link|live|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury" - + "|l[abcikrstuvy])" - + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba" - + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi" - + "|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov" - + "|movie|movistar|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])" - + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk" - + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])" - + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka" - + "|otsuka|ovh|om)" - + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography" - + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation" - + "|plumbing|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties" - + "|property|protection|pub|p[aefghklmnrstwy])" - + "|(?:qpon|quebec|qa)" - + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent" - + "|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip" - + "|rocher|rocks|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])" - + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo" - + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat" - + "|security|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles" - + "|site|ski|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space" - + "|spiegel|spreadbetting|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study" - + "|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems" - + "|s[abcdeghijklmnortuvxyz])" - + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica" - + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo" - + "|tools|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust" - + "|tui|t[cdfghjklmnortvwz])" - + "|(?:ubs|university|uno|uol|u[agksyz])" - + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin" - + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])" - + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki" - + "|williamhill|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])" - + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c" - + "|\u043c\u043a\u0434|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430" - + "|\u043e\u043d\u043b\u0430\u0439\u043d|\u043e\u0440\u0433|\u0440\u0443\u0441" - + "|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431|\u0443\u043a\u0440" - + "|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd" - + "|\u0627\u0631\u0627\u0645\u0643\u0648|\u0627\u0644\u0627\u0631\u062f\u0646" - + "|\u0627\u0644\u062c\u0632\u0627\u0626\u0631" - + "|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629" - + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a" - + "|\u0627\u06cc\u0631\u0627\u0646|\u0628\u0627\u0632\u0627\u0631" - + "|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633" - + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629" - + "|\u0634\u0628\u0643\u0629|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646" - + "|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0643\u0648\u0645" - + "|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627|\u0645\u0648\u0642\u0639" - + "|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924" - + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24" - + "|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe" - + "|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8" - + "|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd" - + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21" - + "|\u0e44\u0e17\u0e22|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb" - + "|\u30b3\u30e0|\u4e16\u754c|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51" - + "|\u4f01\u4e1a|\u4f5b\u5c71|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8" - + "|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063|\u5546\u57ce|\u5546\u5e97|\u5546\u6807" - + "|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c|\u5e7f\u4e1c|\u6148\u5584" - + "|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c|\u65b0\u52a0\u5761" - + "|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f|\u70b9\u770b" - + "|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc" - + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137" - + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox|xerox|xin|xn\\-\\-11b4c3d" - + "|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g|xn\\-\\-3e0b707e" - + "|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim" - + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks" - + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais" - + "|xn\\-\\-9dbq2a|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g" - + "|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-czr694b|xn\\-\\-czrs0t" - + "|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h|xn\\-\\-estv75g" - + "|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s" - + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c" - + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i" - + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d" - + "|xn\\-\\-kpry57d|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf" - + "|xn\\-\\-mgba3a3ejt|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd" - + "|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar" - + "|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m" - + "|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema|xn\\-\\-nyqy26a" - + "|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh" - + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g" - + "|xn\\-\\-s9brj9c|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y" - + "|xn\\-\\-vermgensberater\\-ctb|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv" - + "|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a" - + "|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx" - + "|xn\\-\\-zfr164b|xperia|xxx|xyz)" - + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])" - + "|(?:zara|zip|zone|zuerich|z[amw]))"; - - public static final Pattern IP_ADDRESS - = Pattern.compile( + public static final Pattern IP_ADDRESS = Pattern.compile( "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" @@ -204,28 +58,11 @@ public final class PatternsCompat { */ private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR; - /** - * Valid characters for IRI TLD defined in RFC 3987. - */ - private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR; - /** * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. */ - private static final String IRI_LABEL - = "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; - - /** - * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters. - */ - private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w"; - - private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")"; - - private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; - - public static final Pattern DOMAIN_NAME - = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + private static final String IRI_LABEL = + "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // CHANGED: Removed rtsp from supported protocols // @@ -245,59 +82,11 @@ public final class PatternsCompat { + ";/\\?:@&=#~" // plus optional query params + "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2}))*"; - /** - * Regular expression pattern to match most part of RFC 3987 - * Internationalized URLs, aka IRIs. - */ - public static final Pattern WEB_URL = Pattern.compile("(" - + "(" - + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?" - + "(?:" + DOMAIN_NAME + ")" - + "(?:" + PORT_NUMBER + ")?" - + ")" - + "(" + PATH_AND_QUERY + ")?" - + WORD_BOUNDARY - + ")"); - - /** - * Regular expression that matches known TLDs and punycode TLDs. - */ - private static final String STRICT_TLD = "(?:" - + IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")"; - - /** - * Regular expression that matches host names using {@link #STRICT_TLD}. - */ - private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+" - + STRICT_TLD + ")"; - - /** - * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or - * {@link #IP_ADDRESS}. - */ - private static final Pattern STRICT_DOMAIN_NAME - = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); - /** * Regular expression that matches domain names without a TLD. */ - private static final String RELAXED_DOMAIN_NAME - = "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS + ")"; - - /** - * Regular expression to match strings that do not start with a supported protocol. The TLDs - * are expected to be one of the known TLDs. - */ - private static final String WEB_URL_WITHOUT_PROTOCOL = "(" - + WORD_BOUNDARY - + "(? views, final int direction, for (int i = 0; i < getChildCount(); ++i) { final View child = getChildAt(i); - final DrawerLayout.LayoutParams lp - = (DrawerLayout.LayoutParams) child.getLayoutParams(); + final DrawerLayout.LayoutParams lp = + (DrawerLayout.LayoutParams) child.getLayoutParams(); if (lp.gravity == 0) { content = child; diff --git a/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java b/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java index 679c9da1d81..4f0f125ecc1 100644 --- a/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java @@ -1,5 +1,8 @@ package org.schabi.newpipe.local.subscription.services; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import org.junit.Test; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.extractor.subscription.SubscriptionItem; @@ -11,9 +14,6 @@ import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * @see ImportExportJsonHelper */ @@ -47,8 +47,8 @@ public void testInvalidSource() { fail("didn't throw exception"); } catch (final Exception e) { - final boolean isExpectedException - = e instanceof SubscriptionExtractor.InvalidSourceException; + final boolean isExpectedException = + e instanceof SubscriptionExtractor.InvalidSourceException; assertTrue("\"" + e.getClass().getSimpleName() + "\" is not the expected exception", isExpectedException); } diff --git a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java index 7cdc6c69ccd..bddb130fec5 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java @@ -1,5 +1,10 @@ package org.schabi.newpipe.settings.tabs; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static java.util.Objects.requireNonNull; + import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; @@ -11,11 +16,6 @@ import java.util.Collections; import java.util.List; -import static java.util.Objects.requireNonNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - public class TabsJsonHelperTest { private static final String JSON_TABS_ARRAY_KEY = "tabs"; private static final String JSON_TAB_ID_KEY = "tab_id"; @@ -59,8 +59,8 @@ public void testInvalidRead() { fail("didn't throw exception"); } catch (final Exception e) { - final boolean isExpectedException - = e instanceof TabsJsonHelper.InvalidJsonException; + final boolean isExpectedException = + e instanceof TabsJsonHelper.InvalidJsonException; assertTrue("\"" + e.getClass().getSimpleName() + "\" is not the expected exception", isExpectedException); } From 3890d0abdb9842955a34023f261a39635103f7d3 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:49:41 +0200 Subject: [PATCH 153/240] Added note that explains that unused code was removed. --- .../org/schabi/newpipe/util/urlfinder/PatternsCompat.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java index 5fededbcb01..49be86ae03b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java @@ -24,6 +24,10 @@ * Commonly used regular expression patterns. */ public final class PatternsCompat { + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // CHANGED: Removed unused code // + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + public static final Pattern IP_ADDRESS = Pattern.compile( "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" From 3ba04f179fb3d6b5ad9505ed663d1e09b0579cd3 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:53:48 +0200 Subject: [PATCH 154/240] Fixed conflicts/build --- .../org/schabi/newpipe/DownloaderImpl.java | 2 - .../fragments/detail/VideoDetailFragment.java | 5 ++- .../notification/NotificationConstants.java | 39 ++++++++++--------- .../newpipe/player/ui/MainPlayerUi.java | 18 +++++---- .../newpipe/player/ui/VideoPlayerUi.java | 7 ++-- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 3579a0bad39..74ca39e12ad 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -1,7 +1,5 @@ package org.schabi.newpipe; -import static org.schabi.newpipe.MainActivity.DEBUG; - import android.content.Context; import androidx.annotation.NonNull; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index a24f667a70b..55a6f5d5b9d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1555,8 +1555,8 @@ public void handleResult(@NonNull final StreamInfo info) { binding.detailUploaderThumbnailView.setVisibility(View.GONE); } - final Drawable buddyDrawable - = AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); + final Drawable buddyDrawable = + AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); @@ -2030,6 +2030,7 @@ private void hideSystemUi() { } // Listener implementation + @Override public void hideSystemUiIfNeeded() { if (isFullscreen() && bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 53ef752bd8a..b8e39e56432 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -30,25 +30,26 @@ private NotificationConstants() { // Intent actions //////////////////////////////////////////////////////////////////////////*/ - public static final String ACTION_CLOSE - = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE - = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; - public static final String ACTION_REPEAT - = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; - public static final String ACTION_PLAY_NEXT - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; - public static final String ACTION_PLAY_PREVIOUS - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; - public static final String ACTION_FAST_REWIND - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; - public static final String ACTION_FAST_FORWARD - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; - public static final String ACTION_SHUFFLE - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; - public static final String ACTION_RECREATE_NOTIFICATION - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; - + private static final String BASE_ACTION = + App.PACKAGE_NAME + ".player.MainPlayer."; + public static final String ACTION_CLOSE = + BASE_ACTION + "CLOSE"; + public static final String ACTION_PLAY_PAUSE = + BASE_ACTION + ".player.MainPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT = + BASE_ACTION + ".player.MainPlayer.REPEAT"; + public static final String ACTION_PLAY_NEXT = + BASE_ACTION + ".player.MainPlayer.ACTION_PLAY_NEXT"; + public static final String ACTION_PLAY_PREVIOUS = + BASE_ACTION + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND = + BASE_ACTION + ".player.MainPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD = + BASE_ACTION + ".player.MainPlayer.ACTION_FAST_FORWARD"; + public static final String ACTION_SHUFFLE = + BASE_ACTION + ".player.MainPlayer.ACTION_SHUFFLE"; + public static final String ACTION_RECREATE_NOTIFICATION = + BASE_ACTION + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; public static final int NOTHING = 0; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 52a486addc6..81dc954d1a5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -534,8 +534,8 @@ public void onLayoutChange(final View view, final int l, final int t, final int binding.brightnessProgressBar.setMax(maxGestureLength); setInitialGestureValues(); - binding.itemsListPanel.getLayoutParams().height - = height - binding.itemsListPanel.getTop(); + binding.itemsListPanel.getLayoutParams().height = + height - binding.itemsListPanel.getTop(); } } @@ -710,8 +710,9 @@ private int getNearestStreamSegmentPosition(final long playbackPosition) { } int nearestPosition = 0; - final List segments - = player.getCurrentStreamInfo().get().getStreamSegments(); + final List segments = player.getCurrentStreamInfo() + .get() + .getStreamSegments(); for (int i = 0; i < segments.size(); i++) { if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { @@ -912,8 +913,8 @@ public void toggleFullscreen() { if (DEBUG) { Log.d(TAG, "toggleFullscreen() called"); } - final PlayerServiceEventListener fragmentListener - = player.getFragmentListener().orElse(null); + final PlayerServiceEventListener fragmentListener = player.getFragmentListener() + .orElse(null); if (fragmentListener == null || player.exoPlayerIsNull()) { return; } @@ -939,8 +940,9 @@ public void toggleFullscreen() { public void checkLandscape() { // check if landscape is correct - final boolean videoInLandscapeButNotInFullscreen - = isLandscape() && !isFullscreen && !player.isAudioOnly(); + final boolean videoInLandscapeButNotInFullscreen = isLandscape() + && !isFullscreen + && !player.isAudioOnly(); final boolean notPaused = player.getCurrentState() != STATE_COMPLETED && player.getCurrentState() != STATE_PAUSED; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index d38c8cfe4bd..217dd38b311 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -226,8 +226,8 @@ protected void initListeners() { // PlaybackControlRoot already consumed window insets but we should pass them to // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - onLayoutChangeListener - = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + onLayoutChangeListener = + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { binding.playerOverlays.setPadding( v.getPaddingLeft(), v.getPaddingTop(), @@ -1053,8 +1053,7 @@ private void buildQualityMenu() { } qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); - @Nullable final List availableStreams - = Optional.ofNullable(player.getCurrentMetadata()) + final List availableStreams = Optional.ofNullable(player.getCurrentMetadata()) .flatMap(MediaItemTag::getMaybeQuality) .map(MediaItemTag.Quality::getSortedVideoStreams) .orElse(null); From 5da8d5fc7324be346ea9f431aeb74e7ad76b8702 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 05:47:57 +0530 Subject: [PATCH 155/240] Use ViewCompat.setBackgroundTintList(). --- app/src/main/java/org/schabi/newpipe/ktx/View.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt index ea680dd60ba..56c9d825abd 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -12,6 +12,7 @@ import android.view.View import androidx.annotation.ColorInt import androidx.annotation.FloatRange import androidx.core.animation.addListener +import androidx.core.view.ViewCompat import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -101,11 +102,11 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - backgroundTintList = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) + ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(animation.animatedValue as Int))) } viewPropertyAnimator.addListener( - onCancel = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) }, - onEnd = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) } + onCancel = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) }, + onEnd = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) } ) viewPropertyAnimator.start() } From f1de353b74f10e6834bf3e2ea33cd784b31a0654 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 08:34:04 +0530 Subject: [PATCH 156/240] Use stream sorting. --- .../database/playlist/PlaylistLocalItem.java | 17 ++++++----------- .../fragments/detail/DescriptionFragment.java | 7 +++---- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java index 43dbd89ea46..695f9ec5a69 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java @@ -3,10 +3,10 @@ import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; public interface PlaylistLocalItem extends LocalItem { String getOrderingName(); @@ -14,14 +14,9 @@ public interface PlaylistLocalItem extends LocalItem { static List merge( final List localPlaylists, final List remotePlaylists) { - final List items = new ArrayList<>( - localPlaylists.size() + remotePlaylists.size()); - items.addAll(localPlaylists); - items.addAll(remotePlaylists); - - Collections.sort(items, Comparator.comparing(PlaylistLocalItem::getOrderingName, - Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))); - - return items; + return Stream.concat(localPlaylists.stream(), remotePlaylists.stream()) + .sorted(Comparator.comparing(PlaylistLocalItem::getOrderingName, + Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))) + .collect(Collectors.toList()); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index d57ddb02df6..36238831425 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -26,9 +26,8 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.TextLinkifier; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -209,8 +208,8 @@ private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayo final ItemMetadataTagsBinding itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - final List tags = new ArrayList<>(streamInfo.getTags()); - Collections.sort(tags); + final List tags = streamInfo.getTags().stream().sorted() + .collect(Collectors.toList()); for (final String tag : tags) { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); From a1c6f0073e196a9c742a2addedddb9f79ea7e7f4 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Sun, 17 Jul 2022 20:37:15 +0300 Subject: [PATCH 157/240] Rename "waiting-for-author" label to "waiting for author" (#8642) --- .github/workflows/no-response.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 6a4a8a61aab..b3495135ffa 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -21,4 +21,4 @@ jobs: with: token: ${{ github.token }} daysUntilClose: 14 - responseRequiredLabel: waiting-for-author + responseRequiredLabel: waiting for author From e8669d4ab50e323c2353c908fd1c9660d8cf47d6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 13 Jul 2022 11:37:46 +0200 Subject: [PATCH 158/240] Deduplicate SQL queries to get feed streams --- .../newpipe/database/feed/dao/FeedDAO.kt | 112 ++++-------------- .../newpipe/local/feed/FeedDatabaseManager.kt | 20 ++-- .../newpipe/local/feed/FeedViewModel.kt | 3 +- 3 files changed, 34 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt index d573788a65e..b2b3d18a667 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt @@ -9,6 +9,7 @@ import androidx.room.Update import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import org.schabi.newpipe.database.feed.model.FeedEntity +import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity import org.schabi.newpipe.database.stream.StreamWithState import org.schabi.newpipe.database.stream.model.StreamStateEntity @@ -21,92 +22,16 @@ abstract class FeedDAO { @Query("DELETE FROM feed") abstract fun deleteAll(): Int - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getAllStreams(): Maybe> - - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - INNER JOIN feed_group_subscription_join fgs - ON fgs.subscription_id = f.subscription_id - - WHERE fgs.group_id = :groupId - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getAllStreamsForGroup(groupId: Long): Maybe> - - /** - * @see StreamStateEntity.isFinished() - * @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS - * @return all of the non-live, never-played and non-finished streams in the feed - * (all of the cited conditions must hold for a stream to be in the returned list) - */ - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - WHERE ( - sh.stream_id IS NULL - OR sst.stream_id IS NULL - OR sst.progress_time < s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} - OR sst.progress_time < s.duration * 1000 * 3 / 4 - OR s.stream_type = 'LIVE_STREAM' - OR s.stream_type = 'AUDIO_LIVE_STREAM' - ) - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getLiveOrNotPlayedStreams(): Maybe> - /** + * @param groupId the group id to get feed streams of; use + * [FeedGroupEntity.GROUP_ALL_ID] to not filter by group + * @param includePlayed if false, only return all of the live, never-played or non-finished + * feed streams (see `@see` items); if true no filter is applied + * @param uploadDateBefore get only streams uploaded before this date (useful to filter out + * future streams); use null to not filter by upload date + * @return the feed streams filtered according to the conditions provided in the parameters * @see StreamStateEntity.isFinished() * @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS - * @param groupId the group id to get streams of - * @return all of the non-live, never-played and non-finished streams for the given feed group - * (all of the cited conditions must hold for a stream to be in the returned list) */ @Query( """ @@ -122,24 +47,37 @@ abstract class FeedDAO { INNER JOIN feed f ON s.uid = f.stream_id - INNER JOIN feed_group_subscription_join fgs + LEFT JOIN feed_group_subscription_join fgs ON fgs.subscription_id = f.subscription_id - WHERE fgs.group_id = :groupId + WHERE ( + :groupId = ${FeedGroupEntity.GROUP_ALL_ID} + OR fgs.group_id = :groupId + ) AND ( - sh.stream_id IS NULL + :includePlayed + OR sh.stream_id IS NULL OR sst.stream_id IS NULL OR sst.progress_time < s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} OR sst.progress_time < s.duration * 1000 * 3 / 4 OR s.stream_type = 'LIVE_STREAM' OR s.stream_type = 'AUDIO_LIVE_STREAM' ) + AND ( + :uploadDateBefore IS NULL + OR s.upload_date IS NULL + OR s.upload_date < :uploadDateBefore + ) ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC LIMIT 500 """ ) - abstract fun getLiveOrNotPlayedStreamsForGroup(groupId: Long): Maybe> + abstract fun getStreams( + groupId: Long, + includePlayed: Boolean, + uploadDateBefore: OffsetDateTime? + ): Maybe> @Query( """ diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index 7a8723ceb2f..07edb04997d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -41,19 +41,15 @@ class FeedDatabaseManager(context: Context) { fun database() = database fun getStreams( - groupId: Long = FeedGroupEntity.GROUP_ALL_ID, - getPlayedStreams: Boolean = true + groupId: Long, + includePlayedStreams: Boolean, + includeFutureStreams: Boolean ): Maybe> { - return when (groupId) { - FeedGroupEntity.GROUP_ALL_ID -> { - if (getPlayedStreams) feedTable.getAllStreams() - else feedTable.getLiveOrNotPlayedStreams() - } - else -> { - if (getPlayedStreams) feedTable.getAllStreamsForGroup(groupId) - else feedTable.getLiveOrNotPlayedStreamsForGroup(groupId) - } - } + return feedTable.getStreams( + groupId, + includePlayedStreams, + if (includeFutureStreams) null else OffsetDateTime.now() + ) } fun outdatedSubscriptions(outdatedThreshold: OffsetDateTime) = feedTable.getAllOutdated(outdatedThreshold) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 87409ddae76..7f5ef4301fc 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -65,9 +65,8 @@ class FeedViewModel( .map { (event, showPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) -> val streamItems = if (event is SuccessResultEvent || event is IdleEvent) feedDatabaseManager - .getStreams(groupId, showPlayedItems) + .getStreams(groupId, showPlayedItems, showFutureItems) .blockingGet(arrayListOf()) - .filter { s -> showFutureItems || s.stream.uploadDate?.isBefore(OffsetDateTime.now()) ?: true } else arrayListOf() From e77224444051323fe506b350ebd4df5546ca4de2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com> Date: Tue, 19 Jul 2022 05:10:51 +0530 Subject: [PATCH 159/240] Update app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java Co-authored-by: Stypox --- .../newpipe/fragments/detail/DescriptionFragment.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index 36238831425..ab488376813 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -26,9 +26,6 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.TextLinkifier; -import java.util.List; -import java.util.stream.Collectors; - import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -208,16 +205,14 @@ private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayo final ItemMetadataTagsBinding itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - final List tags = streamInfo.getTags().stream().sorted() - .collect(Collectors.toList()); - for (final String tag : tags) { + streamInfo.getTags().stream().sorted().forEach(tag -> { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); chip.setText(tag); chip.setOnClickListener(this::onTagClick); chip.setOnLongClickListener(this::onTagLongClick); itemBinding.metadataTagsChips.addView(chip); - } + }); layout.addView(itemBinding.getRoot()); } From c53143ef4fe86b4ca43f8b8828c3b4eacae35acd Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 07:40:25 +0530 Subject: [PATCH 160/240] Use Set.of(). --- app/src/main/java/org/schabi/newpipe/util/CookieUtils.java | 3 +-- app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java index d970ec472e9..b906c1c4f80 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java @@ -2,7 +2,6 @@ import android.text.TextUtils; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -20,6 +19,6 @@ public static String concatCookies(final Collection cookieStrings) { } public static Set splitCookies(final String cookies) { - return new HashSet<>(Arrays.asList(cookies.split("; *"))); + return Set.of(cookies.split("; *")); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index b0b4bae74c7..9707956ed86 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -40,9 +39,8 @@ public final class ListHelper { // Audio format in order of efficiency. 0=most efficient, n=least efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); - // Use a HashSet for better performance - private static final Set HIGH_RESOLUTION_LIST = new HashSet<>( - Arrays.asList("1440p", "2160p")); + // Use a Set for better performance + private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); private ListHelper() { } From 4eddd2c3d1e246d92e8da81e335c3dc10fc1e68c Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 19 Jul 2022 20:01:46 +0200 Subject: [PATCH 161/240] Fix random NullPointerException when adding video player view --- .../newpipe/fragments/detail/VideoDetailFragment.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 55a6f5d5b9d..cc80075969d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1313,9 +1313,11 @@ private void addVideoPlayerView() { // Prevent from re-adding a view multiple times new Handler(Looper.getMainLooper()).post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); + if (binding != null) { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + } })); } From ca26fcb0ebf906d10cdc9551e1043ee074fc88c1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 07:40:29 +0530 Subject: [PATCH 162/240] Use List.of(). --- .../material/appbar/FlingBehavior.java | 5 +- app/src/main/java/org/schabi/newpipe/App.java | 65 +++++++++---------- .../org/schabi/newpipe/QueueItemMenuUtil.java | 4 +- .../org/schabi/newpipe/RouterActivity.java | 3 +- .../fragments/detail/VideoDetailFragment.java | 3 +- .../dialog/StreamDialogDefaultEntry.java | 4 +- .../gesture/CustomBottomSheetBehavior.java | 5 +- .../player/mediasource/FailedMediaSource.java | 6 +- .../newpipe/player/playqueue/PlayQueue.java | 3 +- .../player/playqueue/SinglePlayQueue.java | 7 +- .../PreferenceSearchConfiguration.java | 8 +-- .../PreferenceSearchItem.java | 7 +- .../newpipe/settings/tabs/TabsJsonHelper.java | 10 +-- .../org/schabi/newpipe/util/ListHelper.java | 6 +- .../schabi/newpipe/util/PeertubeHelper.java | 6 +- .../player/playqueue/PlayQueueTest.java | 2 +- .../schabi/newpipe/util/ListHelperTest.java | 33 +++++----- 17 files changed, 75 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 47c8deb837b..52754e8fa9f 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -14,7 +14,6 @@ import org.schabi.newpipe.R; import java.lang.reflect.Field; -import java.util.Arrays; import java.util.List; // See https://stackoverflow.com/questions/56849221#57997489 @@ -27,7 +26,7 @@ public FlingBehavior(final Context context, final AttributeSet attrs) { private boolean allowScroll = true; private final Rect globalRect = new Rect(); - private final List skipInterceptionOfElements = Arrays.asList( + private final List skipInterceptionOfElements = List.of( R.id.itemsListPanel, R.id.playbackSeekBar, R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); @@ -67,7 +66,7 @@ public boolean onRequestChildRectangleOnScreen( public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, @NonNull final AppBarLayout child, @NonNull final MotionEvent ev) { - for (final Integer element : skipInterceptionOfElements) { + for (final int element : skipInterceptionOfElements) { final View view = child.findViewById(element); if (view != null) { final boolean visible = view.getGlobalVisibleRect(globalRect); diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index c63f07891bd..f4410a31b2a 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -27,9 +27,8 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketException; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Objects; import io.reactivex.rxjava3.exceptions.CompositeException; import io.reactivex.rxjava3.exceptions.MissingBackpressureException; @@ -140,7 +139,7 @@ public void accept(@NonNull final Throwable throwable) { if (throwable instanceof UndeliverableException) { // As UndeliverableException is a wrapper, // get the cause of it to get the "real" exception - actualThrowable = throwable.getCause(); + actualThrowable = Objects.requireNonNull(throwable.getCause()); } else { actualThrowable = throwable; } @@ -149,7 +148,7 @@ public void accept(@NonNull final Throwable throwable) { if (actualThrowable instanceof CompositeException) { errors = ((CompositeException) actualThrowable).getExceptions(); } else { - errors = Collections.singletonList(actualThrowable); + errors = List.of(actualThrowable); } for (final Throwable error : errors) { @@ -213,41 +212,37 @@ protected void initACRA() { private void initNotificationChannels() { // Keep the importance below DEFAULT to avoid making noise on every notification update for // the main and update channels - final List notificationChannelCompats = new ArrayList<>(); - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.notification_channel_id), + final List notificationChannelCompats = List.of( + new NotificationChannelCompat.Builder(getString(R.string.notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.notification_channel_name)) - .setDescription(getString(R.string.notification_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.app_update_notification_channel_id), + .setName(getString(R.string.notification_channel_name)) + .setDescription(getString(R.string.notification_channel_description)) + .build(), + new NotificationChannelCompat + .Builder(getString(R.string.app_update_notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.app_update_notification_channel_name)) - .setDescription(getString(R.string.app_update_notification_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.hash_channel_id), + .setName(getString(R.string.app_update_notification_channel_name)) + .setDescription( + getString(R.string.app_update_notification_channel_description)) + .build(), + new NotificationChannelCompat.Builder(getString(R.string.hash_channel_id), NotificationManagerCompat.IMPORTANCE_HIGH) - .setName(getString(R.string.hash_channel_name)) - .setDescription(getString(R.string.hash_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.error_report_channel_id), + .setName(getString(R.string.hash_channel_name)) + .setDescription(getString(R.string.hash_channel_description)) + .build(), + new NotificationChannelCompat.Builder(getString(R.string.error_report_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.error_report_channel_name)) - .setDescription(getString(R.string.error_report_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.streams_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_DEFAULT) - .setName(getString(R.string.streams_notification_channel_name)) - .setDescription(getString(R.string.streams_notification_channel_description)) - .build()); + .setName(getString(R.string.error_report_channel_name)) + .setDescription(getString(R.string.error_report_channel_description)) + .build(), + new NotificationChannelCompat + .Builder(getString(R.string.streams_notification_channel_id), + NotificationManagerCompat.IMPORTANCE_DEFAULT) + .setName(getString(R.string.streams_notification_channel_name)) + .setDescription( + getString(R.string.streams_notification_channel_description)) + .build() + ); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.createNotificationChannelsCompat(notificationChannelCompats); diff --git a/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java b/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java index c7604e51283..7c646d0e42a 100644 --- a/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java +++ b/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java @@ -16,7 +16,7 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.SparseItemUtil; -import java.util.Collections; +import java.util.List; public final class QueueItemMenuUtil { private QueueItemMenuUtil() { @@ -53,7 +53,7 @@ public static void openPopupMenu(final PlayQueue playQueue, case R.id.menu_item_append_playlist: PlaylistDialog.createCorrespondingDialog( context, - Collections.singletonList(new StreamEntity(item)), + List.of(new StreamEntity(item)), dialog -> dialog.show( fragmentManager, "QueueItemMenuUtil@append_playlist" diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index d055da1e8f6..522f25df867 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -81,7 +81,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import icepick.Icepick; @@ -649,7 +648,7 @@ private void openAddToPlaylistDialog() { .subscribe( info -> PlaylistDialog.createCorrespondingDialog( getThemeWrapperContext(), - Collections.singletonList(new StreamEntity(info)), + List.of(new StreamEntity(info)), playlistDialog -> { playlistDialog.setOnDismissListener(dialog -> finish()); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 55a6f5d5b9d..f4838482b97 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -114,7 +114,6 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -453,7 +452,7 @@ public void onClick(final View v) { disposables.add( PlaylistDialog.createCorrespondingDialog( getContext(), - Collections.singletonList(new StreamEntity(currentInfo)), + List.of(new StreamEntity(currentInfo)), dialog -> dialog.show(getFM(), TAG) ) ); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java index 2b3f73926e1..1265e976726 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java @@ -20,7 +20,7 @@ import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; -import java.util.Collections; +import java.util.List; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -89,7 +89,7 @@ public enum StreamDialogDefaultEntry { APPEND_PLAYLIST(R.string.add_to_playlist, (fragment, item) -> PlaylistDialog.createCorrespondingDialog( fragment.getContext(), - Collections.singletonList(new StreamEntity(item)), + List.of(new StreamEntity(item)), dialog -> dialog.show( fragment.getParentFragmentManager(), "StreamDialogEntry@" diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index 2400091057a..41046784fed 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -14,7 +14,6 @@ import org.schabi.newpipe.R; -import java.util.Arrays; import java.util.List; public class CustomBottomSheetBehavior extends BottomSheetBehavior { @@ -25,7 +24,7 @@ public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs Rect globalRect = new Rect(); private boolean skippingInterception = false; - private final List skipInterceptionOfElements = Arrays.asList( + private final List skipInterceptionOfElements = List.of( R.id.detail_content_root_layout, R.id.relatedItemsLayout, R.id.itemsListPanel, R.id.view_pager, R.id.tab_layout, R.id.bottomControls, R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); @@ -57,7 +56,7 @@ public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, if (getState() == BottomSheetBehavior.STATE_EXPANDED && event.getAction() == MotionEvent.ACTION_DOWN) { // Without overriding scrolling will not work when user touches these elements - for (final Integer element : skipInterceptionOfElements) { + for (final int element : skipInterceptionOfElements) { final View view = child.findViewById(element); if (view != null) { final boolean visible = view.getGlobalVisibleRect(globalRect); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java index 8aad356d0ae..b9ca90d89fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java @@ -16,7 +16,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; import java.io.IOException; -import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; @@ -56,9 +56,7 @@ public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem, this.playQueueItem = playQueueItem; this.error = error; this.retryTimestamp = retryTimestamp; - this.mediaItem = ExceptionTag - .of(playQueueItem, Collections.singletonList(error)) - .withExtras(this) + this.mediaItem = ExceptionTag.of(playQueueItem, List.of(error)).withExtras(this) .asMediaItem(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index f46c9d72fd0..d54fed248e6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -16,7 +16,6 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -264,7 +263,7 @@ public synchronized void offsetIndex(final int offset) { * @param items {@link PlayQueueItem}s to append */ public synchronized void append(@NonNull final PlayQueueItem... items) { - append(Arrays.asList(items)); + append(List.of(items)); } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java index 527e804703c..e5117321407 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java @@ -4,20 +4,19 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public final class SinglePlayQueue extends PlayQueue { public SinglePlayQueue(final StreamInfoItem item) { - super(0, Collections.singletonList(new PlayQueueItem(item))); + super(0, List.of(new PlayQueueItem(item))); } public SinglePlayQueue(final StreamInfo info) { - super(0, Collections.singletonList(new PlayQueueItem(info))); + super(0, List.of(new PlayQueueItem(info))); } public SinglePlayQueue(final StreamInfo info, final long startPosition) { - super(0, Collections.singletonList(new PlayQueueItem(info))); + super(0, List.of(new PlayQueueItem(info))); getItem().setRecoveryPosition(startPosition); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java index a445ea3095b..88b0b773571 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java @@ -3,8 +3,6 @@ import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -12,9 +10,9 @@ public class PreferenceSearchConfiguration { private PreferenceSearchFunction searcher = new PreferenceFuzzySearchFunction(); - private final List parserIgnoreElements = Collections.singletonList( - PreferenceCategory.class.getSimpleName()); - private final List parserContainerElements = Arrays.asList( + private final List parserIgnoreElements = List.of(PreferenceCategory.class + .getSimpleName()); + private final List parserContainerElements = List.of( PreferenceCategory.class.getSimpleName(), PreferenceScreen.class.getSimpleName()); diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java index 98d2a5d843e..33856326cb7 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java @@ -3,7 +3,6 @@ import androidx.annotation.NonNull; import androidx.annotation.XmlRes; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -92,11 +91,7 @@ boolean hasData() { } public List getAllRelevantSearchFields() { - return Arrays.asList( - getTitle(), - getSummary(), - getEntries(), - getBreadcrumbs()); + return List.of(getTitle(), getSummary(), getEntries(), getBreadcrumbs()); } @NonNull diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 057ca50f0e2..40dacc96471 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -10,8 +10,6 @@ import com.grack.nanojson.JsonWriter; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -20,11 +18,9 @@ public final class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; - private static final List FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList( - Arrays.asList( - Tab.Type.DEFAULT_KIOSK.getTab(), - Tab.Type.SUBSCRIPTIONS.getTab(), - Tab.Type.BOOKMARKS.getTab())); + private static final List FALLBACK_INITIAL_TABS_LIST = List.of( + Tab.Type.DEFAULT_KIOSK.getTab(), Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.BOOKMARKS.getTab()); private TabsJsonHelper() { } diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 9707956ed86..1b3cb1651a0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -31,14 +31,14 @@ public final class ListHelper { // Video format in order of quality. 0=lowest quality, n=highest quality private static final List VIDEO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); + List.of(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); // Audio format in order of quality. 0=lowest quality, n=highest quality private static final List AUDIO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); + List.of(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); // Audio format in order of efficiency. 0=most efficient, n=least efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = - Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); + List.of(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); // Use a Set for better performance private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index 97e6e756397..34f99d26252 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -17,7 +17,6 @@ import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public final class PeertubeHelper { @@ -29,7 +28,7 @@ public static List getInstanceList(final Context context) { final String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key); final String savedJson = sharedPreferences.getString(savedInstanceListKey, null); if (null == savedJson) { - return Collections.singletonList(getCurrentInstance()); + return List.of(getCurrentInstance()); } try { @@ -45,9 +44,8 @@ public static List getInstanceList(final Context context) { } return result; } catch (final JsonParserException e) { - return Collections.singletonList(getCurrentInstance()); + return List.of(getCurrentInstance()); } - } public static PeertubeInstance selectInstance(final PeertubeInstance instance, diff --git a/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java b/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java index f92863eabb2..a130359a338 100644 --- a/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java +++ b/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java @@ -153,7 +153,7 @@ public void outOfBounds() { @Test public void itemsAreNotCloned() { final PlayQueueItem item = makeItemWithUrl("A url"); - final PlayQueue playQueue = makePlayQueue(0, Collections.singletonList(item)); + final PlayQueue playQueue = makePlayQueue(0, List.of(item)); // make sure that items are not cloned when added to the queue assertSame(playQueue.getItem(), item); diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index c9d570c7d4d..4f9b0b49779 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -6,7 +6,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; @@ -18,7 +17,7 @@ public class ListHelperTest { private static final String BEST_RESOLUTION_KEY = "best_resolution"; - private static final List AUDIO_STREAMS_TEST_LIST = Arrays.asList( + private static final List AUDIO_STREAMS_TEST_LIST = List.of( generateAudioStream("m4a-128-1", MediaFormat.M4A, 128), generateAudioStream("webma-192", MediaFormat.WEBMA, 192), generateAudioStream("mp3-64", MediaFormat.MP3, 64), @@ -30,7 +29,7 @@ public class ListHelperTest { generateAudioStream("mp3-192", MediaFormat.MP3, 192), generateAudioStream("webma-320", MediaFormat.WEBMA, 320)); - private static final List VIDEO_STREAMS_TEST_LIST = Arrays.asList( + private static final List VIDEO_STREAMS_TEST_LIST = List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -38,7 +37,7 @@ public class ListHelperTest { generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false), generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false)); - private static final List VIDEO_ONLY_STREAMS_TEST_LIST = Arrays.asList( + private static final List VIDEO_ONLY_STREAMS_TEST_LIST = List.of( generateVideoStream("mpeg_4-720-1", MediaFormat.MPEG_4, "720p", true), generateVideoStream("mpeg_4-720-2", MediaFormat.MPEG_4, "720p", true), generateVideoStream("mpeg_4-2160", MediaFormat.MPEG_4, "2160p", true), @@ -54,7 +53,7 @@ public void getSortedStreamVideosListTest() { List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, false); - List expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", + List expected = List.of("144p", "240p", "360p", "480p", "720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); @@ -69,7 +68,7 @@ public void getSortedStreamVideosListTest() { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false); - expected = Arrays.asList("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", + expected = List.of("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -83,7 +82,7 @@ public void getSortedStreamVideosListWithPreferVideoOnlyStreamsTest() { null, VIDEO_ONLY_STREAMS_TEST_LIST, true, true); List expected = - Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); + List.of("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -97,7 +96,7 @@ public void getSortedStreamVideosListWithPreferVideoOnlyStreamsTest() { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, null, false, true); - expected = Arrays.asList("720p", "480p", "360p", "240p", "144p"); + expected = List.of("720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { assertEquals(expected.get(i), result.get(i).getResolution()); @@ -110,10 +109,10 @@ public void getSortedStreamVideosListWithPreferVideoOnlyStreamsTest() { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, true); - expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", + expected = List.of("144p", "240p", "360p", "480p", "720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); final List expectedVideoOnly = - Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); + List.of("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -131,7 +130,7 @@ public void getSortedStreamVideosExceptHighResolutionsTest() { final List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, false, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false); - final List expected = Arrays.asList( + final List expected = List.of( "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -141,7 +140,7 @@ public void getSortedStreamVideosExceptHighResolutionsTest() { @Test public void getDefaultResolutionTest() { - final List testList = Arrays.asList( + final List testList = List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -223,7 +222,7 @@ public void getHighestQualityAudioFormatPreferredAbsent() { // Doesn't contain the preferred format // //////////////////////////////////////// - List testList = Arrays.asList( + List testList = List.of( generateAudioStream("m4a-128", MediaFormat.M4A, 128), generateAudioStream("webma-192", MediaFormat.WEBMA, 192)); // List doesn't contains this format @@ -237,7 +236,7 @@ public void getHighestQualityAudioFormatPreferredAbsent() { // Multiple not-preferred-formats and equal bitrates // ////////////////////////////////////////////////////// - testList = new ArrayList<>(Arrays.asList( + testList = new ArrayList<>(List.of( generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192), generateAudioStream("m4a-192-1", MediaFormat.M4A, 192), generateAudioStream("webma-192-2", MediaFormat.WEBMA, 192), @@ -290,7 +289,7 @@ public void getLowestQualityAudioFormatPreferredAbsent() { // Doesn't contain the preferred format // //////////////////////////////////////// - List testList = new ArrayList<>(Arrays.asList( + List testList = new ArrayList<>(List.of( generateAudioStream("m4a-128", MediaFormat.M4A, 128), generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192))); // List doesn't contains this format @@ -310,7 +309,7 @@ public void getLowestQualityAudioFormatPreferredAbsent() { // Multiple not-preferred-formats and equal bitrates // ////////////////////////////////////////////////////// - testList = new ArrayList<>(Arrays.asList( + testList = new ArrayList<>(List.of( generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192), generateAudioStream("m4a-192-1", MediaFormat.M4A, 192), generateAudioStream("webma-256", MediaFormat.WEBMA, 256), @@ -337,7 +336,7 @@ public void getLowestQualityAudioNull() { @Test public void getVideoDefaultStreamIndexCombinations() { - final List testList = Arrays.asList( + final List testList = List.of( generateVideoStream("mpeg_4-1080", MediaFormat.MPEG_4, "1080p", false), generateVideoStream("mpeg_4-720_60", MediaFormat.MPEG_4, "720p60", false), generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), From 55a995c4cd5990eb0a6b0058b58e7fa2e16a1c00 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 08:13:08 +0530 Subject: [PATCH 163/240] Replace LinkedHashMap with List.of(). --- .../detail/VideoDetailPlayerCrasher.java | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index 55336a42f0e..c816723ff7c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -6,6 +6,7 @@ import android.content.Context; import android.util.Log; +import android.util.Pair; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -28,9 +29,7 @@ import org.schabi.newpipe.util.ThemeHelper; import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.List; import java.util.function.Supplier; /** @@ -43,50 +42,34 @@ public final class VideoDetailPlayerCrasher { // https://stackoverflow.com/a/54744028 private static final String TAG = "VideoDetPlayerCrasher"; - private static final Map> AVAILABLE_EXCEPTION_TYPES = - getExceptionTypes(); + private static final String DEFAULT_MSG = "Dummy"; + + private static final List>> + AVAILABLE_EXCEPTION_TYPES = List.of( + new Pair<>("Source", () -> ExoPlaybackException.createForSource( + new IOException(DEFAULT_MSG), + ERROR_CODE_BEHIND_LIVE_WINDOW + )), + new Pair<>("Renderer", () -> ExoPlaybackException.createForRenderer( + new Exception(DEFAULT_MSG), + "Dummy renderer", + 0, + null, + C.FORMAT_HANDLED, + /*isRecoverable=*/false, + ERROR_CODE_DECODING_FAILED + )), + new Pair<>("Unexpected", () -> ExoPlaybackException.createForUnexpected( + new RuntimeException(DEFAULT_MSG), + ERROR_CODE_UNSPECIFIED + )), + new Pair<>("Remote", () -> ExoPlaybackException.createForRemote(DEFAULT_MSG)) + ); private VideoDetailPlayerCrasher() { // No impls } - private static Map> getExceptionTypes() { - final String defaultMsg = "Dummy"; - final Map> exceptionTypes = new LinkedHashMap<>(); - exceptionTypes.put( - "Source", - () -> ExoPlaybackException.createForSource( - new IOException(defaultMsg), - ERROR_CODE_BEHIND_LIVE_WINDOW - ) - ); - exceptionTypes.put( - "Renderer", - () -> ExoPlaybackException.createForRenderer( - new Exception(defaultMsg), - "Dummy renderer", - 0, - null, - C.FORMAT_HANDLED, - /*isRecoverable=*/false, - ERROR_CODE_DECODING_FAILED - ) - ); - exceptionTypes.put( - "Unexpected", - () -> ExoPlaybackException.createForUnexpected( - new RuntimeException(defaultMsg), - ERROR_CODE_UNSPECIFIED - ) - ); - exceptionTypes.put( - "Remote", - () -> ExoPlaybackException.createForRemote(defaultMsg) - ); - - return Collections.unmodifiableMap(exceptionTypes); - } - private static Context getThemeWrapperContext(final Context context) { return new ContextThemeWrapper( context, @@ -121,10 +104,9 @@ public static void onCrashThePlayer( .setNegativeButton(R.string.cancel, null) .create(); - for (final Map.Entry> entry - : AVAILABLE_EXCEPTION_TYPES.entrySet()) { + for (final Pair> entry : AVAILABLE_EXCEPTION_TYPES) { final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot(); - radioButton.setText(entry.getKey()); + radioButton.setText(entry.first); radioButton.setChecked(false); radioButton.setLayoutParams( new RadioGroup.LayoutParams( @@ -133,7 +115,7 @@ public static void onCrashThePlayer( ) ); radioButton.setOnClickListener(v -> { - tryCrashPlayerWith(player, entry.getValue().get()); + tryCrashPlayerWith(player, entry.second.get()); alertDialog.cancel(); }); binding.list.addView(radioButton); From a6cc13845a70dd80aaa31efa291078c3534cca48 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Tue, 19 Jul 2022 08:52:16 +0530 Subject: [PATCH 164/240] Use Map.of(). --- .../helper/PlaybackParameterDialog.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 8a5a4f8d204..d542e1c531c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -30,7 +30,6 @@ import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -334,10 +333,8 @@ private void setupPitchControlModeTextView( } private Map getPitchControlModeComponentMappings() { - final Map mappings = new HashMap<>(); - mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent); - mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); - return mappings; + return Map.of(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent, + PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); } private void changePitchControlMode(final boolean semitones) { @@ -407,13 +404,11 @@ private void setupStepTextView( } private Map getStepSizeComponentMappings() { - final Map mappings = new HashMap<>(); - mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent); - mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent); - mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent); - mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent); - mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent); - return mappings; + 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); } private void setStepSizeToUI(final double newStepSize) { From d62cdc659f2933a2f522e7054838f3d86162bde6 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 09:00:11 +0530 Subject: [PATCH 165/240] Use MathUtils.clamp(). Co-authored-by: Stypox --- .../org/schabi/newpipe/RouterActivity.java | 3 ++- .../org/schabi/newpipe/player/Player.java | 13 ++++--------- .../helper/PlaybackParameterDialog.java | 10 +++------- .../player/helper/PlayerSemitoneHelper.java | 4 +++- .../playqueue/PlayQueueItemTouchCallback.java | 15 +++++++++------ .../SeekbarPreviewThumbnailHelper.java | 19 ++++++------------- .../newpipe/player/ui/PopupPlayerUi.java | 16 +++++----------- .../newpipe/player/ui/VideoPlayerUi.java | 14 ++++---------- .../org/schabi/newpipe/util/Localization.java | 4 ++-- 9 files changed, 38 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index d055da1e8f6..9daceda080f 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -30,6 +30,7 @@ import androidx.appcompat.content.res.AppCompatResources; import androidx.core.app.NotificationCompat; import androidx.core.app.ServiceCompat; +import androidx.core.math.MathUtils; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; @@ -452,7 +453,7 @@ private void showDialog(final List choices) { } } - selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.size() - 1); + selectedRadioPosition = MathUtils.clamp(selectedRadioPosition, -1, choices.size() - 1); if (selectedRadioPosition != -1) { ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true); } diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 43427ac2762..159d361c144 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -62,6 +62,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; @@ -591,7 +592,7 @@ public void setRecovery() { final long duration = simpleExoPlayer.getDuration(); // No checks due to https://github.com/TeamNewPipe/NewPipe/pull/7195#issuecomment-962624380 - setRecovery(queuePos, Math.max(0, Math.min(windowPos, duration))); + setRecovery(queuePos, MathUtils.clamp(windowPos, 0, duration)); } private void setRecovery(final int queuePos, final long windowPos) { @@ -1534,14 +1535,8 @@ public void seekTo(final long positionMillis) { } if (!exoPlayerIsNull()) { // prevent invalid positions when fast-forwarding/-rewinding - long normalizedPositionMillis = positionMillis; - if (normalizedPositionMillis < 0) { - normalizedPositionMillis = 0; - } else if (normalizedPositionMillis > simpleExoPlayer.getDuration()) { - normalizedPositionMillis = simpleExoPlayer.getDuration(); - } - - simpleExoPlayer.seekTo(normalizedPositionMillis); + simpleExoPlayer.seekTo(MathUtils.clamp(positionMillis, 0, + simpleExoPlayer.getDuration())); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 8a5a4f8d204..83258bcd2fb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -21,6 +21,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; +import androidx.core.math.MathUtils; import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; @@ -532,7 +533,7 @@ private void setSliders(final double newValue) { } private void setAndUpdateTempo(final double newTempo) { - this.tempo = calcValidTempo(newTempo); + this.tempo = MathUtils.clamp(newTempo, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED); binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); @@ -551,13 +552,8 @@ private void setAndUpdatePitch(final double newPitch) { pitchPercent); } - private double calcValidTempo(final double newTempo) { - return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo)); - } - private double calcValidPitch(final double newPitch) { - final double calcPitch = - Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch)); + final double calcPitch = MathUtils.clamp(newPitch, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED); if (!isCurrentPitchControlModeSemitone()) { return calcPitch; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java index f3a71d7cd9e..f1ba90f8ed4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.helper; +import androidx.core.math.MathUtils; + /** * Converts between percent and 12-tone equal temperament semitones. *
@@ -33,6 +35,6 @@ public static int percentToSemitones(final double percent) { } private static int ensureSemitonesInRange(final int semitones) { - return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones)); + return MathUtils.clamp(semitones, -SEMITONE_COUNT, SEMITONE_COUNT); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java index b283e105ec6..de1359bcab3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.playqueue; +import androidx.annotation.NonNull; +import androidx.core.math.MathUtils; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -16,18 +18,19 @@ public PlayQueueItemTouchCallback() { public abstract void onSwiped(int index); @Override - public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, final int viewSize, - final int viewSizeOutOfBounds, final int totalSize, - final long msSinceStartScroll) { + public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView, + final int viewSize, final int viewSizeOutOfBounds, + final int totalSize, final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, - Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY)); + final int clampedAbsVelocity = MathUtils.clamp(Math.abs(standardSpeed), + MINIMUM_INITIAL_DRAG_VELOCITY, MAXIMUM_INITIAL_DRAG_VELOCITY); return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); } @Override - public boolean onMove(final RecyclerView recyclerView, final RecyclerView.ViewHolder source, + public boolean onMove(@NonNull final RecyclerView recyclerView, + final RecyclerView.ViewHolder source, final RecyclerView.ViewHolder target) { if (source.getItemViewType() != target.getItemViewType()) { return false; diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java index 54d11da8380..dba28a69a54 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java @@ -8,13 +8,13 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.util.DeviceUtils; import java.lang.annotation.Retention; -import java.util.Objects; import java.util.Optional; import java.util.function.IntSupplier; @@ -79,19 +79,12 @@ public static void tryResizeAndSetSeekbarPreviewThumbnail( // Resize original bitmap try { - Objects.requireNonNull(srcBitmap); - final int srcWidth = srcBitmap.getWidth() > 0 ? srcBitmap.getWidth() : 1; - final int newWidth = Math.max( - Math.min( - // Use 1/4 of the width for the preview - Math.round(baseViewWidthSupplier.getAsInt() / 4f), - // Scaling more than that factor looks really pixelated -> max - Math.round(srcWidth * 2.5f) - ), - // Min width = 10dp - DeviceUtils.dpToPx(10, context) - ); + // Use 1/4 of the width for the preview + final int newWidth = MathUtils.clamp(Math.round(baseViewWidthSupplier.getAsInt() / 4f), + DeviceUtils.dpToPx(10, context), + // Scaling more than that factor looks really pixelated -> max + Math.round(srcWidth * 2.5f)); final float scaleFactor = (float) newWidth / srcWidth; final int newHeight = (int) (srcBitmap.getHeight() * scaleFactor); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index bb810f86bc1..74adf281c9f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -27,6 +27,7 @@ import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; +import androidx.core.math.MathUtils; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.SubtitleView; @@ -247,17 +248,10 @@ public void checkPopupPositionBounds() { return; } - if (popupLayoutParams.x < 0) { - popupLayoutParams.x = 0; - } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { - popupLayoutParams.x = screenWidth - popupLayoutParams.width; - } - - if (popupLayoutParams.y < 0) { - popupLayoutParams.y = 0; - } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { - popupLayoutParams.y = screenHeight - popupLayoutParams.height; - } + popupLayoutParams.x = MathUtils.clamp(popupLayoutParams.x, 0, screenWidth + - popupLayoutParams.width); + popupLayoutParams.y = MathUtils.clamp(popupLayoutParams.y, 0, screenHeight + - popupLayoutParams.height); } public void updateScreenSize() { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 217dd38b311..a972d2f7115 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -43,6 +43,7 @@ import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.PopupMenu; import androidx.core.graphics.Insets; +import androidx.core.math.MathUtils; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; @@ -580,16 +581,9 @@ private void adjustSeekbarPreviewContainer() { currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); // Fix the position so it's within the boundaries - final int checkedContainerLeft = - Math.max( - Math.min( - uncheckedContainerLeft, - // Max left - binding.playbackWindowRoot.getWidth() - - binding.seekbarPreviewContainer.getWidth() - ), - 0 // Min left - ); + final int checkedContainerLeft = MathUtils.clamp(uncheckedContainerLeft, + 0, binding.playbackWindowRoot.getWidth() + - binding.seekbarPreviewContainer.getWidth()); // See also: https://stackoverflow.com/a/23249734 final LinearLayout.LayoutParams params = diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index b222f6abf96..5ac24503d97 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -13,6 +13,7 @@ import androidx.annotation.NonNull; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import org.ocpsoft.prettytime.PrettyTime; @@ -247,8 +248,7 @@ private static String getQuantity(final Context context, @PluralsRes final int p // is not the responsibility of this method handle long numbers // (it probably will fall in the "other" category, // or some language have some specific rule... then we have to change it) - final int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE - : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count; + final int safeCount = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE); return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); } From 394eb92e714e4c2f7375aea4bf22afd5a84ba660 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 09:09:18 +0530 Subject: [PATCH 166/240] Use coerceIn(). --- .../gesture/PopupPlayerGestureListener.kt | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index bda6ee8d10f..01b15f30a07 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -233,22 +233,14 @@ class PopupPlayerGestureListener( isMoving = true - val diffX: Float = (movingEvent.rawX - initialEvent.rawX) - var posX: Float = (initialPopupX + diffX) - val diffY: Float = (movingEvent.rawY - initialEvent.rawY) - var posY: Float = (initialPopupY + diffY) - - if (posX > playerUi.screenWidth - playerUi.popupLayoutParams.width) { - posX = (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() - } else if (posX < 0) { - posX = 0f - } - - if (posY > playerUi.screenHeight - playerUi.popupLayoutParams.height) { - posY = (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() - } else if (posY < 0) { - posY = 0f - } + val diffX = (movingEvent.rawX - initialEvent.rawX) + val posX = (initialPopupX + diffX).coerceIn( + 0f, (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() + ) + val diffY = (movingEvent.rawY - initialEvent.rawY) + val posY = (initialPopupY + diffY).coerceIn( + 0f, (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() + ) playerUi.popupLayoutParams.x = posX.toInt() playerUi.popupLayoutParams.y = posY.toInt() From c5b970cca3e6ba2f8071e9483167697c591925e1 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 20 Jul 2022 14:50:23 +0200 Subject: [PATCH 167/240] Improve code style in List.of() --- .../preferencesearch/PreferenceSearchConfiguration.java | 4 ++-- .../java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java index 88b0b773571..1ded181c825 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java @@ -10,8 +10,8 @@ public class PreferenceSearchConfiguration { private PreferenceSearchFunction searcher = new PreferenceFuzzySearchFunction(); - private final List parserIgnoreElements = List.of(PreferenceCategory.class - .getSimpleName()); + private final List parserIgnoreElements = List.of( + PreferenceCategory.class.getSimpleName()); private final List parserContainerElements = List.of( PreferenceCategory.class.getSimpleName(), PreferenceScreen.class.getSimpleName()); diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 40dacc96471..32f25ccbdf9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -19,7 +19,8 @@ public final class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; private static final List FALLBACK_INITIAL_TABS_LIST = List.of( - Tab.Type.DEFAULT_KIOSK.getTab(), Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.DEFAULT_KIOSK.getTab(), + Tab.Type.SUBSCRIPTIONS.getTab(), Tab.Type.BOOKMARKS.getTab()); private TabsJsonHelper() { } From 373ee53143fd4de2cfb6cd5cbf2ce8f27f04ac99 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 20 Jul 2022 15:05:25 +0200 Subject: [PATCH 168/240] Improve code style --- .../player/playqueue/PlayQueueItemTouchCallback.java | 6 ++++-- .../seekbarpreview/SeekbarPreviewThumbnailHelper.java | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java index de1359bcab3..6e2792d4f85 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java @@ -19,8 +19,10 @@ public PlayQueueItemTouchCallback() { @Override public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView, - final int viewSize, final int viewSizeOutOfBounds, - final int totalSize, final long msSinceStartScroll) { + final int viewSize, + final int viewSizeOutOfBounds, + final int totalSize, + final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); final int clampedAbsVelocity = MathUtils.clamp(Math.abs(standardSpeed), diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java index dba28a69a54..43d89055c7b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java @@ -80,10 +80,12 @@ public static void tryResizeAndSetSeekbarPreviewThumbnail( // Resize original bitmap try { final int srcWidth = srcBitmap.getWidth() > 0 ? srcBitmap.getWidth() : 1; - // Use 1/4 of the width for the preview - final int newWidth = MathUtils.clamp(Math.round(baseViewWidthSupplier.getAsInt() / 4f), + final int newWidth = MathUtils.clamp( + // Use 1/4 of the width for the preview + Math.round(baseViewWidthSupplier.getAsInt() / 4f), + // But have a min width of 10dp DeviceUtils.dpToPx(10, context), - // Scaling more than that factor looks really pixelated -> max + // And scaling more than that factor looks really pixelated -> max Math.round(srcWidth * 2.5f)); final float scaleFactor = (float) newWidth / srcWidth; From b845645b80c07b77b97fc464fdc451c8eb289be2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 06:21:51 +0530 Subject: [PATCH 169/240] Use IO extensions. Co-authored-by: Stypox --- .../newpipe/about/LicenseFragmentHelper.kt | 32 +++---------------- .../settings/ContentSettingsManager.kt | 9 ++---- .../schabi/newpipe/util/ReleaseVersionUtil.kt | 6 +--- 3 files changed, 8 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index c1dd38389c5..3acb50cb9de 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -12,10 +12,7 @@ import org.schabi.newpipe.R import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.ShareUtils -import java.io.BufferedReader import java.io.IOException -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets object LicenseFragmentHelper { /** @@ -25,32 +22,13 @@ object LicenseFragmentHelper { * styled according to the context's theme */ private fun getFormattedLicense(context: Context, license: License): String { - val licenseContent = StringBuilder() - val webViewData: String try { - BufferedReader( - InputStreamReader( - context.assets.open(license.filename), - StandardCharsets.UTF_8 - ) - ).use { `in` -> - var str: String? - while (`in`.readLine().also { str = it } != null) { - licenseContent.append(str) - } - + return context.assets.open(license.filename).bufferedReader().use { it.readText() } // split the HTML file and insert the stylesheet into the HEAD of the file - webViewData = "$licenseContent".replace( - "", - "" - ) - } + .replace("", "") } catch (e: IOException) { - throw IllegalArgumentException( - "Could not get license file: " + license.filename, e - ) + throw IllegalArgumentException("Could not get license file: ${license.filename}", e) } - return webViewData } /** @@ -118,9 +96,7 @@ object LicenseFragmentHelper { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { formattedLicense -> - val webViewData = Base64.encodeToString( - formattedLicense.toByteArray(StandardCharsets.UTF_8), Base64.NO_PADDING - ) + val webViewData = Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) val webView = WebView(context) webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt index 3ac2756952e..8adf6a64909 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt @@ -5,9 +5,6 @@ import android.util.Log import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper -import java.io.BufferedOutputStream -import java.io.FileInputStream -import java.io.FileOutputStream import java.io.IOException import java.io.ObjectInputStream import java.io.ObjectOutputStream @@ -25,12 +22,12 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { @Throws(Exception::class) fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) { file.create() - ZipOutputStream(BufferedOutputStream(SharpOutputStream(file.stream))) + ZipOutputStream(SharpOutputStream(file.stream).buffered()) .use { outZip -> ZipHelper.addFileToZip(outZip, fileLocator.db.path, "newpipe.db") try { - ObjectOutputStream(FileOutputStream(fileLocator.settings)).use { output -> + ObjectOutputStream(fileLocator.settings.outputStream()).use { output -> output.writeObject(preferences.all) output.flush() } @@ -74,7 +71,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { try { val preferenceEditor = preferences.edit() - ObjectInputStream(FileInputStream(fileLocator.settings)).use { input -> + ObjectInputStream(fileLocator.settings.inputStream()).use { input -> preferenceEditor.clear() @Suppress("UNCHECKED_CAST") val entries = input.readObject() as Map diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index 21a9059e289..0c66cc6d417 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -7,8 +7,6 @@ import org.schabi.newpipe.App import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorUtil.Companion.createNotification import org.schabi.newpipe.error.UserAction -import java.io.ByteArrayInputStream -import java.io.InputStream import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.security.cert.CertificateEncodingException @@ -47,10 +45,8 @@ object ReleaseVersionUtil { return "" } val x509cert = try { - val cert = signatures[0].toByteArray() - val input: InputStream = ByteArrayInputStream(cert) val cf = CertificateFactory.getInstance("X509") - cf.generateCertificate(input) as X509Certificate + cf.generateCertificate(signatures[0].toByteArray().inputStream()) as X509Certificate } catch (e: CertificateException) { showRequestError(app, e, "Certificate error") return "" From 8b400b48f7ad5ecfa7762854b55a06aa5c828dd0 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Jul 2022 04:36:54 +0530 Subject: [PATCH 170/240] Refactor notifying method in PlayQueue. --- .../newpipe/player/playqueue/AbstractInfoPlayQueue.java | 4 ++-- .../org/schabi/newpipe/player/playqueue/PlayQueue.java | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index df2747c3b74..e51ee4720d4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -82,7 +82,7 @@ public void onSuccess(@NonNull final T result) { public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; - append(); // Notify change + notifyChange(); } }; } @@ -117,7 +117,7 @@ public void onSuccess( public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; - append(); // Notify change + notifyChange(); } }; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index d54fed248e6..edf5a771c02 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -257,13 +257,10 @@ public synchronized void offsetIndex(final int offset) { } /** - * Appends the given {@link PlayQueueItem}s to the current play queue. - * - * @see #append(List items) - * @param items {@link PlayQueueItem}s to append + * Notifies that a change has occurred. */ - public synchronized void append(@NonNull final PlayQueueItem... items) { - append(List.of(items)); + public synchronized void notifyChange() { + broadcast(new AppendEvent(0)); } /** From 6d1c61407d45e54b810fec7ce187afbbfc224b41 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Jul 2022 07:14:40 +0530 Subject: [PATCH 171/240] Remove unnecessary method in ChannelFragment. --- .../newpipe/fragments/list/channel/ChannelFragment.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index e44048473aa..8ed9389c39d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -578,17 +578,13 @@ private void showContentNotSupportedIfNeeded() { } private PlayQueue getPlayQueue() { - return getPlayQueue(0); - } - - private PlayQueue getPlayQueue(final int index) { final List streamItems = infoListAdapter.getItemsList().stream() .filter(StreamInfoItem.class::isInstance) .map(StreamInfoItem.class::cast) .collect(Collectors.toList()); return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(), - currentInfo.getNextPage(), streamItems, index); + currentInfo.getNextPage(), streamItems, 0); } /*////////////////////////////////////////////////////////////////////////// From f1dab11f1fecdca4c8f3fd3c393718e6528d81e3 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 21 Jul 2022 07:19:23 +0530 Subject: [PATCH 172/240] Remove deprecated method calls in FocusAwareCoordinator. --- .../newpipe/views/FocusAwareCoordinator.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java index 747aed025cf..d4fafc31a93 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java @@ -27,6 +27,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.WindowInsetsCompat; import org.schabi.newpipe.R; @@ -83,23 +84,22 @@ public WindowInsets dispatchApplyWindowInsets(final WindowInsets insets) { } } - if (consumed) { - insets.consumeSystemWindowInsets(); - } - return insets; + return consumed ? WindowInsetsCompat.CONSUMED.toWindowInsets() : insets; } /** - * Adjusts player's controls manually because fitsSystemWindows doesn't work when multiple + * Adjusts player's controls manually because onApplyWindowInsets doesn't work when multiple * receivers adjust its bounds. So when two listeners are present (like in profile page) * the player's controls will not receive insets. This method fixes it */ @Override - protected boolean fitSystemWindows(final Rect insets) { + public WindowInsets onApplyWindowInsets(final WindowInsets windowInsets) { + final var windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets, this); + final var insets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.systemBars()); final ViewGroup controls = findViewById(R.id.playbackControlRoot); if (controls != null) { controls.setPadding(insets.left, insets.top, insets.right, insets.bottom); } - return super.fitSystemWindows(insets); + return super.onApplyWindowInsets(windowInsets); } } From 37275e8fe3b07b3ac7360a0e2e85a54188f78304 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 15:10:35 +0200 Subject: [PATCH 173/240] Fix wrong thumbnail used as placeholder for channel --- .../newpipe/info_list/holder/ChannelMiniInfoItemHolder.java | 2 +- .../org/schabi/newpipe/local/subscription/item/ChannelItem.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index aa4f4c9f023..89398a1e52a 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -42,7 +42,7 @@ public void updateFromItem(final InfoItem infoItem, itemTitleView.setText(item.getName()); itemAdditionalDetailView.setText(getDetailLine(item)); - PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); + PicassoHelper.loadAvatar(item.getThumbnailUrl()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnChannelSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index a8c05838f48..bee2e910a8d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -39,7 +39,7 @@ class ChannelItem( itemChannelDescriptionView.text = infoItem.description } - PicassoHelper.loadThumbnail(infoItem.thumbnailUrl).into(itemThumbnailView) + PicassoHelper.loadAvatar(infoItem.thumbnailUrl).into(itemThumbnailView) gesturesListener?.run { viewHolder.root.setOnClickListener { selected(infoItem) } From f4fe5fcb165d9545b95e050652d54b6e3cc9453d Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 16:04:41 +0200 Subject: [PATCH 174/240] Fix ListHelperTest failure caused by immutable list being used --- .../main/java/org/schabi/newpipe/util/ListHelper.java | 9 +++++---- .../java/org/schabi/newpipe/util/ListHelperTest.java | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 1b3cb1651a0..fbbe43513b7 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -230,14 +230,15 @@ private static String computeDefaultResolution(final Context context, final int } /** - * Return the index of the default stream in the list, based on the parameters - * defaultResolution and defaultFormat. + * Return the index of the default stream in the list, that will be sorted in the process, based + * on the parameters defaultResolution and defaultFormat. * * @param defaultResolution the default resolution to look for * @param bestResolutionKey key of the best resolution * @param defaultFormat the default format to look for - * @param videoStreams list of the video streams to check - * @return index of the default resolution&format + * @param videoStreams a mutable list of the video streams to check (it will be sorted in + * place) + * @return index of the default resolution&format in the sorted videoStreams */ static int getDefaultResolutionIndex(final String defaultResolution, final String bestResolutionKey, diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index 4f9b0b49779..8a75b1b4e07 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -140,7 +140,7 @@ public void getSortedStreamVideosExceptHighResolutionsTest() { @Test public void getDefaultResolutionTest() { - final List testList = List.of( + final List testList = new ArrayList<>(List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -148,7 +148,7 @@ public void getDefaultResolutionTest() { generateVideoStream("mpeg_4-240", MediaFormat.MPEG_4, "240p", false), generateVideoStream("webm-144", MediaFormat.WEBM, "144p", false), generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false), - generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false)); + generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false))); VideoStream result = testList.get(ListHelper.getDefaultResolutionIndex( "720p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList)); assertEquals("720p", result.getResolution()); From 51e72d1a0544ed47789cd102e46a92ec82eaea29 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 24 Jul 2022 14:57:23 +0200 Subject: [PATCH 175/240] Removed the "(beta)"-tag from services (#8637) --- app/src/main/java/org/schabi/newpipe/MainActivity.java | 9 ++++----- .../main/java/org/schabi/newpipe/util/ServiceHelper.java | 9 --------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 4a982874c5b..d4b2305c7e3 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -374,8 +374,7 @@ private void toggleServices() { private void showServices() { for (final StreamingService s : NewPipe.getServices()) { - final String title = s.getServiceInfo().getName() - + (ServiceHelper.isBeta(s) ? " (beta)" : ""); + final String title = s.getServiceInfo().getName(); final MenuItem menuItem = drawerLayoutBinding.navigation.getMenu() .add(R.id.menu_services_group, s.getServiceId(), ORDER, title) @@ -383,7 +382,7 @@ private void showServices() { // peertube specifics if (s.getServiceId() == 3) { - enhancePeertubeMenu(s, menuItem); + enhancePeertubeMenu(menuItem); } } drawerLayoutBinding.navigation.getMenu() @@ -391,9 +390,9 @@ private void showServices() { .setChecked(true); } - private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) { + private void enhancePeertubeMenu(final MenuItem menuItem) { final PeertubeInstance currentInstance = PeertubeHelper.getCurrentInstance(); - menuItem.setTitle(currentInstance.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : "")); + menuItem.setTitle(currentInstance.getName()); final Spinner spinner = InstanceSpinnerLayoutBinding.inflate(LayoutInflater.from(this)) .getRoot(); final List instances = PeertubeHelper.getInstanceList(this); diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index b13ae4a9762..acd019ba077 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -169,15 +169,6 @@ public static long getCacheExpirationMillis(final int serviceId) { } } - public static boolean isBeta(final StreamingService s) { - switch (s.getServiceInfo().getName()) { - case "YouTube": - return false; - default: - return true; - } - } - public static void initService(final Context context, final int serviceId) { if (serviceId == ServiceList.PeerTube.getServiceId()) { final SharedPreferences sharedPreferences = PreferenceManager From 8f5d564f8430ec6d5e582a2d1e9267d84d4c0f4a Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 20 Jul 2022 21:01:17 -0400 Subject: [PATCH 176/240] Migrate NoNonsense-FilePicker to our updated fork --- app/build.gradle | 4 +--- .../org/schabi/newpipe/util/FilePickerActivityHelper.java | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120cee5..dfa512d22fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,6 +188,7 @@ dependencies { // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:NewPipeExtractor:5219a705bab539cf8c6624d0cec216e76e85f0b1' + implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" @@ -257,9 +258,6 @@ dependencies { implementation "io.noties.markwon:core:${markwonVersion}" implementation "io.noties.markwon:linkify:${markwonVersion}" - // File picker - implementation "com.nononsenseapps:filepicker:4.2.1" - // Crash reporting implementation "ch.acra:acra-core:5.9.3" diff --git a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java index 20d8ce30c3d..d7fb3965116 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java @@ -76,7 +76,7 @@ protected AbstractFilePickerFragment getFragment(@Nullable final String st public static class CustomFilePickerFragment extends FilePickerFragment { @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @@ -138,7 +138,7 @@ public boolean isBackTop() { } @Override - public void onLoadFinished(final Loader> loader, + public void onLoadFinished(@NonNull final Loader> loader, final SortedList data) { super.onLoadFinished(loader, data); layoutManager.scrollToPosition(0); From baabba1deade7e798b7852871039ed148ce6b1f6 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 20 Jul 2022 21:32:27 -0400 Subject: [PATCH 177/240] Disable Jetifier --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 76b51ef0bb0..032d70cee99 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -android.enableJetifier=true +android.enableJetifier=false android.useAndroidX=true org.gradle.jvmargs=-Xmx2048M systemProp.file.encoding=utf-8 From 229422bfa95c42f436d4bd21a947c0e5642b64ea Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 14:11:31 -0400 Subject: [PATCH 178/240] Update ExoPlayer to 2.18.1 --- app/build.gradle | 2 +- .../schabi/newpipe/player/datasource/YoutubeHttpDataSource.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120cee5..64378378ec0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -102,7 +102,7 @@ ext { androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' - exoPlayerVersion = '2.18.0' + exoPlayerVersion = '2.18.1' googleAutoServiceVersion = '1.0.1' groupieVersion = '2.10.1' markwonVersion = '4.6.2' diff --git a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java index 2eecddf36ac..cf1f03b4597 100644 --- a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java @@ -1,5 +1,5 @@ /* - * Based on ExoPlayer's DefaultHttpDataSource, version 2.18.0. + * Based on ExoPlayer's DefaultHttpDataSource, version 2.18.1. * * Original source code copyright (C) 2016 The Android Open Source Project, licensed under the * Apache License, Version 2.0. From d7a654fc27d72334a01980e1b527007053734b48 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 15:35:33 -0400 Subject: [PATCH 179/240] Update AndroidX Fragment to 1.4.1 --- app/build.gradle | 2 +- .../schabi/newpipe/player/helper/PlaybackParameterDialog.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120cee5..7aa0fec2899 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,7 +202,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' - implementation 'androidx.fragment:fragment-ktx:1.3.6' + implementation 'androidx.fragment:fragment-ktx:1.4.1' implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index a9b507bd40b..796208a0498 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -11,7 +11,6 @@ import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; import android.widget.SeekBar; @@ -149,7 +148,7 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); Icepick.restoreInstanceState(this, savedInstanceState); - binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); + binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater()); initUI(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) From d66997c2ed3afa66b5fe7432dfad6dfc3ab92383 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 16:51:26 -0400 Subject: [PATCH 180/240] Update Google Material to 1.6.1 --- app/build.gradle | 2 +- .../newpipe/player/gesture/CustomBottomSheetBehavior.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120cee5..b4f17e41f79 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -218,7 +218,7 @@ dependencies { implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" - implementation 'com.google.android.material:material:1.5.0' + implementation 'com.google.android.material:material:1.6.1' /** Third-party libraries **/ // Instance state boilerplate elimination diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index 41046784fed..0970dbeb693 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -8,6 +8,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -18,7 +19,8 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior { - public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) { + public CustomBottomSheetBehavior(@NonNull final Context context, + @Nullable final AttributeSet attrs) { super(context, attrs); } @@ -32,7 +34,7 @@ public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs @Override public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, @NonNull final FrameLayout child, - final MotionEvent event) { + @NonNull final MotionEvent event) { // Drop following when action ends if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { From 81fb44c45c8acf6d235e8afff31f348bccf8e2b7 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 21 Jul 2022 06:56:51 +0530 Subject: [PATCH 181/240] Remove uses of setBottomSheetCallback(). --- .../fragments/detail/VideoDetailFragment.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index f4838482b97..4ad270c501c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -208,6 +208,8 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); + private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; + /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ @@ -386,7 +388,7 @@ public void onDestroy() { disposables.clear(); positionSubscriber = null; currentWorker = null; - bottomSheetBehavior.setBottomSheetCallback(null); + bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallback); if (activity.isFinishing()) { playQueue = null; @@ -2289,7 +2291,7 @@ private void setupBottomPlayer() { } } - bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull final View bottomSheet, final int newState) { bottomSheetState = newState; @@ -2343,12 +2345,14 @@ && isPlayerAvailable() } if (isPlayerAvailable()) { player.UIs().get(MainPlayerUi.class).ifPresent(ui -> { - if (ui.isControlsVisible()) { - ui.hideControls(0, 0); - } + if (ui.isControlsVisible()) { + ui.hideControls(0, 0); + } }); } break; + case BottomSheetBehavior.STATE_HALF_EXPANDED: + break; } } @@ -2356,7 +2360,9 @@ && isPlayerAvailable() public void onSlide(@NonNull final View bottomSheet, final float slideOffset) { setOverlayLook(binding.appBarLayout, behavior, slideOffset); } - }); + }; + + bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback); // User opened a new page and the player will hide itself activity.getSupportFragmentManager().addOnBackStackChangedListener(() -> { From 81c4b822e0e1af82e1ec031457b57e80611ae003 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Tue, 26 Jul 2022 23:29:43 +0300 Subject: [PATCH 182/240] Add "needs triage" label to issue templates (#8643) This label would make it easier for issue triagers to know what they haven't triaged yet. --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/question.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a0a9f9ef58d..3abb1fbb1f8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug report description: Create a bug report to help us improve -labels: [bug] +labels: [bug, needs triage] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 83d6f029997..9fc3c1632ea 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,6 @@ name: Feature request description: Suggest an idea for this project -labels: [enhancement] +labels: [enhancement, needs triage] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 4c42ab26a97..8cf22d8af3b 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -1,6 +1,6 @@ name: Question description: Ask about anything NewPipe-related -labels: [question] +labels: [question, needs triage] body: - type: markdown attributes: From af9c2bd59db4da18b707c2ae178d5852c168a5a5 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 27 Jul 2022 07:54:49 +0530 Subject: [PATCH 183/240] Use stackTraceToString(). --- .../java/org/schabi/newpipe/error/ErrorInfo.kt | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index f9f9f003aa0..d87fa333060 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -14,8 +14,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.util.ServiceHelper -import java.io.PrintWriter -import java.io.StringWriter @Parcelize class ErrorInfo( @@ -80,19 +78,10 @@ class ErrorInfo( companion object { const val SERVICE_NONE = "none" - private fun getStackTrace(throwable: Throwable): String { - StringWriter().use { stringWriter -> - PrintWriter(stringWriter, true).use { printWriter -> - throwable.printStackTrace(printWriter) - return stringWriter.buffer.toString() - } - } - } - - fun throwableToStringList(throwable: Throwable) = arrayOf(getStackTrace(throwable)) + fun throwableToStringList(throwable: Throwable) = arrayOf(throwable.stackTraceToString()) - fun throwableListToStringList(throwable: List) = - Array(throwable.size) { i -> getStackTrace(throwable[i]) } + fun throwableListToStringList(throwableList: List) = + throwableList.map { it.stackTraceToString() }.toTypedArray() private fun getInfoServiceName(info: Info?) = if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId) From 69942003f7dcb0adc0564dd1bc7e47fcd9a11965 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:21:02 +0530 Subject: [PATCH 184/240] Sort tags case-insensitively. --- .../schabi/newpipe/fragments/detail/DescriptionFragment.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index bbe52f2e935..bf7f8fa5d97 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -202,10 +202,9 @@ private void addMetadataItem(final LayoutInflater inflater, private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) { - final ItemMetadataTagsBinding itemBinding = - ItemMetadataTagsBinding.inflate(inflater, layout, false); + final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - streamInfo.getTags().stream().sorted().forEach(tag -> { + streamInfo.getTags().stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); chip.setText(tag); From 630558ed4f55e77bed1509c05d0b4a1ae22c3d45 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 01:39:02 +0530 Subject: [PATCH 185/240] Use nested functions. --- .../main/java/org/schabi/newpipe/ktx/View.kt | 49 ++++++------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt index 56c9d825abd..5ab8dbdc2b7 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -90,64 +90,43 @@ fun View.animate( */ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateBackgroundColor() called with: " + - "view = [" + this + "], duration = [" + duration + "], " + - "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]" + Log.d(TAG, "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + + "colorStart = [$colorStart], colorEnd = [$colorEnd]" ) } - val empty = arrayOf(IntArray(0)) val viewPropertyAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorStart, colorEnd) viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration - viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(animation.animatedValue as Int))) + + fun listenerAction(color: Int) { + ViewCompat.setBackgroundTintList(this, ColorStateList.valueOf(color)) } - viewPropertyAnimator.addListener( - onCancel = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) }, - onEnd = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) } - ) + viewPropertyAnimator.addUpdateListener { listenerAction(it.animatedValue as Int) } + viewPropertyAnimator.addListener(onCancel = { listenerAction(colorEnd) }, onEnd = { listenerAction(colorEnd) }) viewPropertyAnimator.start() } fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateHeight: duration = [" + duration + "], " + - "from " + height + " to → " + targetHeight + " in: " + this - ) + Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this") } val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat()) animator.interpolator = FastOutSlowInInterpolator() animator.duration = duration - animator.addUpdateListener { animation: ValueAnimator -> - val value = animation.animatedValue as Float - layoutParams.height = value.toInt() + + fun listenerAction(value: Int) { + layoutParams.height = value requestLayout() } - animator.addListener( - onCancel = { - layoutParams.height = targetHeight - requestLayout() - }, - onEnd = { - layoutParams.height = targetHeight - requestLayout() - } - ) + animator.addUpdateListener { listenerAction((it.animatedValue as Float).toInt()) } + animator.addListener(onCancel = { listenerAction(targetHeight) }, onEnd = { listenerAction(targetHeight) }) animator.start() return animator } fun View.animateRotation(duration: Long, targetRotation: Int) { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateRotation: duration = [" + duration + "], " + - "from " + rotation + " to → " + targetRotation + " in: " + this - ) + Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this") } animate().setListener(null).cancel() animate() From 4d7a6fb6deb36125a4f74490ebe11e48b92f0a58 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 08:53:29 +0530 Subject: [PATCH 186/240] Use WindowMetrics API in VideoDetailFragment and PopupPlayerUi. --- .../fragments/detail/VideoDetailFragment.java | 11 ++++------ .../newpipe/player/ui/PopupPlayerUi.java | 20 ++++++++++++++----- .../org/schabi/newpipe/util/DeviceUtils.java | 17 ++++++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index f4838482b97..8079221661f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -21,7 +21,6 @@ import android.content.pm.ActivityInfo; import android.database.ContentObserver; import android.graphics.Color; -import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -1051,15 +1050,13 @@ public void updateTabLayoutVisibility() { // call `post()` to be sure `viewPager.getHitRect()` // is up to date and not being currently recomputed binding.tabLayout.post(() -> { - if (getContext() != null) { + final var activity = getActivity(); + if (activity != null) { final Rect pagerHitRect = new Rect(); binding.viewPager.getHitRect(pagerHitRect); - final Point displaySize = new Point(); - Objects.requireNonNull(ContextCompat.getSystemService(getContext(), - WindowManager.class)).getDefaultDisplay().getSize(displaySize); - - final int viewPagerVisibleHeight = displaySize.y - pagerHitRect.top; + final int height = DeviceUtils.getWindowHeight(activity.getWindowManager()); + final int viewPagerVisibleHeight = height - pagerHitRect.top; // see TabLayout.DEFAULT_HEIGHT, which is equal to 48dp final float tabLayoutHeight = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics()); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 74adf281c9f..aa36a6a5a97 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -21,6 +21,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.AnticipateInterpolator; import android.widget.LinearLayout; @@ -255,11 +256,20 @@ public void checkPopupPositionBounds() { } public void updateScreenSize() { - final DisplayMetrics metrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(metrics); - - screenWidth = metrics.widthPixels; - screenHeight = metrics.heightPixels; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final var windowMetrics = windowManager.getCurrentWindowMetrics(); + final var bounds = windowMetrics.getBounds(); + final var windowInsets = windowMetrics.getWindowInsets(); + final var insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + screenWidth = bounds.width() - (insets.left + insets.right); + screenHeight = bounds.height() - (insets.top + insets.bottom); + } else { + final DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(metrics); + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + } if (DEBUG) { Log.d(TAG, "updateScreenSize() called: screenWidth = [" + screenWidth + "], screenHeight = [" + screenHeight + "]"); diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 7f4b33f44ee..3c20dc04b5a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -4,11 +4,14 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Point; import android.os.BatteryManager; import android.os.Build; import android.provider.Settings; import android.util.TypedValue; import android.view.KeyEvent; +import android.view.WindowInsets; +import android.view.WindowManager; import androidx.annotation.Dimension; import androidx.annotation.NonNull; @@ -151,4 +154,18 @@ public static boolean hasAnimationsAnimatorDurationEnabled(final Context context Settings.Global.ANIMATOR_DURATION_SCALE, 1F) != 0F; } + + public static int getWindowHeight(@NonNull final WindowManager windowManager) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final var windowMetrics = windowManager.getCurrentWindowMetrics(); + final var windowInsets = windowMetrics.getWindowInsets(); + final var insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + return windowMetrics.getBounds().height() - (insets.top + insets.bottom); + } else { + final Point point = new Point(); + windowManager.getDefaultDisplay().getSize(point); + return point.y; + } + } } From 5c68c8ece8ab36734db7cabeb722fa05ca7f2739 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 13:52:56 +0530 Subject: [PATCH 187/240] Update Lifecycle to 2.5.1. --- app/build.gradle | 6 +++--- .../java/org/schabi/newpipe/local/feed/FeedViewModel.kt | 2 +- .../local/subscription/dialog/FeedGroupDialogViewModel.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120cee5..c4a9aa1ee6c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,7 +97,7 @@ android { ext { checkstyleVersion = '10.3.1' - androidxLifecycleVersion = '2.3.1' + androidxLifecycleVersion = '2.5.1' androidxRoomVersion = '2.4.2' androidxWorkVersion = '2.7.1' @@ -203,8 +203,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' - implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" - implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' implementation 'androidx.media:media:1.6.0' implementation 'androidx.preference:preference:1.2.0' diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 7f5ef4301fc..fd8c5f5de4f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -146,7 +146,7 @@ class FeedViewModel( private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return FeedViewModel( context.applicationContext, groupId, diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt index 54ba1c6dc53..dfdb2b47af9 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt @@ -122,7 +122,7 @@ class FeedGroupDialogViewModel( private val initialShowOnlyUngrouped: Boolean = false ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return FeedGroupDialogViewModel( context.applicationContext, groupId, initialQuery, initialShowOnlyUngrouped From 404c13d4c1d5bc887a45733018a87cee26056fc2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 26 Jul 2022 17:31:14 +0200 Subject: [PATCH 188/240] Improve FeedViewModel factory --- .../schabi/newpipe/local/feed/FeedFragment.kt | 4 +-- .../newpipe/local/feed/FeedViewModel.kt | 28 ++++++++----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index f0ebabd8545..89916305084 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -135,8 +135,8 @@ class FeedFragment : BaseStateFragment() { _feedBinding = FragmentFeedBinding.bind(rootView) super.onViewCreated(rootView, savedInstanceState) - val factory = FeedViewModel.Factory(requireContext(), groupId) - viewModel = ViewModelProvider(this, factory).get(FeedViewModel::class.java) + val factory = FeedViewModel.getFactory(requireContext(), groupId) + viewModel = ViewModelProvider(this, factory)[FeedViewModel::class.java] showPlayedItems = viewModel.getShowPlayedItemsFromPreferences() showFutureItems = viewModel.getShowFutureItemsFromPreferences() viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(::handleResult) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index fd8c5f5de4f..896d815bde6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -5,7 +5,8 @@ import androidx.core.content.edit import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import androidx.preference.PreferenceManager import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable @@ -139,21 +140,16 @@ class FeedViewModel( private fun getShowFutureItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.feed_show_future_items_key), true) - } - - class Factory( - private val context: Context, - private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return FeedViewModel( - context.applicationContext, - groupId, - // Read initial value from preferences - getShowPlayedItemsFromPreferences(context.applicationContext), - getShowFutureItemsFromPreferences(context.applicationContext) - ) as T + fun getFactory(context: Context, groupId: Long) = viewModelFactory { + initializer { + FeedViewModel( + context.applicationContext, + groupId, + // Read initial value from preferences + getShowPlayedItemsFromPreferences(context.applicationContext), + getShowFutureItemsFromPreferences(context.applicationContext) + ) + } } } } From 311d392386696a4185405d80f8f271f17005da7f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 08:35:50 +0530 Subject: [PATCH 189/240] Use Application instead of Context in FeedViewModel. --- .../newpipe/local/feed/FeedViewModel.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 896d815bde6..76d5e9d632b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -1,5 +1,6 @@ package org.schabi.newpipe.local.feed +import android.app.Application import android.content.Context import androidx.core.content.edit import androidx.lifecycle.LiveData @@ -13,6 +14,7 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.functions.Function5 import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.App import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.stream.StreamWithState @@ -27,12 +29,12 @@ import java.time.OffsetDateTime import java.util.concurrent.TimeUnit class FeedViewModel( - private val applicationContext: Context, + private val application: Application, groupId: Long = FeedGroupEntity.GROUP_ALL_ID, initialShowPlayedItems: Boolean = true, initialShowFutureItems: Boolean = true ) : ViewModel() { - private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext) + private val feedDatabaseManager = FeedDatabaseManager(application) private val toggleShowPlayedItems = BehaviorProcessor.create() private val toggleShowPlayedItemsFlowable = toggleShowPlayedItems @@ -114,24 +116,24 @@ class FeedViewModel( } fun saveShowPlayedItemsToPreferences(showPlayedItems: Boolean) = - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { - this.putBoolean(applicationContext.getString(R.string.feed_show_played_items_key), showPlayedItems) + PreferenceManager.getDefaultSharedPreferences(application).edit { + this.putBoolean(application.getString(R.string.feed_show_played_items_key), showPlayedItems) this.apply() } - fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(applicationContext) + fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(application) fun toggleFutureItems(showFutureItems: Boolean) { toggleShowFutureItems.onNext(showFutureItems) } fun saveShowFutureItemsToPreferences(showFutureItems: Boolean) = - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { - this.putBoolean(applicationContext.getString(R.string.feed_show_future_items_key), showFutureItems) + PreferenceManager.getDefaultSharedPreferences(application).edit { + this.putBoolean(application.getString(R.string.feed_show_future_items_key), showFutureItems) this.apply() } - fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(applicationContext) + fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(application) companion object { private fun getShowPlayedItemsFromPreferences(context: Context) = @@ -143,7 +145,7 @@ class FeedViewModel( fun getFactory(context: Context, groupId: Long) = viewModelFactory { initializer { FeedViewModel( - context.applicationContext, + App.getApp(), groupId, // Read initial value from preferences getShowPlayedItemsFromPreferences(context.applicationContext), From 582032f37299cdfdf6854d00718e96646bb07d2b Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 31 Jul 2022 00:14:23 -0400 Subject: [PATCH 190/240] Update AndroidX Room to 2.4.3 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120cee5..c77335ad948 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -98,7 +98,7 @@ ext { checkstyleVersion = '10.3.1' androidxLifecycleVersion = '2.3.1' - androidxRoomVersion = '2.4.2' + androidxRoomVersion = '2.4.3' androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' From 47f58040d1fc843ba8f4b7abcc1d40b1f9852cc1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 13:38:24 +0530 Subject: [PATCH 191/240] Make OnClickGesture an interface. --- .../fragments/list/BaseListFragment.java | 52 ++++++------------- .../local/bookmark/BookmarkFragment.java | 2 +- .../local/dialog/PlaylistAppendDialog.java | 22 ++------ .../history/StatisticsPlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../subscription/SubscriptionFragment.kt | 4 +- .../schabi/newpipe/util/OnClickGesture.java | 9 ++-- 7 files changed, 29 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 27e5a8571e4..9e7cb757ccc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -23,14 +23,11 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; -import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.InfoListAdapter; +import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.StateSaver; @@ -264,45 +261,28 @@ public void held(final StreamInfoItem selectedItem) { } }); - infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final ChannelInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openChannelFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar( - BaseListFragment.this, "Opening channel fragment", e); - } - } - }); - - infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final PlaylistInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openPlaylistFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar(BaseListFragment.this, - "Opening playlist fragment", e); - } + infoListAdapter.setOnChannelSelectedListener(selectedItem -> { + try { + onItemSelected(selectedItem); + NavigationHelper.openChannelFragment(getFM(), selectedItem.getServiceId(), + selectedItem.getUrl(), selectedItem.getName()); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e); } }); - infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final CommentsInfoItem selectedItem) { + infoListAdapter.setOnPlaylistSelectedListener(selectedItem -> { + try { onItemSelected(selectedItem); + NavigationHelper.openPlaylistFragment(getFM(), selectedItem.getServiceId(), + selectedItem.getUrl(), selectedItem.getName()); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening playlist fragment", e); } }); + infoListAdapter.setOnCommentsSelectedListener(this::onItemSelected); + // Ensure that there is always a scroll listener (e.g. when rotating the device) useNormalItemListScrollListener(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index ac11d007f49..be74145424c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -98,7 +98,7 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { protected void initListeners() { super.initListeners(); - itemListAdapter.setSelectedListener(new OnClickGesture() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { final FragmentManager fragmentManager = getFM(); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index a97eb0c1844..3d5d16c39c1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -13,12 +13,10 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.local.LocalItemListAdapter; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; -import org.schabi.newpipe.util.OnClickGesture; import java.util.List; @@ -63,18 +61,10 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext())); playlistAdapter = new LocalItemListAdapter(getActivity()); - playlistAdapter.setSelectedListener(new OnClickGesture() { - @Override - public void selected(final LocalItem selectedItem) { - if (!(selectedItem instanceof PlaylistMetadataEntry) - || getStreamEntities() == null) { - return; - } - onPlaylistSelected( - playlistManager, - (PlaylistMetadataEntry) selectedItem, - getStreamEntities() - ); + playlistAdapter.setSelectedListener(selectedItem -> { + final List entities = getStreamEntities(); + if (selectedItem instanceof PlaylistMetadataEntry && entities != null) { + onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, entities); } }); @@ -138,10 +128,6 @@ private void onPlaylistsReceived(@NonNull final List play private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager, @NonNull final PlaylistMetadataEntry playlist, @NonNull final List streams) { - if (getStreamEntities() == null) { - return; - } - final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 01df342920b..a20a80ae985 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -135,7 +135,7 @@ protected ViewBinding getListHeader() { protected void initListeners() { super.initListeners(); - itemListAdapter.setSelectedListener(new OnClickGesture() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { if (selectedItem instanceof StreamStatisticsEntry) { diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index fa789d97d8a..11d54f1efd8 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -167,7 +167,7 @@ protected void initListeners() { itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(itemsList); - itemListAdapter.setSelectedListener(new OnClickGesture() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { if (selectedItem instanceof PlaylistStreamEntry) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 4295424e695..20f8a01c132 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -346,7 +346,7 @@ class SubscriptionFragment : BaseStateFragment() { override fun doInitialLoadLogic() = Unit override fun startLoading(forceLoad: Boolean) = Unit - private val listenerFeedGroups = object : OnClickGesture>() { + private val listenerFeedGroups = object : OnClickGesture> { override fun selected(selectedItem: Item<*>?) { when (selectedItem) { is FeedGroupCardItem -> NavigationHelper.openFeedFragment(fm, selectedItem.groupId, selectedItem.name) @@ -361,7 +361,7 @@ class SubscriptionFragment : BaseStateFragment() { } } - private val listenerChannelItem = object : OnClickGesture() { + private val listenerChannelItem = object : OnClickGesture { override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment( fm, selectedItem.serviceId, selectedItem.url, selectedItem.name diff --git a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java index 5f44cab8b67..ae8d86af1a8 100644 --- a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java +++ b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java @@ -2,15 +2,14 @@ import androidx.recyclerview.widget.RecyclerView; -public abstract class OnClickGesture { +public interface OnClickGesture { + void selected(T selectedItem); - public abstract void selected(T selectedItem); - - public void held(final T selectedItem) { + default void held(final T selectedItem) { // Optional gesture } - public void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) { + default void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) { // Optional gesture } } From 8a896114c110ba4fb6441465fbb88317e597666f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 1 Aug 2022 08:25:24 +0530 Subject: [PATCH 192/240] Apply code review change. --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 4ad270c501c..9d13cb931b7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -192,6 +192,7 @@ public final class VideoDetailFragment private Disposable positionSubscriber = null; private BottomSheetBehavior bottomSheetBehavior; + private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; private BroadcastReceiver broadcastReceiver; /*////////////////////////////////////////////////////////////////////////// @@ -208,8 +209,6 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); - private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; - /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ From 947242d9134d20696c72b98b39cd098b30fc1029 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 06:33:53 +0530 Subject: [PATCH 193/240] Update AppCompat to 1.4.2. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c4a9aa1ee6c..d672940a960 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" /** AndroidX **/ - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.8.0' From 013522c3766816db2bb23c144e7fdd36b1562aaa Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 05:20:06 +0530 Subject: [PATCH 194/240] Convert LicenseFragmentHelper methods to top-level declarations. --- .../schabi/newpipe/about/LicenseFragment.kt | 1 - .../newpipe/about/LicenseFragmentHelper.kt | 137 +++++++++--------- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt index c816d78be5d..f19ecd74a02 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt @@ -8,7 +8,6 @@ import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import io.reactivex.rxjava3.disposables.CompositeDisposable import org.schabi.newpipe.R -import org.schabi.newpipe.about.LicenseFragmentHelper.showLicense import org.schabi.newpipe.databinding.FragmentLicensesBinding import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 3acb50cb9de..34dfe8fb45a 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -14,30 +14,29 @@ import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.ShareUtils import java.io.IOException -object LicenseFragmentHelper { - /** - * @param context the context to use - * @param license the license - * @return String which contains a HTML formatted license page - * styled according to the context's theme - */ - private fun getFormattedLicense(context: Context, license: License): String { - try { - return context.assets.open(license.filename).bufferedReader().use { it.readText() } - // split the HTML file and insert the stylesheet into the HEAD of the file - .replace("", "") - } catch (e: IOException) { - throw IllegalArgumentException("Could not get license file: ${license.filename}", e) - } +/** + * @param context the context to use + * @param license the license + * @return String which contains a HTML formatted license page + * styled according to the context's theme + */ +private fun getFormattedLicense(context: Context, license: License): String { + try { + return context.assets.open(license.filename).bufferedReader().use { it.readText() } + // split the HTML file and insert the stylesheet into the HEAD of the file + .replace("", "") + } catch (e: IOException) { + throw IllegalArgumentException("Could not get license file: ${license.filename}", e) } +} - /** - * @param context the Android context - * @return String which is a CSS stylesheet according to the context's theme - */ - private fun getLicenseStylesheet(context: Context): String { - val isLightTheme = ThemeHelper.isLightThemeSelected(context) - return ( +/** + * @param context the Android context + * @return String which is a CSS stylesheet according to the context's theme + */ +private fun getLicenseStylesheet(context: Context): String { + val isLightTheme = ThemeHelper.isLightThemeSelected(context) + return ( "body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor( context, if (isLightTheme) R.color.light_license_background_color @@ -52,62 +51,62 @@ object LicenseFragmentHelper { else R.color.dark_youtube_primary_color ) + "}" + "pre{white-space:pre-wrap}" ) - } +} - /** - * Cast R.color to a hexadecimal color value. - * - * @param context the context to use - * @param color the color number from R.color - * @return a six characters long String with hexadecimal RGB values - */ - private fun getHexRGBColor(context: Context, color: Int): String { - return context.getString(color).substring(3) - } +/** + * Cast R.color to a hexadecimal color value. + * + * @param context the context to use + * @param color the color number from R.color + * @return a six characters long String with hexadecimal RGB values + */ +private fun getHexRGBColor(context: Context, color: Int): String { + return context.getString(color).substring(3) +} - fun showLicense(context: Context?, license: License): Disposable { - return showLicense(context, license) { alertDialog -> - alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> - dialog.dismiss() - } +fun showLicense(context: Context?, license: License): Disposable { + return showLicense(context, license) { alertDialog -> + alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> + dialog.dismiss() } } +} - fun showLicense(context: Context?, component: SoftwareComponent): Disposable { - return showLicense(context, component.license) { alertDialog -> - alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> - dialog.dismiss() - } - alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> - ShareUtils.openUrlInBrowser(context!!, component.link) - } +fun showLicense(context: Context?, component: SoftwareComponent): Disposable { + return showLicense(context, component.license) { alertDialog -> + alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> + dialog.dismiss() + } + alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> + ShareUtils.openUrlInBrowser(context!!, component.link) } } +} - private fun showLicense( - context: Context?, - license: License, - block: (AlertDialog.Builder) -> Unit - ): Disposable { - return if (context == null) { - Disposable.empty() - } else { - Observable.fromCallable { getFormattedLicense(context, license) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { formattedLicense -> - val webViewData = Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) - val webView = WebView(context) - webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") +private fun showLicense( + context: Context?, + license: License, + block: (AlertDialog.Builder) -> Unit +): Disposable { + return if (context == null) { + Disposable.empty() + } else { + Observable.fromCallable { getFormattedLicense(context, license) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { formattedLicense -> + val webViewData = + Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) + val webView = WebView(context) + webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") - AlertDialog.Builder(context).apply { - setTitle(license.name) - setView(webView) - Localization.assureCorrectAppLanguage(context) - block(this) - show() - } + AlertDialog.Builder(context).apply { + setTitle(license.name) + setView(webView) + Localization.assureCorrectAppLanguage(context) + block(this) + show() } - } + } } } From a9095ca2ad3abcce665d14a609973a3410f39f1d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 05:38:33 +0530 Subject: [PATCH 195/240] Make block parameter an extension lambda. --- .../newpipe/about/LicenseFragmentHelper.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 34dfe8fb45a..05873c05d29 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -64,29 +64,25 @@ private fun getHexRGBColor(context: Context, color: Int): String { return context.getString(color).substring(3) } -fun showLicense(context: Context?, license: License): Disposable { - return showLicense(context, license) { alertDialog -> - alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> - dialog.dismiss() - } - } -} - fun showLicense(context: Context?, component: SoftwareComponent): Disposable { - return showLicense(context, component.license) { alertDialog -> - alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> + return showLicense(context, component.license) { + setPositiveButton(R.string.dismiss) { dialog, _ -> dialog.dismiss() } - alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> + setNeutralButton(R.string.open_website_license) { _, _ -> ShareUtils.openUrlInBrowser(context!!, component.link) } } } +fun showLicense(context: Context?, license: License) = showLicense(context, license) { + setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } +} + private fun showLicense( context: Context?, license: License, - block: (AlertDialog.Builder) -> Unit + block: AlertDialog.Builder.() -> AlertDialog.Builder ): Disposable { return if (context == null) { Disposable.empty() @@ -100,13 +96,12 @@ private fun showLicense( val webView = WebView(context) webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") - AlertDialog.Builder(context).apply { - setTitle(license.name) - setView(webView) - Localization.assureCorrectAppLanguage(context) - block(this) - show() - } + Localization.assureCorrectAppLanguage(context) + AlertDialog.Builder(context) + .setTitle(license.name) + .setView(webView) + .block() + .show() } } } From 4b7de86a92bfe22a20a017b615523a64e1bb9e80 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 06:00:08 +0530 Subject: [PATCH 196/240] Clean up getLicenseStylesheet(). --- .../newpipe/about/LicenseFragmentHelper.kt | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 05873c05d29..6e3aa4be878 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -36,21 +36,17 @@ private fun getFormattedLicense(context: Context, license: License): String { */ private fun getLicenseStylesheet(context: Context): String { val isLightTheme = ThemeHelper.isLightThemeSelected(context) - return ( - "body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_background_color - else R.color.dark_license_background_color - ) + ";" + "color:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_text_color - else R.color.dark_license_text_color - ) + "}" + "a[href]{color:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_youtube_primary_color - else R.color.dark_youtube_primary_color - ) + "}" + "pre{white-space:pre-wrap}" - ) + val licenseBackgroundColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_license_background_color else R.color.dark_license_background_color + ) + val licenseTextColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_license_text_color else R.color.dark_license_text_color + ) + val youtubePrimaryColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_youtube_primary_color else R.color.dark_youtube_primary_color + ) + return "body{padding:12px 15px;margin:0;background:#$licenseBackgroundColor;color:#$licenseTextColor}" + + "a[href]{color:#$youtubePrimaryColor}pre{white-space:pre-wrap}" } /** From 059cfcbad26422bff114cab9e94da03219243d70 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 3 Aug 2022 04:38:47 +0530 Subject: [PATCH 197/240] Use Comparator factory methods in ListHelper. --- .../org/schabi/newpipe/util/ListHelper.java | 62 ++++--------------- 1 file changed, 12 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index fbbe43513b7..45234706bc4 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -343,7 +343,10 @@ static List getSortedStreamVideosList( */ private static List sortStreamList(final List videoStreams, final boolean ascendingOrder) { - final Comparator comparator = ListHelper::compareVideoStreamResolution; + // Compares the quality of two video streams. + final Comparator comparator = Comparator.nullsLast(Comparator + .comparing(VideoStream::getResolution, ListHelper::compareVideoStreamResolution) + .thenComparingInt(s -> VIDEO_FORMAT_QUALITY_RANKING.indexOf(s.getFormat()))); Collections.sort(videoStreams, ascendingOrder ? comparator : comparator.reversed()); return videoStreams; } @@ -360,8 +363,7 @@ static int getHighestQualityAudioIndex(@Nullable final MediaFormat format, @Nullable final List audioStreams) { return getAudioIndexByHighestRank(format, audioStreams, // Compares descending (last = highest rank) - (s1, s2) -> compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_QUALITY_RANKING) - ); + getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING)); } /** @@ -374,11 +376,15 @@ static int getHighestQualityAudioIndex(@Nullable final MediaFormat format, */ static int getMostCompactAudioIndex(@Nullable final MediaFormat format, @Nullable final List audioStreams) { - return getAudioIndexByHighestRank(format, audioStreams, // The "-" is important -> Compares ascending (first = highest rank) - (s1, s2) -> -compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_EFFICIENCY_RANKING) - ); + getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed()); + } + + private static Comparator getAudioStreamComparator( + final List formatRanking) { + return Comparator.nullsLast(Comparator.comparingInt(AudioStream::getAverageBitrate)) + .thenComparingInt(stream -> formatRanking.indexOf(stream.getFormat())); } /** @@ -544,28 +550,6 @@ private static MediaFormat getMediaFormatFromKey(@NonNull final Context context, return format; } - // Compares the quality of two audio streams - private static int compareAudioStreamBitrate(final AudioStream streamA, - final AudioStream streamB, - final List formatRanking) { - if (streamA == null) { - return -1; - } - if (streamB == null) { - return 1; - } - if (streamA.getAverageBitrate() < streamB.getAverageBitrate()) { - return -1; - } - if (streamA.getAverageBitrate() > streamB.getAverageBitrate()) { - return 1; - } - - // Same bitrate and format - return formatRanking.indexOf(streamA.getFormat()) - - formatRanking.indexOf(streamB.getFormat()); - } - private static int compareVideoStreamResolution(@NonNull final String r1, @NonNull final String r2) { try { @@ -582,28 +566,6 @@ private static int compareVideoStreamResolution(@NonNull final String r1, } } - // Compares the quality of two video streams. - private static int compareVideoStreamResolution(final VideoStream streamA, - final VideoStream streamB) { - if (streamA == null) { - return -1; - } - if (streamB == null) { - return 1; - } - - final int resComp = compareVideoStreamResolution(streamA.getResolution(), - streamB.getResolution()); - if (resComp != 0) { - return resComp; - } - - // Same bitrate and format - return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat()) - - ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat()); - } - - private static boolean isLimitingDataUsage(final Context context) { return getResolutionLimit(context) != null; } From d1f3f15478bada81a2172b45f6ba4385bc0ff7a7 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 3 Aug 2022 06:01:32 +0530 Subject: [PATCH 198/240] Use Comparator.comparingDouble(). --- .../preferencesearch/PreferenceFuzzySearchFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java index 7c231cafb42..ea45c68d203 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java @@ -31,7 +31,7 @@ public Stream search( // Specific search - Used for determining order of search results // Calculate a score based on specific search fields .map(item -> new FuzzySearchSpecificDTO(item, keyword)) - .sorted(Comparator.comparing(FuzzySearchSpecificDTO::getScore).reversed()) + .sorted(Comparator.comparingDouble(FuzzySearchSpecificDTO::getScore).reversed()) .map(FuzzySearchSpecificDTO::getItem) // Limit the amount of search results .limit(20); From 8024b437e9dfeeec29be83b7b6d0049f53d5090f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 01:50:23 +0530 Subject: [PATCH 199/240] Add reusable classes extending AnimatorListenerAdapter. --- .../main/java/org/schabi/newpipe/ktx/View.kt | 99 +++++++------------ 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt index 5ab8dbdc2b7..bf0dcb2012c 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -90,7 +90,9 @@ fun View.animate( */ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { if (MainActivity.DEBUG) { - Log.d(TAG, "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + + Log.d( + TAG, + "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + "colorStart = [$colorStart], colorEnd = [$colorEnd]" ) } @@ -147,20 +149,13 @@ private fun View.animateAlpha(enterOrExit: Boolean, duration: Long, delay: Long, if (enterOrExit) { animate().setInterpolator(FastOutSlowInInterpolator()).alpha(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate().setInterpolator(FastOutSlowInInterpolator()).alpha(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -172,11 +167,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela .setInterpolator(FastOutSlowInInterpolator()) .alpha(1f).scaleX(1f).scaleY(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { scaleX = 1f scaleY = 1f @@ -184,12 +176,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).scaleX(.8f).scaleY(.8f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -202,11 +190,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, .setInterpolator(FastOutSlowInInterpolator()) .alpha(1f).scaleX(1f).scaleY(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { alpha = 1f scaleX = 1f @@ -215,12 +200,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).scaleX(.95f).scaleY(.95f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -231,22 +212,15 @@ private fun View.animateSlideAndAlpha(enterOrExit: Boolean, duration: Long, dela animate() .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate() .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).translationY(-height.toFloat()) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -257,21 +231,14 @@ private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long, animate() .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate().setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).translationY(-height / 2.0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -293,11 +260,7 @@ fun View.slideUp( .setStartDelay(delay) .setDuration(duration) .setInterpolator(FastOutSlowInInterpolator()) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }) + .setListener(ExecOnEndListener(execOnEnd)) .start() } @@ -311,6 +274,20 @@ fun View.animateHideRecyclerViewAllowingScrolling() { animate().alpha(0.0f).setDuration(200).start() } +private open class ExecOnEndListener(private val execOnEnd: Runnable?) : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + execOnEnd?.run() + } +} + +private class HideAndExecOnEndListener(private val view: View, execOnEnd: Runnable?) : + ExecOnEndListener(execOnEnd) { + override fun onAnimationEnd(animation: Animator) { + view.isGone = true + super.onAnimationEnd(animation) + } +} + enum class AnimationType { ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA } From 2eec2e9128924bbe708eeb4958191b0d3fe3ff6c Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 5 Aug 2022 06:19:06 +0530 Subject: [PATCH 200/240] Replace coerceIn() with MathUtils.clamp(). --- .../newpipe/player/gesture/PopupPlayerGestureListener.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index 01b15f30a07..666ea6a461f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -4,6 +4,7 @@ import android.util.Log import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration +import androidx.core.math.MathUtils import org.schabi.newpipe.MainActivity import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate @@ -234,11 +235,13 @@ class PopupPlayerGestureListener( isMoving = true val diffX = (movingEvent.rawX - initialEvent.rawX) - val posX = (initialPopupX + diffX).coerceIn( + val posX = MathUtils.clamp( + initialPopupX + diffX, 0f, (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() ) val diffY = (movingEvent.rawY - initialEvent.rawY) - val posY = (initialPopupY + diffY).coerceIn( + val posY = MathUtils.clamp( + initialPopupY + diffY, 0f, (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() ) From fc46233baf5827250b852d2118f15ea89b7ef132 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 00:51:15 +0530 Subject: [PATCH 201/240] Use toArray() with zero-length arrays. --- .../FragmentStatePagerAdapterMenuWorkaround.java | 6 ++---- .../fragments/detail/VideoDetailFragment.java | 8 ++------ .../fragments/list/search/SearchFragment.java | 5 ++--- .../player/notification/NotificationConstants.java | 5 ++--- .../player/notification/NotificationUtil.java | 5 +---- .../custom/NotificationActionsPreference.java | 13 ++++++------- .../java/org/schabi/newpipe/streams/WebMReader.java | 3 +-- 7 files changed, 16 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java index 6394433773c..8d87e90bddf 100644 --- a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java +++ b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java @@ -282,11 +282,9 @@ public boolean isViewFromObject(@NonNull final View view, @NonNull final Object @Nullable public Parcelable saveState() { Bundle state = null; - if (mSavedState.size() > 0) { + if (!mSavedState.isEmpty()) { state = new Bundle(); - final Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; - mSavedState.toArray(fss); - state.putParcelableArray("states", fss); + state.putParcelableArray("states", mSavedState.toArray(new Fragment.SavedState[0])); } for (int i = 0; i < mFragments.size(); i++) { final Fragment f = mFragments.get(i); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index f0672cd412c..3b1bdaedeb7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -2167,12 +2167,8 @@ private void showExternalPlaybackDialog() { } else { final int selectedVideoStreamIndexForExternalPlayers = ListHelper.getDefaultResolutionIndex(activity, videoStreamsForExternalPlayers); - final CharSequence[] resolutions = - new CharSequence[videoStreamsForExternalPlayers.size()]; - - for (int i = 0; i < videoStreamsForExternalPlayers.size(); i++) { - resolutions[i] = videoStreamsForExternalPlayers.get(i).getResolution(); - } + final CharSequence[] resolutions = videoStreamsForExternalPlayers.stream() + .map(VideoStream::getResolution).toArray(CharSequence[]::new); builder.setSingleChoiceItems(resolutions, selectedVideoStreamIndexForExternalPlayers, null); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 44f8328a5ea..008163890aa 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -919,7 +919,7 @@ private void changeContentFilter(final MenuItem item, final List theCont filterItemCheckedId = item.getItemId(); item.setChecked(true); - contentFilter = new String[]{theContentFilter.get(0)}; + contentFilter = theContentFilter.toArray(new String[0]); if (!TextUtils.isEmpty(searchString)) { search(searchString, contentFilter, sortFilter); @@ -980,8 +980,7 @@ public void handleResult(@NonNull final SearchInfo result) { isCorrectedSearch = result.isCorrectedSearch(); // List cannot be bundled without creating some containers - metaInfo = new MetaInfo[result.getMetaInfo().size()]; - metaInfo = result.getMetaInfo().toArray(metaInfo); + metaInfo = result.getMetaInfo().toArray(new MetaInfo[0]); showMetaInfoInTextView(result.getMetaInfo(), searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator, disposables); diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index b8e39e56432..7015e071769 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -14,7 +14,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; @@ -115,7 +114,7 @@ private NotificationConstants() { }; - public static final Integer[] SLOT_COMPACT_DEFAULTS = {0, 1, 2}; + public static final List SLOT_COMPACT_DEFAULTS = List.of(0, 1, 2); public static final int[] SLOT_COMPACT_PREF_KEYS = { R.string.notification_slot_compact_0_key, @@ -181,7 +180,7 @@ public static List getCompactSlotsFromPreferences( if (compactSlot == Integer.MAX_VALUE) { // settings not yet populated, return default values - return new ArrayList<>(Arrays.asList(SLOT_COMPACT_DEFAULTS)); + return SLOT_COMPACT_DEFAULTS; } // a negative value (-1) is set when the user does not want a particular compact slot diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 2ba754500ff..ef225b14ea1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -99,10 +99,7 @@ private synchronized NotificationCompat.Builder createNotification() { // build the compact slot indices array (need code to convert from Integer... because Java) final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences( player.getContext(), player.getPrefs(), nonNothingSlotCount); - final int[] compactSlots = new int[compactSlotList.size()]; - for (int i = 0; i < compactSlotList.size(); i++) { - compactSlots[i] = compactSlotList.get(i); - } + final int[] compactSlots = compactSlotList.stream().mapToInt(i -> i).toArray(); builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(player.getMediaSessionManager().getSessionToken()) diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 03b5a5a9507..1101f410dcb 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -34,7 +34,9 @@ import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; +import java.util.ArrayList; import java.util.List; +import java.util.stream.IntStream; public class NotificationActionsPreference extends Preference { @@ -74,13 +76,10 @@ public void onDetached() { //////////////////////////////////////////////////////////////////////////// private void setupActions(@NonNull final View view) { - compactSlots = - NotificationConstants.getCompactSlotsFromPreferences( - getContext(), getSharedPreferences(), 5); - notificationSlots = new NotificationSlot[5]; - for (int i = 0; i < 5; i++) { - notificationSlots[i] = new NotificationSlot(i, view); - } + compactSlots = new ArrayList<>(NotificationConstants + .getCompactSlotsFromPreferences(getContext(), getSharedPreferences(), 5)); + notificationSlots = IntStream.range(0, 5).mapToObj(i -> new NotificationSlot(i, view)) + .toArray(NotificationSlot[]::new); } diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 8253ad6af17..678974ccebd 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -348,8 +348,7 @@ private WebMTrack[] readTracks(final Element ref, final int lacingExpected) thro ensure(elemTrackEntry); } - final WebMTrack[] entries = new WebMTrack[trackEntries.size()]; - trackEntries.toArray(entries); + final WebMTrack[] entries = trackEntries.toArray(new WebMTrack[0]); for (final WebMTrack entry : entries) { switch (entry.trackType) { From a9af1dfdd28a70b2668f201eedf25265b946adb5 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 5 Aug 2022 06:54:03 +0530 Subject: [PATCH 202/240] Applied code review changes. --- .../player/notification/NotificationConstants.java | 2 +- .../newpipe/player/notification/NotificationUtil.java | 2 +- .../settings/custom/NotificationActionsPreference.java | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 7015e071769..89bf0b22ae2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -180,7 +180,7 @@ public static List getCompactSlotsFromPreferences( if (compactSlot == Integer.MAX_VALUE) { // settings not yet populated, return default values - return SLOT_COMPACT_DEFAULTS; + return new ArrayList<>(SLOT_COMPACT_DEFAULTS); } // a negative value (-1) is set when the user does not want a particular compact slot diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index ef225b14ea1..1a91bc66d85 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -99,7 +99,7 @@ private synchronized NotificationCompat.Builder createNotification() { // build the compact slot indices array (need code to convert from Integer... because Java) final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences( player.getContext(), player.getPrefs(), nonNothingSlotCount); - final int[] compactSlots = compactSlotList.stream().mapToInt(i -> i).toArray(); + final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(player.getMediaSessionManager().getSessionToken()) diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 1101f410dcb..1770685e4e8 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -34,7 +34,6 @@ import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; -import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; @@ -76,9 +75,10 @@ public void onDetached() { //////////////////////////////////////////////////////////////////////////// private void setupActions(@NonNull final View view) { - compactSlots = new ArrayList<>(NotificationConstants - .getCompactSlotsFromPreferences(getContext(), getSharedPreferences(), 5)); - notificationSlots = IntStream.range(0, 5).mapToObj(i -> new NotificationSlot(i, view)) + compactSlots = NotificationConstants.getCompactSlotsFromPreferences(getContext(), + getSharedPreferences(), 5); + notificationSlots = IntStream.range(0, 5) + .mapToObj(i -> new NotificationSlot(i, view)) .toArray(NotificationSlot[]::new); } From ee6a27959647df5a39c57ee429046fb838457357 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 6 Aug 2022 05:09:54 +0530 Subject: [PATCH 203/240] Remove unused methods in HistoryRecordManager. --- .../org/schabi/newpipe/database/BasicDAO.java | 11 +--- .../local/history/HistoryRecordManager.java | 54 ------------------- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java index 1b8540808c0..255f5ba8deb 100644 --- a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java @@ -3,7 +3,6 @@ import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; -import androidx.room.OnConflictStrategy; import androidx.room.Update; import java.util.Collection; @@ -14,13 +13,10 @@ @Dao public interface BasicDAO { /* Inserts */ - @Insert(onConflict = OnConflictStrategy.ABORT) + @Insert long insert(Entity entity); - @Insert(onConflict = OnConflictStrategy.ABORT) - List insertAll(Entity... entities); - - @Insert(onConflict = OnConflictStrategy.ABORT) + @Insert List insertAll(Collection entities); /* Searches */ @@ -32,9 +28,6 @@ public interface BasicDAO { @Delete void delete(Entity entity); - @Delete - int delete(Collection entities); - int deleteAll(); /* Updates */ diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index 19f7afce534..b8d2eae2d97 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -28,7 +28,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.AppDatabase; import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.database.feed.dao.FeedDAO; import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; @@ -51,7 +50,6 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import io.reactivex.rxjava3.core.Completable; @@ -89,7 +87,6 @@ public HistoryRecordManager(final Context context) { * Marks a stream item as watched such that it is hidden from the feed if watched videos are * hidden. Adds a history entry and updates the stream progress to 100%. * - * @see FeedDAO#getLiveOrNotPlayedStreams * @see FeedViewModel#togglePlayedItems * @param info the item to mark as watched * @return a Maybe containing the ID of the item if successful @@ -176,10 +173,6 @@ public Single deleteCompleteStreamStateHistory() { .subscribeOn(Schedulers.io()); } - public Flowable> getStreamHistory() { - return streamHistoryTable.getHistory().subscribeOn(Schedulers.io()); - } - public Flowable> getStreamHistorySortedById() { return streamHistoryTable.getHistorySortedById().subscribeOn(Schedulers.io()); } @@ -188,24 +181,6 @@ public Flowable> getStreamStatistics() { return streamHistoryTable.getStatistics().subscribeOn(Schedulers.io()); } - public Single> insertStreamHistory(final Collection entries) { - final List entities = new ArrayList<>(entries.size()); - for (final StreamHistoryEntry entry : entries) { - entities.add(entry.toStreamHistoryEntity()); - } - return Single.fromCallable(() -> streamHistoryTable.insertAll(entities)) - .subscribeOn(Schedulers.io()); - } - - public Single deleteStreamHistory(final Collection entries) { - final List entities = new ArrayList<>(entries.size()); - for (final StreamHistoryEntry entry : entries) { - entities.add(entry.toStreamHistoryEntity()); - } - return Single.fromCallable(() -> streamHistoryTable.delete(entities)) - .subscribeOn(Schedulers.io()); - } - private boolean isStreamHistoryEnabled() { return sharedPreferences.getBoolean(streamHistoryKey, false); } @@ -259,13 +234,6 @@ private boolean isSearchHistoryEnabled() { // Stream State History /////////////////////////////////////////////////////// - public Maybe getStreamHistory(final StreamInfo info) { - return Maybe.fromCallable(() -> { - final long streamId = streamTable.upsert(new StreamEntity(info)); - return streamHistoryTable.getLatestEntry(streamId); - }).subscribeOn(Schedulers.io()); - } - public Maybe loadStreamState(final PlayQueueItem queueItem) { return queueItem.getStream() .map(info -> streamTable.upsert(new StreamEntity(info))) @@ -311,28 +279,6 @@ public Single loadStreamState(final InfoItem info) { }).subscribeOn(Schedulers.io()); } - public Single> loadStreamStateBatch(final List infos) { - return Single.fromCallable(() -> { - final List result = new ArrayList<>(infos.size()); - for (final InfoItem info : infos) { - final List entities = streamTable - .getStream(info.getServiceId(), info.getUrl()).blockingFirst(); - if (entities.isEmpty()) { - result.add(null); - continue; - } - final List states = streamStateTable - .getState(entities.get(0).getUid()).blockingFirst(); - if (states.isEmpty()) { - result.add(null); - } else { - result.add(states.get(0)); - } - } - return result; - }).subscribeOn(Schedulers.io()); - } - public Single> loadLocalStreamStateBatch( final List items) { return Single.fromCallable(() -> { From 7aacaf8c38358ccd3da98fcb26ed9420d6fdd9ee Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:05:50 +0530 Subject: [PATCH 204/240] Use Collectors.joining(). --- .../org/schabi/newpipe/DownloaderImpl.java | 26 +++++++++---------- .../schabi/newpipe/error/ErrorActivity.java | 12 +++------ .../preferencesearch/PreferenceParser.java | 10 ++----- .../org/schabi/newpipe/util/CookieUtils.java | 24 ----------------- .../org/schabi/newpipe/util/Localization.java | 23 +++++----------- 5 files changed, 24 insertions(+), 71 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/CookieUtils.java diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 7bb394bf41b..9ddbe96dfc9 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -11,15 +11,17 @@ import org.schabi.newpipe.extractor.downloader.Request; import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.util.CookieUtils; import org.schabi.newpipe.util.InfoCache; import java.io.IOException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import okhttp3.OkHttpClient; import okhttp3.RequestBody; @@ -63,19 +65,15 @@ public static DownloaderImpl getInstance() { } public String getCookies(final String url) { - final List resultCookies = new ArrayList<>(); - if (url.contains(YOUTUBE_DOMAIN)) { - final String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY); - if (youtubeCookie != null) { - resultCookies.add(youtubeCookie); - } - } + final String youtubeCookie = url.contains(YOUTUBE_DOMAIN) + ? getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY) : null; + // Recaptcha cookie is always added TODO: not sure if this is necessary - final String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY); - if (recaptchaCookie != null) { - resultCookies.add(recaptchaCookie); - } - return CookieUtils.concatCookies(resultCookies); + return Stream.of(youtubeCookie, getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY)) + .filter(Objects::nonNull) + .flatMap(cookies -> Arrays.stream(cookies.split("; *"))) + .distinct() + .collect(Collectors.joining("; ")); } public String getCookie(final String key) { diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index 8b2ac37dcd4..e1dd929d4d4 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -31,6 +31,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.stream.Collectors; /* * Created by Christian Schabesberger on 24.10.15. @@ -182,14 +183,9 @@ private void openPrivacyPolicyDialog(final Context context, final String action) } private String formErrorText(final String[] el) { - final StringBuilder text = new StringBuilder(); - if (el != null) { - for (final String e : el) { - text.append("-------------------------------------\n").append(e); - } - } - text.append("-------------------------------------"); - return text.toString(); + final String separator = "-------------------------------------"; + return Arrays.stream(el) + .collect(Collectors.joining(separator + "\n", separator + "\n", separator)); } /** diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java index 1f507c7f1ff..b925e8b5fcf 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java @@ -9,13 +9,13 @@ import androidx.annotation.XmlRes; import androidx.preference.PreferenceManager; +import org.schabi.newpipe.util.Localization; import org.xmlpull.v1.XmlPullParser; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * Parses the corresponding preference-file(s). @@ -54,7 +54,7 @@ public List parse( if (xpp.getEventType() == XmlPullParser.START_TAG) { final PreferenceSearchItem result = parseSearchResult( xpp, - joinBreadcrumbs(breadcrumbs), + Localization.concatenateStrings(" > ", breadcrumbs), resId ); @@ -82,12 +82,6 @@ public List parse( return results; } - private String joinBreadcrumbs(final List breadcrumbs) { - return breadcrumbs.stream() - .filter(crumb -> !TextUtils.isEmpty(crumb)) - .collect(Collectors.joining(" > ")); - } - private String getAttribute( final XmlPullParser xpp, @NonNull final String attribute diff --git a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java deleted file mode 100644 index b906c1c4f80..00000000000 --- a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.schabi.newpipe.util; - -import android.text.TextUtils; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public final class CookieUtils { - private CookieUtils() { - } - - public static String concatCookies(final Collection cookieStrings) { - final Set cookieSet = new HashSet<>(); - for (final String cookies : cookieStrings) { - cookieSet.addAll(splitCookies(cookies)); - } - return TextUtils.join("; ", cookieSet).trim(); - } - - public static Set splitCookies(final String cookies) { - return Set.of(cookies.split("; *")); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 5ac24503d97..28712bb4d7c 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; /* @@ -63,26 +64,14 @@ private Localization() { } @NonNull public static String concatenateStrings(final String... strings) { - return concatenateStrings(Arrays.asList(strings)); + return concatenateStrings(DOT_SEPARATOR, Arrays.asList(strings)); } @NonNull - public static String concatenateStrings(final List strings) { - if (strings.isEmpty()) { - return ""; - } - - final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(strings.get(0)); - - for (int i = 1; i < strings.size(); i++) { - final String string = strings.get(i); - if (!TextUtils.isEmpty(string)) { - stringBuilder.append(DOT_SEPARATOR).append(strings.get(i)); - } - } - - return stringBuilder.toString(); + public static String concatenateStrings(final String delimiter, final List strings) { + return strings.stream() + .filter(string -> !TextUtils.isEmpty(string)) + .collect(Collectors.joining(delimiter)); } public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization( From 5c7dfd1d69bb7726b0f91cb6d5c5296f51173647 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:18:21 +0530 Subject: [PATCH 205/240] Remove unused method. --- .../org/schabi/newpipe/util/Localization.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 28712bb4d7c..e20955a766b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -347,19 +347,4 @@ public static void assureCorrectAppLanguage(final Context c) { private static double round(final double value, final int places) { return new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue(); } - - /** - * Workaround to match normalized captions like english to English or deutsch to Deutsch. - * @param list the list to search into - * @param toFind the string to look for - * @return whether the string was found or not - */ - public static boolean containsCaseInsensitive(final List list, final String toFind) { - for (final String i : list) { - if (i.equalsIgnoreCase(toFind)) { - return true; - } - } - return false; - } } From ebd06bdd246403384f6d93795d102bc5d22cfce0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 6 Aug 2022 11:56:00 +0200 Subject: [PATCH 206/240] Improve comment --- app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 45234706bc4..b3b7c1792db 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -377,7 +377,7 @@ static int getHighestQualityAudioIndex(@Nullable final MediaFormat format, static int getMostCompactAudioIndex(@Nullable final MediaFormat format, @Nullable final List audioStreams) { return getAudioIndexByHighestRank(format, audioStreams, - // The "-" is important -> Compares ascending (first = highest rank) + // The "reversed()" is important -> Compares ascending (first = highest rank) getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed()); } From 0b11afaf2fddff26de7927605994dea1c4be5b87 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Mon, 8 Aug 2022 19:32:21 -0400 Subject: [PATCH 207/240] Update Gradle to 7.5.1 --- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 6 ++++++ gradlew.bat | 14 ++++++++------ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 10197 zcmaKS1ymhDwk=#NxVyW%y9U<)A-Dv)xI0|j{UX8L-JRg>5ZnnKAh;%chM6~S-g^K4 z>eZ{yK4;gd>gwvXs=Id8Jk-J}R4pT911;+{Jp9@aiz6!p1Oz9z&_kGLA%J5%3Ih@0 zQ|U}%$)3u|G`jIfPzMVfcWs?jV2BO^*3+q2><~>3j+Z`^Z%=;19VWg0XndJ zwJ~;f4$;t6pBKaWn}UNO-wLCFHBd^1)^v%$P)fJk1PbK5<;Z1K&>k~MUod6d%@Bq9 z>(44uiaK&sdhwTTxFJvC$JDnl;f}*Q-^01T508(8{+!WyquuyB7R!d!J)8Ni0p!cV6$CHsLLy6}7C zYv_$eD;)@L)tLj0GkGpBoa727hs%wH$>EhfuFy{_8Q8@1HI%ZAjlpX$ob{=%g6`Ox zLzM!d^zy`VV1dT9U9(^}YvlTO9Bf8v^wMK37`4wFNFzW?HWDY(U(k6@tp(crHD)X5>8S-# zW1qgdaZa*Sh6i%60e1+hty}34dD%vKgb?QmQiZ=-j+isA4={V_*R$oGN#j|#ia@n6 zuZx4e2Xx?^lUwYFn2&Tmbx0qA3Z8;y+zKoeQu;~k~FZGy!FU_TFxYd!Ck;5QvMx9gj5fI2@BLNp~Ps@ zf@k<&Q2GS5Ia9?_D?v~$I%_CLA4x~eiKIZ>9w^c#r|vB?wXxZ(vXd*vH(Fd%Me8p( z=_0)k=iRh%8i`FYRF>E97uOFTBfajv{IOz(7CU zv0Gd84+o&ciHlVtY)wn6yhZTQQO*4Mvc#dxa>h}82mEKKy7arOqU$enb9sgh#E=Lq zU;_RVm{)30{bw+|056%jMVcZRGEBSJ+JZ@jH#~DvaDQm92^TyUq=bY*+AkEakpK>8 zB{)CkK48&nE5AzTqT;WysOG|!y}5fshxR8Ek(^H6i>|Fd&wu?c&Q@N9ZrJ=?ABHI! z`*z8D`w=~AJ!P-9M=T}f`;76$qZRllB&8#9WgbuO$P7lVqdX1=g*t=7z6!0AQ^ux_ z9rcfUv^t}o_l-ZE+TqvqFsA*~W<^78!k;~!i8(eS+(+@u8FxK+Q7;mHZ<1}|4m<}vh@p`t%|@eM_J(P% zI>M7C)Ir{l|J;$G_EGGEhbP4?6{sYzMqBv+x95N&YWFH6UcE@b}B?q)G*4<4mR@sy1#vPnLMK51tb#ED(8TA1nE zYfhK7bo1!R5WJF$5Y?zG21)6+_(_5oSX9sGIW;(O&S?Rh(nydNQYzKjjJ54aDJ-1F zrJ=np8LsN?%?Rt7f~3aAX!2E{`fh_pb?2(;HOB3W+I*~A>W%iY+v45+^e$cE10fA} zXPvw9=Bd+(;+!rl)pkYj0HGB}+3Z!Mr;zr%gz~c-hFMv8b2VRE2R$8V=_XE zq$3=|Yg05(fmwrJ)QK2ptB4no`Y8Dg_vK2QDc6-6sXRQ5k78-+cPi-fH}vpgs|Ive zE=m*XNVs?EWgiNI!5AcD*3QMW)R`EqT!f0e1%hERO&?AT7HWnSf5@#AR{OGuXG3Zb zCnVWg7h|61lGV3k+>L<#d>)InG>ETn1DbOHCfztqzQ_fBiaUt@q6VMy={Fe-w#~2- z0?*f|z$zgjI9>+JVICObBaK=pU}AEOd@q(8d?j7zQFD@=6t`|KmolTr2MfBI$;EGh zD%W0cA_d#V6Lb$us5yIG(|d>r-QleC4;%hEu5W9hyY zY#+ESY&v`8(&mC~?*|e5WEhC!YU2>m_}`K+q9)a(d$bsS<=YkyZGp}YA%TXw>@abA zS_poVPoN+?<6?DAuCNt&5SHV(hp56PJ})swwVFZFXM->F zc|0c8<$H_OV%DR|y7e+s$12@Ac8SUClPg8_O9sTUjpv%6Jsn5vsZCg>wL+db4c+{+ zsg<#wOuV4jeOq`veckdi-1`dz;gvL)bZeH|D*x=8UwRU5&8W1@l>3$)8WzET0%;1J zM3(X<7tKK&9~kWRI{&FmwY5Gg!b5f4kI_vSm)H1#>l6M+OiReDXC{kPy!`%Ecq-+3yZTk=<` zm)pE6xum5q0Qkd#iny0Q-S}@I0;mDhxf>sX)Oiv)FdsAMnpx%oe8OQ`m%Xeozdzx!C1rQR>m1c_}+J4x)K}k{G zo68;oGG&Ox7w^-m7{g4a7NJu-B|~M;oIH~~#`RyUNm##feZH;E?pf}nshmoiIY52n z%pc%lnU4Q#C=RUz)RU6}E_j4#)jh<&a%JyJj$Fufc#&COaxFHtl}zJUGNLBu3~_@1 zn9F^JO9);Duxo&i@>X(kbYga1i>6p1fca8FzQ0>((Lb-aPUbC*d~a03V$y;*RBY!R ziEJ2IF^FjrvO}0Uy{cMn%u<+P5U!UO>pm9#ZYL5i6|xSC+np7IH$GfXs&uI;y4as@ z&AzJh>(S2?3PKKgab3Z(`xbx(C#46XIvVcW8eG_DjT~}Yz_8PWZ`uf6^Xr=vkvL_` zqmvfgJL+Zc`;iq~iP?%@G7}~fal-zqxa0yNyHBJJ5M)9bI>7S_cg?Ya&p(I)C5Ef4 zZ>YAF6x|U=?ec?g*|f2g5Tw3PgxaM_bi_5Az9MO$;_Byw(2d}2%-|bg4ShdQ;)Z|M z4K|tFv)qx*kKGKoyh!DQY<{n&UmAChq@DJrQP>EY7g1JF(ih*D8wCVWyQ z5Jj^|-NVFSh5T0vd1>hUvPV6?=`90^_)t(L9)XOW7jeP45NyA2lzOn&QAPTl&d#6P zSv%36uaN(9i9WlpcH#}rmiP#=L0q(dfhdxvFVaOwM;pY;KvNQ9wMyUKs6{d}29DZQ z{H3&Sosr6)9Z+C>Q5)iHSW~gGoWGgK-0;k~&dyr-bA3O|3PCNzgC?UKS_B=^i8Ri^ zd_*_qI4B07Cayq|p4{`U_E_P=K`N_~{F|+-+`sCgcNxs`%X!$=(?l2aAW}0M=~COb zf19oe^iuAUuDEf)4tgv<=WRPpK@IjToNNC*#&Ykw!)aqWU4h#|U@(cG_=Qx+&xt~a zvCz~Ds3F71dsjNLkfM%TqdVNu=RNMOzh7?b+%hICbFlOAPphrYy>7D-e7{%o_kPFn z;T!?ilE-LcKM0P(GKMseEeW57Vs`=FF}(y@^pQl;rL3fHs8icmA+!6YJt&8 ztSF?%Un35qkv>drkks&BNTJv~xK?vD;aBkp7eIkDYqn+G0%;sT4FcwAoO+vke{8CO z0d76sgg$CannW5T#q`z~L4id)9BCKRU0A!Z-{HpXr)QJrd9@iJB+l32Ql)Z}*v(St zE)Vp=BB=DDB4Pr}B(UHNe31<@!6d{U?XDoxJ@S)9QM)2L%SA0x^~^fb=bdsBy!uh& zU?M_^kvnt%FZzm+>~bEH{2o?v&Iogs`1t-b+Ml`J!ZPS(46YQJKxWE81O$HE5w;** z|8zM%bp`M7J8)4;%DqH`wVTmM0V@D}xd%tRE3_6>ioMJxyi5Hkb>85muF81&EY!73ei zA3e<#ug||EZJ=1GLXNJ)A z791&ge#lF;GVX6IU?iw0jX^1bYaU?+x{zPlpyX6zijyn*nEdZ$fxxkl!a-~*P3bkf zPd*pzu~3GBYkR_>ET`5UM^>>zTV>5m>)f=az{d0sg6a8VzUtXy$ZS?h#Gk-CA?7)c zI%Vu9DN6XSDQn6;?n9`>l$q&>s?K)R8*OsmI+$L_m z_~E`}w694Z*`Xk3Ne=497Si~=RWRqCM?6=88smrxle#s*W znwhTRsMRmg?37GLJ-)%nDZA7r$YG849j8mJWir1bWBy& zZPneYojSbooC8U@tkO`bWx4%E5*;p#Q^1^S3lsfy7(6A{jL0`A__0vm?>xC%1y8_m z57FfWr^@YG2I1K7MGYuYd>JC}@sT2n^rkrY3w%~$J$Y~HSoOHn?zpR$ zjLj_bq@Yj8kd~DXHh30KVbz@K)0S;hPKm+S&-o%IG+@x@MEcrxW2KFh;z^4dJDZix zGRGe&lQD$p)0JVF4NRgGYuh0bYLy)BCy~sbS3^b3 zHixT<%-Vwbht|25T{3^Hk;qZ^3s!OOgljHs+EIf~C%=_>R5%vQI4mQR9qOXThMXlU zS|oSH>0PjnCakb*js2{ObN`}%HYsT6=%(xA| znpUtG_TJ08kHgm5l@G|t?4E3tG2fq?wNtIp*Vqrb{9@bo^~Rx7+J&OnayrX`LDcF~ zd@0m0ZJ#Z@=T>4kTa5e2FjI&5c(F7S{gnRPoGpu9eIqrtSvnT_tk$8T)r%YwZw!gK zj*k@cG)V&@t+mtDi37#>LhVGTfRA^p%x0d#_P|Mktz3*KOoLIqFm`~KGoDDD4OOxe z?}ag_c08u%vu=5Vx=~uoS8Q;}+R2~?Uh|m-+`-2kDo$d6T!nD*hc#dB(*R{LXV=zo z`PJP0V=O!@3l-bw+d`X6(=@fq=4O#ETa8M^fOvO4qja9o3e8ANc9$sI=A4$zUut~w z4+JryRkI{9qWxU1CCMM$@Aj=6)P+z?vqa=UCv_4XyVNoBD{Xb~Oi4cjjhm8fRD!*U z2)zaS;AI78^Wq+5mDInKiMz|z#K`2emQfNH*U;{9^{NqSMVoq?RSo43<8YpJM^+W$ zxy!A5>5Zl16Vi#?nAYywu3w_=KWnd3*QetocWt`3pK67>)ZVwnT3h zbPdD&MZkD?q=-N`MpCCwpM74L+Tr1aa)zJ)8G;(Pg51@U&5W>aNu9rA`bh{vgfE={ zdJ>aKc|2Ayw_bop+dK?Y5$q--WM*+$9&3Q9BBiwU8L<-`T6E?ZC`mT0b}%HR*LPK} z!MCd_Azd{36?Y_>yN{U1w5yrN8q`z(Vh^RnEF+;4b|2+~lfAvPT!`*{MPiDioiix8 zY*GdCwJ{S(5(HId*I%8XF=pHFz<9tAe;!D5$Z(iN#jzSql4sqX5!7Y?q4_%$lH zz8ehZuyl0K=E&gYhlfFWabnSiGty$>md|PpU1VfaC5~kskDnZX&Yu}?-h;OSav=8u z=e3Yq=mi$4A|sB-J00;1d{Sd1+!v0NtU((Nz2;PFFlC}V{@p&4wGcVhU&nI($RAS! zwXn7)?8~1J3*4+VccRSg5JS<(bBhBM&{ELMD4C_NTpvzboH!{Zr*%HP;{UqxI#g&7 zOAqPSW5Qus$8-xtTvD%h{Tw<2!XR(lU54LZG{)Cah*LZbpJkA=PMawg!O>X@&%+5XiyeIf91n2E*hl$k-Y(3iW*E}Mz-h~H~7S9I1I zR#-j`|Hk?$MqFhE4C@=n!hN*o5+M%NxRqP+aLxDdt=wS6rAu6ECK*;AB%Nyg0uyAv zO^DnbVZZo*|Ef{nsYN>cjZC$OHzR_*g%T#oF zCky9HJS;NCi=7(07tQXq?V8I&OA&kPlJ_dfSRdL2bRUt;tA3yKZRMHMXH&#W@$l%-{vQd7y@~i*^qnj^`Z{)V$6@l&!qP_y zg2oOd!Wit#)2A~w-eqw3*Mbe)U?N|q6sXw~E~&$!!@QYX4b@%;3=>)@Z#K^`8~Aki z+LYKJu~Y$;F5%_0aF9$MsbGS9Bz2~VUG@i@3Fi2q(hG^+Ia44LrfSfqtg$4{%qBDM z_9-O#3V+2~W$dW0G)R7l_R_vw(KSkC--u&%Rs^Io&*?R=`)6BN64>6>)`TxyT_(Rd zUn+aIl1mPa#Jse9B3`!T=|e!pIp$(8ZOe0ao?nS7o?oKlj zypC-fMj1DHIDrh1unUI1vp=-Fln;I9e7Jvs3wj*^_1&W|X} zZSL|S|Bb@CV*YC_-T&2!Ht3b6?)d`tHOP?rA;;t#zaXa0Sc;vGnV0BLIf8f-r{QHh z*Zp`4_ItlOR7{u(K+!p_oLDmaAkNag*l4#29F2b_A*0oz0T|#-&f*;c#<`^)(W@gm z#k9k=t%u8<+C1fNUA{Fh7~wgPrEZZ#(6aBI%6bR4RO(e1(ZocjoDek4#MTgZD>1NG zy9~yoZfWYfwe&S-(zk4o6q6o?2*~DOrJ(%5wSnEJMVOKCzHd z=Yhm+HLzoDl{P*Ybro7@sk1!Ez3`hE+&qr7Rw^2glw^M(b(NS2!F|Q!mi|l~lF94o z!QiV)Q{Z>GO5;l1y!$O)=)got;^)%@v#B!ZEVQy1(BJApHr5%Zh&W|gweD+%Ky%CO ztr45vR*y(@*Dg_Qw5v~PJtm^@Lyh*zRuT6~(K+^HWEF{;R#L$vL2!_ndBxCtUvZ(_ zauI7Qq}ERUWjr&XW9SwMbU>*@p)(cuWXCxRK&?ZoOy>2VESII53iPDP64S1pl{NsC zD;@EGPxs&}$W1;P6BB9THF%xfoLX|4?S;cu@$)9OdFst-!A7T{(LXtdNQSx!*GUSIS_lyI`da8>!y_tpJb3Zuf0O*;2y?HCfH z5QT6@nL|%l3&u4;F!~XG9E%1YwF*Fgs5V&uFsx52*iag(?6O|gYCBY3R{qhxT-Etb zq(E%V=MgQnuDGEKOGsmBj9T0-nmI%zys8NSO>gfJT4bP>tI>|ol@ zDt(&SUKrg%cz>AmqtJKEMUM;f47FEOFc%Bbmh~|*#E zDd!Tl(wa)ZZIFwe^*)4>{T+zuRykc3^-=P1aI%0Mh}*x7%SP6wD{_? zisraq`Las#y-6{`y@CU3Ta$tOl|@>4qXcB;1bb)oH9kD6 zKym@d$ zv&PZSSAV1Gwwzqrc?^_1+-ZGY+3_7~a(L+`-WdcJMo>EWZN3%z4y6JyF4NR^urk`c z?osO|J#V}k_6*9*n2?j+`F{B<%?9cdTQyVNm8D}H~T}?HOCXt%r7#2hz97Gx#X%62hyaLbU z_ZepP0<`<;eABrHrJAc!_m?kmu#7j}{empH@iUIEk^jk}^EFwO)vd7NZB=&uk6JG^ zC>xad8X$h|eCAOX&MaX<$tA1~r|hW?-0{t4PkVygTc`yh39c;&efwY(-#;$W)+4Xb z$XFsdG&;@^X`aynAMxsq)J#KZXX!sI@g~YiJdHI~r z$4mj_?S29sIa4c$z)19JmJ;Uj?>Kq=0XuH#k#};I&-6zZ_&>)j>UR0XetRO!-sjF< zd_6b1A2vfi++?>cf}s{@#BvTD|a%{9si7G}T+8ZnwuA z1k8c%lgE<-7f~H`cqgF;qZ|$>R-xNPA$25N1WI3#n%gj}4Ix}vj|e=x)B^roGQpB) zO+^#nO2 zjzJ9kHI6nI5ni&V_#5> z!?<7Qd9{|xwIf4b0bRc;zb}V4>snRg6*wl$Xz`hRDN8laL5tg&+@Dv>U^IjGQ}*=XBnXWrwTy;2nX?<1rkvOs#u(#qJ=A zBy>W`N!?%@Ay=upXFI}%LS9bjw?$h)7Dry0%d}=v0YcCSXf9nnp0tBKT1eqZ-4LU` zyiXglKRX)gtT0VbX1}w0f2ce8{$WH?BQm@$`ua%YP8G@<$n13D#*(Yd5-bHfI8!on zf5q4CPdgJLl;BqIo#>CIkX)G;rh|bzGuz1N%rr+5seP${mEg$;uQ3jC$;TsR&{IX< z;}7j3LnV+xNn^$F1;QarDf6rNYj7He+VsjJk6R@0MAkcwrsq4?(~`GKy|mgkfkd1msc2>%B!HpZ~HOzj}kl|ZF(IqB=D6ZTVcKe=I7)LlAI=!XU?J*i#9VXeKeaG zwx_l@Z(w`)5Cclw`6kQKlS<;_Knj)^Dh2pL`hQo!=GPOMR0iqEtx12ORLpN(KBOm5 zontAH5X5!9WHS_=tJfbACz@Dnkuw|^7t=l&x8yb2a~q|aqE_W&0M|tI7@ilGXqE)MONI8p67OiQGqKEQWw;LGga=ZM1;{pSw1jJK_y$vhY6 ztFrV7-xf>lbeKH1U)j3R=?w*>(Yh~NNEPVmeQ8n}0x01$-o z2Jyjn+sXhgOz>AzcZ zAbJZ@f}MBS0lLKR=IE{z;Fav%tcb+`Yi*!`HTDPqSCsFr>;yt^^&SI2mhKJ8f*%ji zz%JkZGvOn{JFn;)5jf^21AvO-9nRzsg0&CPz;OEn07`CfT@gK4abFBT$Mu?8fCcscmRkK+ zbAVJZ~#_a z{|(FFX}~8d3;DW8zuY9?r#Dt>!aD>} zlYw>D7y#eDy+PLZ&XKIY&Df0hsLDDi(Yrq8O==d30RchrUw8a=Eex>Dd?)3+k=}Q> z-b85lun-V$I}86Vg#l1S@1%=$2BQD5_waAZKQfJ${3{b2SZ#w1u+jMr{dJMvI|Og= zpQ9D={XK|ggbe04zTUd}iF{`GO1dV%zWK~?sM9OM(= zVK9&y4F^w1WFW{$qi|xQk0F`@HG8oLI5|5$j~ci9xTMT69v5KS-Yym--raU5kn2#C z<~5q^Bf0rTXVhctG2%&MG(cUGaz(gC(rcG~>qgO$W6>!#NOVQJ;pIYe-lLy(S=HgI zPh;lkL$l+FfMHItHnw_^bj8}CKM19t(C_2vSrhX2$K@-gFlH};#C?1;kk&U1L%4S~ zR^h%h+O1WE7DI$~dly?-_C7>(!E`~#REJ~Xa7lyrB$T!`&qYV5QreAa^aKr%toUJR zPWh)J3iD`(P6BI5k$oE$us#%!4$>`iH2p-88?WV0M$-K)JDibvA4 zpef%_*txN$Ei3=Lt(BBxZ&mhl|mUz-z*OD1=r9nfN zc5vOMFWpi>K=!$6f{eb?5Ru4M3o;t9xLpry|C%j~`@$f)OFB5+xo8XM8g&US@UU-sB|dAoc20y(F@=-2Ggp_`SWjEb#>IG^@j zuQK}e^>So#W2%|-)~K!+)wdU#6l>w5wnZt2pRL5Dz#~N`*UyC9tYechBTc2`@(OI# zNvcE*+zZZjU-H`QOITK^tZwOyLo)ZCLk>>Wm+flMsr5X{A<|m`Y281n?8H_2Fkz5}X?i%Rfm5s+n`J zDB&->=U+LtOIJ|jdYXjQWSQZFEs>Rm{`knop4Sq)(}O_@gk{14y51)iOcGQ5J=b#e z2Yx^6^*F^F7q_m-AGFFgx5uqyw6_4w?yKCJKDGGprWyekr;X(!4CnM5_5?KgN=3qCm03 z##6k%kIU5%g!cCL(+aK>`Wd;dZ4h$h_jb7n?nqx5&o9cUJfr%h#m4+Bh)>HodKcDcsXDXwzJ3jR(sSFqWV(OKHC*cV8;;&bH=ZI0YbW3PgIHwTjiWy z?2MXWO2u0RAEEq(zv9e%Rsz|0(OKB?_3*kkXwHxEuazIZ7=JhaNV*P~hv57q55LoebmJpfHXA@yuS{Esg+ z*C}0V-`x^=0nOa@SPUJek>td~tJ{U1T&m)~`FLp*4DF77S^{|0g%|JIqd-=5)p6a` zpJOsEkKT(FPS@t^80V!I-YJbLE@{5KmVXjEq{QbCnir%}3 zB)-J379=wrBNK6rbUL7Mh^tVmQYn-BJJP=n?P&m-7)P#OZjQoK0{5?}XqJScV6>QX zPR>G{xvU_P;q!;S9Y7*07=Z!=wxIUorMQP(m?te~6&Z0PXQ@I=EYhD*XomZ^z;`Os z4>Uh4)Cg2_##mUa>i1Dxi+R~g#!!i{?SMj%9rfaBPlWj_Yk)lCV--e^&3INB>I?lu z9YXCY5(9U`3o?w2Xa5ErMbl5+pDVpu8v+KJzI9{KFk1H?(1`_W>Cu903Hg81vEX32l{nP2vROa1Fi!Wou0+ZX7Rp`g;B$*Ni3MC-vZ`f zFTi7}c+D)!4hz6NH2e%%t_;tkA0nfkmhLtRW%){TpIqD_ev>}#mVc)<$-1GKO_oK8 zy$CF^aV#x7>F4-J;P@tqWKG0|D1+7h+{ZHU5OVjh>#aa8+V;6BQ)8L5k9t`>)>7zr zfIlv77^`Fvm<)_+^z@ac%D&hnlUAFt8!x=jdaUo{)M9Ar;Tz5Dcd_|~Hl6CaRnK3R zYn${wZe8_BZ0l0c%qbP}>($jsNDay>8+JG@F!uV4F;#zGsBP0f$f3HqEHDz_sCr^q z1;1}7KJ9&`AX2Qdav1(nNzz+GPdEk5K3;hGXe{Hq13{)c zZy%fFEEH#nlJoG{f*M^#8yXuW%!9svN8ry-Vi7AOFnN~r&D`%6d#lvMXBgZkX^vFj z;tkent^62jUr$Cc^@y31Lka6hS>F?1tE8JW$iXO*n9CQMk}D*At3U(-W1E~z>tG?> z5f`5R5LbrhRNR8kv&5d9SL7ke2a*Xr)Qp#75 z6?-p035n2<7hK;sb>t9GAwG4{9v~iEIG>}7B5zcCgZhu$M0-z8?eUO^E?g)md^XT_ z2^~-u$yak>LBy(=*GsTj6p<>b5PO&un@5hGCxpBQlOB3DpsItKZRC*oXq-r{u}Wb; z&ko>#fbnl2Z;o@KqS-d6DTeCG?m1 z&E>p}SEc*)SD&QjZbs!Csjx~0+$@ekuzV_wAalnQvX3a^n~3ui)|rDO+9HW|JPEeBGP4 z)?zcZ<8qv47`EWA*_X~H^vr(lP|f%=%cWFM;u)OFHruKT<~?>5Y8l?56>&;=WdZU# zZEK4-C8s-3zPMA^&y~e*9z)!ZJghr3N^pJa2A$??Xqx-BR*TytGYor&l8Q+^^r%Yq02xay^f#;;wO6K7G!v>wRd6531WnDI~h$PN( z+4#08uX?r&zVKsQ;?5eBX=FxsXaGyH4Gth4a&L|{8LnNCHFr1M{KjJ!BfBS_aiy-E zxtmNcXq3}WTwQ7Dq-9YS5o758sT(5b`Sg-NcH>M9OH1oW6&sZ@|GYk|cJI`vm zO<$~q!3_$&GfWetudRc*mp8)M)q7DEY-#@8w=ItkApfq3sa)*GRqofuL7)dafznKf zLuembr#8gm*lIqKH)KMxSDqbik*B(1bFt%3Vv|ypehXLCa&wc7#u!cJNlUfWs8iQ` z$66(F=1fkxwg745-8_eqV>nWGY3DjB9gE23$R5g&w|C{|xvT@7j*@aZNB199scGchI7pINb5iyqYn)O=yJJX)Ca3&Ca+{n<=1w|(|f0)h<9gs$pVSV<<9Og-V z8ki@nKwE)x)^wmHBMk?mpMT=g{S#^8W|>&rI#Ceh;9za}io0k@0JxiCqi-jHlxbt3 zjJA?RihhRvhk6%G5-D{ePh1jare*fQS<328P-DcVAxPTrw=n6k?C6EV75f}cnBRPT zMYDqqKu(ND&aOtc!QRV`vzJSVxx8i~WB#5Ml{b#eQqNnSi7l-bS-`ITW<^zyYQA(b zbj4SuRK>q9o`_v%+C=S?h>2e4!66Ij(P5{7Uz$3u6YJJC$W%EoBa{-(=tQ|y1vov%ZkXVOV z##_UVg4V^4ne#4~<-1DkJqkKqgT+E_=&4Ue&eQ-JC+gi?7G@d6= zximz{zE)WW{b@QCJ!7l&N5x=dXS?$5RBU-VvN4Uec-GHK&jPa&P2z+qDdLhIB+HU) zu0CW&uLvE^4I5xtK-$+oe|58)7m6*PO%Xt<+-XEA%jG_BEachkF3e@pn?tl!`8lOF zbi2QOuNXX)YT*MCYflILO{VZ*9GiC%R4FO20zMK?p+&aCMm2oeMK7(aW=UDzr=AO0 z$5mJ%=qRsR8rZ>_YsL+vi{3*J_9Kzq(;ZwRj+4_f0-*wbkSMPWahX#Fj_a8BnrhJ6 zo^ZZ?Vah1@&6#r=JkuaYDBdp;J3@ii+CHM&@9*er&#P}$@wI$bfrH)&c!*|nkvhf%^*Y6b%dKz%QBSIo@U z{?V^qEs4`q<8@n+u8YiB^sc@6g>TncG<|GsmC3egwE6aO=EwLr~3-2 zNr`+)`i+-83?|1Xy0^8ps&pb}YT?w1eWVnC9Ps1=KM;Rw)bH6O!7Did1NwpnqVPZc z*%Qo~qkDL>@^<^fmIBtx$WUWQiNtAB2x-LO^BB=|w~-zTnJNEdm1Ou(?8PF&U88X@ z#8rdaTd||)dG^uJw~N_-%!XNbuAyh4`>Shea=pSj0TqP+w4!`nxsmVSv02kb`DBr% zyX=e>5IJ3JYPtdbCHvKMdhXUO_*E9jc_?se7%VJF#&ZaBD;7+eFN3x+hER7!u&`Wz z7zMvBPR4y`*$a250KYjFhAKS%*XG&c;R-kS0wNY1=836wL6q02mqx;IPcH(6ThA@2 zXKQF|9H>6AW$KUF#^A%l6y5{fel77_+cR_zZ0(7=6bmNXABv}R!B-{(E^O6Y?ZS)n zs1QEmh_Fm7p}oRyT3zxUNr4UV8NGs+2b8|4shO$OGFj3D&7_e?#yDi=TTe%$2QbG5 zk<;q7aQ;p!M-Osm{vFdmXZ@!z9uWh!;*%>(vTRggufuUGP9Hols@vhx z73pn$3u2;vzRvnXuT&$Os7J@6y12*j!{ix%3B4YU1466ItmJs0NsU(4ZYRYh7wEA6q{b*Hs6@k~ zi7Yq@Ax!et0cUMTvk7P%ym){MHpcliHEI~e3HP0NV=}7;xFv#IC?a<=`>~j_sk{e> z7vg-tK*p83HZ0=QK@ zRIHo^r{D8&Ms-^WZp+6US_Quqjh$Q66W^1}=Uz&XJ8AQE9&2}P zY|FXZzZ|0IiaBd2qdt6dIjQr(ZMIOU%NG1F&fu6Po9m^?BvLhI6T0R!H2d8;U(&p2 zYA|MFscMqcO(ye~Jp?F;0>Ke+5hzVr?aBNe>GsGgr$XrpS9uajN2kNQ3o$V5rp0T( z0$6TJC;3)26SNG#XcX7l^MKTn$ga?6r4Jzfb%ZgA(Zbwit0$kY=avSnI$@Gk%+^pu zS5mHrcRS8LFPC*uVWH4DDD1pY$H8N>X?KIJZuZ2SvTqc5Nr0GHdD8TCJcd$zIhOdC zZX0ErnsozQh;t^==4zTfrZO421AL?)O)l#GSxU#|LTTg4#&yeK=^w#;q63!Nv~1(@ zs^-RNRuF&qgcr+bIzc@7$h9L;_yjdifE*$j0Q&Np=1AuHL--zdkv@}`1 zo~LlDl_YAq*z?vmr4M`GjDkl9?p|-tl(DtX76oZv25_DtZutLS9Ez!5~p?th@4 zyc_uax4W#<(#)LMkvo)yp|5tKsC2=p#6PyhpH|449T<9Zdk|%CAb5cw?fhvQtBO&7 zpQ9$24yLqPHP;$N&fe2wm%8qdctwIna<3SwGtQA3{C77s%CW%LYxtK(SBGustL0<( zu~U9r0UOkr(c{OJxZS0Ntu3+cJlF7R`7k-Bsa&q?9Ae5{{|o~?cM+T7{lB1^#vT8R z?>c9fNWey`1dKDY%F3d2O*8^qYhjlB8*7HMKE<*=(A`{>=1%s1}Pm&#_t1xy!FkPk@%SMEka2@*= zxDuM|vJJ5s+xgDls{>*o!7eOcs|xuVBPWX&+y5vEiADK%hi`#Dbd>;;Pbk2H4*-X&R?_-6ZEutSd8hC+sSjhIo z;D(j4P;2EVpEj#UF7IjM6PC+X$C5T&=nL`*!*hm9U)#O?>wqOgC>jXKN3Slk_yaQX zLf|4D8T4k|wHW`;#ZQVocNF|3izi0sOqXzi7@KlYC3CXBG`94wD;tMI1bj|8Vm zY}9`VI9!plSfhAal$M_HlaYOVNU?9Z#0<$o?lXXbX3O(l_?f)i3_~r+GcO-x#+x^X zfsZl0>Rj2iP1rsT;+b;Mr? z4Vu&O)Q5ru4j;qaSP5gA{az@XTS1NpT0d9Xhl_FkkRpcEGA0(QQ~YMh#&zwDUkNzm z6cgkdgl9W{iL6ArJ1TQHqnQ^SQ1WGu?FT|93$Ba}mPCH~!$3}0Y0g zcoG%bdTd$bmBx9Y<`Jc+=Cp4}c@EUfjiz;Rcz101p z=?#i$wo>gBE9|szaZMt-d4nUIhBnYRuBVyx+p?5#aZQgUe(!ah`J#l1$%bl5avL27 zU2~@V`3Ic&!?FhDX@Cw!R4%xtWark#p8DLT)HCZ?VJxf^yr@AD*!ERK3#L$E^*Yr? zzN&uF9Roh4rP+r`Z#7U$tzl6>k!b~HgM$C<_crP=vC>6=q{j?(I}!9>g3rJU(&){o z`R^E*9%+kEa8H_fkD9VT7(Fks&Y-RcHaUJYf-|B+eMXMaRM;{FKRiTB>1(=Iij4k1(X__|WqAd-~t#2@UQ}Z&<1Th0azdXfoll!dd)6>1miA z!&=6sDJm=e$?L&06+Q3`D-HNSkK-3$3DdZMX-6Xjn;wd#9A{~ur!2NcX>(qY_oZL0~H7dnQ9sgLe!W>~2|RSW7|hWn<({Pg*xF$%B-!rKe^_R_vc z(LO!0agxxP;FWPV({8#lEv$&&GVakGus=@!3YVG`y^AO1m{2%Np;>HNA1e{=?ra1C}H zAwT0sbwG|!am;fl?*_t^^#yLDXZ*Nx)_FqueZi0c-G~omtpHW0Cu)mEJ`Z1X8brq$ z%vK##b~o*^b&Hz!hgrD=^6P8}aW40lhzMLB5T5*v`1QH?+L~-@CDi3+C@nRf2{7UE zyDIe{@LKw`Eu=Z%6<<_=#V|yxJIKiq_N?ZJ_v0$c)N4l07ZV_mIXG}glfBSPivOhw z-~+9GdckSpMBNR9eR`Y|9_)sXS+u_OiQ%!9rE(2AFjoxN8lk16Sb~^Sq6kRoEp3yD(mm`HsYIXcag_EAB8MHc}nahxVVUTts~U9P|f;7Ul$_` zStR4v&P4q_$KXOEni$lkxy8=9w8G&47VY0oDb^+jT+>ARe3NHUg~St`$RDxY)?;_F znqTujR&chZd2qHF7y8D$4&E3+e@J~!X3&BW4BF(Ebp#TEjrd+9SU!)j;qH+ZkL@AW z?J6Mj}v0_+D zH0qlbzCkHf|EZ`6c>5ig5NAFF%|La%M-}g(7&}Vx8K)qg30YD;H!S!??{;YivzrH0 z(M%2*b_S-)yh&Aiqai)GF^c!<1Xemj|13>dZ_M#)41SrP;OEMaRJ)bCeX*ZT7W`4Y zQ|8L@NHpD@Tf(5>1U(s5iW~Zdf7$@pAL`a3X@YUv1J>q-uJ_(Dy5nYTCUHC}1(dlI zt;5>DLcHh&jbysqt?G01MhXI3!8wgf){Hv}=0N|L$t8M#L7d6WscO8Om2|NBz2Ga^ zs86y%x$H18)~akOWD7@em7)ldlWgb?_sRN>-EcYQO_}aX@+b$dR{146>{kXWP4$nN{V0_+|3{Lt|8uX_fhKh~i{(x%cj*PU$i{PO(5$uA? zQzO>a6oPj-TUk&{zq?JD2MNb6Mf~V3g$ra+PB;ujLJ2JM(a7N*b`y{MX--!fAd}5C zF$D_b8S;+Np(!cW)(hnv5b@@|EMt*RLKF*wy>ykFhEhlPN~n_Bj>LT9B^_yj>z#fx z3JuE4H&?Cc!;G@}E*3k`HK#8ag`yE3Z1)5JUlSua%qkF zkTu|<9{w9OSi$qr)WD#7EzITnch=xnR63E*d~WGvi*Co9BBE?ETHud;!Z)7&wz+l6 zuKODYG1>I1U#a%&(GNJ`AqRfg=H!BtSl+_;CEeufF-#+*2EMMz-22@>18=8PH{PHd z);mN=aR0MPF>eutLiS#-AOX>#2%+pTGEOj!j4L(m0~&xR=0+g#HNpno6@veLhJp}e zyNVC$a>4;!9&iGvU_dj&xbKt@^t6r%f^)+}eV^suRTLP52+BVs0kOLwg6n`=NUv50E7My8XQUh?y%mW62OT1pMrKI3Q(r`7vU&@93=G~A?b(^pvC-8x=bSk zZ60BQR96WB1Z@9Df(M1IQh+YrU8sEjB=Tc2;(zBn-pete*icZE|M&Uc+oHg`|1o`g zH~m+k=D$o);{Rs)b<9Zo|9_Z6L6QHLNki(N>Dw^^i1LITprZeeqIaT#+)fw)PlllU zldphHC)t!0Gf(i9zgVm>`*TbmITF zH1FZ4{wrjRCx{t^26VK_2srZuWuY*EMAsMrJYFFCH35Ky7bq8<0K|ey2wHnrFMZyr z&^yEgX{{3i@&iE5>xKZ{Ads36G3a!i50D!C4?^~cLB<<|fc1!XN(HJRM)H^21sEs%vv+Mu0h*HkLHaEffMwc0n6)JhNXY#M5w@iO@dfXY z0c6dM2a4Hd1SA*#qYj@jK}uVgAZdaBj8t6uuhUNe>)ne9vfd#C6qLV9+@Q7{MnF#0 zJ7fd-ivG_~u3bVvOzpcw1u~ZSp8-kl(sunnX>L~*K-ByWDM2E8>;Si6kn^58AZQxI xVa^It*?521mj4+UJO?7%w*+`EfEcU=@KhDx-s^WzP+ae~{CgHDE&XryzW}Nww%-5% diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4ed3bdea443..5116c5b1869 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip -distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337f..a69d9cb6c20 100755 --- a/gradlew +++ b/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9382..53a6b238d41 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 7f21f6e80e7b452d0a37797538da14bd80d3fcc6 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Tue, 9 Aug 2022 19:19:03 -0400 Subject: [PATCH 208/240] Update AGP and clojars Maven URL --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 322a47a6f8e..739f2e61810 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -20,6 +20,6 @@ allprojects { google() mavenCentral() maven { url "https://jitpack.io" } - maven { url "https://clojars.org/repo" } + maven { url "https://repo.clojars.org" } } } From 2de33d8d075d17eb9885dd2c1302c80aa42a14c9 Mon Sep 17 00:00:00 2001 From: mhmdanas Date: Thu, 11 Aug 2022 19:24:14 +0300 Subject: [PATCH 209/240] Clarify that span shouldn't be in translated READMEs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3a34816d59..52e6eef1a96 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Also, since they are free and open source software, neither the app nor the Extr * Open in Kodi * Watch/Block age-restricted material - + ## Installation and updates From 737a331c85ba8387535c14e5882f46c336cf8061 Mon Sep 17 00:00:00 2001 From: mhmdanas Date: Thu, 11 Aug 2022 19:34:23 +0300 Subject: [PATCH 210/240] Remove extra whitespace from issue and PR templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +++--- .github/ISSUE_TEMPLATE/feature_request.yml | 3 +-- .github/ISSUE_TEMPLATE/question.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3abb1fbb1f8..37ce61376d7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -40,7 +40,7 @@ body: label: Steps to reproduce the bug description: | What did you do for the bug to show up? - + If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. placeholder: | 1. Go to '...' @@ -69,11 +69,11 @@ body: label: Screenshots/Screen recordings description: | A picture or video is worth a thousand words. - + If applicable, add screenshots or a screen recording to help explain your problem. GitHub supports uploading them directly in the text box. If your file is too big for Github to accept, try to compress it (ZIP-file) or feel free to paste a link to an image/video hoster here instead. - + :heavy_exclamation_mark: DON'T POST SCREENSHOTS OF THE ERROR PAGE. Instead, follow the instructions in the "Logs" section below. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9fc3c1632ea..7290d8c5be6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -8,7 +8,6 @@ body: Thank you for helping to make NewPipe better by suggesting a feature. :hugs: Your ideas are highly welcome! The app is made for you, the users, after all. - - type: checkboxes id: checklist attributes: @@ -43,7 +42,7 @@ body: Describe any problem or limitation you come across while using the app which would be solved by this feature. validations: required: true - + - type: textarea id: additional-information attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 8cf22d8af3b..f78e2afc671 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -27,7 +27,7 @@ body: label: What is/are your question(s)? validations: required: true - + - type: textarea id: additional-information attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 10e40af2acb..abc1665eb8c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,7 +25,7 @@ - -#### APK testing +#### APK testing The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. From ec3efea05a37d813a036be2c6e2ddc01bd4eb487 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Fri, 12 Aug 2022 23:41:12 +0200 Subject: [PATCH 211/240] Update NewPipe Extractor to fix YouTube playback issues --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 496533c410a..593835e0683 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:5219a705bab539cf8c6624d0cec216e76e85f0b1' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:76aad92fa54524f20c3338ab568c9cd6b50c9d33' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From cde32a8aedf80be4391d09d1a31a71fb358a3ca1 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Fri, 12 Aug 2022 23:49:07 +0200 Subject: [PATCH 212/240] Add changelog for v0.23.2 (988) --- fastlane/metadata/android/en-US/changelogs/988.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/988.txt diff --git a/fastlane/metadata/android/en-US/changelogs/988.txt b/fastlane/metadata/android/en-US/changelogs/988.txt new file mode 100644 index 00000000000..5ef8fbbdede --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Fix "Could not get any stream" error when trying to play any video +[YouTube] Fix "The Following content is not available on this app." message shown instead of the video requested \ No newline at end of file From fcaebc838ec59622396febc1e0a083f458ca6056 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Fri, 12 Aug 2022 23:50:41 +0200 Subject: [PATCH 213/240] Release v0.23.2 (988) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 593835e0683..14f63b7e02c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 19 targetSdk 29 - versionCode 987 - versionName "0.23.1" + versionCode 988 + versionName "0.23.2" multiDexEnabled true From 4f6b5b3b89879a53039a868c2007bcbdbb3972f8 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 15:27:29 +0530 Subject: [PATCH 214/240] Use ListAdapter in PeertubeInstanceListFragment. --- .../PeertubeInstanceListFragment.java | 194 ++++++++---------- 1 file changed, 88 insertions(+), 106 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 92b9a036267..1158b3d8307 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -12,28 +12,27 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; import android.widget.RadioButton; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatImageView; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogEditTextBinding; +import org.schabi.newpipe.databinding.FragmentInstanceListBinding; +import org.schabi.newpipe.databinding.ItemInstanceBinding; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.PeertubeHelper; @@ -41,7 +40,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.List; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; @@ -50,12 +48,11 @@ import io.reactivex.rxjava3.schedulers.Schedulers; public class PeertubeInstanceListFragment extends Fragment { - private final List instanceList = new ArrayList<>(); private PeertubeInstance selectedInstance; private String savedInstanceListKey; private InstanceListAdapter instanceListAdapter; - private ProgressBar progressBar; + private FragmentInstanceListBinding binding; private SharedPreferences sharedPreferences; private CompositeDisposable disposables = new CompositeDisposable(); @@ -71,7 +68,6 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); savedInstanceListKey = getString(R.string.peertube_instance_list_key); selectedInstance = PeertubeHelper.getCurrentInstance(); - updateInstanceList(); setHasOptionsMenu(true); } @@ -79,7 +75,8 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_instance_list, container, false); + binding = FragmentInstanceListBinding.inflate(inflater, container, false); + return binding.getRoot(); } @Override @@ -87,26 +84,17 @@ public void onViewCreated(@NonNull final View rootView, @Nullable final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); - initViews(rootView); - } - - private void initViews(@NonNull final View rootView) { - final TextView instanceHelpTV = rootView.findViewById(R.id.instanceHelpTV); - instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, + binding.instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, getString(R.string.peertube_instance_list_url))); - - initButton(rootView); - - final RecyclerView listInstances = rootView.findViewById(R.id.instances); - listInstances.setLayoutManager(new LinearLayoutManager(requireContext())); + binding.addInstanceButton.setOnClickListener(v -> showAddItemDialog(requireContext())); + binding.instances.setLayoutManager(new LinearLayoutManager(requireContext())); final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); - itemTouchHelper.attachToRecyclerView(listInstances); + itemTouchHelper.attachToRecyclerView(binding.instances); instanceListAdapter = new InstanceListAdapter(requireContext(), itemTouchHelper); - listInstances.setAdapter(instanceListAdapter); - - progressBar = rootView.findViewById(R.id.loading_progress_bar); + binding.instances.setAdapter(instanceListAdapter); + instanceListAdapter.submitList(PeertubeHelper.getInstanceList(requireContext())); } @Override @@ -131,6 +119,12 @@ public void onDestroy() { disposables = null; } + @Override + public void onDestroyView() { + binding = null; + super.onDestroyView(); + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -156,11 +150,6 @@ public boolean onOptionsItemSelected(final MenuItem item) { // Utils //////////////////////////////////////////////////////////////////////////*/ - private void updateInstanceList() { - instanceList.clear(); - instanceList.addAll(PeertubeHelper.getInstanceList(requireContext())); - } - private void selectInstance(final PeertubeInstance instance) { selectedInstance = PeertubeHelper.selectInstance(instance, requireContext()); sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); @@ -168,7 +157,7 @@ private void selectInstance(final PeertubeInstance instance) { private void saveChanges() { final JsonStringWriter jsonWriter = JsonWriter.string().object().array("instances"); - for (final PeertubeInstance instance : instanceList) { + for (final PeertubeInstance instance : instanceListAdapter.getCurrentList()) { jsonWriter.object(); jsonWriter.value("name", instance.getName()); jsonWriter.value("url", instance.getUrl()); @@ -179,28 +168,21 @@ private void saveChanges() { } private void restoreDefaults() { - new AlertDialog.Builder(requireContext()) + final Context context = requireContext(); + new AlertDialog.Builder(context) .setTitle(R.string.restore_defaults) .setMessage(R.string.restore_defaults_confirmation) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok, (dialog, which) -> { sharedPreferences.edit().remove(savedInstanceListKey).apply(); selectInstance(PeertubeInstance.DEFAULT_INSTANCE); - updateInstanceList(); - instanceListAdapter.notifyDataSetChanged(); + instanceListAdapter.submitList(PeertubeHelper.getInstanceList(context)); }) .show(); } - private void initButton(final View rootView) { - final FloatingActionButton fab = rootView.findViewById(R.id.addInstanceButton); - fab.setOnClickListener(v -> - showAddItemDialog(requireContext())); - } - private void showAddItemDialog(final Context c) { - final DialogEditTextBinding dialogBinding = - DialogEditTextBinding.inflate(getLayoutInflater()); + final var dialogBinding = DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); dialogBinding.dialogEditText.setHint(R.string.peertube_instance_add_help); @@ -222,17 +204,17 @@ private void addInstance(final String url) { if (cleanUrl == null) { return; } - progressBar.setVisibility(View.VISIBLE); + binding.loadingProgressBar.setVisibility(View.VISIBLE); final Disposable disposable = Single.fromCallable(() -> { final PeertubeInstance instance = new PeertubeInstance(cleanUrl); instance.fetchInstanceMetaData(); return instance; }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .subscribe((instance) -> { - progressBar.setVisibility(View.GONE); + binding.loadingProgressBar.setVisibility(View.GONE); add(instance); }, e -> { - progressBar.setVisibility(View.GONE); + binding.loadingProgressBar.setVisibility(View.GONE); Toast.makeText(getActivity(), R.string.peertube_instance_add_fail, Toast.LENGTH_SHORT).show(); }); @@ -255,7 +237,7 @@ private String cleanUrl(final String url) { return null; } // only allow if not already exists - for (final PeertubeInstance instance : instanceList) { + for (final PeertubeInstance instance : instanceListAdapter.getCurrentList()) { if (instance.getUrl().equals(cleanUrl)) { Toast.makeText(getActivity(), R.string.peertube_instance_add_exists, Toast.LENGTH_SHORT).show(); @@ -266,8 +248,9 @@ private String cleanUrl(final String url) { } private void add(final PeertubeInstance instance) { - instanceList.add(instance); - instanceListAdapter.notifyDataSetChanged(); + final var list = new ArrayList<>(instanceListAdapter.getCurrentList()); + list.add(instance); + instanceListAdapter.submitList(list); } private ItemTouchHelper.SimpleCallback getItemTouchCallback() { @@ -281,8 +264,7 @@ public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int minimumAbsVelocity = Math.max(12, - Math.abs(standardSpeed)); + final int minimumAbsVelocity = Math.max(12, Math.abs(standardSpeed)); return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); } @@ -316,17 +298,19 @@ public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int swipeDir) { final int position = viewHolder.getBindingAdapterPosition(); // do not allow swiping the selected instance - if (instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) { + if (instanceListAdapter.getCurrentList().get(position).getUrl() + .equals(selectedInstance.getUrl())) { instanceListAdapter.notifyItemChanged(position); return; } - instanceList.remove(position); - instanceListAdapter.notifyItemRemoved(position); + final var list = new ArrayList<>(instanceListAdapter.getCurrentList()); + list.remove(position); - if (instanceList.isEmpty()) { - instanceList.add(selectedInstance); - instanceListAdapter.notifyItemInserted(0); + if (list.isEmpty()) { + list.add(selectedInstance); } + + instanceListAdapter.submitList(list); } }; } @@ -336,96 +320,94 @@ public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, //////////////////////////////////////////////////////////////////////////*/ private class InstanceListAdapter - extends RecyclerView.Adapter { + extends ListAdapter { private final LayoutInflater inflater; private final ItemTouchHelper itemTouchHelper; private RadioButton lastChecked; InstanceListAdapter(final Context context, final ItemTouchHelper itemTouchHelper) { + super(new PeertubeInstanceCallback()); this.itemTouchHelper = itemTouchHelper; this.inflater = LayoutInflater.from(context); } public void swapItems(final int fromPosition, final int toPosition) { - Collections.swap(instanceList, fromPosition, toPosition); - notifyItemMoved(fromPosition, toPosition); + final var list = new ArrayList<>(getCurrentList()); + Collections.swap(list, fromPosition, toPosition); + submitList(list); } @NonNull @Override public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View view = inflater.inflate(R.layout.item_instance, parent, false); - return new InstanceListAdapter.TabViewHolder(view); + return new InstanceListAdapter.TabViewHolder(ItemInstanceBinding.inflate(inflater, + parent, false)); } @Override public void onBindViewHolder(@NonNull final InstanceListAdapter.TabViewHolder holder, final int position) { - holder.bind(position, holder); - } - - @Override - public int getItemCount() { - return instanceList.size(); + holder.bind(position); } class TabViewHolder extends RecyclerView.ViewHolder { - private final AppCompatImageView instanceIconView; - private final TextView instanceNameView; - private final TextView instanceUrlView; - private final RadioButton instanceRB; - private final ImageView handle; - - TabViewHolder(final View itemView) { - super(itemView); - - instanceIconView = itemView.findViewById(R.id.instanceIcon); - instanceNameView = itemView.findViewById(R.id.instanceName); - instanceUrlView = itemView.findViewById(R.id.instanceUrl); - instanceRB = itemView.findViewById(R.id.selectInstanceRB); - handle = itemView.findViewById(R.id.handle); + private final ItemInstanceBinding itemBinding; + + TabViewHolder(final ItemInstanceBinding binding) { + super(binding.getRoot()); + this.itemBinding = binding; } @SuppressLint("ClickableViewAccessibility") - void bind(final int position, final TabViewHolder holder) { - handle.setOnTouchListener(getOnTouchListener(holder)); + void bind(final int position) { + itemBinding.handle.setOnTouchListener((view, motionEvent) -> { + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (itemTouchHelper != null && getItemCount() > 1) { + itemTouchHelper.startDrag(this); + return true; + } + } + return false; + }); - final PeertubeInstance instance = instanceList.get(position); - instanceNameView.setText(instance.getName()); - instanceUrlView.setText(instance.getUrl()); - instanceRB.setOnCheckedChangeListener(null); + final PeertubeInstance instance = getItem(position); + itemBinding.instanceName.setText(instance.getName()); + itemBinding.instanceUrl.setText(instance.getUrl()); + itemBinding.selectInstanceRB.setOnCheckedChangeListener(null); if (selectedInstance.getUrl().equals(instance.getUrl())) { - if (lastChecked != null && lastChecked != instanceRB) { + if (lastChecked != null && lastChecked != itemBinding.selectInstanceRB) { lastChecked.setChecked(false); } - instanceRB.setChecked(true); - lastChecked = instanceRB; + itemBinding.selectInstanceRB.setChecked(true); + lastChecked = itemBinding.selectInstanceRB; } - instanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { + itemBinding.selectInstanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { selectInstance(instance); - if (lastChecked != null && lastChecked != instanceRB) { + if (lastChecked != null && lastChecked != itemBinding.selectInstanceRB) { lastChecked.setChecked(false); } - lastChecked = instanceRB; + lastChecked = itemBinding.selectInstanceRB; } }); - instanceIconView.setImageResource(R.drawable.ic_placeholder_peertube); + itemBinding.instanceIcon.setImageResource(R.drawable.ic_placeholder_peertube); } + } + } - @SuppressLint("ClickableViewAccessibility") - private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { - return (view, motionEvent) -> { - if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { - if (itemTouchHelper != null && getItemCount() > 1) { - itemTouchHelper.startDrag(item); - return true; - } - } - return false; - }; - } + private static class PeertubeInstanceCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final PeertubeInstance oldItem, + @NonNull final PeertubeInstance newItem) { + return oldItem.getUrl().equals(newItem.getUrl()); + } + + @Override + public boolean areContentsTheSame(@NonNull final PeertubeInstance oldItem, + @NonNull final PeertubeInstance newItem) { + return oldItem.getName().equals(newItem.getName()) + && oldItem.getUrl().equals(newItem.getUrl()); } } } From 67669c286b4bad85fef0854ceb3cb3a3f45e471d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 15:53:04 +0530 Subject: [PATCH 215/240] Use ListAdapter in SuggestionListAdapter. --- .../fragments/list/search/SearchFragment.java | 10 +-- .../list/search/SuggestionListAdapter.java | 86 +++++++------------ 2 files changed, 36 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 008163890aa..6827ddaaf47 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -200,7 +200,7 @@ public void onAttach(@NonNull final Context context) { showLocalSuggestions = NewPipeSettings.showLocalSearchSuggestions(activity, prefs); showRemoteSuggestions = NewPipeSettings.showRemoteSearchSuggestions(activity, prefs); - suggestionListAdapter = new SuggestionListAdapter(activity); + suggestionListAdapter = new SuggestionListAdapter(); historyRecordManager = new HistoryRecordManager(context); } @@ -530,7 +530,7 @@ private void initSearchListeners() { searchBinding.correctSuggestion.setVisibility(View.GONE); searchEditText.setText(""); - suggestionListAdapter.setItems(new ArrayList<>()); + suggestionListAdapter.submitList(null); showKeyboardSearch(); }); @@ -945,7 +945,7 @@ public void handleSuggestions(@NonNull final List suggestions) { Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]"); } searchBinding.suggestionsList.smoothScrollToPosition(0); - searchBinding.suggestionsList.post(() -> suggestionListAdapter.setItems(suggestions)); + suggestionListAdapter.submitList(suggestions); if (suggestionsPanelVisible && isErrorPanelVisible()) { hideLoading(); @@ -1066,14 +1066,14 @@ public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder vie return 0; } - final SuggestionItem item = suggestionListAdapter.getItem(position); + final SuggestionItem item = suggestionListAdapter.getCurrentList().get(position); return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0; } public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) { final int position = viewHolder.getBindingAdapterPosition(); - final String query = suggestionListAdapter.getItem(position).query; + final String query = suggestionListAdapter.getCurrentList().get(position).query; final Disposable onDelete = historyRecordManager.deleteSearchHistory(query) .observeOn(AndroidSchedulers.mainThread()) .subscribe( diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java index fb983b01e26..f859c9e54ca 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java @@ -1,34 +1,22 @@ package org.schabi.newpipe.fragments.list.search; -import android.content.Context; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; - -import java.util.ArrayList; -import java.util.List; +import org.schabi.newpipe.databinding.ItemSearchSuggestionBinding; public class SuggestionListAdapter - extends RecyclerView.Adapter { - private final ArrayList items = new ArrayList<>(); - private final Context context; + extends ListAdapter { private OnSuggestionItemSelected listener; - public SuggestionListAdapter(final Context context) { - this.context = context; - } - - public void setItems(final List items) { - this.items.clear(); - this.items.addAll(items); - notifyDataSetChanged(); + public SuggestionListAdapter() { + super(new SuggestionItemCallback()); } public void setListener(final OnSuggestionItemSelected listener) { @@ -39,45 +27,32 @@ public void setListener(final OnSuggestionItemSelected listener) { @Override public SuggestionItemHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - return new SuggestionItemHolder(LayoutInflater.from(context) - .inflate(R.layout.item_search_suggestion, parent, false)); + return new SuggestionItemHolder(ItemSearchSuggestionBinding + .inflate(LayoutInflater.from(parent.getContext()), parent, false)); } @Override public void onBindViewHolder(final SuggestionItemHolder holder, final int position) { final SuggestionItem currentItem = getItem(position); holder.updateFrom(currentItem); - holder.queryView.setOnClickListener(v -> { + holder.itemBinding.suggestionSearch.setOnClickListener(v -> { if (listener != null) { listener.onSuggestionItemSelected(currentItem); } }); - holder.queryView.setOnLongClickListener(v -> { + holder.itemBinding.suggestionSearch.setOnLongClickListener(v -> { if (listener != null) { listener.onSuggestionItemLongClick(currentItem); } return true; }); - holder.insertView.setOnClickListener(v -> { + holder.itemBinding.suggestionInsert.setOnClickListener(v -> { if (listener != null) { listener.onSuggestionItemInserted(currentItem); } }); } - SuggestionItem getItem(final int position) { - return items.get(position); - } - - @Override - public int getItemCount() { - return items.size(); - } - - public boolean isEmpty() { - return getItemCount() == 0; - } - public interface OnSuggestionItemSelected { void onSuggestionItemSelected(SuggestionItem item); @@ -87,30 +62,31 @@ public interface OnSuggestionItemSelected { } public static final class SuggestionItemHolder extends RecyclerView.ViewHolder { - private final TextView itemSuggestionQuery; - private final ImageView suggestionIcon; - private final View queryView; - private final View insertView; + private final ItemSearchSuggestionBinding itemBinding; - // Cache some ids, as they can potentially be constantly updated/recycled - private final int historyResId; - private final int searchResId; - - private SuggestionItemHolder(final View rootView) { - super(rootView); - suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon); - itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query); + private SuggestionItemHolder(final ItemSearchSuggestionBinding binding) { + super(binding.getRoot()); + this.itemBinding = binding; + } - queryView = rootView.findViewById(R.id.suggestion_search); - insertView = rootView.findViewById(R.id.suggestion_insert); + private void updateFrom(final SuggestionItem item) { + itemBinding.itemSuggestionIcon.setImageResource(item.fromHistory ? R.drawable.ic_history + : R.drawable.ic_search); + itemBinding.itemSuggestionQuery.setText(item.query); + } + } - historyResId = R.drawable.ic_history; - searchResId = R.drawable.ic_search; + private static class SuggestionItemCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final SuggestionItem oldItem, + @NonNull final SuggestionItem newItem) { + return oldItem.query.equals(newItem.query); } - private void updateFrom(final SuggestionItem item) { - suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId); - itemSuggestionQuery.setText(item.query); + @Override + public boolean areContentsTheSame(@NonNull final SuggestionItem oldItem, + @NonNull final SuggestionItem newItem) { + return oldItem.equals(newItem); } } } From 5e0788b99c7b0a43936f179cb94273d5046081c3 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 1 Aug 2022 06:11:35 +0530 Subject: [PATCH 216/240] Use ListAdapter in PreferenceSearchAdapter. --- .../PreferenceSearchAdapter.java | 61 +++++++++---------- .../PreferenceSearchFragment.java | 11 +--- .../preferencesearch/PreferenceSearcher.java | 3 +- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java index 02fbf9577b0..d6e2021a15f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java @@ -1,54 +1,48 @@ package org.schabi.newpipe.settings.preferencesearch; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.databinding.SettingsPreferencesearchListItemResultBinding; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; class PreferenceSearchAdapter - extends RecyclerView.Adapter { - private List dataset = new ArrayList<>(); + extends ListAdapter { private Consumer onItemClickListener; + PreferenceSearchAdapter() { + super(new PreferenceCallback()); + } + @NonNull @Override - public PreferenceViewHolder onCreateViewHolder( - @NonNull final ViewGroup parent, - final int viewType - ) { - return new PreferenceViewHolder( - SettingsPreferencesearchListItemResultBinding.inflate( - LayoutInflater.from(parent.getContext()), - parent, - false)); + public PreferenceViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, + final int viewType) { + return new PreferenceViewHolder(SettingsPreferencesearchListItemResultBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); } @Override - public void onBindViewHolder( - @NonNull final PreferenceViewHolder holder, - final int position - ) { - final PreferenceSearchItem item = dataset.get(position); + public void onBindViewHolder(@NonNull final PreferenceViewHolder holder, final int position) { + final PreferenceSearchItem item = getItem(position); holder.binding.title.setText(item.getTitle()); - if (TextUtils.isEmpty(item.getSummary())) { + if (item.getSummary().isEmpty()) { holder.binding.summary.setVisibility(View.GONE); } else { holder.binding.summary.setVisibility(View.VISIBLE); holder.binding.summary.setText(item.getSummary()); } - if (TextUtils.isEmpty(item.getBreadcrumbs())) { + if (item.getBreadcrumbs().isEmpty()) { holder.binding.breadcrumbs.setVisibility(View.GONE); } else { holder.binding.breadcrumbs.setVisibility(View.VISIBLE); @@ -62,16 +56,6 @@ public void onBindViewHolder( }); } - void setContent(final List items) { - dataset = new ArrayList<>(items); - this.notifyDataSetChanged(); - } - - @Override - public int getItemCount() { - return dataset.size(); - } - void setOnItemClickListener(final Consumer onItemClickListener) { this.onItemClickListener = onItemClickListener; } @@ -84,4 +68,19 @@ static class PreferenceViewHolder extends RecyclerView.ViewHolder { this.binding = binding; } } + + private static class PreferenceCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final PreferenceSearchItem oldItem, + @NonNull final PreferenceSearchItem newItem) { + return oldItem.getKey().equals(newItem.getKey()); + } + + @Override + public boolean areContentsTheSame(@NonNull final PreferenceSearchItem oldItem, + @NonNull final PreferenceSearchItem newItem) { + return oldItem.getAllRelevantSearchFields().equals(newItem + .getAllRelevantSearchFields()); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java index 308abbc4e0b..9d169d660ad 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.settings.preferencesearch; import android.os.Bundle; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,7 +12,6 @@ import org.schabi.newpipe.databinding.SettingsPreferencesearchFragmentBinding; -import java.util.ArrayList; import java.util.List; /** @@ -54,13 +52,8 @@ public void updateSearchResults(final String keyword) { return; } - final List results = - !TextUtils.isEmpty(keyword) - ? searcher.searchFor(keyword) - : new ArrayList<>(); - - adapter.setContent(new ArrayList<>(results)); - + final List results = searcher.searchFor(keyword); + adapter.submitList(results); setEmptyViewShown(results.isEmpty()); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java index 176dc5d14ee..b3efc8dd162 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java @@ -3,6 +3,7 @@ import android.text.TextUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -21,7 +22,7 @@ public void add(final List items) { List searchFor(final String keyword) { if (TextUtils.isEmpty(keyword)) { - return new ArrayList<>(); + return Collections.emptyList(); } return configuration.getSearcher() From a50b9bd6ffde21b233a070b4887f1735572953e4 Mon Sep 17 00:00:00 2001 From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Date: Sun, 21 Aug 2022 17:39:57 +0000 Subject: [PATCH 217/240] Add FAQ entry to the template checklists (#8822) Co-authored-by: Mohammed Anas --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 2 ++ .github/ISSUE_TEMPLATE/question.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3abb1fbb1f8..d165daea360 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -18,6 +18,8 @@ body: required: true - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my problem isn't listed." + required: true - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." required: true - label: "This issue contains only one bug." diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9fc3c1632ea..fbc61b579fc 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -16,6 +16,8 @@ body: options: - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my problem isn't listed." + required: true - label: "I'm aware that this is a request for NewPipe itself and that requests for adding a new service need to be made at [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor/issues)." required: true - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 8cf22d8af3b..458ed6f2ede 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -16,6 +16,8 @@ body: options: - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my question isn't listed." + required: true - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." required: true - label: "I have read and understood the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md)." From db45042a566180fd9673e7a08f948fb4ebe16039 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:14:46 +0200 Subject: [PATCH 218/240] Update NewPipeExtractor This removes the usage of the SourceVersion class, which was not available on Android and caused issues such as #8876 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 14f63b7e02c..aff5cafa213 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:76aad92fa54524f20c3338ab568c9cd6b50c9d33' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:6a858368c86bc9a55abee586eb6c733e86c26b97' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From 1a432f2ee39b1e5aee2700f0c1050067bebbf622 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:15:30 +0200 Subject: [PATCH 219/240] Update jsoup to 1.15.3 This fixes a vulnerability issue related to Cross Site Scripting --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index aff5cafa213..610944236e7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -231,7 +231,7 @@ dependencies { kapt "frankiesardo:icepick-processor:${icepickVersion}" // HTML parser - implementation "org.jsoup:jsoup:1.14.3" + implementation "org.jsoup:jsoup:1.15.3" // HTTP client //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users From e1b8a3fbdfa754dc24b81d88932b3acaa4407a71 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:16:56 +0200 Subject: [PATCH 220/240] Hotfix release v0.23.3 (989) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 610944236e7..b297d57541b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 19 targetSdk 29 - versionCode 988 - versionName "0.23.2" + versionCode 989 + versionName "0.23.3" multiDexEnabled true From 5c0ed22b093b0cc4735150ed213bc02fd77b93e6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:23:09 +0200 Subject: [PATCH 221/240] Add changelog for v0.23.3 (989) --- fastlane/metadata/android/en-US/changelogs/989.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/989.txt diff --git a/fastlane/metadata/android/en-US/changelogs/989.txt b/fastlane/metadata/android/en-US/changelogs/989.txt new file mode 100644 index 00000000000..9ded366ea39 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/989.txt @@ -0,0 +1,2 @@ +• [YouTube] Fix videos loading indefinitely +• Upgrade the jsoup library to 1.15.3, which includes a security fix From 4227866fcfc852b57aabfe03bf458e69e5a050ca Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:44:16 +0200 Subject: [PATCH 222/240] Improve changelog for v0.23.3 (989) --- fastlane/metadata/android/en-US/changelogs/989.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/changelogs/989.txt b/fastlane/metadata/android/en-US/changelogs/989.txt index 9ded366ea39..d1330ff8f3a 100644 --- a/fastlane/metadata/android/en-US/changelogs/989.txt +++ b/fastlane/metadata/android/en-US/changelogs/989.txt @@ -1,2 +1,3 @@ -• [YouTube] Fix videos loading indefinitely +• [YouTube] Fix infinite loading when trying to play any video +• [YouTube] Fix throttling on some videos • Upgrade the jsoup library to 1.15.3, which includes a security fix From 52dbfdee00d4ca4879f8915c5eea4a363aebdc35 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 15:06:19 +0200 Subject: [PATCH 223/240] Keep strong references to Picasso notification icon loading targets Before the Target would sometimes be garbage collected before being called with the loaded channel icon, since Picasso holds weak references to targets. This meant that sometimes a new streams notification would not be shown, because the lambda that should have shown it had already been garbage collected. --- .../feed/notifications/NotificationHelper.kt | 31 +++++++++++++++++-- .../schabi/newpipe/util/PicassoHelper.java | 28 +++-------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 3a08b3e4aa5..351975486fb 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -4,6 +4,8 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.provider.Settings @@ -11,6 +13,8 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.service.FeedUpdateInfo @@ -27,6 +31,8 @@ class NotificationHelper(val context: Context) { Context.NOTIFICATION_SERVICE ) as NotificationManager + private val iconLoadingTargets = ArrayList() + /** * Show a notification about new streams from a single channel. * Opening the notification will open the corresponding channel page. @@ -77,10 +83,29 @@ class NotificationHelper(val context: Context) { ) ) - PicassoHelper.loadNotificationIcon(data.avatarUrl) { bitmap -> - bitmap?.let { builder.setLargeIcon(it) } // set only if != null - manager.notify(data.pseudoId, builder.build()) + // a Target is like a listener for image loading events + val target = object : Target { + override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { + builder.setLargeIcon(bitmap) // set only if there is actually one + manager.notify(data.pseudoId, builder.build()) + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { + manager.notify(data.pseudoId, builder.build()) + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable) { + // Nothing to do + } } + + // add the target to the list to hold a strong reference and prevent it from being garbage + // collected, since Picasso only holds weak references to targets + iconLoadingTargets.add(target) + + PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target) } companion object { diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 54140b0fba9..fc7600d4b74 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -5,7 +5,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; import androidx.annotation.Nullable; @@ -14,7 +13,6 @@ import com.squareup.picasso.OkHttp3Downloader; import com.squareup.picasso.Picasso; import com.squareup.picasso.RequestCreator; -import com.squareup.picasso.Target; import com.squareup.picasso.Transformation; import org.schabi.newpipe.R; @@ -22,7 +20,6 @@ import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import okhttp3.OkHttpClient; @@ -120,6 +117,10 @@ public static RequestCreator loadSeekbarThumbnailPreview(final String url) { return picassoInstance.load(url); } + public static RequestCreator loadNotificationIcon(final String url) { + return loadImageDefault(url, R.drawable.ic_newpipe_triangle_white); + } + public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { // scale down the notification thumbnail for performance @@ -170,27 +171,6 @@ public static Bitmap getImageFromCacheIfPresent(final String imageUrl) { return picassoCache.get(imageUrl + "\n"); } - public static void loadNotificationIcon(final String url, - final Consumer bitmapConsumer) { - loadImageDefault(url, R.drawable.ic_newpipe_triangle_white) - .into(new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - bitmapConsumer.accept(bitmap); - } - - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - bitmapConsumer.accept(null); - } - - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - // Nothing to do - } - }); - } - private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { return loadImageDefault(url, placeholderResId, true); From c054ea07372ab33049047cbcfcf5ae5584c05dea Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 18:00:43 +0200 Subject: [PATCH 224/240] Create MediaSessionPlayerUi --- .../org/schabi/newpipe/player/Player.java | 43 +++-------- .../schabi/newpipe/player/PlayerService.java | 6 +- .../MediaSessionManager.java | 4 +- .../mediasession/MediaSessionPlayerUi.java | 74 +++++++++++++++++++ .../player/notification/NotificationUtil.java | 10 ++- .../schabi/newpipe/player/ui/PlayerUi.java | 3 +- .../newpipe/player/ui/PlayerUiList.java | 17 ++++- 7 files changed, 113 insertions(+), 44 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{helper => mediasession}/MediaSessionManager.java (97%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 159d361c144..e1bc2f061d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -99,14 +99,13 @@ import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; -import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; -import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; @@ -196,7 +195,6 @@ public final class Player implements PlaybackListener, Listener { private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; - private MediaSessionManager mediaSessionManager; @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -225,7 +223,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ @SuppressWarnings("MemberName") // keep the unusual member name - private final PlayerUiList UIs = new PlayerUiList(); + private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; @@ -265,6 +263,15 @@ public Player(@NonNull final PlayerService service) { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + + // The UIs added here should always be present. They will be initialized when the player + // reaches the initialization step. Make sure the media session ui is before the + // notification ui in the UIs list, since the notification depends on the media session in + // PlayerUi#initPlayer(), and UIs.call() guarantees UI order is preserved. + UIs = new PlayerUiList( + new MediaSessionPlayerUi(this), + new NotificationPlayerUi(this) + ); } private VideoPlaybackResolver.QualityResolver getQualityResolver() { @@ -431,11 +438,6 @@ && isPlaybackResumeEnabled(this) } private void initUIsForCurrentPlayerType() { - //noinspection SimplifyOptionalCallChains - if (!UIs.get(NotificationPlayerUi.class).isPresent()) { - UIs.addAndPrepare(new NotificationPlayerUi(this)); - } - if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { // correct UI already in place @@ -506,8 +508,6 @@ private void initPlayer(final boolean playOnReady) { simpleExoPlayer.setHandleAudioBecomingNoisy(true); audioReactor = new AudioReactor(context, simpleExoPlayer); - mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, - new PlayerMediaSession(this)); registerBroadcastReceiver(); @@ -558,9 +558,6 @@ private void destroyPlayer() { if (playQueueManager != null) { playQueueManager.dispose(); } - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } } public void destroy() { @@ -723,11 +720,6 @@ private void onBroadcastReceived(final Intent intent) { Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); } break; - case Intent.ACTION_HEADSET_PLUG: //FIXME - /*notificationManager.cancel(NOTIFICATION_ID); - mediaSessionManager.dispose(); - mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/ - break; } UIs.call(playerUi -> playerUi.onBroadcastReceived(intent)); @@ -1738,15 +1730,6 @@ private void updateMetadataWith(@NonNull final StreamInfo info) { initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); - final boolean showThumbnail = prefs.getBoolean( - context.getString(R.string.show_thumbnail_key), true); - mediaSessionManager.setMetadata( - getVideoTitle(), - getUploaderName(), - showThumbnail ? Optional.ofNullable(getThumbnail()) : Optional.empty(), - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - notifyMetadataUpdateToListeners(); UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @@ -2194,10 +2177,6 @@ public SharedPreferences getPrefs() { return prefs; } - public MediaSessionManager getMediaSessionManager() { - return mediaSessionManager; - } - public PlayerType getPlayerType() { return playerType; diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 8d982617a64..33b024e3dc5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -28,6 +28,7 @@ import android.os.IBinder; import android.util.Log; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -73,9 +74,8 @@ public int onStartCommand(final Intent intent, final int flags, final int startI } player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } + player.UIs().get(MediaSessionPlayerUi.class) + .ifPresent(ui -> ui.handleMediaButtonIntent(intent)); return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java similarity index 97% rename from app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java rename to app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index a8735dc08bc..61bc9e2e771 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.helper; +package org.schabi.newpipe.player.mediasession; import android.content.Context; import android.content.Intent; @@ -18,8 +18,6 @@ import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.mediasession.PlayQueueNavigator; import java.util.Optional; diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java new file mode 100644 index 00000000000..d2de11ccfb9 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -0,0 +1,74 @@ +package org.schabi.newpipe.player.mediasession; + +import android.content.Intent; +import android.support.v4.media.session.MediaSessionCompat; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playback.PlayerMediaSession; +import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; + +public class MediaSessionPlayerUi extends PlayerUi { + + private MediaSessionManager mediaSessionManager; + + public MediaSessionPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayer() { + super.initPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + } + mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), + new PlayerMediaSession(player)); + } + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + mediaSessionManager = null; + } + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + // TODO decide whether to handle ACTION_HEADSET_PLUG or not + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + + mediaSessionManager.setMetadata( + player.getVideoTitle(), + player.getUploaderName(), + showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() + ); + } + + public void handleMediaButtonIntent(final Intent intent) { + if (mediaSessionManager != null) { + mediaSessionManager.handleMediaButtonIntent(intent); + } + } + + public Optional getSessionToken() { + return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 1a91bc66d85..29ec7a9816e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -19,11 +19,13 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static androidx.media.app.NotificationCompat.MediaStyle; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; @@ -101,9 +103,11 @@ private synchronized NotificationCompat.Builder createNotification() { player.getContext(), player.getPrefs(), nonNothingSlotCount); final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); - builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() - .setMediaSession(player.getMediaSessionManager().getSessionToken()) - .setShowActionsInCompactView(compactSlots)) + final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); + player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); + + builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setCategory(NotificationCompat.CATEGORY_TRANSPORT) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 9ce04bfd5c9..57e2ec2a2cf 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -29,7 +29,8 @@ public abstract class PlayerUi { @NonNull protected final Player player; /** - * @param player the player instance that will be usable throughout the lifetime of this UI + * @param player the player instance that will be usable throughout the lifetime of this UI; its + * context should already have been initialized */ protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 05c0ed5b3cc..24fec3b8afc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -8,6 +8,19 @@ public final class PlayerUiList { final List playerUis = new ArrayList<>(); + /** + * Creates a {@link PlayerUiList} starting with the provided player uis. The provided player uis + * will not be prepared like those passed to {@link #addAndPrepare(PlayerUi)}, because when + * the {@link PlayerUiList} constructor is called, the player is still not running and it + * wouldn't make sense to initialize uis then. Instead the player will initialize them by doing + * proper calls to {@link #call(Consumer)}. + * + * @param initialPlayerUis the player uis this list should start with; the order will be kept + */ + public PlayerUiList(final PlayerUi... initialPlayerUis) { + playerUis.addAll(List.of(initialPlayerUis)); + } + /** * Adds the provided player ui to the list and calls on it the initialization functions that * apply based on the current player state. The preparation step needs to be done since when UIs @@ -67,11 +80,11 @@ public Optional get(final Class playerUiType) { } /** - * Calls the provided consumer on all player UIs in the list. + * Calls the provided consumer on all player UIs in the list, in order of addition. * @param consumer the consumer to call with player UIs */ public void call(final Consumer consumer) { //noinspection SimplifyStreamApiCallChains - playerUis.stream().forEach(consumer); + playerUis.stream().forEachOrdered(consumer); } } From bc33322d4be00a0c34db81248e938074555874b7 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:40:21 +0200 Subject: [PATCH 225/240] Remove useless MediaSessionCallback The player is now passed directly, it made no sense to wrap around it in a callback that was not really a callback but rather, actually, a wrapper. --- .../mediasession/MediaSessionCallback.java | 21 ---- .../mediasession/MediaSessionManager.java | 16 +-- .../mediasession/MediaSessionPlayerUi.java | 4 +- .../mediasession/PlayQueueNavigator.java | 100 +++++++++++++----- .../player/playback/PlayerMediaSession.java | 99 ----------------- 5 files changed, 83 insertions(+), 157 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java delete mode 100644 app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java deleted file mode 100644 index c4b02d9857f..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.support.v4.media.MediaDescriptionCompat; - -public interface MediaSessionCallback { - void playPrevious(); - - void playNext(); - - void playItemAtIndex(int index); - - int getCurrentPlayingIndex(); - - int getQueueSize(); - - MediaDescriptionCompat getQueueMetadata(int index); - - void play(); - - void pause(); -} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 61bc9e2e771..69f7d38fe7f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -14,10 +14,11 @@ import androidx.media.session.MediaButtonReceiver; import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import java.util.Optional; @@ -36,8 +37,7 @@ public class MediaSessionManager { private int lastAlbumArtHashCode; public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); @@ -53,16 +53,18 @@ public MediaSessionManager(@NonNull final Context context, .build()); sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); - sessionConnector.setPlayer(new ForwardingPlayer(player) { + sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); + sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @Override public void play() { - callback.play(); + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); } @Override public void pause() { - callback.pause(); + player.pause(); } }); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index d2de11ccfb9..a2eca575f4e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -8,7 +8,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.util.StreamTypeUtil; @@ -28,8 +27,7 @@ public void initPlayer() { if (mediaSessionManager != null) { mediaSessionManager.dispose(); } - mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), - new PlayerMediaSession(player)); + mediaSessionManager = new MediaSessionManager(context, player); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 92cd425c5fb..7bd27bfdce7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,106 +1,152 @@ package org.schabi.newpipe.player.mediasession; +import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { - public static final int DEFAULT_MAX_QUEUE_SIZE = 10; + private static final int MAX_QUEUE_SIZE = 10; private final MediaSessionCompat mediaSession; - private final MediaSessionCallback callback; - private final int maxQueueSize; + private final Player player; private long activeQueueItemId; public PlayQueueNavigator(@NonNull final MediaSessionCompat mediaSession, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { this.mediaSession = mediaSession; - this.callback = callback; - this.maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + this.player = player; this.activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; } @Override - public long getSupportedQueueNavigatorActions(@Nullable final Player player) { + public long getSupportedQueueNavigatorActions( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM; } @Override - public void onTimelineChanged(@NonNull final Player player) { + public void onTimelineChanged(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { publishFloatingQueueWindow(); } @Override - public void onCurrentMediaItemIndexChanged(@NonNull final Player player) { + public void onCurrentMediaItemIndexChanged( + @NonNull final com.google.android.exoplayer2.Player exoPlayer) { if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID - || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { + || exoPlayer.getCurrentTimeline().getWindowCount() > MAX_QUEUE_SIZE) { publishFloatingQueueWindow(); - } else if (!player.getCurrentTimeline().isEmpty()) { - activeQueueItemId = player.getCurrentMediaItemIndex(); + } else if (!exoPlayer.getCurrentTimeline().isEmpty()) { + activeQueueItemId = exoPlayer.getCurrentMediaItemIndex(); } } @Override - public long getActiveQueueItemId(@Nullable final Player player) { - return callback.getCurrentPlayingIndex(); + public long getActiveQueueItemId( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { + return Optional.ofNullable(player.getPlayQueue()).map(PlayQueue::getIndex).orElse(-1); } @Override - public void onSkipToPrevious(@NonNull final Player player) { - callback.playPrevious(); + public void onSkipToPrevious(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playPrevious(); } @Override - public void onSkipToQueueItem(@NonNull final Player player, final long id) { - callback.playItemAtIndex((int) id); + public void onSkipToQueueItem(@NonNull final com.google.android.exoplayer2.Player exoPlayer, + final long id) { + if (player.getPlayQueue() != null) { + player.selectQueueItem(player.getPlayQueue().getItem((int) id)); + } } @Override - public void onSkipToNext(@NonNull final Player player) { - callback.playNext(); + public void onSkipToNext(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playNext(); } private void publishFloatingQueueWindow() { - if (callback.getQueueSize() == 0) { + final int windowCount = Optional.ofNullable(player.getPlayQueue()) + .map(PlayQueue::size) + .orElse(0); + if (windowCount == 0) { mediaSession.setQueue(Collections.emptyList()); activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; return; } // Yes this is almost a copypasta, got a problem with that? =\ - final int windowCount = callback.getQueueSize(); - final int currentWindowIndex = callback.getCurrentPlayingIndex(); - final int queueSize = Math.min(maxQueueSize, windowCount); + final int currentWindowIndex = player.getPlayQueue().getIndex(); + final int queueSize = Math.min(MAX_QUEUE_SIZE, windowCount); final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0, windowCount - queueSize); final List queue = new ArrayList<>(); for (int i = startIndex; i < startIndex + queueSize; i++) { - queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i)); + queue.add(new MediaSessionCompat.QueueItem(getQueueMetadata(i), i)); } mediaSession.setQueue(queue); activeQueueItemId = currentWindowIndex; } + public MediaDescriptionCompat getQueueMetadata(final int index) { + if (player.getPlayQueue() == null) { + return null; + } + final PlayQueueItem item = player.getPlayQueue().getItem(index); + if (item == null) { + return null; + } + + final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() + .setMediaId(String.valueOf(index)) + .setTitle(item.getTitle()) + .setSubtitle(item.getUploader()); + + // set additional metadata for A2DP/AVRCP + final Bundle additionalMetadata = new Bundle(); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); + descBuilder.setExtras(additionalMetadata); + + final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); + if (thumbnailUri != null) { + descBuilder.setIconUri(thumbnailUri); + } + + return descBuilder.build(); + } + @Override - public boolean onCommand(@NonNull final Player player, + public boolean onCommand(@NonNull final com.google.android.exoplayer2.Player exoPlayer, @NonNull final String command, @Nullable final Bundle extras, @Nullable final ResultReceiver cb) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java deleted file mode 100644 index 3c41acc7596..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.schabi.newpipe.player.playback; - -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.media.MediaDescriptionCompat; -import android.support.v4.media.MediaMetadataCompat; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.player.ui.VideoPlayerUi; - -public class PlayerMediaSession implements MediaSessionCallback { - private final Player player; - - public PlayerMediaSession(final Player player) { - this.player = player; - } - - @Override - public void playPrevious() { - player.playPrevious(); - } - - @Override - public void playNext() { - player.playNext(); - } - - @Override - public void playItemAtIndex(final int index) { - if (player.getPlayQueue() == null) { - return; - } - player.selectQueueItem(player.getPlayQueue().getItem(index)); - } - - @Override - public int getCurrentPlayingIndex() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().getIndex(); - } - - @Override - public int getQueueSize() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().size(); - } - - @Override - public MediaDescriptionCompat getQueueMetadata(final int index) { - if (player.getPlayQueue() == null) { - return null; - } - final PlayQueueItem item = player.getPlayQueue().getItem(index); - if (item == null) { - return null; - } - - final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() - .setMediaId(String.valueOf(index)) - .setTitle(item.getTitle()) - .setSubtitle(item.getUploader()); - - // set additional metadata for A2DP/AVRCP - final Bundle additionalMetadata = new Bundle(); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); - descBuilder.setExtras(additionalMetadata); - - final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); - if (thumbnailUri != null) { - descBuilder.setIconUri(thumbnailUri); - } - - return descBuilder.build(); - } - - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(playerUi -> playerUi.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } -} From 3cc43e9fb9eb32063deebcfaf29531aa4588fc3d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:44:59 +0200 Subject: [PATCH 226/240] Fix thumbnail sometimes not set to media session metadata The thumbnail was not being updated in the media session metadata after it was loaded, since there was no metadata update in that case, only a notification update. --- .../newpipe/player/mediasession/MediaSessionPlayerUi.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index a2eca575f4e..e0343820ea8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -1,9 +1,11 @@ package org.schabi.newpipe.player.mediasession; import android.content.Intent; +import android.graphics.Bitmap; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; @@ -60,6 +62,12 @@ public void onMetadataChanged(@NonNull final StreamInfo info) { ); } + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + } + public void handleMediaButtonIntent(final Intent intent) { if (mediaSessionManager != null) { mediaSessionManager.handleMediaButtonIntent(intent); From f3a9b81b670c0e999a9d2fcc8b657d3e85e182f0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:03:42 +0200 Subject: [PATCH 227/240] Fix sometimes seeing outdated thumbnail in notification Before the thumbnail finishes loading for the new video the player is now playing, the old thumbnail was being used, leading to wrong thumbnails set in the media session and the notification. --- .../java/org/schabi/newpipe/player/Player.java | 14 +++++++++----- .../org/schabi/newpipe/util/PicassoHelper.java | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index e1bc2f061d1..13dd1d93897 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -748,11 +748,15 @@ private void unregisterBroadcastReceiver() { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading - private void initThumbnail(final String url) { + private void loadCurrentThumbnail(final String url) { if (DEBUG) { - Log.d(TAG, "Thumbnail - initThumbnail() called with url = [" + Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } + + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media + // session metadata while the new thumbnail is being loaded by Picasso. + currentThumbnail = null; if (isNullOrEmpty(url)) { return; } @@ -762,8 +766,8 @@ private void initThumbnail(final String url) { @Override public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingComplete() called with: url = [" + url - + "], " + "loadedImage = [" + bitmap + " -> " + bitmap.getWidth() + "x" + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url + + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + from + "]"); } @@ -1727,7 +1731,7 @@ private void updateMetadataWith(@NonNull final StreamInfo info) { maybeAutoQueueNextStream(info); - initThumbnail(info.getThumbnailUrl()); + loadCurrentThumbnail(info.getThumbnailUrl()); registerStreamViewed(); notifyMetadataUpdateToListeners(); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index fc7600d4b74..5739b930b64 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -1,10 +1,12 @@ package org.schabi.newpipe.util; +import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; +import android.util.Log; import androidx.annotation.Nullable; @@ -24,6 +26,7 @@ import okhttp3.OkHttpClient; public final class PicassoHelper { + private static final String TAG = PicassoHelper.class.getSimpleName(); public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -129,6 +132,10 @@ public static RequestCreator loadScaledDownThumbnail(final Context context, fina .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - transform() called"); + } + final float notificationThumbnailWidth = Math.min( context.getResources() .getDimension(R.dimen.player_notification_thumbnail_width), From d73ca41cfe6dd3e39bf3f7d751d5870f307a336b Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:44:17 +0200 Subject: [PATCH 228/240] Even when thumbnails should not be shown, set it to null in notification This makes sure the thumbnail is removed from the notification if the user disables thumbnails --- .../player/notification/NotificationUtil.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 29ec7a9816e..84e9cc3bc4f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -9,6 +9,7 @@ import android.util.Log; import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; @@ -137,12 +138,9 @@ private synchronized void updateNotification() { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); + updateActions(notificationBuilder); - final boolean showThumbnail = player.getPrefs().getBoolean( - player.getContext().getString(R.string.show_thumbnail_key), true); - if (showThumbnail) { - setLargeIcon(notificationBuilder); - } + setLargeIcon(notificationBuilder); } @@ -344,17 +342,26 @@ private Intent getIntentForNotification() { ///////////////////////////////////////////////////// private void setLargeIcon(final NotificationCompat.Builder builder) { + final boolean showThumbnail = player.getPrefs().getBoolean( + player.getContext().getString(R.string.show_thumbnail_key), true); + final Bitmap thumbnail = player.getThumbnail(); + if (thumbnail == null || !showThumbnail) { + // since the builder is reused, make sure the thumbnail is unset if there is not one + builder.setLargeIcon(null); + return; + } + final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); if (scaleImageToSquareAspectRatio) { - builder.setLargeIcon(getBitmapWithSquareAspectRatio(player.getThumbnail())); + builder.setLargeIcon(getBitmapWithSquareAspectRatio(thumbnail)); } else { - builder.setLargeIcon(player.getThumbnail()); + builder.setLargeIcon(thumbnail); } } - private Bitmap getBitmapWithSquareAspectRatio(final Bitmap bitmap) { + private Bitmap getBitmapWithSquareAspectRatio(@NonNull final Bitmap bitmap) { // Find the smaller dimension and then take a center portion of the image that // has that size. final int w = bitmap.getWidth(); From 8bff445ec3d314a35d3243d4fff2711184265533 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 10:17:22 +0200 Subject: [PATCH 229/240] Remove useless checks before updating metadata A while ago NewPipe called the metadata update function very often, so checks were needed to ensure not wasting time updating metadata if it were already up to date. Now, instead, the metadata update function is called exactly when needed, i.e. when metadata changes, so such checks are not needed anymore (and were probably also a little resource-heavy). --- .../org/schabi/newpipe/player/Player.java | 5 - .../mediasession/MediaSessionManager.java | 123 ++---------------- .../mediasession/MediaSessionPlayerUi.java | 2 +- 3 files changed, 11 insertions(+), 119 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 13dd1d93897..22d46bcbe89 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -54,7 +54,6 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.util.Log; @@ -1773,10 +1772,6 @@ public String getUploaderName() { @Nullable public Bitmap getThumbnail() { - if (currentThumbnail == null) { - currentThumbnail = BitmapFactory.decodeResource( - context.getResources(), R.drawable.placeholder_thumbnail_video); - } return currentThumbnail; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 69f7d38fe7f..98b6d1b3236 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -20,8 +20,6 @@ import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; -import java.util.Optional; - public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); public static final boolean DEBUG = MainActivity.DEBUG; @@ -31,11 +29,6 @@ public class MediaSessionManager { @NonNull private final MediaSessionConnector sessionConnector; - private int lastTitleHashCode; - private int lastArtistHashCode; - private long lastDuration; - private int lastAlbumArtHashCode; - public MediaSessionManager(@NonNull final Context context, @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); @@ -84,134 +77,38 @@ public MediaSessionCompat.Token getSessionToken() { * * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param optAlbumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART} + * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} * - should be a negative value for unknown durations, e.g. for livestreams */ public void setMetadata(@NonNull final String title, @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { + @Nullable final Bitmap albumArt, + final long duration) { if (DEBUG) { - Log.d(TAG, "setMetadata called:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); + Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist + + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) + + "], duration = [" + duration + "]"); } if (!mediaSession.isActive()) { if (DEBUG) { - Log.d(TAG, "setMetadata: mediaSession not active - exiting"); + Log.d(TAG, "setMetadata: media session not active, exiting"); } return; } - if (!checkIfMetadataShouldBeSet(title, artist, optAlbumArt, duration)) { - if (DEBUG) { - Log.d(TAG, "setMetadata: No update required - exiting"); - } - return; - } - - if (DEBUG) { - Log.d(TAG, "setMetadata: N_Metadata update:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); - } - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - if (optAlbumArt.isPresent()) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, optAlbumArt.get()); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, optAlbumArt.get()); + if (albumArt != null) { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); } mediaSession.setMetadata(builder.build()); - - lastTitleHashCode = title.hashCode(); - lastArtistHashCode = artist.hashCode(); - lastDuration = duration; - optAlbumArt.ifPresent(bitmap -> lastAlbumArtHashCode = bitmap.hashCode()); - } - - private boolean checkIfMetadataShouldBeSet( - @NonNull final String title, - @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { - // Check if the values have changed since the last time - if (title.hashCode() != lastTitleHashCode - || artist.hashCode() != lastArtistHashCode - || duration != lastDuration - || (optAlbumArt.isPresent() && optAlbumArt.get().hashCode() != lastAlbumArtHashCode) - ) { - if (DEBUG) { - Log.d(TAG, - "checkIfMetadataShouldBeSet: true - reason: changed values since last"); - } - return true; - } - - // Check if the currently set metadata is valid - if (getMetadataTitle() == null - || getMetadataArtist() == null - // Note that the duration can be <= 0 for live streams - ) { - if (DEBUG) { - if (getMetadataTitle() == null) { - Log.d(TAG, - "N_getMetadataTitle: title == null"); - } else if (getMetadataArtist() == null) { - Log.d(TAG, - "N_getMetadataArtist: artist == null"); - } - } - return true; - } - - // If we got an album art check if the current set AlbumArt is null - if (optAlbumArt.isPresent() && getMetadataAlbumArt() == null) { - if (DEBUG) { - Log.d(TAG, "N_getMetadataAlbumArt: thumb == null"); - } - return true; - } - - // Default - no update required - return false; - } - - - @Nullable - private Bitmap getMetadataAlbumArt() { - return mediaSession.getController().getMetadata() - .getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART); - } - - @Nullable - private String getMetadataTitle() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_TITLE); - } - - @Nullable - private String getMetadataArtist() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_ARTIST); } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index e0343820ea8..2140be26d19 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -57,7 +57,7 @@ public void onMetadataChanged(@NonNull final StreamInfo info) { mediaSessionManager.setMetadata( player.getVideoTitle(), player.getUploaderName(), - showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + showThumbnail ? player.getThumbnail() : null, StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() ); } From f80d1dc48dcc0539ac7faa20b085b0adc2c5fe64 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 11:31:49 +0200 Subject: [PATCH 230/240] Let exoplayer decide when to update metadata Though still make sure metadata is updated after the thumbnail is loaded. This fixes the wrong seekbar properties (duration and current position) being shown in the notification sometimes. --- .../mediasession/MediaSessionManager.java | 87 ++++++++----------- .../mediasession/MediaSessionPlayerUi.java | 22 +---- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 98b6d1b3236..c6766fbcb90 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -2,10 +2,8 @@ import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.view.KeyEvent; @@ -17,8 +15,12 @@ import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); @@ -34,17 +36,6 @@ public MediaSessionManager(@NonNull final Context context, mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); - mediaSession.setPlaybackState(new PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_NONE, -1, 1) - .setActions(PlaybackStateCompat.ACTION_SEEK_TO - | PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE // was play and pause now play/pause - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_SET_REPEAT_MODE - | PlaybackStateCompat.ACTION_STOP) - .build()); - sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @@ -60,6 +51,37 @@ public void pause() { player.pause(); } }); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> { + if (DEBUG) { + Log.d(TAG, "MediaMetadataProvider#getMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams, since they don't have a duration) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); + }); } @Nullable @@ -72,43 +94,8 @@ public MediaSessionCompat.Token getSessionToken() { return mediaSession.getSessionToken(); } - /** - * sets the Metadata - if required. - * - * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} - * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null - * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} - * - should be a negative value for unknown durations, e.g. for livestreams - */ - public void setMetadata(@NonNull final String title, - @NonNull final String artist, - @Nullable final Bitmap albumArt, - final long duration) { - if (DEBUG) { - Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist - + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) - + "], duration = [" + duration + "]"); - } - - if (!mediaSession.isActive()) { - if (DEBUG) { - Log.d(TAG, "setMetadata: media session not active, exiting"); - } - return; - } - - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - if (albumArt != null) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); - } - - mediaSession.setMetadata(builder.build()); + void triggerMetadataUpdate() { + sessionConnector.invalidateMediaSessionMetadata(); } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index 2140be26d19..c78a3a6b1fe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -7,11 +7,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; @@ -47,25 +44,12 @@ public void onBroadcastReceived(final Intent intent) { // TODO decide whether to handle ACTION_HEADSET_PLUG or not } - @Override - public void onMetadataChanged(@NonNull final StreamInfo info) { - super.onMetadataChanged(info); - - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - - mediaSessionManager.setMetadata( - player.getVideoTitle(), - player.getUploaderName(), - showThumbnail ? player.getThumbnail() : null, - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + if (mediaSessionManager != null) { + mediaSessionManager.triggerMetadataUpdate(); + } } public void handleMediaButtonIntent(final Intent intent) { From 11bd2369e5e8c0e5352fe76dada02089664f36c0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 12:00:32 +0200 Subject: [PATCH 231/240] Merge MediaSessionManager into MediaSessionPlayerUi --- .../mediasession/MediaSessionManager.java | 110 ------------------ .../mediasession/MediaSessionPlayerUi.java | 104 ++++++++++++++--- 2 files changed, 90 insertions(+), 124 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java deleted file mode 100644 index c6766fbcb90..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.content.Context; -import android.content.Intent; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.util.Log; -import android.view.KeyEvent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.media.session.MediaButtonReceiver; - -import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; - -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.ui.VideoPlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; - -import java.util.Optional; - -public class MediaSessionManager { - private static final String TAG = MediaSessionManager.class.getSimpleName(); - public static final boolean DEBUG = MainActivity.DEBUG; - - @NonNull - private final MediaSessionCompat mediaSession; - @NonNull - private final MediaSessionConnector sessionConnector; - - public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player) { - mediaSession = new MediaSessionCompat(context, TAG); - mediaSession.setActive(true); - - sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); - sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } - }); - - sessionConnector.setMetadataDeduplicationEnabled(true); - sessionConnector.setMediaMetadataProvider(exoPlayer -> { - if (DEBUG) { - Log.d(TAG, "MediaMetadataProvider#getMetadata called"); - } - - // set title and artist - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); - - // set duration (-1 for livestreams, since they don't have a duration) - final long duration = player.getCurrentStreamInfo() - .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) - .map(info -> info.getDuration() * 1000L) - .orElse(-1L); - builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - // set album art, unless the user asked not to, or there is no thumbnail available - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - Optional.ofNullable(player.getThumbnail()) - .filter(bitmap -> showThumbnail) - .ifPresent(bitmap -> { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); - }); - - return builder.build(); - }); - } - - @Nullable - @SuppressWarnings("UnusedReturnValue") - public KeyEvent handleMediaButtonIntent(final Intent intent) { - return MediaButtonReceiver.handleIntent(mediaSession, intent); - } - - public MediaSessionCompat.Token getSessionToken() { - return mediaSession.getSessionToken(); - } - - void triggerMetadataUpdate() { - sessionConnector.invalidateMediaSessionMetadata(); - } - - /** - * Should be called on player destruction to prevent leakage. - */ - public void dispose() { - sessionConnector.setPlayer(null); - sessionConnector.setQueueNavigator(null); - mediaSession.setActive(false); - mediaSession.release(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index c78a3a6b1fe..92a137900fc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -1,20 +1,33 @@ package org.schabi.newpipe.player.mediasession; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Intent; import android.graphics.Bitmap; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.media.session.MediaButtonReceiver; + +import com.google.android.exoplayer2.ForwardingPlayer; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; public class MediaSessionPlayerUi extends PlayerUi { + private static final String TAG = "MediaSessUi"; - private MediaSessionManager mediaSessionManager; + private MediaSessionCompat mediaSession; + private MediaSessionConnector sessionConnector; public MediaSessionPlayerUi(@NonNull final Player player) { super(player); @@ -23,18 +36,31 @@ public MediaSessionPlayerUi(@NonNull final Player player) { @Override public void initPlayer() { super.initPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } - mediaSessionManager = new MediaSessionManager(context, player); + destroyPlayer(); // release previously used resources + + mediaSession = new MediaSessionCompat(context, TAG); + mediaSession.setActive(true); + + sessionConnector = new MediaSessionConnector(mediaSession); + sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); + sessionConnector.setPlayer(getForwardingPlayer()); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata()); } @Override public void destroyPlayer() { super.destroyPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - mediaSessionManager = null; + if (sessionConnector != null) { + sessionConnector.setPlayer(null); + sessionConnector.setQueueNavigator(null); + sessionConnector = null; + } + if (mediaSession != null) { + mediaSession.setActive(false); + mediaSession.release(); + mediaSession = null; } } @@ -47,18 +73,68 @@ public void onBroadcastReceived(final Intent intent) { @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - if (mediaSessionManager != null) { - mediaSessionManager.triggerMetadataUpdate(); + if (sessionConnector != null) { + // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update + sessionConnector.invalidateMediaSessionMetadata(); } } + public void handleMediaButtonIntent(final Intent intent) { - if (mediaSessionManager != null) { - mediaSessionManager.handleMediaButtonIntent(intent); - } + MediaButtonReceiver.handleIntent(mediaSession, intent); } public Optional getSessionToken() { - return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + return Optional.ofNullable(mediaSession).map(MediaSessionCompat::getSessionToken); + } + + + private ForwardingPlayer getForwardingPlayer() { + // ForwardingPlayer means that all media session actions called on this player are + // forwarded directly to the connected exoplayer, except for the overridden methods. So + // override play and pause since our player adds more functionality to them over exoplayer. + return new ForwardingPlayer(player.getExoPlayer()) { + @Override + public void play() { + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); + } + + @Override + public void pause() { + player.pause(); + } + }; + } + + private MediaMetadataCompat buildMediaMetadata() { + if (DEBUG) { + Log.d(TAG, "buildMediaMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams or if unknown, see the METADATA_KEY_DURATION docs) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); } } From 510efaae976c1fe15a85c3f81c4a49573760097f Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 14:39:25 +0200 Subject: [PATCH 232/240] Keep strong reference to Picasso thumbnail loading target Before the Target would sometimes be garbage collected before being called with the loaded thumbnail, since Picasso holds weak references to targets --- .../org/schabi/newpipe/player/Player.java | 71 ++++++++++++------- .../schabi/newpipe/util/PicassoHelper.java | 2 - 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 22d46bcbe89..45b9b0fde29 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -174,6 +174,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ public static final int RENDERER_UNAVAILABLE = -1; + private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -232,6 +233,11 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); + // This is the only listener we need for thumbnail loading, since there is always at most only + // one thumbnail being loaded at a time. This field is also here to maintain a strong reference, + // which would otherwise be garbage collected since Picasso holds weak references to targets. + @NonNull private final Target currentThumbnailTarget; + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -263,6 +269,8 @@ public Player(@NonNull final PlayerService service) { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + currentThumbnailTarget = getCurrentThumbnailTarget(); + // The UIs added here should always be present. They will be initialized when the player // reaches the initialization step. Make sure the media session ui is before the // notification ui in the UIs list, since the notification depends on the media session in @@ -573,7 +581,7 @@ public void destroy() { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); - PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading + cancelLoadingCurrentThumbnail(); UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } @@ -747,48 +755,63 @@ private void unregisterBroadcastReceiver() { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading - private void loadCurrentThumbnail(final String url) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" - + (url == null ? "null" : url) + "]"); - } - - // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media - // session metadata while the new thumbnail is being loaded by Picasso. - currentThumbnail = null; - if (isNullOrEmpty(url)) { - return; - } - - // scale down the notification thumbnail for performance - PicassoHelper.loadScaledDownThumbnail(context, url).into(new Target() { + private Target getCurrentThumbnailTarget() { + // a Picasso target is just a listener for thumbnail loading events + return new Target() { @Override public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url - + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" - + bitmap.getHeight() + "], from = [" + from + "]"); + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap + + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + + from + "]"); } - currentThumbnail = bitmap; - // there is a new thumbnail, so changed the end screen thumbnail, too. + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @Override public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); + Log.e(TAG, "Thumbnail - onBitmapFailed() called", e); currentThumbnail = null; + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); } @Override public void onPrepareLoad(final Drawable placeHolderDrawable) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onPrepareLoad() called: url = [" + url + "]"); + Log.d(TAG, "Thumbnail - onPrepareLoad() called"); } } - }); + }; + } + + private void loadCurrentThumbnail(final String url) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + + (url == null ? "null" : url) + "]"); + } + + // first cancel any previous loading + cancelLoadingCurrentThumbnail(); + + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media + // session metadata while the new thumbnail is being loaded by Picasso. + currentThumbnail = null; + if (isNullOrEmpty(url)) { + return; + } + + // scale down the notification thumbnail for performance + PicassoHelper.loadScaledDownThumbnail(context, url) + .tag(PICASSO_PLAYER_THUMBNAIL_TAG) + .into(currentThumbnailTarget); + } + + private void cancelLoadingCurrentThumbnail() { + // cancel the Picasso job associated with the player thumbnail, if any + PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG); } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 5739b930b64..2e781631e1d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -27,7 +27,6 @@ public final class PicassoHelper { private static final String TAG = PicassoHelper.class.getSimpleName(); - public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -128,7 +127,6 @@ public static RequestCreator loadNotificationIcon(final String url) { public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { // scale down the notification thumbnail for performance return PicassoHelper.loadThumbnail(url) - .tag(PLAYER_THUMBNAIL_TAG) .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { From 973a9660115cc809a99ae571af45f0b67a9c7c03 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 26 Jul 2022 16:35:57 +0200 Subject: [PATCH 233/240] Review suggestions --- .../newpipe/player/mediasession/PlayQueueNavigator.java | 2 +- .../newpipe/player/notification/NotificationUtil.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 7bd27bfdce7..e84c0837bee 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -126,7 +126,7 @@ public MediaDescriptionCompat getQueueMetadata(final int index) { .setTitle(item.getTitle()) .setSubtitle(item.getUploader()); - // set additional metadata for A2DP/AVRCP + // set additional metadata for A2DP/AVRCP (Audio/Video Bluetooth profiles) final Bundle additionalMetadata = new Bundle(); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 84e9cc3bc4f..2c3199a284c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -105,8 +105,10 @@ private synchronized NotificationCompat.Builder createNotification() { final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); - player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) - .ifPresent(mediaStyle::setMediaSession); + player.UIs() + .get(MediaSessionPlayerUi.class) + .flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) From 59d1ded94e876bd040a7c23a7a2e3ae18987df07 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:59:46 +0200 Subject: [PATCH 234/240] Fixed sonar detected problems + Automatically fixed code style (imports) --- .../java/org/schabi/newpipe/player/Player.java | 2 +- .../mediasession/MediaSessionPlayerUi.java | 6 ------ .../mediasession/PlayQueueNavigator.java | 18 +++++++++--------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 45b9b0fde29..319c163e850 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -222,7 +222,7 @@ public final class Player implements PlaybackListener, Listener { // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ - @SuppressWarnings("MemberName") // keep the unusual member name + @SuppressWarnings({"MemberName", "java:S116"}) // keep the unusual member name private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index 92a137900fc..e9541ab06d8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -64,12 +64,6 @@ public void destroyPlayer() { } } - @Override - public void onBroadcastReceived(final Intent intent) { - super.onBroadcastReceived(intent); - // TODO decide whether to handle ACTION_HEADSET_PLUG or not - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index e84c0837bee..2e54b1129d7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.player.mediasession; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -13,19 +17,15 @@ import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; - public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { private static final int MAX_QUEUE_SIZE = 10; @@ -132,7 +132,7 @@ public MediaDescriptionCompat getQueueMetadata(final int index) { additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1L); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); descBuilder.setExtras(additionalMetadata); From 6805c75c9cf90f9448e266eda34866136f0c71c8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 2 Aug 2022 14:46:11 +0200 Subject: [PATCH 235/240] Fix surface view not resizing video correctly Also fix yet another random null pointer exception that could happen when adding the video player view --- .../fragments/detail/VideoDetailFragment.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 3b1bdaedeb7..594006ab5f9 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1220,7 +1220,7 @@ private void openMainPlayer() { } final PlayQueue queue = setupPlayQueueForIntent(false); - addVideoPlayerView(); + tryAddVideoPlayerView(); final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), PlayerService.class, queue, true, autoPlayEnabled); @@ -1301,21 +1301,27 @@ private boolean isAutoplayEnabled() { && PlayerHelper.isAutoplayAllowedByUser(requireContext()); } - private void addVideoPlayerView() { - if (!isPlayerAvailable() || getView() == null) { - return; - } - setHeightThumbnail(); + private void tryAddVideoPlayerView() { + // do all the null checks in the posted lambda, since the player, the binding and the view + // could be set or unset before the lambda gets executed on the next main thread cycle + new Handler(Looper.getMainLooper()).post(() -> { + if (!isPlayerAvailable() || getView() == null) { + return; + } - // Prevent from re-adding a view multiple times - new Handler(Looper.getMainLooper()).post(() -> - player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - if (binding != null) { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); - } - })); + // setup the surface view height, so that it fits the video correctly + setHeightThumbnail(); + + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + // sometimes binding would be null here, even though getView() != null above u.u + if (binding != null) { + // prevent from re-adding a view multiple times + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + } + }); + }); } private void removeVideoPlayerView() { @@ -1784,7 +1790,7 @@ private void showPlaybackProgress(final long progress, final long duration) { @Override public void onViewCreated() { - addVideoPlayerView(); + tryAddVideoPlayerView(); } @Override @@ -1926,7 +1932,7 @@ public void onFullscreenStateChanged(final boolean fullscreen) { } scrollToTop(); - addVideoPlayerView(); + tryAddVideoPlayerView(); } @Override From 500acce178d1e9f21d811f7e9f3b3bfac6c4aeb0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 15:08:24 +0200 Subject: [PATCH 236/240] Fix regression in screen rotation animation --- .../newpipe/fragments/detail/VideoDetailFragment.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 594006ab5f9..9800b2b0ab9 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1302,8 +1302,14 @@ private boolean isAutoplayEnabled() { } private void tryAddVideoPlayerView() { - // do all the null checks in the posted lambda, since the player, the binding and the view - // could be set or unset before the lambda gets executed on the next main thread cycle + if (isPlayerAvailable() && getView() != null) { + // Setup the surface view height, so that it fits the video correctly; this is done also + // here, and not only in the Handler, to avoid a choppy fullscreen rotation animation. + setHeightThumbnail(); + } + + // do all the null checks in the posted lambda, too, since the player, the binding and the + // view could be set or unset before the lambda gets executed on the next main thread cycle new Handler(Looper.getMainLooper()).post(() -> { if (!isPlayerAvailable() || getView() == null) { return; From ca0f56eea815bd2112f2e36655c4f753dd975a70 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 16:03:15 +0200 Subject: [PATCH 237/240] Avoid setting invalid states to bottom sheet callback --- .../fragments/detail/VideoDetailFragment.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 9800b2b0ab9..0ec1efe571f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -180,6 +180,8 @@ public final class VideoDetailFragment @State int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; @State + int lastStableBottomSheetState = BottomSheetBehavior.STATE_EXPANDED; + @State protected boolean autoPlayEnabled = true; @Nullable @@ -269,7 +271,7 @@ public static VideoDetailFragment getInstance(final int serviceId, public static VideoDetailFragment getInstanceInCollapsedState() { final VideoDetailFragment instance = new VideoDetailFragment(); - instance.bottomSheetState = BottomSheetBehavior.STATE_COLLAPSED; + instance.updateBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); return instance; } @@ -1170,7 +1172,7 @@ public void openVideoPlayer(final boolean directlyFullscreenIfApplicable) { // doesn't tell which state it was settling to, and thus the bottom sheet settles to // STATE_COLLAPSED. This can be solved by manually setting the state that will be // restored (i.e. bottomSheetState) to STATE_EXPANDED. - bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; + updateBottomSheetState(BottomSheetBehavior.STATE_EXPANDED); // toggle landscape in order to open directly in fullscreen onScreenRotationButtonClicked(); } @@ -2284,7 +2286,9 @@ private void setupBottomPlayer() { final FrameLayout bottomSheetLayout = activity.findViewById(R.id.fragment_player_holder); bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout); - bottomSheetBehavior.setState(bottomSheetState); + bottomSheetBehavior.setState(lastStableBottomSheetState); + updateBottomSheetState(lastStableBottomSheetState); + final int peekHeight = getResources().getDimensionPixelSize(R.dimen.mini_player_height); if (bottomSheetState != BottomSheetBehavior.STATE_HIDDEN) { manageSpaceAtTheBottom(false); @@ -2300,7 +2304,7 @@ private void setupBottomPlayer() { bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull final View bottomSheet, final int newState) { - bottomSheetState = newState; + updateBottomSheetState(newState); switch (newState) { case BottomSheetBehavior.STATE_HIDDEN: @@ -2441,4 +2445,12 @@ public Optional getRoot() { return player.UIs().get(VideoPlayerUi.class) .map(playerUi -> playerUi.getBinding().getRoot()); } + + private void updateBottomSheetState(final int newState) { + bottomSheetState = newState; + if (newState != BottomSheetBehavior.STATE_DRAGGING + && newState != BottomSheetBehavior.STATE_SETTLING) { + lastStableBottomSheetState = newState; + } + } } From f9994abb94453f97080bdd48200c6009b965a43c Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 17:48:02 +0200 Subject: [PATCH 238/240] Prevent tapping on thumbnail if video details are not loaded --- .../fragments/detail/VideoDetailFragment.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 0ec1efe571f..09e0857910b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -505,12 +505,18 @@ public void onClick(final View v) { } break; case R.id.detail_thumbnail_root_layout: - autoPlayEnabled = true; // forcefully start playing - // FIXME Workaround #7427 - if (isPlayerAvailable()) { - player.setRecovery(); + // make sure not to open any player if there is nothing currently loaded! + // FIXME removing this `if` causes the player service to start correctly, then stop, + // then restart badly without calling `startForeground()`, causing a crash when + // later closing the detail fragment + if (currentInfo != null) { + autoPlayEnabled = true; // forcefully start playing + // FIXME Workaround #7427 + if (isPlayerAvailable()) { + player.setRecovery(); + } + openVideoPlayerAutoFullscreen(); } - openVideoPlayerAutoFullscreen(); break; case R.id.detail_title_root_layout: toggleTitleAndSecondaryControls(); From 67b5de38b1cdb88a160b34ec18a0ae2e4248ce41 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 27 Aug 2022 11:53:53 +0200 Subject: [PATCH 239/240] Translated using Weblate (Chinese (Simplified)) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Czech) Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Interlingua) Currently translated at 35.0% (224 of 640 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 62.3% (43 of 69 strings) Translated using Weblate (Korean) Currently translated at 10.1% (7 of 69 strings) Translated using Weblate (French) Currently translated at 89.8% (62 of 69 strings) Translated using Weblate (Hebrew) Currently translated at 55.0% (38 of 69 strings) Translated using Weblate (Czech) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 13.0% (9 of 69 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bengali (Bangladesh)) Currently translated at 5.8% (4 of 68 strings) Translated using Weblate (French) Currently translated at 89.7% (61 of 68 strings) Translated using Weblate (French) Currently translated at 89.7% (61 of 68 strings) Translated using Weblate (Bengali) Currently translated at 22.0% (15 of 68 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 73.5% (50 of 68 strings) Translated using Weblate (Russian) Currently translated at 30.8% (21 of 68 strings) Translated using Weblate (German) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 7.3% (5 of 68 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Czech) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Polish) Currently translated at 58.8% (40 of 68 strings) Translated using Weblate (Bengali) Currently translated at 88.7% (568 of 640 strings) Translated using Weblate (Malayalam) Currently translated at 90.7% (581 of 640 strings) Translated using Weblate (Interlingua) Currently translated at 33.5% (215 of 640 strings) Translated using Weblate (Croatian) Currently translated at 98.1% (628 of 640 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 93.9% (601 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Somali) Currently translated at 89.0% (570 of 640 strings) Translated using Weblate (German) Currently translated at 66.1% (45 of 68 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Armenian) Currently translated at 29.2% (187 of 640 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 99.5% (637 of 640 strings) Translated using Weblate (Urdu) Currently translated at 67.1% (430 of 640 strings) Translated using Weblate (Croatian) Currently translated at 97.5% (624 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 61.7% (42 of 68 strings) Translated using Weblate (Russian) Currently translated at 22.0% (15 of 68 strings) Translated using Weblate (Russian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 72.0% (49 of 68 strings) Translated using Weblate (Italian) Currently translated at 41.1% (28 of 68 strings) Translated using Weblate (Czech) Currently translated at 100.0% (68 of 68 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 99.6% (638 of 640 strings) Translated using Weblate (Sardinian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Galician) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bulgarian) Currently translated at 72.0% (461 of 640 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bengali (Bangladesh)) Currently translated at 64.6% (414 of 640 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Persian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Polish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Czech) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Greek) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Basque) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (German) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Slovak) Currently translated at 8.8% (6 of 68 strings) Translated using Weblate (Slovak) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 70.5% (48 of 68 strings) Translated using Weblate (Filipino) Currently translated at 36.8% (236 of 640 strings) Translated using Weblate (Bulgarian) Currently translated at 70.6% (452 of 640 strings) Translated using Weblate (Russian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 32.3% (22 of 68 strings) Translated using Weblate (Telugu) Currently translated at 65.6% (420 of 640 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 97.1% (622 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Japanese) Currently translated at 99.2% (635 of 640 strings) Co-authored-by: Agnieszka C Co-authored-by: Ajeje Brazorf Co-authored-by: Alex25820 Co-authored-by: Danial Behzadi Co-authored-by: Davit Mayilyan Co-authored-by: Edward Co-authored-by: Emin Tufan Çetin Co-authored-by: Eric Co-authored-by: Evghenii Botnari Co-authored-by: Fjuro Co-authored-by: Francisco Ruiz Co-authored-by: Giovanni Donisi Co-authored-by: GnuPGを使うべきだ Co-authored-by: Gontzal Manuel Pujana Onaindia Co-authored-by: Hin Weisner Co-authored-by: Hosted Weblate Co-authored-by: Igor Sorocean Co-authored-by: Ihor Hordiichuk Co-authored-by: JScocktail Co-authored-by: Jalaluddin Co-authored-by: Jeff Huang Co-authored-by: Josu Co-authored-by: Karl Tammik Co-authored-by: Laura Vasconcelos Pereira Felippe Co-authored-by: Lenn Art Co-authored-by: Linerly Co-authored-by: Louis V Co-authored-by: Marian Hanzel Co-authored-by: MatthieuPh Co-authored-by: Milo Ivir Co-authored-by: Mohammed Anas Co-authored-by: MΛX Co-authored-by: Nadir Nour Co-authored-by: Napstaguy04 Co-authored-by: Nizami Co-authored-by: Oymate Co-authored-by: Pieter van der Razemond Co-authored-by: Ray Co-authored-by: Rex_sa Co-authored-by: Ricardo Co-authored-by: S3aBreeze Co-authored-by: SC Co-authored-by: STV Co-authored-by: Samar Ali Co-authored-by: Santhosh J Co-authored-by: Software In Interlingua Co-authored-by: TXRdev Archive Co-authored-by: Tadeusz Dudek Co-authored-by: ThePlanetaryDroid Co-authored-by: Translator Co-authored-by: Vasilis K Co-authored-by: VfBFan Co-authored-by: Viktor Co-authored-by: WB Co-authored-by: Xəyyam Qocayev Co-authored-by: Yaron Shahrabani Co-authored-by: Zoldtukor Co-authored-by: chr56 Co-authored-by: i-am-SangWoo-Lee Co-authored-by: nautilusx Co-authored-by: pjammo Co-authored-by: rakijagamer-2003 Co-authored-by: remon-drk Co-authored-by: ssantos Co-authored-by: subba raidu Co-authored-by: yunna Co-authored-by: Симеон Цветков Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn_BD/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 11 +- app/src/main/res/values-az/strings.xml | 7 +- app/src/main/res/values-bg/strings.xml | 15 +- app/src/main/res/values-bn-rBD/strings.xml | 1 + app/src/main/res/values-bn/strings.xml | 2 + app/src/main/res/values-cs/strings.xml | 49 +- app/src/main/res/values-de/strings.xml | 9 +- app/src/main/res/values-el/strings.xml | 7 +- app/src/main/res/values-es/strings.xml | 12 +- app/src/main/res/values-et/strings.xml | 6 +- app/src/main/res/values-eu/strings.xml | 13 +- app/src/main/res/values-fa/strings.xml | 6 +- app/src/main/res/values-fil/strings.xml | 7 +- app/src/main/res/values-fr/strings.xml | 15 +- app/src/main/res/values-gl/strings.xml | 13 +- app/src/main/res/values-he/strings.xml | 6 +- app/src/main/res/values-hr/strings.xml | 494 +++++++++--------- app/src/main/res/values-hu/strings.xml | 41 +- app/src/main/res/values-hy/strings.xml | 3 + app/src/main/res/values-ia/strings.xml | 12 + app/src/main/res/values-in/strings.xml | 5 +- app/src/main/res/values-it/strings.xml | 7 +- app/src/main/res/values-ja/strings.xml | 83 +-- app/src/main/res/values-ml/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 8 + app/src/main/res/values-pl/strings.xml | 5 +- app/src/main/res/values-pt-rBR/strings.xml | 5 +- app/src/main/res/values-pt-rPT/strings.xml | 2 + app/src/main/res/values-pt/strings.xml | 6 +- app/src/main/res/values-ro/strings.xml | 13 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-sc/strings.xml | 6 +- app/src/main/res/values-sk/strings.xml | 8 + app/src/main/res/values-so/strings.xml | 4 +- app/src/main/res/values-sv/strings.xml | 9 +- app/src/main/res/values-te/strings.xml | 25 + app/src/main/res/values-tr/strings.xml | 37 +- app/src/main/res/values-uk/strings.xml | 5 +- app/src/main/res/values-ur/strings.xml | 6 +- app/src/main/res/values-vi/strings.xml | 95 ++-- app/src/main/res/values-zh-rCN/strings.xml | 6 +- app/src/main/res/values-zh-rHK/strings.xml | 61 ++- app/src/main/res/values-zh-rTW/strings.xml | 5 +- .../metadata/android/az/changelogs/988.txt | 2 + .../metadata/android/bn/changelogs/66.txt | 33 ++ .../metadata/android/bn/changelogs/730.txt | 4 +- .../metadata/android/bn/changelogs/770.txt | 2 +- .../metadata/android/bn/changelogs/956.txt | 2 +- .../metadata/android/bn/short_description.txt | 2 +- .../metadata/android/bn_BD/changelogs/63.txt | 8 + .../metadata/android/bn_BD/changelogs/64.txt | 8 + .../android/bn_BD/full_description.txt | 1 + .../android/bn_BD/short_description.txt | 1 + .../metadata/android/cs/changelogs/63.txt | 8 + .../metadata/android/cs/changelogs/64.txt | 8 + .../metadata/android/cs/changelogs/65.txt | 26 + .../metadata/android/cs/changelogs/66.txt | 16 + .../metadata/android/cs/changelogs/68.txt | 15 + .../metadata/android/cs/changelogs/69.txt | 9 + .../metadata/android/cs/changelogs/70.txt | 7 + .../metadata/android/cs/changelogs/71.txt | 7 + .../metadata/android/cs/changelogs/730.txt | 2 + .../metadata/android/cs/changelogs/740.txt | 23 + .../metadata/android/cs/changelogs/750.txt | 14 + .../metadata/android/cs/changelogs/760.txt | 14 + .../metadata/android/cs/changelogs/770.txt | 4 + .../metadata/android/cs/changelogs/780.txt | 11 + .../metadata/android/cs/changelogs/790.txt | 9 + .../metadata/android/cs/changelogs/800.txt | 10 + .../metadata/android/cs/changelogs/810.txt | 8 + .../metadata/android/cs/changelogs/820.txt | 1 + .../metadata/android/cs/changelogs/830.txt | 1 + .../metadata/android/cs/changelogs/840.txt | 8 + .../metadata/android/cs/changelogs/850.txt | 1 + .../metadata/android/cs/changelogs/860.txt | 7 + .../metadata/android/cs/changelogs/870.txt | 2 + .../metadata/android/cs/changelogs/900.txt | 13 + .../metadata/android/cs/changelogs/910.txt | 1 + .../metadata/android/cs/changelogs/920.txt | 9 + .../metadata/android/cs/changelogs/930.txt | 10 + .../metadata/android/cs/changelogs/940.txt | 9 + .../metadata/android/cs/changelogs/950.txt | 4 + .../metadata/android/cs/changelogs/951.txt | 6 + .../metadata/android/cs/changelogs/953.txt | 1 + .../metadata/android/cs/changelogs/954.txt | 6 + .../metadata/android/cs/changelogs/955.txt | 3 + .../metadata/android/cs/changelogs/956.txt | 1 + .../metadata/android/cs/changelogs/957.txt | 8 + .../metadata/android/cs/changelogs/958.txt | 15 + .../metadata/android/cs/changelogs/959.txt | 3 + .../metadata/android/cs/changelogs/960.txt | 4 + .../metadata/android/cs/changelogs/961.txt | 12 + .../metadata/android/cs/changelogs/963.txt | 1 + .../metadata/android/cs/changelogs/964.txt | 8 + .../metadata/android/cs/changelogs/965.txt | 6 + .../metadata/android/cs/changelogs/966.txt | 14 + .../metadata/android/cs/changelogs/967.txt | 1 + .../metadata/android/cs/changelogs/968.txt | 7 + .../metadata/android/cs/changelogs/969.txt | 8 + .../metadata/android/cs/changelogs/970.txt | 11 + .../metadata/android/cs/changelogs/971.txt | 3 + .../metadata/android/cs/changelogs/972.txt | 14 + .../metadata/android/cs/changelogs/973.txt | 4 + .../metadata/android/cs/changelogs/974.txt | 5 + .../metadata/android/cs/changelogs/975.txt | 17 + .../metadata/android/cs/changelogs/976.txt | 10 + .../metadata/android/cs/changelogs/977.txt | 10 + .../metadata/android/cs/changelogs/978.txt | 1 + .../metadata/android/cs/changelogs/979.txt | 2 + .../metadata/android/cs/changelogs/980.txt | 13 + .../metadata/android/cs/changelogs/981.txt | 2 + .../metadata/android/cs/changelogs/982.txt | 1 + .../metadata/android/cs/changelogs/983.txt | 9 + .../metadata/android/cs/changelogs/984.txt | 7 + .../metadata/android/cs/changelogs/985.txt | 1 + .../metadata/android/cs/changelogs/986.txt | 16 + .../metadata/android/cs/changelogs/987.txt | 12 + .../metadata/android/cs/changelogs/988.txt | 2 + .../metadata/android/cs/changelogs/989.txt | 3 + .../metadata/android/de/changelogs/987.txt | 2 +- .../metadata/android/fr/changelogs/63.txt | 4 +- .../metadata/android/fr/changelogs/65.txt | 26 + .../metadata/android/fr/changelogs/66.txt | 28 + .../metadata/android/fr/changelogs/68.txt | 31 ++ .../metadata/android/fr/changelogs/69.txt | 19 + .../metadata/android/fr/changelogs/70.txt | 25 + .../metadata/android/fr/changelogs/71.txt | 14 +- .../metadata/android/fr/changelogs/964.txt | 8 + .../metadata/android/fr/changelogs/966.txt | 14 + .../metadata/android/fr/changelogs/969.txt | 8 + .../metadata/android/fr/changelogs/970.txt | 11 + .../metadata/android/fr/changelogs/971.txt | 3 + .../metadata/android/fr/changelogs/973.txt | 4 + .../metadata/android/fr/changelogs/974.txt | 5 + .../metadata/android/fr/changelogs/977.txt | 8 + .../metadata/android/fr/changelogs/980.txt | 13 + .../metadata/android/fr/changelogs/987.txt | 6 +- .../metadata/android/fr/changelogs/988.txt | 2 + .../metadata/android/fr/short_description.txt | 2 +- .../metadata/android/he/changelogs/988.txt | 2 + .../metadata/android/hu/changelogs/65.txt | 26 + .../metadata/android/hu/short_description.txt | 2 +- .../metadata/android/it/changelogs/63.txt | 10 +- .../metadata/android/it/changelogs/730.txt | 2 + .../metadata/android/ko/changelogs/63.txt | 8 + .../metadata/android/ko/changelogs/64.txt | 8 + .../metadata/android/pl/changelogs/964.txt | 8 + .../metadata/android/pl/changelogs/965.txt | 5 + .../metadata/android/pt/changelogs/955.txt | 6 +- .../metadata/android/ru/changelogs/65.txt | 1 + .../metadata/android/ru/changelogs/66.txt | 1 + .../metadata/android/ru/changelogs/68.txt | 1 + .../metadata/android/ru/changelogs/69.txt | 1 + .../metadata/android/ru/changelogs/70.txt | 1 + .../metadata/android/ru/changelogs/780.txt | 12 + .../metadata/android/ru/changelogs/790.txt | 1 + .../metadata/android/ru/changelogs/985.txt | 1 + .../metadata/android/ru/changelogs/987.txt | 12 + .../metadata/android/sk/changelogs/987.txt | 12 + .../metadata/android/tr/changelogs/63.txt | 4 +- .../metadata/android/tr/full_description.txt | 3 +- .../metadata/android/uk/changelogs/988.txt | 2 + .../metadata/android/uk/changelogs/989.txt | 3 + .../android/zh-Hans/changelogs/988.txt | 2 + .../android/zh-Hans/changelogs/989.txt | 3 + .../android/zh-Hant/changelogs/988.txt | 2 + .../android/zh_Hant_HK/changelogs/988.txt | 2 + 167 files changed, 1613 insertions(+), 466 deletions(-) create mode 100644 fastlane/metadata/android/az/changelogs/988.txt create mode 100644 fastlane/metadata/android/bn/changelogs/66.txt create mode 100644 fastlane/metadata/android/bn_BD/changelogs/63.txt create mode 100644 fastlane/metadata/android/bn_BD/changelogs/64.txt create mode 100644 fastlane/metadata/android/bn_BD/full_description.txt create mode 100644 fastlane/metadata/android/bn_BD/short_description.txt create mode 100644 fastlane/metadata/android/cs/changelogs/63.txt create mode 100644 fastlane/metadata/android/cs/changelogs/64.txt create mode 100644 fastlane/metadata/android/cs/changelogs/65.txt create mode 100644 fastlane/metadata/android/cs/changelogs/66.txt create mode 100644 fastlane/metadata/android/cs/changelogs/68.txt create mode 100644 fastlane/metadata/android/cs/changelogs/69.txt create mode 100644 fastlane/metadata/android/cs/changelogs/70.txt create mode 100644 fastlane/metadata/android/cs/changelogs/71.txt create mode 100644 fastlane/metadata/android/cs/changelogs/730.txt create mode 100644 fastlane/metadata/android/cs/changelogs/740.txt create mode 100644 fastlane/metadata/android/cs/changelogs/750.txt create mode 100644 fastlane/metadata/android/cs/changelogs/760.txt create mode 100644 fastlane/metadata/android/cs/changelogs/770.txt create mode 100644 fastlane/metadata/android/cs/changelogs/780.txt create mode 100644 fastlane/metadata/android/cs/changelogs/790.txt create mode 100644 fastlane/metadata/android/cs/changelogs/800.txt create mode 100644 fastlane/metadata/android/cs/changelogs/810.txt create mode 100644 fastlane/metadata/android/cs/changelogs/820.txt create mode 100644 fastlane/metadata/android/cs/changelogs/830.txt create mode 100644 fastlane/metadata/android/cs/changelogs/840.txt create mode 100644 fastlane/metadata/android/cs/changelogs/850.txt create mode 100644 fastlane/metadata/android/cs/changelogs/860.txt create mode 100644 fastlane/metadata/android/cs/changelogs/870.txt create mode 100644 fastlane/metadata/android/cs/changelogs/900.txt create mode 100644 fastlane/metadata/android/cs/changelogs/910.txt create mode 100644 fastlane/metadata/android/cs/changelogs/920.txt create mode 100644 fastlane/metadata/android/cs/changelogs/930.txt create mode 100644 fastlane/metadata/android/cs/changelogs/940.txt create mode 100644 fastlane/metadata/android/cs/changelogs/950.txt create mode 100644 fastlane/metadata/android/cs/changelogs/951.txt create mode 100644 fastlane/metadata/android/cs/changelogs/953.txt create mode 100644 fastlane/metadata/android/cs/changelogs/954.txt create mode 100644 fastlane/metadata/android/cs/changelogs/955.txt create mode 100644 fastlane/metadata/android/cs/changelogs/956.txt create mode 100644 fastlane/metadata/android/cs/changelogs/957.txt create mode 100644 fastlane/metadata/android/cs/changelogs/958.txt create mode 100644 fastlane/metadata/android/cs/changelogs/959.txt create mode 100644 fastlane/metadata/android/cs/changelogs/960.txt create mode 100644 fastlane/metadata/android/cs/changelogs/961.txt create mode 100644 fastlane/metadata/android/cs/changelogs/963.txt create mode 100644 fastlane/metadata/android/cs/changelogs/964.txt create mode 100644 fastlane/metadata/android/cs/changelogs/965.txt create mode 100644 fastlane/metadata/android/cs/changelogs/966.txt create mode 100644 fastlane/metadata/android/cs/changelogs/967.txt create mode 100644 fastlane/metadata/android/cs/changelogs/968.txt create mode 100644 fastlane/metadata/android/cs/changelogs/969.txt create mode 100644 fastlane/metadata/android/cs/changelogs/970.txt create mode 100644 fastlane/metadata/android/cs/changelogs/971.txt create mode 100644 fastlane/metadata/android/cs/changelogs/972.txt create mode 100644 fastlane/metadata/android/cs/changelogs/973.txt create mode 100644 fastlane/metadata/android/cs/changelogs/974.txt create mode 100644 fastlane/metadata/android/cs/changelogs/975.txt create mode 100644 fastlane/metadata/android/cs/changelogs/976.txt create mode 100644 fastlane/metadata/android/cs/changelogs/977.txt create mode 100644 fastlane/metadata/android/cs/changelogs/978.txt create mode 100644 fastlane/metadata/android/cs/changelogs/979.txt create mode 100644 fastlane/metadata/android/cs/changelogs/980.txt create mode 100644 fastlane/metadata/android/cs/changelogs/981.txt create mode 100644 fastlane/metadata/android/cs/changelogs/982.txt create mode 100644 fastlane/metadata/android/cs/changelogs/983.txt create mode 100644 fastlane/metadata/android/cs/changelogs/984.txt create mode 100644 fastlane/metadata/android/cs/changelogs/985.txt create mode 100644 fastlane/metadata/android/cs/changelogs/986.txt create mode 100644 fastlane/metadata/android/cs/changelogs/987.txt create mode 100644 fastlane/metadata/android/cs/changelogs/988.txt create mode 100644 fastlane/metadata/android/cs/changelogs/989.txt create mode 100644 fastlane/metadata/android/fr/changelogs/65.txt create mode 100644 fastlane/metadata/android/fr/changelogs/66.txt create mode 100644 fastlane/metadata/android/fr/changelogs/68.txt create mode 100644 fastlane/metadata/android/fr/changelogs/69.txt create mode 100644 fastlane/metadata/android/fr/changelogs/70.txt create mode 100644 fastlane/metadata/android/fr/changelogs/964.txt create mode 100644 fastlane/metadata/android/fr/changelogs/966.txt create mode 100644 fastlane/metadata/android/fr/changelogs/969.txt create mode 100644 fastlane/metadata/android/fr/changelogs/970.txt create mode 100644 fastlane/metadata/android/fr/changelogs/971.txt create mode 100644 fastlane/metadata/android/fr/changelogs/973.txt create mode 100644 fastlane/metadata/android/fr/changelogs/974.txt create mode 100644 fastlane/metadata/android/fr/changelogs/977.txt create mode 100644 fastlane/metadata/android/fr/changelogs/980.txt create mode 100644 fastlane/metadata/android/fr/changelogs/988.txt create mode 100644 fastlane/metadata/android/he/changelogs/988.txt create mode 100644 fastlane/metadata/android/hu/changelogs/65.txt create mode 100644 fastlane/metadata/android/it/changelogs/730.txt create mode 100644 fastlane/metadata/android/ko/changelogs/63.txt create mode 100644 fastlane/metadata/android/ko/changelogs/64.txt create mode 100644 fastlane/metadata/android/pl/changelogs/964.txt create mode 100644 fastlane/metadata/android/pl/changelogs/965.txt create mode 100644 fastlane/metadata/android/ru/changelogs/65.txt create mode 100644 fastlane/metadata/android/ru/changelogs/66.txt create mode 100644 fastlane/metadata/android/ru/changelogs/68.txt create mode 100644 fastlane/metadata/android/ru/changelogs/69.txt create mode 100644 fastlane/metadata/android/ru/changelogs/70.txt create mode 100644 fastlane/metadata/android/ru/changelogs/780.txt create mode 100644 fastlane/metadata/android/ru/changelogs/790.txt create mode 100644 fastlane/metadata/android/ru/changelogs/985.txt create mode 100644 fastlane/metadata/android/ru/changelogs/987.txt create mode 100644 fastlane/metadata/android/sk/changelogs/987.txt create mode 100644 fastlane/metadata/android/uk/changelogs/988.txt create mode 100644 fastlane/metadata/android/uk/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/988.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/988.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 71d849a2f5b..700a612b8c6 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -200,7 +200,7 @@ © %1$sبواسطة%2$sتحت%3$s صفحة الكشك حدد كشك - ابدأ التشغيل في الخلفية + بدأ التشغيل في الخلفية المحتوى الإفتراضي حسب البلد الإنتقال إلى التشغيل في الخلفية الإنتقال إلى التشغيل في النافذة المنبثقة @@ -250,7 +250,7 @@ لا يمكن أن يكون اسم الملف فارغًا حدث خطأٌ ما: %1$s ملف مضغوط ZIP غير صالح - إزالة الفواصل المرجعية + إزالة الإشارة المرجعية تناسب مع الشاشة توليد تلقائي إستيراد @@ -600,8 +600,8 @@ زر الإجراء الثالث زر الإجراء الثاني زر الإجراء الأول - قياس الصورة المصغرة للفيديو المعروض في الإشعار من 16:9 إلى 1:1 نسبة العرض إلى الارتفاع (قد يؤدي إلى تشوهات) - مقياس الصورة المصغرة إلى نسبة عرض إلى ارتفاع 1:1 + قم بقص الصورة المصغرة للفيديو الموضحة في الإشعار من نسبة العرض إلى الارتفاع 16: 9 إلى 1: 1 + اقتصاص الصورة المصغرة إلى نسبة العرض إلى الارتفاع 1:1 امسح ملفات تعريف الارتباط التي يخزنها NewPipe عند حل reCAPTCHA تم مسح ملفات تعريف الارتباط reCAPTCHA امسح ملفات تعريف الارتباط reCAPTCHA @@ -684,7 +684,7 @@ جودة منخفضة (أصغر) جودة عالية (أكبر) معاينة مصغرة على شريط التمرير - علّمه كفيديو تمت مشاهدته + تعليم كفيديو تمت مشاهدته أُعجب بها منشئ المحتوى أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة إظهار مؤشرات الصور @@ -769,4 +769,5 @@ تنسيق غير معروف جودة غير معروفة حجم الفاصل الزمني لتحميل التشغيل + عرض مقاطع الفيديو المستقبلية \ No newline at end of file diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 2d9257e3c11..bd5948622a2 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -405,7 +405,7 @@ Sükut zamanı sürətlə irəlilə Yeni yayım bildirişləri Abunəliklərdən yeni yayımlar haqqında bildiriş göndər - Tezliyin yoxlanılması + Yoxlama tezliyi Tələb olunan şəbəkə bağlantısı İstənilən şəbəkə Tətbiq keçidində kiçildin @@ -496,7 +496,7 @@ Video oynadıcı Video fayl xülasəsi prosesi üçün bildirişlər Açın - Kiçik şəkili 1:1 aspekt nisbətinə ölçün + Miniatürü 1:1 aspekt nisbətinə kəsin Yükləmə intervalının həcmini dəyişdirin (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir. Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndürün Əlaqədar yayımı əlavə etməklə (təkrar etməyən) sonlanacaq oynatma sırasını davam etdir @@ -706,7 +706,7 @@ \nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın. Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil. İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə bilər və keçidlər kliklənməyə bilər. - Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər ölçün (pozuntulara səbəb ola bilər) + Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər kəsin Aşağıdakı bildiriş fəaliyyətini hər birinin üzərinə toxunaraq redaktə edin. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərilməsi üçün onlardan üçə qədərini seçin Belə fayl/məzmun mənbəyi yoxdur Seçilmiş yayım xarici oynadıcılar tərəfindən dəstəklənmir @@ -717,4 +717,5 @@ Naməlum format Naməlum keyfiyyət Oynatma yükləmə intervalı həcmi + Gələcək videoları göstərin \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index dc13a16c292..5ac2b1db8ef 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -446,7 +446,7 @@ Лиценз %s посочва следната причина: Маркери - Достъпност + Поверителност Език Вътрешен Включен @@ -553,4 +553,17 @@ Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта) Автоматична (тази на устройството) Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания) + Покажи гледани + Избете плейлист + Известия + Изчистване на бисквитките от reCAPTCHA + Бисквитките от reCAPTCHA бяха почистени + Проверяване за актуализации… + , + Провери за актуализации + Процент + Неизвестно качество + Неизвестен формат + Наскоро добавено + Буфериране \ No newline at end of file diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 02d35d384f1..1de4d3a5030 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -459,4 +459,5 @@ দেখা হিসেবে মার্ক করো ট্যাগসমূহ অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়) + অজানা ধরন \ No newline at end of file diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index a1acf9fd18b..6a634a3a4e8 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -622,4 +622,6 @@ বাহ্যিক প্লেয়ারের জন্য কোনো অডিও স্ট্রিম নেই বাহ্যিক প্লেয়ারের জন্য কোনো ভিডিও স্ট্রিম নেই অজ্ঞাত মান + নতুন ধারার বিজ্ঞপ্তি + নেটওয়ার্ক সংযোগ দরকার \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 2e840629ddd..8ab1e2584b7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -366,7 +366,7 @@ Vyčkávání Pozastaveno ve frontě - post-processing + zpracování Zařadit do fronty Akce odmítnuta systémem Stahování se nezdařilo @@ -384,7 +384,7 @@ Server neposílá data Server neakceptuje vícevláknové stahování, opakujte akci s @string/msg_threads = 1 Nenalezeno - Post-processing selhal + Zpracování selhalo Zastavit Maximální počet pokusů o opakování Maximální počet pokusů před zrušením stahování @@ -570,8 +570,8 @@ Třetí akční tlačítko Druhé akční tlačítko První akční tlačítko - Zmenšit miniaturu videa zobrazenou v oznámení z poměru stran 16: 9 na 1: 1 (může způsobit zkreslení) - Změnit poměr stran miniatury na 1:1 + Oříznout miniaturu videa zobrazenou v oznámení z poměru stran 16:9 na 1:1 + Oříznout poměr stran miniatury na 1:1 Ukázat memory leaks Zařazeno do fronty Zařadit do fronty @@ -584,10 +584,10 @@ Přibarvit oznámení Použít miniaturu pro pozadí zamknuté obrazovky a oznámení Zobrazit miniaturu - Oznámení o hašování videa + Oznámení o hashování Nedávné - Počítám haš - Oznámení o postupu hašování videa + Počítám hash + Oznámení o postupu hashování videa Vypnout, abyste skryli rámečky s meta informací s údaji o autorovi streamu, obsahu streamu nebo požadavků hledání Zobrazit meta informaci Kapitoly @@ -596,7 +596,7 @@ Zobrazit popis Otevřít s Na Vašem zařízení není aplikace, která to umí otevřít - Podobné strýmy + Podobné položky Vypnout pro skrytí popisu videa a doplňkové informace Zbořit aplikaci Stahování bylo zahájeno @@ -683,7 +683,7 @@ Vytvořit oznámení o chybě Kontrola aktualizací… Ukázat „Shodit přehrávač“ - Nové položky feedů + Nové položky Pro tuto akci nebyl nalezen žádný vhodný správce souborů. \nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework. Oznámení o hlášení chyb @@ -697,29 +697,38 @@ Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače. LeakCanary není dostupné Výchozí ExoPlayer - Nastavit oznámení k právě přehrávanému strýmu - Oznámení o nových strýmech - Oznámit o nových strýmech k objednání + Nastavit oznámení o právě přehrávaném streamu + Oznámení o nových streamech + Oznámit o nových strýmech od vašich odběrů Frekvence kontroly Jakákoli síť Nutné síťové připojení Smazat všechny stažené soubory z disku\? Objednali jste si nyní tento kanál Všechny přepnout - Nové strýmy - Oznámení o nových strýmech k objednání - Spustit kontrolu nových strýmů - Oznámení přehrávače + Nové streamy + Oznámení o nových streamech od odběrů + Spustit kontrolu nových streamů + Upozornění přehrávače Oznámení - Načítám podrobnosti o strýmu… + Načítám podrobnosti o streamu… Oznámení jsou vypnuta Přijímat oznámení , - %s nový strým - %s nové strýmy - %s nových strýmů + %s nový stream + %s nové streamy + %s nových streamů Procento Půltón + Velikost intervalu načtení přehrávání + Vybraný stream není podporován externími přehrávači + U externích přehrávačů nejsou dostupné žádné zvukové streamy + Neznámý formát + Neznámá kvalita + Zobrazit nadcházející videa + Streamy, které zatím nejsou podporovány systémem stahování, nebudou zobrazeny + Vyberte kvalitu pro externí přehrávače + U externích přehrávačů nejsou dostupné žádné video streamy \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4c8e9cfe1cf..5ff079f7a90 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,7 +1,7 @@ Veröffentlicht am %1$s - Kein Stream-Player gefunden. Möchtest du den VLC installieren\? + Kein Stream-Player gefunden. Möchtest du VLC installieren\? Installieren Abbrechen Im Browser öffnen @@ -161,7 +161,7 @@ Die meisten Sonderzeichen Wiedergabe fortsetzen Player - Nichts hier außer dem Zirpen der Grillen + Nichts hier, außer dem Zirpen der Grillen Möchtest du dieses Element aus dem Suchverlauf löschen\? Leere Seite Einen Kanal auswählen @@ -558,8 +558,8 @@ Dritte Aktionstaste Zweite Aktionstaste Erste Aktionstaste - Skaliert das in der Benachrichtigung angezeigte Vorschaubild von 16:9 auf ein 1:1 Seitenverhältnis (kann zu Verzerrungen führen) - Vorschaubild auf 1:1 Seitenverhältnis skalieren + Beschneidet das in der Benachrichtigung angezeigte Video-Vorschaubild von 16:9 auf ein 1:1 Seitenverhältnis + Vorschaubild auf 1:1 Seitenverhältnis zuschneiden Zufällig Puffern Wiederholen @@ -717,4 +717,5 @@ Streams, die noch nicht vom Downloader unterstützt werden, werden nicht angezeigt Der ausgewählte Stream wird von externen Playern nicht unterstützt Größe des Ladeintervalls für die Wiedergabe + Zukünftige Videos anzeigen \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d9239547ba4..b1b11e24d4e 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -498,8 +498,8 @@ Κουμπί τρίτης ενέργειας Κουμπί δεύτερης ενέργειας Κουμπί πρώτης ενέργειας - Κλιμάκωση της μικρογραφίας βίντεο που εμφανίζεται στην ειδοποίηση από 16:9 σε αναλογία διαστάσεων 1:1 (μπορεί να προκαλέσει παραμορφώσεις) - Κλιμάκωση μικρογραφίας σε αναλογία διαστάσεων 1:1 + Περικοπή της μικρογραφίας βίντεο που εμφανίζεται στην ειδοποίηση από 16:9 σε αναλογία διαστάσεων 1:1 + Περικοπή μικρογραφίας σε αναλογία διαστάσεων 1:1 Φόρτωση Πιστεύετε ότι η ροή φορτώνει πολύ αργά; Δοκιμάστε να ενεργοποιήσετε τη γρήγορη φόρτωση (από τις ρυθμίσεις ή πατώντας το παρακάτω κουμπί). \n @@ -717,4 +717,5 @@ Άγνωστος τύπος αρχείου Άγνωστη ποιότητα Μέγεθος διαστήματος φόρτωσης αναπαραγωγής - + Εμφάνιση μελλοντικών βίντεο + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 56d529b0484..edf4769e814 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -544,7 +544,7 @@ Copiar informe con formato Mostrando resultados para: %s Orden aleatorio - Escalar miniatura a relación de aspecto 1:1 + Recortar miniatura a relación de aspecto 1:1 Nunca Solo en Wi-Fi Comenzar reproducción automáticamente — %s @@ -558,13 +558,13 @@ Almacenar en memoria (búfer) Repetir ¡Puedes seleccionar como máximo tres acciones para mostrar en la notificación compacta! - Edita cada acción de notificación debajo pulsando sobre ella. Selecciona hasta tres de ellas para que aparezcan en la notificación compacta usando las casillas de verificación a la derecha + Edita cada una de las acciones en la notificación pulsando sobre ellas. Selecciona hasta tres de ellas para mostrarlas en la notificación compacta usando las casillas de verificación de la derecha. Botón de quinta acción Botón de cuarta acción Botón de tercera acción Botón de segunda acción Botón de primera acción - Escalar la relación de aspecto de la miniatura del vídeo mostrada en la notificación de 16:9 a 1:1 (puede ocasionar distorsiones) + Recortar la relación de aspecto de la miniatura del vídeo mostrada en la notificación de 16:9 a 1:1 Vaciar las cookies que NewPipe guarda al resolver un reCAPTCHA Mostrar contenido inapropiado para niños porque tiene un limite de edad (como 18+) Mostrar pérdidas de memoria @@ -574,7 +574,7 @@ Limpiar las cookies reCAPTCHA YouTube provee un «Modo restringido», el cual oculta contenido potencialmente solo apto para adultos Ajustar color de notificación - Permitir a Android personalizar el color de la notificación con el color principal de la imagen (ten en cuenta que esta opción no funciona en todos los dispositivos) + Permitir a Android personalizar el color de la notificación usando el color principal de la miniatura (ten en cuenta que esta opción no funciona en todos los dispositivos) Usar miniatura como fondo de pantalla de bloqueo y notificaciones Mostrar vista previa Desactivar para ocultar información adicional sobre el creador o contenido de la transmisión @@ -603,7 +603,7 @@ La descarga ha comenzado Resolver Puedes seleccionar tu tema nocturno favorito a continuación - Tema de Noche + Modo oscuro Selecciona tu tema nocturno favorito — %s Automático (tema del dispositivo) Mostrar detalles del canal @@ -719,4 +719,6 @@ Elija la calidad para reproductores externos Formato desconocido Calidad desconocida + Mostrar videos futuros + Tamaño del intervalo de carga de reproducción \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 11b5266b04c..2d776155c2d 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -426,8 +426,8 @@ Kolmas tegevusnupp Teine tegevusnupp Esimene tegevusnupp - Skaleeri teavituses kuvatav video pisipilt 16:9 külgede suhtest 1:1 suhtesse (võib põhjustada häireid) - Skaleeri pisipilt 1:1 küljesuhtesse + Kadreeri teavituses kuvatav video pisipilt 16:9 külgede suhtest 1:1 suhtesse + Kadreeri pisipilt 1:1 küljesuhtesse Arvutan räsi Hiljutised Kirjeldus @@ -716,4 +716,6 @@ Valitud meediavood ei ole toetatud välises pleieris Protsent Pooltoon + Taasesituseks vajalike andmete laadimise samm + Näita tulevasi videoid \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 8098db146dc..c2d92c517c3 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -562,8 +562,8 @@ Hirugarren ekintzaren botoia Bigarren ekintzaren botoia Lehenego ekintzaren botoia - Eskalatu jakinarazpenetan erakusten den bideo miniaturaren formatu-ratioa 16:9tik 1:1era (distortsioak sor ditzake) - Miniatura 1:1 formatu-ratiora eskalatu + Ebaki jakinarazpenetan erakusten den bideo miniaturaren formatu-ratioa 16:9tik 1:1era + Miniatura 1:1 formatu-ratiora ebaki %s bilaketaren erantzunak erakusten Ilaran jarri da Jarri ilaran @@ -709,4 +709,13 @@ Beharrezko sare konexioa Portzentaia Semitonoa + Erreprodukzioaren kargatze-tartearen tamaina + Deskargatzaileak onartzen ez dituen jarioak ez dira erakusten + Hautatutako jarioa ez dago kanpoko erreproduzigailu batengatik onartuta + Ez dago kanpoko erreproduzigailu batengatik onartuta dagoen audio jariorik + Ez dago kanpoko erreproduzigailu batengatik onartuta dagoen bideo jariorik + Formatu ezezaguna + Kalitate ezezaguna + Erakutsi etorkizuneko bideoak + Hautatu kanpoko erreproduzigailuen kalitatea \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 97fd534f12e..6b607b6ae50 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -557,8 +557,8 @@ سومین دکمه کنشی دومین دکمه کنشی اولین دکمه کنشی - تصویر بندانگشتی ویدیو که در اعلان نمایش می‌یابد، از نسبت ۱۶:۹ به ۱:۱ تغییر اندازه پیدا کند (ممکن است منجر به اعوجاج شود) - تغییر مقیاس تصویر بندانگشتی به نسبت ۱:۱ + تصویر بندانگشتی ویدیو که در اعلان نمایش می‌یابد، از نسبت ۱۶:۹ به ۱:۱ بریده می‌شود + برش تصویر بندانگشتی به نسبت ۱:۱ کیفیت پایین (کوچک‌تر) کیفیت بالا (بزرگ‌تر) نظرها از کار افتاده‌اند @@ -716,4 +716,6 @@ گزینش کیفیت برای پخش‌کننده‌های خارجی قالب ناشناخته کیفیت ناشناخته + اندازهٔ دورهٔ بار کردن پخش + نمایش ویدیوهای آینده \ No newline at end of file diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index d8c6682d64e..758e522cd5a 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -12,7 +12,7 @@ I-download I-download ang stream file Maghanap - Ayos ng App + Pagsasaayos \"%1$s\" ba ang tinutukoy mo\? Ibahagi sa Gumamit ng ibang video player @@ -30,7 +30,7 @@ Pumili ng Tab Anong Bago Likuran - Popup + Naka-lutang Idagdag sa Download folder ng mga video Pumili ng download folder para sa mga video file @@ -257,4 +257,7 @@ Notipikasyon ng player Mga track Mga gumagamit + Hangganan ng Edad + Oo, pati na rin ang mga napanood nang video + Kusa (tema ng device) \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e09f292b02f..4a79fbea9a2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,7 +6,7 @@ Télécharger Dossier de téléchargement vidéo Choisissez le dossier de téléchargement des vidéos - Les fichiers vidéo téléchargés sont stockées ici + Les vidéos téléchargées sont stockées ici Installer Installer l’application Kore manquante \? Aucun lecteur de flux trouvé. Installer VLC \? @@ -99,7 +99,7 @@ Seuls certains appareils peuvent lire des vidéos 2K/4K Format vidéo par défaut Mémoriser les propriétés de la fenêtre flottante - Mémorise les dernières taille et position de la fenêtre flottante + Mémoriser les dernières taille et position de la fenêtre flottante Effacer G Le son peut être absent à certaines définitions @@ -551,7 +551,7 @@ Impossible de reconnaitre l’URL fournie. Voulez-vous l’ouvrir avec une autre application \? Ajouter automatiquement à la liste de lecture La liste de lecture du lecteur actif sera remplacée - Confirmer avant de supprimer la liste de lecture + Confirmer avant de supprimer une liste de lecture Rien Chargement Lire aléatoirement @@ -563,8 +563,8 @@ Troisième bouton d’action Deuxième bouton d’action Premier bouton d’action - Mettre à l\'échelle la miniature de la vidéo affichée dans la notification du format 16:9 au format 1:1 (peut provoquer des déformations) - Redimensionner la miniature au format 1:1 + Recadrer la miniature de la vidéo affichée dans la notification du format 16:9 au format 1:1 + Recadrer la miniature au format 1:1 Afficher les fuites de mémoire Ajouté à la file d’attente Ajouter à la file d’attente @@ -663,8 +663,8 @@ Balayez un élément pour le supprimer Ne pas lancer les vidéos dans le mini lecteur mais directement en plein écran si la rotation automatique est verrouillée. Vous pouvez toujours accéder au mini-lecteur en quittant le mode plein écran Lancer le lecteur principal en plein écran - Ajouter à la liste de lecture - Suivant dans la liste de lecture + Lire consécutivement + Video placée après la lecture Traitement en cours… Veuillez patienter Vérifier manuellement de nouvelles versions Vérification des mises à jour… @@ -719,4 +719,5 @@ Le flux séléctionné n\'est pas supporté par les lecteurs externes Aucun flux vidéo n\'est disponible pour les lecteurs externes Taille de l\'intervalle de chargement de la lecture + Afficher les futures vidéos \ No newline at end of file diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 5cf8abcf0d4..701d1b8cc75 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -548,8 +548,8 @@ Terceiro botón de acción Cuarto botón de acción Quinto botón de acción - Escalar a miniatura do vídeo amosado na notificación da relación de aspecto 16:9 a 1:1 (pode intruducir distorsións) - Escala miniatura á relación de aspecto 1:1 + Cortar a miniatura do vídeo amosado na notificación da relación de aspecto 16:9 a 1:1 + Cortar miniatura á relación de aspecto 1:1 Apagado Modo tableta Abrir sitio Web @@ -709,4 +709,13 @@ As notificacións están desactivadas Recibir notificacións Agora está subscrito a esta canle + Emisións non soportadas polo descarregador non son mostradas + Seleccione a calidade para reprodutores externos + Formato descoñecido + Calidade descoñecida + Tamaño do intervalo de carregamento da reprodución + As emisións seleccionadas non son soportadas polos reprodutores externos + Non hai emisións de vídeo dispoñíbeis para reprodutores externos + Ver futuros vídeos + Non hai emisións de audio dispoñíbeis para reprodutores externos \ No newline at end of file diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index de5128493a2..b9056922a5c 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -580,8 +580,8 @@ כפתור פעולה שלישי כפתור פעולה ראשון כפתור פעולה שני - לשנות את יחס התצוגה הממוזערת שמופיעה בהתראות מיחס תצוגה של 16:9 ל־1:1 (עשוי לעוות את התמונה) - שינוי גודל התצוגה הממוזערת ליחס תצוגה 1:1 + חיתוך התצוגה הממוזערת שמופיעה בהתראות מיחס תצוגה של 16:9 ל־1:1 + חיתוך התצוגה הממוזערת ליחס תצוגה 1:1 הצגת דליפות זיכרון נוסף לתור הוספה לתור @@ -742,4 +742,6 @@ אין תזרימי וידאו שזמינים לנגנים חיצוניים בחירת איכות לנגנים חיצוניים תצורה לא מוכרת + גודל משך טעינת נגינה + הצגת סרטונים עתידיים \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index cb7db9f44c6..439835e06bd 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1,63 +1,63 @@ - Za početak dodirnite povećalo. + Počni dodirom na povećalo. Objavljeno %1$s - Reproduktor za stream nije pronađen. Instalirati VLC\? + Nije pronađen nijedan player streamova. Želiš li instalirati VLC\? Instaliraj Odustani Otvori u pregledniku Otvori skočni prozor - Podijeli + Dijeli Preuzimanje - Pretraživanje + Pretraga Postavke - Jeste li mislili „%1$s”\? - Podijeli pomoću - Koristi vanjski reproduktor videozapisa - Uklanja zvuk pri nekim rezolucijama - Koristi vanjski reproduktor za zvuk + Misliš li „%1$s”\? + Dijeli s + Koristi vanjski video player + Uklanja audiosnimku pri nekim rezolucijama + Koristi vanjski audio player Pretplati se Pretplaćeno - Pretplata na kanalu otkazana - Nije moguće promijeniti pretplatu - Nije moguće osvježiti pretplatu + Pretplata na kanal otkazana + Nije bilo moguće promijeniti pretplatu + Nije bilo moguće aktualizirati pretplatu Pretplate Što je novo Pozadina Skočni prozor Mapa za preuzimanje videozapisa - Preuzeti videozapisi spremaju se ovdje - Odaberi mapu za preuzimanje videozapisa - Mapa za preuzimanje zvuka - Preuzete audio-datoteke spremaju se ovdje - Odaberi mapu za preuzimanje audio-datoteka - Standardna rezolucija - Standardna rezolucija skočnog prozora - Prikaži više rezolucije - Samo neki uređaji podržavaju reprodukciju 2K/4K videozapisa + Preuzete video datoteke se spremaju ovdje + Odaberi mapu za preuzimanje video datoteka + Mapa za preuzimanje audiosnimaka + Preuzete datoteke audiosnimaka se spremaju ovdje + Odaberi mapu za preuzimanje datoteka audiosnimaka + Zadana rezolucija + Zadana rezolucija skočnog prozora + Prikaži veće rezolucije + Samo neki uređaji podržavaju reprodukciju 2K/4K videa Reproduciraj s Kodijem Instalirati nedostajući Kore program\? - Prikaži opciju \"Reproduciraj putem Kodija\" + Prikaži opciju „Reproduciraj pomoću Kodija” Prikaži opciju za reproduciranje videozapisa putem Kodija - Zvuk - Zadani format zvuka - Zadani format videozapisa + Audiosnimka + Zadani audio format + Zadani video format Tema Svijetla Tamna Crna Zapamti veličinu i poziciju skočnog prozora Zapamti posljednju veličinu i poziciju skočnog prozora - Prijedlozi pri traženju + Prijedlozi pretrage Odaberi prijedloge koji se prikazuju pri traženju Povijest pretraživanja - Svaku pretragu spremi lokalno - Pregledna Povijest - Spremaj povijest gledanja + Spremi pretrage lokalno + Povijest gledanja + Prati gledana videa Nastavi reprodukciju Nastavi reproducirati nakon prekidanja (npr. telefonski pozivi) Preuzmi - Prikaži \'Sljedeće\' i \'Slične\' videozapise + Prikaži videa „Sljedeći” i „Slični” URL nije podržan Zadani jezik sadržaja Video i audio @@ -76,11 +76,11 @@ Najbolja rezolucija Greška Greška u mreži - Nije moguće učitati sve ikone - Nije moguće dešifrirati URL potpis videozapisa - Nije moguće dohvatiti stranicu + Nije bilo moguće učitati sve sličice + Nije bilo moguće dešifrirati URL potpis videozapisa + Nije bilo moguće obraditi stranicu Sadržaj nije dostupan - Nije moguće postaviti izbornik za preuzimanje + Nije bilo moguće postaviti izbornik za preuzimanje Program/korisničko sučelje su preknuli raditi Oprosti, ovo se nije trebalo dogoditi. Prijavi pogrešku putem e-maila @@ -89,14 +89,14 @@ Informacije: Što se dogodilo: Što:\\nZahtjev:\\nJezik sadržaja:\\nZemlja sadržaja:\\nJezik programa:\\nUsluga:\\nGMT vrijeme:\\nPaket:\\nVerzija:\\nVerzija OS-a: - Vaš komentar (na engleskom): + Tvoj komentar (na engleskom): Detalji: Pokreni video, trajanje: - Profilna slika prenositelja - Goreglasovi - Doljeglasovi - Videozapis - Zvuk + Sličica avatara prenositelja + Sviđanja + Nesviđanja + Video + Audio Pokušaj ponovo tis. mil @@ -104,26 +104,26 @@ Počni Pauziraj Izbriši - Kontrolna suma + Kontrolni zbroj U redu Naziv datoteke - Niti + Komponente procesa Greška - NewPipe preuzima - Dodirni za detalje - Molimo pričekajte… + NewPipe preuzimanje + Dodirni za prikaz detalja + Pričekaj … Kopirano u međuspremnik - Molimo kasnije u postavkama odaberite mapu za preuzimanje + Odaberi mapu za preuzimanje kasnije u postavkama Ova dozvola je potrebna za \notvaranje skočnog prozora reCAPTCHA zadatak - Traži se reCAPTCHA zadatak + Zatražen je reCAPTCHA zadatak Preuzimanja Dozvoljeni znakovi u nazivima datoteka Nedozvoljeni znakovi su zamjenjeni ovima Znak za zamjenu Slova i brojevi - Posebni znakovi + Najviše posebnih znakova O NewPipeu Licence treće strane © %1$s od %2$s pod %3$s @@ -131,15 +131,15 @@ Licence Slobodan i mali YouTube program za Android. Pogledaj na GitHubu - Licenca za NewPipe + NewPipe licenca Ako imate ideja za prijevod, promjene u dizajnu, čišćenje koda ili neke veće promjene u kodu, pomoć je uvijek dobro došla. Što više radimo, to bolji postajemo! Pročitaj licencu Doprinos Povijest Povijest NewPipe obavijest - Obavijesti za NewPipe reproduktore - Reproduktor + Obavijesti za NewPipe playera + Player Ponašanje Povijest i predmemorija Poništi @@ -155,70 +155,70 @@ %s pregled %s pregleda - %s pregledi + %s pregleda - Nema videozapisa + Nema videa - %s videozapis - %s videozapisa - %s videozapisa + %s video + %s videa + %s videa Reproduciraj sve - Nije moguće reproducirati ovaj stream - Dogodila se neoporavljiva greška reproduktora - Oporavljanje od greške reproduktora + Nije bilo moguće reproducirati ovaj stream + Dogodila se neoporavljiva greška playera + Oporavljanje od greške playera Prikaži savjet za držanje - Prikaži savjet kad se pritisne gumb za pozadinsku ili skočni prozor u detaljima videa: - Želite li izbrisati ovu stavku iz povijesti pretraživanja? + Prikaži savjet kad se pritisne gumb za pozadinu ili skočni gumb u videu „Detalji:” + Želiš li izbrisati ovu stavku iz povijesti pretrage\? Sadržaj Prazna stranica - Kiosk stranica - Kanal - Odaberite kanal - Niste pretplaćeni na nijedan kanal - Odaberite kiosk + Stranica kioska + Stranica kanala + Odaberi kanal + Još nema pretplata na nijedan kanal + Odaberi jedan kiosk U trendu - Vrh 50 - Novo i popularno + 50 najboljih + Novi i popularni Ukloni Detalji - Postavke zvuka + Postavke za audiosnimke Drži pritisnuto za dodavanje u popis izvođenja [Nepoznato] Doniraj - Web stranica + Web-stranica Započni reprodukciju u pozadini Reproduciraj u skočnom prozoru Otvori ladicu Zatvori ladicu - Video reproduktor - Pozadinski reproduktor - Skočni reproduktor + Video player + Pozadinski player + Skočni player Uvjek pitaj Dohvaćanje podataka … - Učitava se odabrani sadržaj + Učitavanje traženog sadržaja Nova playlista Preimenuj Ime Dodaj u playlistu - Postavi kao minijaturu playliste + Postavi kao sličicu playliste Zabilježi playlistu Ukloni zabilješku Izbrisati ovu playlistu\? Playlista je stvorena - Dodano kao playlistu - Minijatura playliste se promijenila. + Dodano u playlistu + Sličica playliste je promijenjena. Bez titlova - Popuni - Ispuniti - Povećaj - Auto generirano + Prilagodi + Ispuni + Zumiraj + Automatski generirani Praćenje curenja memorije može uzrokovati greške u radu programa prilikom odlaganje gomile Izvijesti o krajevima životnog ciklusa Prikaži informacije - Zabilježeni popisi + Zabilježene playliste Dodaj u - Učitaj slike + Učitaj sličice Slikovna predmemorija obrisana Izbriši metapodatke iz predmemorije Kanali @@ -233,7 +233,7 @@ Prijeđi na glavni Uvezi bazu podataka Izvezi bazu podataka - Poništava vašu trenutačnu povijest, pretplate, playliste i (opcionalno) postavke + Poništava tvoju trenutačnu povijest, pretplate, playliste i (opcionalno) postavke Izvezi povijest, pretplate, playliste i postavke Izbriši povijest gledanja Briše povijest reproduciranih streamova i pozicije reprodukcije @@ -245,29 +245,29 @@ Nema takve mape Naziv datoteke ne može biti prazan Dogodila se greška: %1$s - Povucite za promjenu redoslijeda + Povuci za promjenu redoslijeda Stvori Odbaci Preimenuj 1 stavka izbrisana. Nijedan program nije instaliran za reprodukciju ove datoteke Vrati - Posjetite web stranicu NewPipe za više informacija i vijesti. - NewPipeova pravila o privatnosti - Pročitajte pravila o privatnosti + Posjeti NewPipe web-stranicu za više informacija i vijesti. + NewPipe pravila o privatnosti + Pročitaj pravila o privatnosti Zadnje svirano - Najviše svirano + Najviše reproducirano Izvezeno Uvezeno Nema važeće ZIP datoteke Upozorenje: Nije moguće uvesti sve datoteke. - Ovo će poništiti vaše trenutne postavke. - Želite li također uvesti postavke? + Ovo će prepisati tvoje trenutačne postavke. + Želiš li također uvesti postavke\? Uvoz Uvoz iz Izvoz u - Uvoz… - Izvoz… + Uvoz … + Izvoz … Uvoz datoteke Prethodni izvoz Nije bilo moguće uvesti pretplate @@ -277,14 +277,14 @@ \n1. Idi na ovaj URL: %1$s \n2. Prijavi se \n3. Pritisni „Uključeni svi podaci”, zatim „Poništi odabir svih”, a zatim odaberi samo „pretplate” i pritisni „U redu” -\n4. Pritisni na „Nastavi”, a zatim „Stvori izvoz” -\n5. Pritisni na „Preuzmi” -\n6. Dolje pritisni na UVEZI DATOEKU i odaberi .zip datoteku za peuzimanje -\n7. [Ako uvoz .zip datoteke ne uspije] Izdvoji .csv datoteku (pod \"YouTube and YouTube Music/subscriptions/subscriptions.json\"). Dolje pritisni UVEZI DATOTEKU i odaberi izdvojenu csv datoteku - vašID, soundcloud.com/vašID - Uzmite u obzir da ova operacija može uzrokovat veliku potrošnju prometa. +\n4. Pritisni „Sljedeći korak”, a zatim „Stvori izvoz” +\n5. Pritisni gumb „Preuzmi” nakon što se pojavi +\n6. Dolje pritisni UVEZI DATOEKU i odaberi preuzetu .zip datoteku +\n7. [Ako uvoz .zip datoteke ne uspije] izdvoji .csv datoteku (pod „YouTube and YouTube Music/subscriptions/subscriptions.json”), dolje pritisni UVEZI DATOTEKU i odaberi izdvojenu csv datoteku + tvojID, soundcloud.com/tvojid + Ova operacija može prouzročiti veliku potrošnju mrežnog prometa. \n -\nŽelite li nastaviti? +\nŽeliš li nastaviti\? Kontrole brzine reprodukcije Premotaj naprijed tijekom šutnje Korak @@ -294,19 +294,19 @@ Bez ograničenja Ograniči rezoluciju tijekom korištenja mobilnih podataka Nijedan - Reproduktor za stream nije pronađen (možeš instalirati VLC za reprodukciju). + Nije pronađen nijedan player streamova (možeš instalirati VLC za reprodukciju). Preuzmi datoteku streama Koristi brzo netočno premotavanje - Netočno premotavanje omogućava reproduktoru da premota brže uz manju točnost. Premotavanje od 5, 15 ili 25 sekundi s ovime nije moguće + Netočno premotavanje omogućuje playeru brže premotavanje uz manju točnost. Premotavanje od 5, 15 ili 25 sekundi s ovime ne radi Otkaži pretplatu Odaberi karticu - Ažuriranja + Aktualiziranja Događaji Datoteka obrisana Obavijest za nove NewPipe verzije Briše povijest ključnih riječi pretraživanja Vanjska pohrana nije dostupna - Ažuriranja + Aktualiziranja Prikaži obavijest i zatraži aktualiziranje programa kad je dostupna nova verzija Popis Popločeno @@ -314,51 +314,51 @@ Dodirni za preuzimanje Preuzimanje nije uspjelo Prikaži pogrešku - Isključi za sprječavanje učitavanja sličica, čime se štede podatci i memorija. Promjena postavke čisti predmemoriju u radnoj memoriji i u pohrani + Isključi za sprečavanje učitavanja sličica, čime se štedi korištenje podataka i memorije. Promjene čiste predmemoriju slika radne memorije i diska Izbriši sve podatke web-stranica iz predmemorije Metapodaci su izbrisani Automatski dodaj sljedeći stream u popisa izvođenja - Nastavi završavati (ne ponavljajući) popis izvođenja dodavanjem povezanog streama + Nastavi završavati (ne ponavljajući) popis reprodukcija dodavanjem povezanog streama Kontrola glasnoće pomoću gesti - Koristi geste za kontrolu glasnoće + Koristi geste za upravljanje glasnoćom playera Kontrola svjetline pomoću gesti - Koristi gesture za kontrolu svjetline + Koristi gesture za upravljanje svjetlinom playera Zadana zemlja sadržaja Otkrivanje grešaka Obavijest o novoj verziji programa Preuzimanje na vanjsku SD karticu nije moguće. Ponovo postaviti lokaciju mape za preuzimanje\? - Vanjski reproduktori ne podržavaju ove vrste veza + Vanjski playeri ne podržavaju ove vrste poveznica Nije pronađen nijedan videozapis - Nije pronađen nijedan audio zapis + Nije pronađena nijedna audiosnimka Nema takve datoteke/izvora sadržaja Datoteka ne postoji ili joj nedostaje dopuštenje za čitanje ili pisanje Nema dostupnih zapisa za preuzimanje - Neuspjelo čitanje spremljenih kartica, stoga se koriste zadane - Vratiti zadane - Želite li vratiti zadane postavke\? + Nije bilo moguće čitati spremljene kartice, stoga se koriste zadane + Obnovi standardne vrijednosti + Želiš li obnoviti standardne vrijednosti\? Broj pretplatnika nije dostupan - NewPipe razvijaju volonteri koji provode vrijeme donoseći vam najbolje iskustvo. Vratite im kako biste programerima učinili da NewPipe bude još bolji dok uživate u šalici kave. + NewPipe razvijaju volonteri koji provode vrijeme kako bi doprinijeli najboljem iskustvu. Doprinesi programerima kako bi poboljšali NewPipe dok uživaju u šalici kave. Koje su kartice prikazane na glavnoj stranici Konferencije - Željena radnja otvaranja streama + Željena radnja za otvaranje Zadana radnja pri otvaranju sadržaja — %s Titlovi - Promijeni veličinu podnaslova reproduktora i pozadinske stilove reproduktora. Za stupanje na snagu, program se mora ponovo pokrenuti - Prisilno izvješćivanje o greškama Rx-a koje se ne mogu isporučiti izvan \'fragmenta\' ili životnog ciklusa aktivnosti nakon odlaganja - Uvezite SoundCloud profil tako da upišete URL ili svoj ID: + Promijeni veličinu podnaslova i pozadinske stilove playera. Zahtijeva ponovno pokretanje programa + Prisilno izvijesti o neisporučivim Rx iznimaka izvan fragmenta ili životnog ciklusa aktivnosti nakon odlaganja + Uvezi SoundCloud profil upisom URL-a ili svog ID-a: \n -\n1. Omogućite \"način rada na radnoj površini\" u web-pregledniku (stranica nije dostupna na mobilnim uređajima) -\n2. Idite na ovaj URL: %1$s -\n3. Ulogirajte se -\n4. Kopirajte URL profila na koji ste preusmjereni. - brzina +\n1. Omogući „način rada na radnoj površini” u web-pregledniku (stranica nije dostupna na mobilnim uređajima) +\n2. Idi na ovaj URL: %1$s +\n3. Prijavi se +\n4. Kopiraj URL profila na koji te se preusmjerava. + Brzina Visina tona Odspoji (može prouzročiti izobličenje) - Sklopi prilikom mijenjanje programa - Radnja prilikom prebacivanja na drugi program iz glavnog video reproduktora – %s - Smanji na pozadinski reproduktor - Smanji na skočni reproduktor - Način prikaza popisa + Smanji prilikom mijenjanje programa + Radnja prilikom prebacivanja na drugi program iz glavnog video playera – %s + Smanji na pozadinski player + Smanji na skočni player + Način prikaza kao popis Automatski Gotovo Na čekanju @@ -367,18 +367,18 @@ naknadna obrada Popis izvođenja Sustav je odbio radnju - Generirajte jedinstveni naziv + Generiraj jedinstveni naziv Prepiši Datoteka s tim nazivom već postoji Preuzeta datoteka s tim nazivom već postoji Datoteka s ovim nazivom se već preuzima Odredišna mapa ne može biti stvorena Datoteka se ne može stvoriti - Nije moguće uspostaviti sigurnu vezu - Nije moguće pronaći server + Nije bilo moguće uspostaviti sigurnu vezu + Nije bilo moguće pronaći server Nije moguće povezati se s serverom Server ne šalje podatke - Poslužitelj ne prihvaća preuzimanja s više niti, pokušaj ponovo s @string/msg_threads = 1 + Poslužitelj ne prihvaća preuzimanja višestrukih procesa, pokušaj ponovo s @string/msg_threads = 1 Nije pronađeno Naknadna obrada nije uspjela Stop @@ -390,23 +390,23 @@ Isključi, kako bi se komentari sakrili Automatska reprodukcija Nema komentara - Komentare nije moguće učitati + Nije bilo moguće učitati komentare Zatvori - NewPipe je copyleft libre software: Može se koristiti, proučavati i poboljšavati po volji. Konkretno, može se redistribuirati i / ili modificirati pod uvjetima GNU opće javne licence koju je objavila Free Software Foundation, bilo licence verzije 3, ili (po vlastitom izboru) bilo koje kasnije verzije. + NewPipe je copyleft libre softver: Može se koristiti, proučavati i poboljšavati po volji. Konkretno, može se redistribuirati i / ili modificirati pod uvjetima GNU opće javne licence koju je objavila zaklada Free Software Foundation, pod verzijom 3 licence, ili (po vlastitom izboru) bilo koje kasnije verzije. Projekt NewPipe ozbiljno shvaća tvou privatnost. Stoga program ne prikuplja nikakve podatke bez tvog pristanka. \nNewPipe pravila o privatnosti detaljno objašnjavaju koji se podaci šalju i spremaju kad šalješ izvještaje o prekidu rada programa. - Kako bismo se uskladili s Europskom općom uredbom o zaštiti podataka (GDPR), upozoravamo vas na politiku privatnosti tvrtke NewPipe. Pažljivo ga pročitajte. -\nZa slanje izvješća o pogreškama potrebno je prihvatiti politiku privatnosti. + Kako bismo se uskladili s Europskom općom uredbom o zaštiti podataka (GDPR), ovime upozoravamo na NewPipe politiku privatnosti. Pažljivo je pročitaj. +\nZa slanje izvješća o pogreškama moraš prihvatiti politiku privatnosti. Nastavi reprodukciju - Vrati zadnji položaj reprodukcije + Obnovi zadnji položaj reprodukcije Pozicije na popisima - Prikaži poziciju reprodukcije na listi + Prikaži poziciju reprodukcije u popisima Obriši podatke Pozicije reprodukcije su izbrisane Datoteka je premještena ili izbrisana Datoteka s ovim nazivom već čeka na preuzimanje Vrijeme povezanosti je isteklo - Želite li očistiti povijest preuzimanja ili izbrisati sve preuzete datoteke\? + Želiš li izbrisati povijest preuzimanja ili izbrisati sve preuzete datoteke\? Započni preuzimanja Zaustavi preuzimanja Pitaj gdje preuzeti @@ -416,50 +416,50 @@ Nitko ne gleda Nitko ne sluša Jezik će se promijeniti nakon ponovnog pokretanja programa - Zadani Kiosk + Standardni kiosk Podržani su samo HTTP URL-ovi - Lokalno - Nedavno dodano - Autogenerirano (prenositelj nedefiniran) + Lokalni + Nedavno dodani + Automatski generirano (prenositelj nedefiniran) Očisti povijest preuzimanja Izbriši preuzete datoteke Dopusti prikaz iznad drugih programa Jezik programa Zadani sustav - Videozapisi - Isključi + Videa + Isključi zvuk Uključi Pomoć - Učitavanje feeda… - Želite li izbrisati ovu grupu\? - Novi - Uvijek ažuriraj + Učitavanje feeda … + Želiš li izbrisati ovu grupu\? + Nova + Uvijek aktualiziraj Omogući brz način Onemogući brz način Memorija uređaja je popunjena - Najomiljenije - Pritisnite \"Gotovo\" kad riješeno + Najomiljeniji + Pritisni „Gotovo” kad je riješeno Gotovo - ∞ videozapisa - 100+ videozapisa - Prijavite grešku na GitHub + ∞ videa + Više od 100 videa + Prijavi grešku na GitHub-u Umjetnici Albumi Pjesme - Napravio %s + Stvoren od %s Nikada Ograniči popis preuzimanja Koristi birač mapa sustava (SAF) Ukloni pregledano - Ukloni pogledane videozapise\? + Ukloni pogledana videa\? %d sekunda - %d sekundi + %d sekunde %d sekundi - %d minutu - %d minuta + %d minuta + %d minute %d minuta @@ -469,7 +469,7 @@ %d sat - %d sati + %d sata %d sati Nije učitano: %d @@ -481,63 +481,63 @@ Gumb druge radnje Gumb prve radnje Prikazuju se rezultati za: %s - Nije moguće prepoznati URL. Želiš li otvoriti s drugim programom\? - Smanjiti omjer minijatura na 1:1 + Nije bilo moguće prepoznati URL. Želiš li otvoriti s drugim programom\? + Odreži sličicu na omjer 1:1 Učitavanje u predmemoriju Istovremeno se pokreće jedno preuzimanje Dodano u popis izvođenja Dodaj u popis izvođenja - Popis izvođenja + Reproduciraj popis izvođenja Automatski popis izvođenja - Popis izvođenja aktivnog reproduktora će se zamijeniti - Prebacivanje s jednog reproduktora na drugi može zamijeniti popisa izvođenja + Popis izvođenja aktivnog playera će se zamijeniti + Prebacivanje s jednog playera na drugi može zamijeniti tvoj popis izvođenja Pitaj prije pražnjenja popisa izvođenja %s slušatelj %s slušatelja %s slušatelja - datoteka ne može biti prepisana - Promijeni omjer prikazane minijature videa u obavijesti iz 16:9 na 1:1 (može prouzročiti izobličenja) + datoteka se ne može prepisato + Odreži prikazane sličice videa u obavijesti iz omjera 16:9 na 1:1 U kompaktnom prikazu obavijesti mogu se odabrati najviše 3 radnje! Od %s - Minijatura avatara kanala + Sličica avatara kanala Dohvati iz određenog feeda kad je dostupno Vrijeme nakon zadnjeg aktualiziranja prije nego što se pretplata smatra zastarjelom – %s - Prag aktualiziranja feedova + Prag za aktualiziranje feedova Feed Prikaži samo negrupirane pretplate Prazno ime grupe - %d odabrani - %d odabrana + %d odabrana + %d odabrane %d odabranih - Obrada feeda … + Obrada feeda u tijeku … Zadnje aktualiziranje feeda: %s Grupe kanala - Da, i djelomično pogledane videozapise - Odaberi primjerak + Da, i djelomično pogledana videa + Odaberi jednu instancu Aplikacija će te pitati kamo spremati preuzimanja. \nOmogući birač mapa sustava (SAF), ako želiš preuzimati na vanjsku SD karticu Nije moguće obnoviti ovo preuzimanje Napredak je izgubljen, jer je datoteka izbrisana NewPipe se zatvorio tijekom rada s datotekom Stranica playliste - Videzapisi koji su gledani prije i nakon dodavanja u playlistu će se ukloniti. + Videa koji su gledani prije i nakon dodavanja u playlistu će se ukloniti. \nStvarno ih želiš ukloniti\? Ovo je nepovratna radnja! Još nema zabilježenih playlista Odaberi playlistu obnavljanje Samo na Wi-Fi mreži - Pokreni automatski – %s + Pokreni reprodukciju automatski – %s Prikaži curenje memorije %s gledatelj %s gledatelja %s gledatelja - Uklj/Isklj uslugu, trenutačno odabrana: + Uključi/isključi uslugu, trenutačno odabrana: Kopiraj formatirani izveštaj Izbriši kolačiće koje NewPipe sprema nakon rješavanja reCAPTCHA reCAPTCHA kolačići su izbrisani @@ -548,51 +548,51 @@ YouTube nudi postavku „Ograničeni način rada”, čime se skriva sadržaj za odrasle Uključi YouTube postavku „Ograničeni način rada” Prikaži sadržaj koji vjerojatno nije prikladan za djecu, jer je dobno ograničen (kategorija 18) - Primjerak već postoji - Neuspjela provjera primjerka - Upiši URL primjerka - Dodaj primjerak - Pronađi omiljene primjerke na %s - Odaberi tvoje omiljene PeerTube primjerke - PeerTube primjerci + Instanca već postoji + Nije bilo moguće provjeriti instancu + Upiši URL instance + Dodaj instancu + Pronađi instance koje voliš na %s + Odaberi svoje omiljene PeerTube instance + PeerTube instance Vrijeme premotavanja prema naprijed ili natrag Ništa Promiješaj Ponovi - Provjeri je li tvoj problem već postoji. Dupla pojava problema krade nam vrijeme koje bismo mogli utrošiti na ispravljanje same greške. + Provjeri je li problem već postoji. Prijavljivanje istog već prijavljenog problema krade nam vrijeme koje bismo mogli utrošiti na ispravljanje greške. Za uređivanje radnji u obavijestima, dodirni ih. Označi do tri radnje za prikaz u kompaktnoj obavijesti koristeći oznake na desnoj strani - Zbog ograničenja ExoPlayera, trajanje traženja postavljeno je na %d s - Neka Android prilagodi boju obavijesti prema glavnoj boji minijature (ovo nije dostupno na svim uređajima) + Zbog ograničenja ExoPlayera, trajanje premotavanja postavljeno je na %d s + Neka Android prilagodi boju obavijesti prema glavnoj boji sličice (ovo nije dostupno na svim uređajima) Oboji obavijest NewPipe još ne podržava ovaj sadržaj. \n \nNadamo se da će biti podržan u budućoj verziji. - Prikaži minijaturu kao pozadinu pri zaključanom ekranu i unutar obavijesti - Prikaži minijaturu + Koristi sličicu za pozadinu zaključanog ekrana i za obavijesti + Prikaži sličicu Prikaži izvorno vrijeme elemenata - „Okvir za pristup spremištu” omogućuje preuzimanje na SD karticu + „Storage Access Framework” omogućuje preuzimanje na SD karticu Izvorni tekstovi usluga bit će vidljivi u elementima prijenosa Dostupno je u nekim uslugama. Obično je puno brže, ali može dohvatiti ograničenu količinu stavki i često nepotpune podatke (npr. bez trajanja, vrste stavke, bez stanja uživo) - Mislite li da je učitavanje feeda prespor\? Ako je to slučaj, pokušajte omogućiti brzo učitavanje (možete ga promijeniti u postavkama ili pritiskom na donji gumb). + Misliš da je učitavanje feeda presporo\? Ako da, pokušaj omogućiti brzo učitavanje (možeš ga promijeniti u postavkama ili pritiskom na donji gumb). \n -\nNewPipe nudi dvije strategije ulaganja feeda: -\n• Dohvaćanje cijelog pretplatničkog kanala, koji je spor, ali cjelovit. -\n• Korištenje namjenske krajnje točke usluge, koja je brza, ali obično nije potpuna. +\nNewPipe nudi dvije strategije učitavanja feeda: +\n• Dohvaćanje cijelog pretplatničkog kanala, što je sporo, ali cjelovito. +\n• Korištenje namjenske krajnje točke usluge, što je brzo, ali obično nepotpuno. \n -\nRazlika je u tome što brzom obično nedostaju neke informacije, poput trajanja ili vrste stavke (ne može razlikovati videozapise uživo od uobičajenih), a možda će vratiti i manje predmeta. +\nRazlika je u tome što brzom načinu obično nedostaju neke informacije, poput trajanja ili vrste predmeta (ne može razlikovati videa uživo od običnih videa), a možda će vratiti i manje predmeta. \n \nYouTube je primjer usluge koja nudi ovaj brzi način sa svojim RSS feedom. \n -\nDakle, izbor se svodi na ono što više volite: brzinu ili precizne informacije. +\nDakle, izbor se svodi na ono što više voliš: brzinu ili precizne informacije. Izračunavanje šifriranja Obavijest šifriranja videa Obavijesti o napretku šifriranja videa Nedavni Isključi za skrivanje polja metapodataka s dodatnim podacima o autoru streama, sadržaju streama ili zahtjevu za pretraživanje Prikaži metapodatke - Slični videozapisi + Povezani predmeti Nijedan program na tvom uređaju ovo ne može otvoriti - Poglavlja videozapisa + Poglavlja Opis Komentari Isključi za skrivanje opisa videozapisa i dodatnih informacija @@ -620,12 +620,12 @@ Isklj. Uklj. Način rada na tabletu - Otvori stranicu - Srce autora + Otvori web-stranicu + Od autora obilježeno srcem Privatno Nenavedeno Javno - URL minijature + URL sličice Poslužitelj Podrška Jezik @@ -633,41 +633,41 @@ Licenca Oznake Kategorija - Onemogućite odabir teksta u opisu - Omogućite odabir teksta u opisu + Onemogući biranje teksta u opisu + Omogući biranje teksta u opisu Račun ukinut Prikaži pogledane stavke Autorov račun je ukinut. -\nNewPipe ubuduće neće moći učitavati ovaj feed. -\nŽelite li otkazati pretplatu na ovaj kanal\? - Nije moguće učitati feed za \'%s\'. +\nNewPipe ubuduće neće moći učitavati ovaj feed. +\nŽeliš li otkazati pretplatu na ovaj kanal\? + Nije bilo moguće učitati feed za „%s”. Pogreška pri učitavanju feeda - Počevši od Androida 10, podržan je samo \'Storage Access Framework\' + Počevši od Androida 10, podržano je samo radno okruženje „Storage Access Framework” Od vas će se tražiti gdje spremiti svako preuzimanje Ne prikazuj - Niska kvaliteta (manji) + Niska kvaliteta (manja) Visoka kvaliteta (veća) - Pregled sličica trake za pretraživanje - Mapa za preuzimanje još nije postavljena, odaberite zadanu mapu za preuzimanje + Pregled sličica premotavanja + Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje Komentari su onemogućeni Označi kao pogledano Način rada brzog feeda ne pruža više informacija o ovome. Interno Privatnost Sada možeš odabrati tekst u opisu. Napomena: stranica će možda treperiti i možda nećeš moći kliknuti poveznice u načinu rada za odabir teksta. - %s daje ovaj razlog: - Obrada... Pričekajte trenutak - Povucite stavke da biste ih uklonili + %s pruža ovaj razlog: + Obrada u tijeku … Može malo potrajati + Za ukljanjanje stavki povuci ih Prikazati indikatore slike - Dovršeno %s preuzimanje - Dovršena %s preuzimanja - Dovršeno %s preuzimanja + Preuzimanje je gotovo + %s preuzimanja su gotova + %s preuzimanja su gotova - Pokreni glavni reproduktor u cjeloekranskom prikazu - Reproduciraj sljedeće - U redu čekanja - Prikažite Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju + Pokreni glavni player u cjeloekranskom prikazu + Reproduciraj sljedeći + Sljedeći u popisu izvođenja + Prikaži Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju Izbrisano %1$s preuzimanje Izbrisana %1$s preuzimanja @@ -678,37 +678,59 @@ Traženje novih verzija … Prijedlozi lokalne pretrage Traži nove verzije - Nemoj pokretati videa u mini reproduktoru, već se izravno pokreni cjeloekranski prikaz, ako je automatsko okretanje zaključano. Mini reproduktoru i dalje možeš pristupiti izlaskom iz cjeloekranskog prikaza - Novi feedovi + Nemoj pokretati videa u mini playeru, već izravno pokreni cjeloekranski prikaz, ako je automatsko okretanje zaključano. Mini playeru i dalje možeš pristupiti napuštanjem cjeloekranskog prikaza + Novi elementi feeda Obavijest o prijavi greške Obavijesti za prijavu grešaka Stvori obavijest o grešci NewPipe je naišao na grešku, dodirni za prijavu Došlo je do greške, pogledaj obavijest Prekini rad playera - Obavijest reproduktora - Prilagođavanje obavijesti reproduktora + Obavijest playera + Konfiguriraj obavijest trenutačno reproduciranog streama Obavijesti Novi videozapisi - Obavijesti novih streamova pretplaćenih kanala - Želite li izbrisati sve preuzete datoteke\? + Obavijesti novih streamova od pretplaćenih kanala + Želiš li izbrisati sve preuzete datoteke\? Obavijesti su onemogućene - Pretplatili ste se ovome kanalu + Pretplatio/la si se na ovaj kanal , - Uključiti/isključiti sve + Uključi/isključi sve Bilo kakva mreža Obavijesti novih streamova pretplaćenih kanala - Pokaži zalogajnicu greške - Učitavanje pojedinosti streama… - Pokrenite provjeru novih streamova - Učestalost provjere - LeakCanary nije dostupno + Prikaži kratku poruku greške + Učitavanje pojedinosti streama … + Pokreni traženje novih streamova + Prvjeravanje učestalosti + Biblioteka „LeakCanary” nije dostupna Podešavanje visine tona po glazbenim polutonovima Obavijesti o novim streamovima Potrebna mrežna veza Zadano za ExoPlayer - Primite obavijesti - Za ovu radnju nije pronađen odgovarajući upravitelj datoteka. -\nMolimo vas da instalirate upravitelj za datoteke ili da pokušate onemogućiti \'%s\' u postavkama preuzimanja. + Primaj obavijesti + Za ovu radnju nije pronađen odgovarajući upravljač datoteka. +\nInstaliraj upravljač datoteka ili pokušaj onemogućiti „%s” u postavkama preuzimanja. Prikvačeni komentar + Prikazuje opciju prekida rada kad se player koristi + Prikaži „Prekini rad playera” + ExoPlayer standard + Posto + Poluton + Streamovi koje program za preuzimanje još ne podržava se ne prikazuju + Vanjski playeri ne podržavaju odabrani stream + Promijeni veličinu intervala učitavanja (trenutačno %s). Niža vrijednost može ubrzati početno učitavanje videa. Promjene zahtijevaju ponovno pokretanje playera. + + %s novi stream + %s nova streama + %s novih streamova + + Veličina intervala učitavanja reprodukcije + Nepoznat format + Nepoznata kvaliteta + Nijedan stream audiosnimaka nije dostupan za vanjske playere + Nijedan video stream nije dostupan za vanjske playere + Odaberi kvalitetu za vanjske playere + Prikaži buduća videa + Za ovu radnju nije pronađen odgovrajući upravljač datoteka. +\nInstaliraj „Storage Access Framework” kompatibilni upravljač datoteka. \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e2518398540..49b47237960 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -319,7 +319,7 @@ Eltüntetés Lejátszási lista könyvjelzőzése Egy hasonló videó hozzáadása a befejeződő (nem ismétlődő) lejátszási sorhoz - Sor + Sorba állítás a fájl nem írható felül Az előre- és visszatekerés időtartama Utolsó lejátszási pozíció visszaállítása @@ -362,11 +362,11 @@ Nincs talalat A kiszolgáló nem fogad többszálú letöltést, próbálkozzon újra ezzel: @string/msg_threads = 1 A kiszolgáló nem küld adatokat - Nem lehet csatlakozni a kiszolgálóhoz + A kiszolgáló szerver nem elérhető A kiszolgáló nem található Nem sikerült biztonságos kapcsolatot létesíteni A célmappa nem hozható létre - A fájlt nem lehet létrehozni + A fájlt nem sikerült létrehozni Hiba megjelenítése Ezzel a névvel egy letöltés már várakozik Ezzel a névvel egy letöltés már folyamatban van @@ -683,4 +683,39 @@ Rögzített megjegyzés LeakCanary nem elérhető + Lejátszó értesítés + Módosítsa a betöltési intervallum méretét (jelenleg %s). Az alacsonyabb érték felgyorsíthatja a videó kezdeti betöltését. A változtatásokhoz a lejátszó újraindítása szükséges. + Az aktuális lejátszás konfigurálása értesítés + Értesítések + Új élő közvetítések + Értesítések új élő közvetítésekről a feliratkozott csatornák esetén + Élő közvetítés betöltése.… + Keressen új élő közvetítést + Új élő közvetítés értesítések + Értesítésen új élő közvetítés esetén a feliratkozott csatornákhoz + Ellenőrzési gyakoriság + Szükséges hálózati kapcsolat + Bármilyen hálózat + Törli az összes letöltött fájlt a lemezről\? + Értesítsen + Értesítéstek kikapcsolva + Lejátszási intervallum mérete + Százaléka + + %s új elő közvetítés + %s új elő közvetítések + + ExoPlayer alapértelmezett + Feliratkoztál erre a csatornára + , + Azok az élő adások melyek nem támogatottak a letöltő által, rejtve vannak. + A választott élő adást nem lehet külső lejátszóval lejátszani. + Összes váltása + Külső lejátszók számára nem érhető el az hang csatorna + Külső lejátszók számára nem érhető el videó + Válassz minőséget külső lejátszókhoz + Ismeretlen formátum + Ismeretlen minőség + Félhang + Jövőbeli videók megjelenítése \ No newline at end of file diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index 10d397aef68..2188548413f 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -217,4 +217,7 @@ Հանրային Պիտակներ Ծանուցումները անջատված են + Ոչինչ բացի դատարկությունից + Կրկին փորձել + Հոսքի նորերը \ No newline at end of file diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index d5bc90e6f55..f4383defb7c 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -233,4 +233,16 @@ Solmente alicun apparatos pote reproducer videos 2K/4K Initiar le reproductor principal in schermo plen Solmente le URLs HTTPS es supportate + Repeter + Aleatori + Cargante fe + Privacitate + Licentia + Comenciava le discarga + Private + Aperir le sito web + Per %s + Monstrar le videos futur + Radio + Create per %s \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index e84fb393a53..d4d2db5a858 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -121,7 +121,7 @@ Terlepas apakah Anda memiliki ide untuk; terjemahan, perubahan desain, pembersihan kode, atau perubahan kode yang signifikan, segala bantuan akan selalu diterima. Semakin banyak akan semakin baik jadinya! Baca lisensi Kontribusi - Subscribe + Berlangganan Disubscribe Apa Yang Baru Lanjutkan pemutaran @@ -550,7 +550,7 @@ Tombol tindakan ketiga Tombol tindakan kedua Tombol tindakan pertama - Ubah ukuran thumbnail yang ditampilkan di notifikasi dari rasio aspek 16:9 ke 1:1 (mungkin terdistorsi) + Ubah ukuran thumbnail yang ditampilkan di notifikasi dari rasio aspek 16:9 ke 1:1 Ubah ukuran thumbnail ke rasio aspek 1:1 Tampilkan kebocoran memori Ditambahkan @@ -704,4 +704,5 @@ Pilih kualitas untuk pemain eksternal Format tidak diketahui Ukuran interval pemuatan playback + Tampilkan video mendatang \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 96d541d805e..c2d0a2c6fd4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -560,8 +560,8 @@ Casuale Niente Ripeti - Ridimensiona copertina alla proporzione 1:1 - Modifica la proporzione della copertina del video mostrata nella notifica da 16:9 a 1:1 (può introdurre distorsioni) + Ritaglia copertina con proporzione 1:1 + Ritaglia la copertina del video mostrata nella notifica, cambiando la proporzione da 16:9 a 1:1 Mostra memory leak Aggiunto alla coda Accoda @@ -716,5 +716,6 @@ Seleziona qualità per lettori esterni Qualità sconosciuta Formato sconosciuto - Dimensione dell\'intervallo di caricamento della riproduzione + Dimensione intervallo di caricamento della riproduzione + Mostra video futuri \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e65897de31b..223a5f21a34 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,7 +14,7 @@ 動画を保存するフォルダー ダウンロードした動画をここに保存します 動画ファイルをダウンロードするフォルダーを選択して下さい - デフォルトの解像度 + デフォルトの画質 Kodi で再生 Kore をインストールしますか? 「Kodi で再生」オプションを表示 @@ -24,7 +24,7 @@ ダウンロード 「次の動画」と「関連動画」を表示 対応していないURLです - デフォルトの言語 + デフォルトの言語設定 動画と音声 ビデオ再生、時間: 投稿者アイコンのサムネイル @@ -49,8 +49,8 @@ 保存メニューを設定できませんでした コンテンツ 年齢制限のあるコンテンツを表示 - 申し訳ありません。発生すべきでものではありませんでした。 - メールで不具合を報告 + 申し訳ありません。想定外のエラーが発生しました。 + 不具合をメールで報告 申し訳ありません、不具合が発生しました。 報告 情報: @@ -70,15 +70,15 @@ ファイル名 同時接続数 エラー - NewPipe ダウンロード中 + ダウンロード中 (NewPipe) タップして詳細を表示 お待ちください… クリップボードにコピーしました - 後ほど設定でダウンロードフォルダを定義してください + 後ほど、ダウンロードフォルダーを設定してください ダウンロード ダウンロード 不具合報告 - アプリ/UI がクラッシュしました + アプリ(UI)がクラッシュしました どんな問題:\\nリクエスト:\\nコンテンツ言語:\\nコンテンツ国:\\nアプリ言語:\\nサービス:\\nGMT 時間:\\nパッケージ:\\nバージョン:\\nOSバージョン: reCAPTCHA の要求 reCAPTCHA を要求しました @@ -93,18 +93,18 @@ ポップアップモードで再生中 無効 デフォルトの動画形式 - デフォルトのポップアップ解像度 - より高い解像度を表示 - 一部のデバイスのみ2K/4K動画を再生できます + デフォルトの画質 (ポップアップ表示) + より高い画質を表示 + 2K/4K動画は一部のデバイスでのみ再生できます バックグラウンド ポップアップ 消去 - ポップアップの属性を記憶 - ポップアップしたサイズと位置を記憶します + ポップアップの属性を記憶する + ポップアップのサイズと位置を記憶する 一部の解像度では音声がありません 検索候補の表示 検索時に表示する候補を選択します - 最高の解像度 + 最高 NewPipe について サードパーティー ライセンス © %1$s 作者 %2$s ライセンス %3$s @@ -125,8 +125,8 @@ 新着 検索履歴 検索履歴を記憶します - 視聴履歴 - 再生した履歴を記憶します + 再生履歴 + 再生履歴を記憶します 再生の再開 電話などによる中断の後、再生を再開します プレイヤー @@ -138,9 +138,9 @@ NewPipe の通知 [不明] 動画の再生ができませんでした - 回復不能な再生エラーが発生しました - 何も見つかりませんでした - チャンネル登録なし + 回復不能なエラーが発生しました + 一致する結果はありませんでした + チャンネル登録者なし 動画がありません 保存 ファイル名に使用可能な文字 @@ -178,7 +178,7 @@ データベースをエクスポート 既存の履歴、登録リスト、プレイリストおよび (任意) 設定は上書きされます 再生履歴、登録チャンネル一覧、プレイリストおよび設定をエクスポートします - 再生エラーからの回復中 + エラーから回復中です 外部プレイヤーは、これらのタイプのリンクをサポートしていません エクスポートしました インポートしました @@ -201,7 +201,7 @@ キャッシュを消去 アプリ内のキャッシュデータをすべて削除します キャッシュが消去されました - 次のを自動でキューに追加する + 次の動画を自動でキューに追加する デバッグ ファイル 動画が見つかりません @@ -255,7 +255,7 @@ プライバシーポリシーを確認 おおまかなシーク おおまかなシークを使用することで精度が下がる代わりに高速にシークができます。5 秒、15 秒または 25 秒間隔のシークはできません - すべてのサムネイルの読み込みと保存を無効化します。このオプションを切り替えるとメモリおよびディスク上の画像キャッシュが消去されます + サムネイルの読み込みと保存を無効化します。(このオプションを切り替えるとメモリとディスク上の画像キャッシュが消去されます) キューに関連動画を追加して再生を続ける (繰り返ししない場合) すべての再生履歴を削除しますか? すべての検索履歴を削除しますか? @@ -276,11 +276,11 @@ 最も再生された動画 拡大 プレイリスト - 「長押ししてキュー」のヒントを表示 + 「長押しでキューに追加」のヒントを表示 トラック NewPipe のプレイヤーの通知 新着と人気 - 長押ししてキューに追加 + 長押しでキューに追加 ポップアップで連続再生を開始 お好みの「開く」アクション コンテンツを開くときのデフォルト動作 — %s @@ -302,7 +302,7 @@ 同意する 拒否する 制限なし - モバイルデータ使用時の解像度の制限 + モバイルネットワーク使用時の画質 アプリ切り替え時の最小化 プレイヤーから他のアプリに切り替え時の動作 — %s 何もしない @@ -345,7 +345,7 @@ NewPipe のアップデートがあります! タップでダウンロード 完了 - 保留中 + 順番に処理中 一時停止 順番待ちに追加しました 保存処理をしています @@ -397,7 +397,7 @@ 再生位置を削除しました ファイルが移動または削除されました ファイルを上書きできません - この名前の保留中のダウンロードがあります + 同じファイル名のダウンロードが既に進行中です ファイルの作業中に NewPipe が閉じられました デバイスに空き容量がありません ファイルが削除されたため、進行状況が失われました @@ -428,7 +428,7 @@ %s 人が聴取中 アプリを再起動すると、言語が変更されます - 高速早送り/巻き戻し時間 + 高速早送り/巻き戻し間隔 PeerTube インスタンス PeerTube インスタンスを選択する あなたに最適なインスタンスを探す: %s @@ -531,7 +531,7 @@ プレイリスト ページ プレイリストを選択してください 自動的に再生を開始します — %s - 自動キュー + 自動でキューに追加 アクティブなプレイヤーのキューが入れ替わります プレイヤーを別のプレイヤーに切り替えるとキューが置き換わる可能性があります しない @@ -539,8 +539,8 @@ キューを再生 キューを消去する前に確認する URL を認識できませんでした。他のアプリで開きますか? - 通知に表示される動画サムネイルを 16:9 から 1:1 のアスペクト比にスケールします (ゆがみが生じるかもしれません) - サムネイルを 1:1 のアスペクト比にスケールする + 通知に表示されるサムネイルを 16:9 から正方形にします + サムネイルを正方形にする 以下をタップして通知のアクションを編集します。右側にあるチェックボックスを使用してコンパクトな通知に表示するものを 3 つまで選択します コンパクトな通知に表示されるアクションは 3 つまで選ぶことができます! 5 番目のアクションボタン @@ -569,8 +569,8 @@ 動画のハッシュ化通知 動画のハッシュ化進行状況の通知 コメント - 無効にするとビデオの概要と追加情報を非表示にします - 説明を表示 + 無効にすると動画の概要欄と追加情報を非表示にします + 概要欄を表示 最近 開く 説明 @@ -612,7 +612,7 @@ サムネイルの URL ウェブサイトを開く ダウンロードのたびに保存する場所を尋ねます - ダウンロードフォルダがまだ設定されていません。今すぐデフォルトのフォルダを選択してください + ダウンロードフォルダーがまだ設定されていません。今すぐデフォルトのフォルダーを選択してください Android 10 以降は \'Storage Access Framework\' のみがサポートされます 高速モードでこの情報の詳細は提供されません。 \'%s\' のフィードを読み込めませんでした。 @@ -633,13 +633,13 @@ 低品質 (小) 高品質 (大) シークバーのサムネイルプレビュー - コメントは無効です + コメントは無効になっています 視聴済みとしてマーク リモート検索候補 ローカル検索候補 アイテムをスワイプして削除 直接フルスクリーンモードに切り替えて、ミニプレイヤーで動画を開始しません。自動回転がロックされている場合でも、フルスクリーンを終了することでミニプレイヤーにアクセスできます - フルスクリーンでメインプレイヤーを開始 + プレイヤーをフルスクリーンで開始 %1$s つのダウンロードを削除しました @@ -660,7 +660,7 @@ 新しいフィードアイテム エラー報告通知 エラーが発生しました。通知をご覧ください - NewPipe はエラーに遭遇しました。タップして報告 + エラーが発生しました (タップすると報告できます) スナックバーにエラーを表示 固定されたコメント この動作に適切なファイルマネージャが見つかりませんでした。 @@ -691,9 +691,18 @@ 新しいストリーム 通知 現在再生しているストリームの通知を構成 - 読み込む間隔を変更します (現在 %s)。小さい値にすると初回読み込み時間が短くなります。変更にはプレイヤーの再起動が必要です。 + 読み込み間隔を変更します (現在 %s)。小さくすると再生開始までの時間が短くなります。変更を適用するには再起動が必要です。 必要なネットワークの種類 パーセント 半音 すべてのネットワーク + データの読み込み間隔 + 未知の形式 + 未知の品質 + サポートされてない動画は表示されていません + 選択された動画は外部プレイヤーではサポートされていません + 外部プレイヤーで利用できる音声情報がありません + 外部プレイヤーで利用できる映像情報がありません + 外部プレイヤーでの品質を選択 + 次の動画を表示する \ No newline at end of file diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 0e2cf696270..9e4d29cfefe 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -539,7 +539,7 @@ ഫോർമാറ്റുചെയ്‌ത റിപ്പോർട്ട് പകർത്തുക പ്ലേലിസ്റ്റ് പേജ് GitHub- ൽ റിപ്പോർട്ട് ചെയ്യുക - ലഘുചിത്രം 1: 1 വീക്ഷണാനുപാതത്തിലേക്ക് സ്കെയിൽ ചെയ്യുക + ലഘുചിത്രം 1: 1 വീക്ഷണാനുപാതത്തിലേക്ക് ക്രോപ് ചെയ്യുക വീഡിയോ കവർ ചിത്രത്തിന്റെ പ്രധാന നിറത്തിന് അനുസരിച്ചു നോട്ടിഫിക്കേഷന്റെ കളർ മാറ്റാൻ ആൻഡ്രോയ്ഡിനെ അനുവദിക്കുക (ഇത് എല്ലാം ഉപകരണങ്ങളിലും ലഭ്യമല്ല ) നോട്ടിഫിക്കേഷൻ വർണ്ണാഭമാകുക ഒന്നുമില്ല @@ -547,7 +547,7 @@ ആവർത്തിക്കുക കോം‌പാക്റ്റ് അറിയിപ്പിൽ‌ കാണിക്കുന്നതിന് നിങ്ങൾക്ക് പരമാവധി മൂന്ന് പ്രവർ‌ത്തനങ്ങൾ‌ തിരഞ്ഞെടുക്കാനാകും! ഇതിനോടൊപ്പം തുറക്കുക - നോട്ടിഫിക്കേഷനിൽ കാണിക്കുന്ന വീഡിയോ കവർ ചിത്രം 16:9 എന്ന അനുപാതത്തിൽ നിന്നും 1:1 ലേക്ക് മാറ്റാം (പ്രശ്നങ്ങൾ ഉണ്ടാവാൻ സാധ്യത ) + നോട്ടിഫിക്കേഷനിൽ കാണിക്കുന്ന വീഡിയോ കവർ ചിത്രം 16:9 എന്ന അനുപാതത്തിൽ നിന്നും 1:1 ലേക്ക് ക്രോപ് ചെയ്യുക ഡൗൺലോഡ് ആരംഭിച്ചു ചുവടെ നിങ്ങളുടെ പ്രിയപ്പെട്ട രാത്രി തീം തിരഞ്ഞെടുക്കാം നിങ്ങളുടെ പ്രിയപ്പെട്ട രാത്രി തീം തിരഞ്ഞെടുക്കുക — %s diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index fd80a7d547e..c6d7697c535 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -709,4 +709,12 @@ Je bent nu geabonneerd op dit kanaal Alle gedownloade bestanden van schijf wissen\? + De gekozen stream wordt niet ondersteund door externe spelers + Er zijn geen videostreams beschikbaar voor externe spelers + Onbekende kwaliteit + Streams die nog niet kunnen worden opgeslagen, worden niet getoond + Geen audiostreams beschikbaar voor externe spelers + Kies kwaliteit voor externe spelers + Onbekend bestandstype + Intervalgrootte tijdens afspelen \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 283c1f22333..a009023d55c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -575,8 +575,8 @@ Przycisk trzeciej akcji Przycisk drugiej akcji Przycisk pierwszej akcji - Skaluj miniaturę wideo wyświetlaną w powiadomieniu z proporcji 16:9 do 1:1 (może powodować zniekształcenia) - Skaluj miniaturę do proporcji 1:1 + Przycinaj miniaturę wideo wyświetlaną w powiadomieniu z proporcji 16:9 do 1:1 + Przycinaj miniaturę do proporcji 1:1 Dodano do kolejki Dodaj do kolejki Pokaż wycieki pamięci @@ -738,4 +738,5 @@ Wybierz jakość dla zewnętrznych odtwarzaczy Nieznany format Nieznana jakość + Pokaż przyszłe wideo \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0a4b2c2ad5a..9d19a858549 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -560,8 +560,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Dimensionar a miniatura do vídeo mostrada na notificação da proporção 16:9 para 1:1 (pode apresentar distorções) - Dimensionar a miniatura para a proporção de 1:1 + Cortar a miniatura do vídeo mostrada na notificação da proporção 16:9 para 1:1 + Cortar a miniatura para a proporção de 1:1 Mostrar vazamentos de memória Na fila Pôr na fila @@ -717,4 +717,5 @@ Formato desconhecido Qualidade desconhecida Tamanho do intervalo de carregamento da reprodução + Mostrar vídeos futuros \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 4f3559eeb87..1f6b755017f 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -716,4 +716,6 @@ Formato desconhecido Qualidade desconhecida Selecione a qualidade para reprodutores externos + Tamanho do intervalo de carregamento da reprodução + Mostrar vídeos futuros \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b1f57929824..86306377f69 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -544,8 +544,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 (pode haver distorções) - Ajustar miniatura à proporção de 1:1 + Cortar a miniatura de vídeo mostrada na notificação de 16:9 para 1:1 + Cortar miniatura na proporção 1:1 Iniciar reprodução automaticamente — %s Reproduzir fila Nunca @@ -716,4 +716,6 @@ Formato desconhecido Qualidade desconhecida A transmissão selecionada não é suportada por reprodutores externos + Mostrar vídeos futuros + Tamanho do intervalo de carregamento da reprodução \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 58a38ea6ed2..52c1ef5a31c 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -378,8 +378,8 @@ Al treilea buton de acțiune Al doilea buton de acțiune Primul buton de acțiune - Scalați miniatura video afișată în notificare de la raportul de aspect 16:9 la 1:1 (poate introduce distorsiuni) - Scalare miniatură la raport aspect 1:1 + Tăiați miniatura video afișată în notificare de la raportul de aspect 16:9 la 1:1 (poate introduce distorsiuni) + Tăiere miniatură la raportul de aspect 1:1 Se arată rezultate pentru:%s Nicio aplicație de pe dispozitivul dvs. nu poate deschide acesta Capitole @@ -722,4 +722,13 @@ V-ați abonat la acest canal Comutați toate , + Fluxurile care încă nu pot fi descărcate nu sunt afișate + Fluxul selectat nu este acceptat de playerele externe + Nu sunt disponibile fluxuri audio pentru playerele externe + Nu sunt disponibile fluxuri video pentru playerele externe + Selectați calitatea pentru playerele externe + Format necunoscut + Calitate necunoscută + Dimensiunea intervalului de încărcare de redare + Afișați videoclipurile din viitor \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c91368c2a70..50e0b4d4814 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -718,7 +718,7 @@ Закреплённый комментарий LeakCanary недоступна Стандартное значение ExoPlayer - Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. + Изменить размер предварительной загрузки (сейчас %s). Меньшее значение может ускорить загрузку видео. При изменении требуется перезапуск плеера. Загрузка деталей трансляции… Проверить на наличие новых трансляций Удалить все загруженные файлы\? @@ -733,4 +733,6 @@ Нет ни одного доступного аудио потока для внешних проигрывателей Выберите качество для внешних проигрывателей Неизвестное качество + Размер предварительной загрузки + Показывать будущие видео \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 81a71402e4c..d3a92b116e4 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -550,7 +550,7 @@ Pedi una cunfirma in antis de iscantzellare una lista Òrdine casuale Modìfica cada atzione de notìfica inoghe in suta incarchende·la. Ischerta·nde finas a tres de ammustrare in sa notìfica cumpata impreende sas casellas de controllu a destra - Iscala sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 (diat pòdere causare istorchimentos) + Sega sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 Nudda Carrighende Repite @@ -560,7 +560,7 @@ Su de tres butones de atzione Su de duos butones de atzione Su de unu butone de atzione - Pone in iscala sa miniadura in formadu 1:1 + Sega sa miniadura in formadu 1:1 Ammustra sas pèrdidas de memòria Annànghidu a sa lista Pone in lista @@ -716,4 +716,6 @@ Formadu disconnotu Calidade disconnota Su flussu seletzionadu no est galu suportadu dae letores esternos + Mannària de s\'intervallu de carrigamentu de sa riprodutzione + Ammustra sos vìdeos imbenientes \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4645fcfffa3..3d788843237 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -722,4 +722,12 @@ Poltón Nahrávanie podrobností streamu… Percent + Vybraný stream nie je podporovaný externými prehrávačmi + Žiadne audio streamy nie sú k dispozícií pre externé prehrávače + Vybrať kvalitu pre externé prehrávače + Neznámy formát + Interval medzipamäte + Streamy nepodporované sťahovačom sa nezobrazujú + Žiadne video streamy nie sú k dispozícií pre externé prehrávače + Neznáma kvalita \ No newline at end of file diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index d12b1e4d4f2..431ef57ad24 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -19,7 +19,7 @@ Batoonka hawsha sadexaad Batoonka hawsha labaad Batoonka hawsha koowaad - La ekaysii galka muuqaalka xaga ogaysiisyada ka muuqda cabirka 1:1 ayadoo laga soo baddalayo 16:9 (wuxuu keeni karaa shucaac) + La ekaysii galka muuqaalka xaga ogaysiisyada ka muuqda cabirka 1:1 ayadoo laga soo baddalayo 16:9 Galka la ekaysii cabirka 1:1 Soo bandhig istikhyaar ah in muuqaalka lagu furo xarunta madadaalada Kodi Soodhig istikhyaarka \"Ku fur Kodi\" @@ -649,4 +649,6 @@ Tus tilmaamayaasha sawirka Soojeedinada raadinta banaanka Soojeedinada raadinta gudaha + Cabirka soodaarida udhexeeya + Jabi Daareha \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index edcc13b08c2..daf2a448519 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -524,9 +524,9 @@ Skapad av %s Ingen spellista har bokmärkts än Visa endast prenumerationer som inte grupperats - Skala miniatyrbild till 1: 1 bildförhållande + Beskär miniatyrbild till 1: 1 bildförhållande Endast över Wi-Fi - Skala videominiatyrbilden som visas i aviseringen från 16:9- till 1:1-förhållande (kan orsaka bildförvrängning) + Beskär videominiatyrbilden som visas i aviseringen från 16:9 till 1:1 bildförhållande Starta uppspelning automatiskt — %s Uppspelningskö Kunde inte känna igen URL:en. Vill du öppna med annan app\? @@ -713,4 +713,9 @@ Okänd kvalitet Inga ljudströmmar tillgängliga för externa spelare Okänt format + Videoströmmar som ännu inte stöds av nedladdaren visas inte + Inläsningsintervalls storlek + Välj kvalitet för externa spelare + Visa framtida videor + Den valda videoströmmen stöds inte av externa spelare \ No newline at end of file diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 3958e02b005..22ad6fbf5df 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -425,4 +425,29 @@ స్వయంచాలకంగా రూపొందించబడింది (ఎక్కించినవారు కనబడుటలేదు) స్వయంచాలకంగా రూపొందించబడింది శీర్షికలు + సభ్యత్వాల కోసం కొత్త స్ట్రీమ్‌ల గురించి నోటిఫికేషన్‌లు + స్థితి పునరుద్ధరణ + లయ + కొత్త స్ట్రీమ్స్ + + %s కొత్త స్ట్రీమ్ + %s కొత్త స్ట్రీమ్స్ + + స్ట్రీమ్ వివరాలను లోడ్ చేస్తోంది… + మెమరీ లీక్‌లను చూపించు + జీవితచక్రం లేని లోపాలను నివేదించండి + ఈ చర్య నెట్‌వర్క్ ఖరీదైనదని గుర్తుంచుకోండి. +\n +\nమీరు కొనసాగించాలనుకుంటున్నారా\? + శాతం + అర్ధరాగం + ప్లేబ్యాక్ లోడ్ విరామం పరిమాణం + వస్తువులపై అసలు క్రిత సమయాన్ని చూపుము + పారవేయడం తర్వాత ఫ్రాగ్మెంట్ లేదా యాక్టివిటీ లైఫ్‌సైకిల్ వెలుపల బట్వాడా చేయలేని Rx మినహాయింపులను బలవంతంగా నివేదించడం + మెమరీ లీక్ మానిటరింగ్ హీప్ డంపింగ్ చేసినప్పుడు యాప్ స్పందించక పోవడానికి కారణం కావచ్చు + శృతి + అన్‌హుక్ (వక్రీకరణకు కారణం కావచ్చు) + అడుగు + నిశ్శబ్ద సమయంలో వేగంగా ముందుకు వెళ్లుము + ప్లేబ్యాక్ స్పీడ్ నియంత్రణలు \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0986fd74b48..072b9219377 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,8 +1,8 @@ - Başlamak için büyütece dokunun. + Başlamak için büyütece dokun. %1$s tarihinde yayınlandı - Akış oynatıcısı bulunamadı. VLC yüklensin mi\? + Akış oynatıcısı bulunamadı. VLC kurulsun mu\? Yükle İptal Tarayıcıda aç @@ -13,14 +13,14 @@ Bunu mu demek istediniz: \"%1$s\"\? Şununla paylaş Dış video oynatıcı kullan - Dış ses oynatıcı kullanın + Dış ses oynatıcı kullan Video indirme klasörü İndirilen video dosyaları burada depolanır Video dosyaları için indirme klasörünü seç Ses indirme klasörü İndirilen ses dosyaları burada depolanır Ses dosyaları için indirme klasörünü seç - Varsayılan çözünürlük + Öntanımlı çözünürlük Kodi ile oynat Eksik Kore uygulaması yüklensin mi\? \"Kodi ile oynat\" seçeneğini göster @@ -103,7 +103,7 @@ Açılan pencerenin son boyutunu ve konumunu hatırla Bazı çözünürlüklerde sesi kaldırır Arama önerileri - Arama yaparken gösterilecek önerileri seçin + Ararken gösterilecek önerileri seç En iyi çözünürlük NewPipe Hakkında Üçüncü Taraf Lisansları @@ -130,7 +130,7 @@ Abonelikler Yenilikler Arama geçmişi - Arama sorgularını yerel olarak saklayın + Arama sorgularını yerel olarak sakla İzleme geçmişi İzlenen videoların kaydını tut Oynatmayı sürdür @@ -191,7 +191,7 @@ Ana Görünüme Geç Çekmeceyi Aç Çekmeceyi Kapat - Akış oynatıcı bulunamadı (Oynatmak için VLC yükleyebilirsiniz). + Akış oynatıcı bulunamadı (Oynatmak için VLC kurabilirsiniz). Her Zaman Yalnızca Bir Kez Dış oynatıcılar bu tür bağlantıları desteklemez @@ -560,8 +560,8 @@ Üçüncü eylem düğmesi İkinci eylem düğmesi Birinci eylem düğmesi - Bildirimde gösterilen video küçük resmini 16:9\'dan 1:1 en/boy oranına ölçeklendir (bozulmalara neden olabilir) - Küçük resmi 1:1 en/boy oranına ölçeklendir + Bildirimde gösterilen video küçük resmini 16:9\'dan 1:1 en boy oranına kırp + Küçük resmi 1:1 en boy oranına kırp Bellek sızıntılarını göster Sıraya eklendi Kuyruğa ekle @@ -578,7 +578,7 @@ Video dosya özetleme süreci için bildirimler Video dosya özeti bildirimi En Son - Akış oluşturucu, akış içeriği veya bir arama isteği hakkında ek bilgiler içeren meta bilgi kutularını gizlemek için kapatın + Akış oluşturucu, akış içeriği veya arama isteğiyle ilgili ek bilgiler içeren üst veri bilgi kutularını gizlemek için kapat Üst bilgiyi göster Bölümler Açıklama @@ -693,7 +693,7 @@ Yeni akışları denetlemeyi çalıştır Oynatıcı bildirimi - Oynatılan akış bildirimini yapılandırın + Oynatılan akış bildirimini yapılandır Yeni akışlar Akış ayrıntıları yükleniyor… Abonelikler için yeni akışlarla ilgili bildirimler @@ -701,7 +701,7 @@ Bildirimler devre dışı Bildirim alın Artık bu kanala abone oldunuz - Aboneliklerden yeni akışlar hakkında bildirim gönder + Aboneliklerden yeni akışlarla ilgili bildirim gönder Denetleme sıklığı Herhangi bir ağ İndirilen tüm dosyalar diskten silinsin mi\? @@ -709,12 +709,13 @@ Tümünü değiştir Yüzde Ara ton - Seçilen yayın harici oynatıcılar tarafından desteklenmiyor - İndirici tarafından henüz desteklenmeyen yayınlar gösterilmez - Harici oynatıcılar için ses yayını yok - Harici oynatıcılar için video yayını yok - Harici oynatıcılar için kalite seçin + Seçilen akış dış oynatıcılarca desteklenmiyor + İndiricice henüz desteklenmeyen akışlar gösterilmez + Dış oynatıcılar için ses akışı yok + Dış oynatıcılar için video akışı yok + Dış oynatıcılar için nitelik seç Bilinmeyen biçim - Bilinmeyen kalite + Bilinmeyen nitelik Oynatma yükleme aralığı boyutu + Gelecekteki videoları göster \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 736a49c8e6c..835c87f378a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -558,7 +558,7 @@ Кнопка третьої дії Кнопка другої дії Кнопка першої дії - Збільшити ескіз до масштабу 1:1 + Обрізати ескіз до пропорцій 1:1 Відкрити через Завантаження почалося Виберіть нічну тему — %s @@ -616,7 +616,7 @@ Кольорове сповіщення У компактному сповіщенні є не більше трьох дій! Дії можна змінити, натиснувши на них. Позначте не більше трьох для показу в компактному сповіщенні - Масштабувати мініатюру відео 16: 9 до 1:1 (можливі спотворення) + Обрізати мініатюру відео показувану в сповіщенні з пропорцій 16: 9 до 1:1 Вимкнення тунелювання медіаданих за наявності чорного екрана або гальмування під час відтворення відео Вимкнути тунелювання медіа «Фреймворк доступу до сховища» (SAF) підтримується лише починаючи з Android 10 @@ -734,4 +734,5 @@ Виберіть якість для зовнішніх програвачів Невідома якість Розмір інтервалу завантаження відтворення + Показати наступні відео \ No newline at end of file diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index b58f0946dae..2c35822099c 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -1,6 +1,6 @@ - شروع کرنے کے لیے \"تلاش\" پر ٹیپ کریں + شروع کرنے کے لیے \"کلاں نما شیشہ\" پر ٹیپ کریں %1$s کو شائع ہوا انسٹال منسوخ کریں @@ -494,8 +494,10 @@ تیسرا ایکشن بٹن دوسرا ایکشن بٹن پہلا ایکشن بٹن - نوٹیفیکیشن میں دکھائے جانے والے ویڈیو تھمب نیل کو 16: 9 سے 1:1 پہلو تناسب (شاید بگاڑ پیدا ہوسکتا ہے) میں اسکیل کریں + نوٹیفیکیشن میں دکھائے جانے والے ویڈیو تھمب نیل کو 16: 9 سے 1:1 پہلو تناسب میں اسکیل کریں تھمب نیل کو 1:1 کی تناسب میں رکھیں %s کے لئے نتائج دکھا رہا ہے کے ساتھ کھولیں + ویڈیو پلیئر کو کریش کریں + دیکھے ہوئے کو نشان لگائیں \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 2346a4cf9d1..3a933ef98d5 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -67,13 +67,13 @@ Nội dung không khả dụng Không thể thiết lập menu tải về Ứng dụng / Giao diện người dùng bị lỗi - Ai Da, NewPipe đã gặp lỗi. Tôi lấy làm tiếc cho bạn. + :( Lmao, app đã xảy ra lỗi. Hãy lướt xuống dưới để xem lỗi. Báo lỗi qua email Xin lỗi, đã xảy ra sự cố. Báo cáo Thông tin: Chuyện gì đã xảy ra: - Cái gì:\\nYêu cầu:\\nNgôn ngữ của nội dung:\\nVùng miền (quốc gia) của nội dung:\\nNgôn ngữ của ứng dụng:\\nDịch vụ:\\nThời gian GMT:\\nTên gói:\\nPhiên bản:\\nPhiên bản hệ điều hành: + Loại lỗi:\\nYêu cầu:\\nNgôn ngữ của nội dung:\\nVùng miền (quốc gia) của nội dung:\\nNgôn ngữ của ứng dụng:\\nDịch vụ:\\nThời gian GMT:\\nTên gói:\\nPhiên bản:\\nPhiên bản hệ điều hành: Nhận xét của bạn (bằng tiếng Anh): Chi tiết: Xem video, thời lượng: @@ -83,7 +83,7 @@ Video Âm thanh Thử lại - ngàn + nghìn triệu tỉ Bắt đầu @@ -96,7 +96,7 @@ Lỗi NewPipe đang tải xuống Chạm để biết chi tiết - Xin hãy đợi… + Đợi chút xíu nha… Đã sao chép vào clipboard Hãy chọn một thư mục để tải xuống trong phần cài đặt Chế độ popup cần quyền này @@ -108,10 +108,10 @@ © %1$s bởi %2$s dưới %3$s Thông tin Giấy phép - Trình phát nội dung nhẹ và mã nguồn mở cho Android. + Trình phát video nhẹ và mã nguồn mở cho Android. Xem trên GitHub Giấy phép của NewPipe - Sự đóng góp luôn được hoan nghênh – cho dù bạn dịch, có ý tưởng thiết kế, dọn code, hay thay đổi rất nhiều phần code. Càng làm nhiều thì ứng dụng này sẽ càng tốt! + Sự đóng góp của bạn luôn được hoan nghênh – kể cả khi bạn dịch, thay đổi giao diện, dọn code hay thay đổi những thứ khác, sự giúp đỡ của bạn vẫn đáng được trân trọng. Bạn càng làm nhiều, ứng dụng này sẽ càng tốt hơn bao giờ hết (Miễn đừng dịch vớ vẩn là được, nhé :]] )! Đọc giấy phép Đóng góp Ngôn ngữ nội dung ưu tiên @@ -169,7 +169,7 @@ Chuyển sang Main Nhập cơ sở dữ liệu Xuất cơ sở dữ liệu - Ghi đè lịch sử, danh sách đăng ký, playlist (và cài đặt, nếu có chọn) hiện tại của bạn + Ghi đè lịch sử, kênh đăng ký, playlist hiện tại (và cài đặt, nếu có) của bạn Xuất lịch sử, danh sách đăng ký, playlist và cài đặt Xóa lịch sử xem Xóa lịch sử các luồng đã phát và vị trí phát @@ -179,7 +179,7 @@ Xóa lịch sử của từ khóa tìm kiếm Xóa toàn bộ lịch sử tìm kiếm\? Đã xóa lịch sử tìm kiếm - Không thể phát luồng này + Không thể phát video này Đã xảy ra lỗi trình phát không thể khôi phục Phục hồi lại trình phát bị lỗi Trình phát ngoài không hỗ trợ các loại liên kết này @@ -191,7 +191,7 @@ Tên tệp không được để trống Đã xảy ra lỗi: %1$s Không có luồng nào để tải về - Không có gì ở đây + Không có gì cả Kéo để sắp xếp lại Không có người đăng ký @@ -217,8 +217,8 @@ Hầu hết các ký tự đặc biệt Không có ứng dụng nào được cài đặt để phát tệp này Đóng góp - NewPipe được phát triển bởi các tình nguyện viên dành thời gian mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một tách cà phê để giúp các nhà phát triển làm NewPipe tốt hơn nữa. - Trả lại + NewPipe được phát triển bởi các tình nguyện viên dành thời gian và tâm huyết của mình để mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một chai nước suối để giúp chúng tôi làm NewPipe tốt hơn nữa. + Ủng hộ một chai nước suối Trang web Truy cập trang web NewPipe để biết thêm thông tin và tin tức. Chính sách bảo mật của NewPipe @@ -244,8 +244,8 @@ Cảnh báo: Không thể nhập tất cả các tệp. Thao tác này sẽ ghi đè cài đặt hiện tại của bạn. Bạn cũng muốn nhập cài đặt? - Thịnh hành - Mới và hot + Thịnh hành :D + Mới và đang hot Loại bỏ Chi tiết Cài đặt âm thanh @@ -292,7 +292,7 @@ Xuất trước Không thể nhập đăng ký Không thể xuất đăng ký - Nhập danh sách đăng ký YouTube từ một bản Google Takeout: + Nhập danh sách đăng ký YouTube từ Google Takeout: \n \n1. Vào URL này: %1$s \n2. Đăng nhập khi được yêu cầu @@ -307,14 +307,14 @@ \n2. Truy cập URL này: %1$s \n3. Đăng nhập khi được hỏi \n4. Sao chép URL tiểu sử mà bạn đã được chuyển hướng đến. - Hãy nhớ rằng hoạt động này có thể khiến bạn bị trừ tiền. + Hãy nhớ rằng hoạt động này có thể khiến bạn bị mất kênh bạn đã đăng ký trước đó. \n \nBạn có muốn tiếp tục không\? Điều khiển tốc độ phát lại - Speed - Chiều cao - Bỏ gắn (có thể gây méo) - Tua đi nhanh trong khi im lặng + Tốc độ + Độ cao + Bỏ gắn (có thể gây méo nhưng vui) + Tua nhanh trong im lặng Tiếp theo Đặt lại Để tuân thủ Quy định bảo vệ dữ liệu chung của châu Âu (GDPR), chúng tôi sẽ thu hút sự chú ý của bạn đến chính sách bảo mật của NewPipe. Vui lòng đọc kỹ. @@ -322,7 +322,7 @@ Chấp nhận Từ chối Không giới hạn - Giới hạn độ phân giải khi sử dụng dữ liệu di động + Giới hạn độ phân giải khi sử dụng 3G, 4G Thu nhỏ khi chuyển qua ứng dụng khác Hành động khi chuyển sang ứng dụng khác từ trình phát chính — %s Không @@ -354,7 +354,7 @@ Danh sách Lưới Tự động - Đã có bản cập nhật NewPipe! + Đã có bản cập nhật mới! Nhấn để tải về Xong đã tạm dừng @@ -438,14 +438,14 @@ Video đã xem trước và sau khi được thêm vào playlist sẽ bị xóa. \nBạn có chắc không\? Video sẽ không thể hồi phục được! Xóa video đã xem\? - Xóa đã xem + Xóa video đã xem Mặt định hệ thống Ngôn ngữ ứng dụng \'Storage Access Framework\' cho phép tải về thẻ SD Sử dụng trình chọn thư mục của hệ thống (SAF) Xóa file đã tải về Xóa lịch sử tải về - Không thể khôi phục bản download này + Không thể khôi phục bản tải xuống này Bật tiếng Tắt tiếng Yêu thích nhất @@ -503,8 +503,8 @@ Có thể được với một số dịch vụ, thường sẽ nhanh hơn nhưng có thể bị giới hạn nội dung nhận được hoặc nội dung nhận được không đầy đủ (v.d. thời lượng, trạng thái,..) Luôn cập nhật Khoảng thời gian kể từ lần cuối cập nhật thông tin kênh trước khi nó được coi là hết hạn — %s - Ngưỡng thời gian cập nhật feed - Nguồn cấp (feed) + Ngưỡng thời gian cập nhật thông báo + Thông báo (feed) Tạo mới Bạn muốn xóa nhóm kênh này\? Tên nhóm kênh trống @@ -516,7 +516,7 @@ Đang xử lý thông báo… Số kênh không tải được: %d Đang tải thông báo… - Feed cập nhật lần cuối vào: %s + Thông báo cập nhật lần cuối vào: %s Do giới hạn của ExoPlayer, khoảng thời gian tua đã được đặt lại thành %d giây đang khôi phục Tự tạo (không tìm thấy người upload) @@ -526,11 +526,11 @@ Chỉ hiện các kênh chưa được nhóm Không bao giờ Chỉ trên Wi-Fi - Hành vi tự động phát — %s + Tự động phát — %s Phát hàng đợi (Video) Không có danh sách nào ở đây Chọn danh sách - Vui lòng kiểm tra xem vấn đề bạn đang gặp đã có báo cáo trước đó chưa. Nếu bạn tạo nhiều báo cáo trùng lặp, bạn sẽ làm tốn thời gian để chúng tôi đọc thay vì thực sự sửa lỗi. + Vui lòng kiểm tra xem vấn đề mà bạn đang gặp đã báo cáo trước đó hay chưa. Nếu bạn tạo quá nhiều báo cáo trùng lặp, bạn sẽ khiến cho chúng tôi tốn thời gian để đọc chúng thay vì sửa lỗi bạn gặp. Báo cáo trên GitHub Sao chép bản báo cáo đã được định dạng Không thể đọc URL này. Mở với app khác\? @@ -548,16 +548,16 @@ Nút hành động thứ tư Nút hành động thứ ba Nút hành động thứ hai - Nút hành đông đầu tiên - Phóng ảnh thu nhỏ của video trong thông báo từ tỉ lệ 16:9 xuống 1:1 (có thể gây méo ảnh) - Phóng ảnh thu nhỏ theo tỉ lệ 1:1 + Nút hành động đầu tiên + Chỉnh ảnh thu nhỏ của video trên thanh thông báo từ tỉ lệ 16:9 thành 1:1 (có thể gây méo ảnh) + Chỉnh ảnh thu nhỏ thành tỉ lệ 1:1 Đang hiện kết quả cho: %s - Hàng - Hiển thị nội dung không an toàn cho trẻ em vì có giới hạn độ tuổi (+18) + Thêm vào danh sách đang phát + Hiển thị nội dung không an toàn cho trẻ em vì có giới hạn độ tuổi (18+) Hiện ảnh thu nhỏ (thumbnail) trên nền màn hình khóa và trong thông báo Xem hình thu nhỏ - Kiểm tra bộ - Hàng + Kiểm tra bộ nhớ + Đã thêm vào danh sách đang phát Xoá Cookie mà NewPipe lưu trữ sau khi bạn vượt Cookie reCAPTCHA đã được xóa Xóa bỏ Cookie của reCAPCHA @@ -621,11 +621,11 @@ Tài khoản đã bị chấm dứt Hiện các mục đã xem Chế độ nguồn dữ liệu nhanh không cung cấp thêm thông tin về cái này. - Tài khoản của tác giả đã bị chấm dứt. + Tài khoản của người này đã bị chấm dứt. \nNewPipe sẽ không thể tải nguồn dữ liệu này trong tương lai. \nBạn có muốn huỷ đăng ký kênh này không\? - Không thể tải nguồn dữ liệu cho \'%s\'. - Lỗi khi tải nguồn dữ liệu + Không thể tải thông báo cho \'%s\'. + Lỗi khi tải nguồn thông báo \'Storage Access Framework\' chỉ được hỗ trợ từ Android 10 trở đi Bạn sẽ được hỏi nơi bạn muốn lưu mỗi mục tải xuống Chưa có thư mục tải xuống nào được đặt, hãy chọn thư mục tải xuống mặc định ngay @@ -633,8 +633,8 @@ Chất lượng thấp (nhỏ hơn) Chất lượng cao (lớn hơn) Xem trước hình thu nhỏ trên thanh tua - Bình luận đang bị tắt - Đã được người tạo thả tim + Bình luận đã bị tắt + Đã được chủ kênh thả \"thính\" Đánh dấu là đã xem Hiện ruy băng được tô màu Picasso ở trên cùng các hình ảnh và chỉ ra nguồn của chúng: đỏ đối với mạng, xanh lam đối với ổ đĩa và xanh lá đối với bộ nhớ Hiện dấu chỉ hình ảnh @@ -650,11 +650,11 @@ Không bắt đầu video ở trình phát mini, mà chuyển trực tiếp thành chế độ toàn màn hình, nếu tự động xoay bị khóa. Bạn vẫn có thể truy cập trình phát mini bằng cách thoát khỏi toàn màn hình Khởi động trình phát chính ở toàn màn hình Đã cho mục tiếp vào hàng đợi - Cho mục tiếp vào hàng đợi + Cho video kế tiếp vào hàng đợi Đang thực hiện...Có thể mất một lúc Thông báo lỗi Thông báo để báo cáo lỗi - NewPipe đã gặp lỗi, nhấn để báo cáo + NewPipe đã gặp sự cố, nhấn để xem và báo cáo Có lỗi xảy ra, hãy xem thông báo Hiện \"làm trình phát dừng\" Hiện tùy chọn dừng đột ngột khi sử dụng trình phát @@ -687,7 +687,7 @@ Thông báo về video mới từ kênh bạn đã đăng ký Thời gian kiểm tra Yêu cầu kết nối mạng - Bất kỳ mạng nào (có thể tính phí) + Bất kỳ loại mạng nào (có thể tính phí) Xóa tất cả tệp đã tải xuống khỏi ổ đĩa\? Thông báo bị tắt Được thông báo @@ -696,4 +696,13 @@ Phần trăm , Nửa cung + Luồng video mà không được trình tải xuống hỗ trợ sẽ không hiện + Không có video khả dụng cho trình chạy ngoài + Video bạn chọn không hỗ trợ trình chạy bên ngoài + Video này không có âm thanh khả dụng cho trình chạy ngoài + Chọn chất lượng cho trình chạy ngoài + Định dạng không xác định (:P) + Độ phân giải không xác định + Kích thước khoảng thời gian tải + Hiện video đề xuất \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3c307f195ec..c7f91b7ad2b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -550,8 +550,8 @@ 第三操作按钮 第二操作按钮 第一操作按钮 - 将通知中视频缩略图的长宽比从 16:9 强制缩放到 1:1(可能会导致失真) - 强制缩放缩略图至 1:1 比例 + 将通知中视频缩略图的长宽比从 16:9 裁剪到 1:1 + 裁剪缩略图至 1:1 比例 显示内存泄漏 已加入播放队列 加入播放队列 @@ -704,4 +704,6 @@ 没有视频流可用于外部播放器 不显示下载器尚不支持的串流 未知画质 + 回放加载间隔大小 + 显示未来视频 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 9b142a7470a..9cf2f5ee1c3 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -142,12 +142,12 @@ 本機搜尋建議 遠端搜尋建議 喺本機儲存搵過嘅查詢 - 睇過嘅紀錄 + 睇過有紀錄 恢復播放 返返最後播放到去嗰個位 清單度睇到播到去邊 - 縮圖放到去 1:1 長寬比 - 顯示喺通知嘅影片縮圖由 16:9 放到去 1:1 長寬比 (話唔定會鬆郁矇) + 縮圖以 1:1 長寬比框起 + 顯示喺通知嘅影片縮圖由 16:9 剪成 1:1 長寬比 通知色彩化 等 Android 根據縮圖嘅主色自訂通知嘅顏色 (注意:唔係部部機都用得) 夜色 @@ -200,7 +200,7 @@ 撈起去浮面 匯入資料庫 主播放器用全螢幕開啟 - 如果自動旋轉鎖定,開啟影片嘅時候唔用袖珍播放器就直接飛去全螢幕模式。您仍可結束全螢幕返返去袖珍播放器 + 開啟影片嘅時候唔用袖珍播放器就直接飛去全螢幕模式,如果自動旋轉鎖定嘅話。您仍可結束全螢幕返返去袖珍播放器 認唔出呢個 URL。要唔要用另一個 app 開? YouTube 提供嘅「嚴格篩選模式」可以過濾潛在嘅成人內容 有年齡限制 (例如 18+) 故可能兒童不宜嘅內容都照顯示 @@ -322,7 +322,7 @@ 100+ 部影片 ∞ 部影片 未開放留言 - 建立 + 加新 未設定下載資料夾,請立即揀選預設嘅下載資料夾 刪除咗 1 個項目。 執執佢 @@ -394,7 +394,7 @@ 下載 課金 匯出唔到訂閱 - 修改播放器字幕啲字嘅大細同背景款式。要重新開過個 app 先會生效 + 修改播放器字幕大細同背景款式。要重新開過個 app 先會生效 執好就撳一下「搞掂」 靜音 處理緊… 可能要等等 @@ -466,7 +466,7 @@ 喺幕後開始播放 部機冇晒位 頂櫳重試幾多次 - 若然有機會用到流動數據嘅時候,或者用得著,雖則有啲下載或者冇得暫停 + 若然有機會用到流動數據嘅時候,可能會用得著,雖則有啲下載或者冇得暫停 輪住下載 內部 私人 @@ -615,7 +615,7 @@ 當喺主影片播放器轉去第個 app 嘅時候點做好 — %s 借過幕後播 借過浮面播 - 自動開啟播放 — %s + 自動開始播放 — %s 上返行人路 時間軸預覽縮圖 載入緊摘要… @@ -644,7 +644,7 @@ 著作者嘅帳戶已經被終止。 \nNewPipe 日後唔會載入到呢個摘要。 \n您要唔要取消訂閱呢個頻道? - 某啲服務會提供,通常快趣好多,但項目數量可能有限兼詳情欠奉 (例如片長、項目類型、直播狀態) + 某啲服務有提供,通常會快趣好多,但項目數量可能有限兼欠奉詳情 (例如片長、項目類型、直播狀態) 剷走播放到邊個位 係咪要全部剷走晒播放到邊個位? 百分比 @@ -662,4 +662,47 @@ 揀選一個站 轉換播放器嘅時候,排隊播可能會清零 NewPipe 係「著佐權」(copyleft) 自由軟件:您可以隨意使用、考究、分享同改進佢。具體而言,您可以依據自由軟件基金會發佈嘅《GNU 通用公眾特許條款》第 3 版或 (按您選擇) 之後任一版本之下嘅條款,重新分發及/或修改呢個軟件。 + 載入相距大細 + 互動頁面 + 預設嘅互動站 + 輸入 URL 或者您嘅 ID 去匯入 SoundCloud 個人檔案: +\n +\n一、喺網頁瀏覽器啟用「桌面版模式」(個網唔支援手機版) +\n二、去呢個網址:%1$s +\n三、叫您就登入 +\n四、複製佢彈返您去個人檔案嗰版個 URL。 + 您個 ID、soundcloud.com/您個id + 揀選互動站 + 顯示返項目原底話時隔幾耐 + 停用多媒體隧道 + 頻道成軍 + 成軍名留空 + 黃袍 + 您係咪要刪除呢個成軍? + 淨係顯示未成軍嘅訂閱 + 啲圖都要騷 Picasso 三色碼顯示源頭:紅碼係網絡上高落嚟,藍碼係儲存喺磁碟本地,綠碼係潛伏喺記憶體中 + 服務原底嘅字會騷返喺串流項目上面 + 影像要推三色碼 + 顯示定預告上畫嘅影片 + 若果播片嘅時候窒下窒下或者黑畫面,就停用多媒體隧道啦 + 點樣用 Google 匯出嚟匯入 YouTube 訂閱: +\n +\n一、去呢個網址:%1$s +\n二、叫您就登入 +\n三、撳一下「包含所有資料」,再撳一下「全部不選」,之後淨係剔返「訂閱」,然後撳「確定」 +\n四、撳一下「下一步」然後揀「建立匯出」 +\n五、個掣騷出嚟嘅時候就撳一下「下載」 +\n六、返返嚟呢度,喺下低撳「匯入檔案」,揀返下載咗嗰個 .zip 檔案 +\n七、[個 .zip 匯入唔到點算好] 將個 .csv 檔案解壓縮抽返出嚟 (通常係擺喺「YouTube and YouTube Music/subscriptions/subscriptions.csv」),喺下低撳「匯入檔案」,揀返抽出嚟個 csv 檔案 + 係咪覺得摘要「懸浮於半路太久,可否再快兩步」?可以試下啟用快速載入 (您可以喺設定度更改,又或者撳一下下低個掣)。 +\n +\nNewPipe 提供兩種載入摘要嘅方針: +\n• 攞晒成個訂閱頻道,慢得嚟志在夠完整。 +\n• 用特設嘅服務終端,快得嚟啲料爭少少。 +\n +\n兩者嘅分別在於,快趣嗰個通常都係爭噉啲料:譬如話項目嘅片長同類型 (分唔到係直播定上載),同埋攞返嚟數目可能會少啲。 +\n +\nYouTube 就係其中一個服務,有用 RSS 摘要提供呢個快趣嘅門路。 +\n +\n所以就睇您點揀:想快定要準。 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 47117ee90b2..fb908e84148 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -550,8 +550,8 @@ 第三動作按鈕 第二動作按鈕 第一動作按鈕 - 將通知中顯示的影片縮圖從 16:9 縮放到 1:1(可能會導致變形) - 把縮圖縮放到 1:1 的長寬比 + 將通知中顯示的影片縮圖從 16:9 裁剪到 1:1 + 將縮圖裁剪為 1:1 的長寬比 顯示記憶體洩漏 已加入佇列 加入佇列 @@ -704,4 +704,5 @@ 沒有可用於外部播放程式的視訊串流 選取外部播放程式的畫質 播放載入間隔大小 + 顯示未來影片 \ No newline at end of file diff --git a/fastlane/metadata/android/az/changelogs/988.txt b/fastlane/metadata/android/az/changelogs/988.txt new file mode 100644 index 00000000000..525c0f8cc44 --- /dev/null +++ b/fastlane/metadata/android/az/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Videonu başlatmağa başladıqda "Heç bir yayım əldə etmək mümkün olmadı" xətası aradan qaldırıldı +[YouTube] Vide sorğusu əvəzinə "Bu məzmun bu tətbiqdə əlçatan deyil" xətası aradan qaldırıldı diff --git a/fastlane/metadata/android/bn/changelogs/66.txt b/fastlane/metadata/android/bn/changelogs/66.txt new file mode 100644 index 00000000000..05aeec04cd1 --- /dev/null +++ b/fastlane/metadata/android/bn/changelogs/66.txt @@ -0,0 +1,33 @@ +# v0.13.7 এর পরিবর্তনসূচী + +### স্থির +- v0.13.6 এর বাছাই করা ফিল্টার সমস্যাগুলি ঠিক করুন + +# v0.13.6 এর পরিবর্তনসূচী + +### উন্নতি + +- বার্গারমেনু আইকন অ্যানিমেশন #1486 অক্ষম করা হয়েছে +- ডাউনলোড #1472 মুছে ফেলা হয়েছে পূর্বাবস্থায় +- শেয়ার মেনু #1498 এ ডাউনলোড এর অপশন +- লং ট্যাপ মেনু #1454 এ শেয়ার অপশন যোগ করা হয়েছে +- প্রস্থান #1354 এ প্রধান প্লেয়ার ছোট করা যাবে +- লাইব্রেরি সংস্করণ আপডেট এবং ডাটাবেস ব্যাকআপ নিষ্কাশন #1510 +- ExoPlayer 2.8.2 আপডেট #1392 + - দ্রুত গতি পরিবর্তনের জন্য বিভিন্ন ধাপের আকার সমর্থন করতে প্লেব্যাক গতি নিয়ন্ত্রণ ডায়ালগ পুনরায় কাজ করেছে৷ + - প্লেব্যাক গতি নিয়ন্ত্রণে নীরবতার সময় ফাস্ট-ফরওয়ার্ডে একটি টগল যুক্ত করা হয়েছে। এটি অডিওবুক এবং নির্দিষ্ট সঙ্গীত ঘরানার জন্য সহায়ক হওয়া উচিত এবং একটি সত্যিকারের বিরামহীন অভিজ্ঞতা আনতে পারে (এবং প্রচুর নীরবতার সাথে একটি গান ভাঙতে পারে =\\)। + - ম্যানুয়ালি না করে প্লেয়ারে অভ্যন্তরীণভাবে মিডিয়ার পাশাপাশি মেটাডেটা পাস করার অনুমতি দেওয়ার জন্য রিফ্যাক্টর করা মিডিয়া সোর্স রেজোলিউশন। এখন আমাদের কাছে মেটাডেটার একটি একক উৎস আছে এবং প্লেব্যাক শুরু হলে সরাসরি উপলব্ধ। + - প্লেলিস্ট ফ্র্যাগমেন্ট খোলার সময় নতুন মেটাডেটা উপলব্ধ হলে রিমোট প্লেলিস্ট মেটাডেটা আপডেট হচ্ছে না। + - বিভিন্ন UI ফিক্স: #1383, ব্যাকগ্রাউন্ড প্লেয়ার নোটিফিকেশন কন্ট্রোল এখন সবসময় সাদা, ফ্লিংিংয়ের মাধ্যমে পপআপ প্লেয়ার বন্ধ করা সহজ +- মাল্টি সার্ভিসের জন্য রিফ্যাক্টরযুক্ত আর্কিটেকচার সহ নতুন এক্সট্র্যাক্টর ব্যবহার করুন + +### সমাধান + +- #1440 ব্রোকেন ভিডিও ইনফো লেআউট #1491 ঠিক করুন +- দেখুন ইতিহাস সংশোধন #1497 + - #1495, ব্যবহারকারী প্লেলিস্ট অ্যাক্সেস করার সাথে সাথে মেটাডেটা (থাম্বনেল, শিরোনাম এবং ভিডিও গণনা) আপডেট করে। + - #1475, ডাটাবেসে একটি ভিউ রেজিস্টার করে যখন ব্যবহারকারী ডিটেইল ফ্র্যাগমেন্টে এক্সটার্নাল প্লেয়ারে একটি ভিডিও শুরু করে। +- পপআপ মোডের ক্ষেত্রে ক্রিনের সময়সীমা ঠিক করুন। #1463 (স্থির #640) +- প্রধান ভিডিও প্লেয়ার ফিক্স #1509 + - [#1412] ফিক্সড রিপিট মোড প্লেয়ার এনপিই সৃষ্টি করে যখন প্লেয়ার অ্যাক্টিভিটি ব্যাকগ্রাউন্ডে থাকে তখন নতুন ইন্টেন্ট পাওয়া যায়। + - পপআপের জন্য ফিক্সড মিনিমাইজিং প্লেয়ার প্লেয়ারকে ধ্বংস করে না যখন পপআপের অনুমতি না দেওয়া হয়। diff --git a/fastlane/metadata/android/bn/changelogs/730.txt b/fastlane/metadata/android/bn/changelogs/730.txt index fd2b8e1bb79..4d0e49a84dd 100644 --- a/fastlane/metadata/android/bn/changelogs/730.txt +++ b/fastlane/metadata/android/bn/changelogs/730.txt @@ -1,2 +1,2 @@ -# ঠিক করা -- দ্রুত ঠিককরণ ডিক্রিপ্ট ফাংশন ত্রুটি আবারো। +# নিস্কাসিত +- দ্রুত হট নিষ্কাসন ডিক্রিপ্ট ফাংশন ত্রুটি আরেকবার। diff --git a/fastlane/metadata/android/bn/changelogs/770.txt b/fastlane/metadata/android/bn/changelogs/770.txt index 0c853ea745b..5653c91ff64 100644 --- a/fastlane/metadata/android/bn/changelogs/770.txt +++ b/fastlane/metadata/android/bn/changelogs/770.txt @@ -1,4 +1,4 @@ ০.১৭.২ এ পরিবর্তনসূচী -ঠিককরণ +নিষ্কাসন • কোনো ভিডিও নেই সমস্যা ঠিক করা হয়েছে diff --git a/fastlane/metadata/android/bn/changelogs/956.txt b/fastlane/metadata/android/bn/changelogs/956.txt index a01a4c8ed50..48caa0b1654 100644 --- a/fastlane/metadata/android/bn/changelogs/956.txt +++ b/fastlane/metadata/android/bn/changelogs/956.txt @@ -1 +1 @@ -[ইউটিউব] যখন কোন ভিডিও লোড হচ্ছিলো ক্র্যাশ ফিক্স করা হয়েছে +[ইউটিউব] কোনো ভিডিও লোড হওয়ার মধ্যের ক্র্যাশ নিষ্কাসন করা হয়েছে diff --git a/fastlane/metadata/android/bn/short_description.txt b/fastlane/metadata/android/bn/short_description.txt index 1085f6c73b8..6c5a1ea7370 100644 --- a/fastlane/metadata/android/bn/short_description.txt +++ b/fastlane/metadata/android/bn/short_description.txt @@ -1 +1 @@ -অ্যানড্রয়েডের জন্য একটি মুক্ত ও হালকা ইউটিউব ফ্রন্টএন্ড। +অ্যানড্রয়েডের জন্য একটি মুক্ত ও সরল ইউটিউব ফ্রন্টএন্ড। diff --git a/fastlane/metadata/android/bn_BD/changelogs/63.txt b/fastlane/metadata/android/bn_BD/changelogs/63.txt new file mode 100644 index 00000000000..efb6558d323 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/changelogs/63.txt @@ -0,0 +1,8 @@ +### উন্নতিসমূহ +- Import/export সেটিংস #1333 +- চিত্রণ কমানো (দক্ষতার উন্নতি) #1371 +- ছোটখাটো কোডে উন্নতি #1375 +- GDPR এর সবকিছু যোগ হওয়া #1420 + +### নিষ্কাসীত +- ডাউনলোডার: .giga ফাইল থেকে অসমাপ্ত ডাউনলোড গুলোর থেমে যাওয়া লোড এর নিষ্কাসন #1407 diff --git a/fastlane/metadata/android/bn_BD/changelogs/64.txt b/fastlane/metadata/android/bn_BD/changelogs/64.txt new file mode 100644 index 00000000000..502ceb57923 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/changelogs/64.txt @@ -0,0 +1,8 @@ +### উন্নতিসমুহ +- মোবাইল ডেটা ব্যাবহারে ভিডিও মানে সীমা দেয়ার ক্ষমতা যোগ করা হয়েছে। #1339 +- সেশন এর মাধ্যমে উজ্জ্বলতা মনে রাখা। #1442 +- দুর্বল সিপিইউগুলোতে ডাউনলোডের দক্ষতার উন্নতি। #1431 +- মিডিয়া সেশনগুলোই সাহায্যকারী যোগ করা হয়েছে। #1433 + +### নিষ্কাশন +- ডাউনলোডগুলো খুলতে বিধ্বস্ত হওয়া নিষ্কাসন( ছেড়ে রাখা নির্মাণ গুলোর জন্যেও নিষ্কাসন উপলুদ্ধ) #1441 diff --git a/fastlane/metadata/android/bn_BD/full_description.txt b/fastlane/metadata/android/bn_BD/full_description.txt new file mode 100644 index 00000000000..ff00b080f79 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/full_description.txt @@ -0,0 +1 @@ +নিউপাইপ গুগলের বা ইউটিউবের কোনো ফ্রেমওয়ার্ক লাইব্রেরি ব্যাবহার করেনা। এটা শুধু ওয়েবসাইট গুলোকে পারস করে যে তথ্যগুলো দরকার সেগুলোর প্রয়োজনে। এজন্যেই এই অ্যাপটা গুগলের কোনো সেবা ইনস্টল করা ছাড়াই ব্যাবহার করা যায়। আর, নিউপাইপ ব্যাবহার করতে তোমার কোনো ইউটিউব একাউন্ট প্রয়োজন হবে না, আর এইটা ফেশোর মতো। diff --git a/fastlane/metadata/android/bn_BD/short_description.txt b/fastlane/metadata/android/bn_BD/short_description.txt new file mode 100644 index 00000000000..c14b3261bb0 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/short_description.txt @@ -0,0 +1 @@ +অ্যান্ড্রয়েড এর জন্যে একটা মুক্ত সরল ইউটিউব ফ্রন্টএন্ড। diff --git a/fastlane/metadata/android/cs/changelogs/63.txt b/fastlane/metadata/android/cs/changelogs/63.txt new file mode 100644 index 00000000000..f98de65decd --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/63.txt @@ -0,0 +1,8 @@ +### Improvements +- Import/export settings #1333 +- Reduce overdraw (performance improvement) #1371 +- Small code improvements #1375 +- Add everything about GDPR #1420 + +### Fixed +- Downloader: Fix crash on loading unfinished downloads from .giga files #1107 diff --git a/fastlane/metadata/android/cs/changelogs/64.txt b/fastlane/metadata/android/cs/changelogs/64.txt new file mode 100644 index 00000000000..7b2bd1ce5ca --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/64.txt @@ -0,0 +1,8 @@ +### Vylepšení +- Přidáni možnosti omezení kvality videa při použití mobilních dat. #1339 +- Zapamatování jasu pro relaci. #1442 +- Zlepšený výkon pro stahování se slabším CPU #1431 +- Přidána (fungující) podpora pro mediální relace #1433 + +### Oprava +- Opraveno selhání aplikace při otevření stáhnutých souborů (oprava je nyní k dispozici pro vydané sestavy. #1441 diff --git a/fastlane/metadata/android/cs/changelogs/65.txt b/fastlane/metadata/android/cs/changelogs/65.txt new file mode 100644 index 00000000000..8523ba3131a --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/65.txt @@ -0,0 +1,26 @@ +### Improvements + +- Disable burgermenu icon animation #1486 +- undo delete of downloads #1472 +- Download option in share menu #1498 +- Added share option to long tap menu #1454 +- Minimize main player on exit #1354 +- Library version update and database backup fix #1510 +- ExoPlayer 2.8.2 Update #1392 + - Reworked the playback speed control dialog to support different step sizes for faster speed change. + - + - + - + - +- +- +- +- +- +- + - + - +- +- + - + -. diff --git a/fastlane/metadata/android/cs/changelogs/66.txt b/fastlane/metadata/android/cs/changelogs/66.txt new file mode 100644 index 00000000000..d62f6db4fa8 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/66.txt @@ -0,0 +1,16 @@ +# Changelog of v0.13.7 + +### Fixed +- Fix sort filter issues of v0.13.6 + +# Changelog of v0.13.6 + +### Improvements + +- Disable burgermenu icon animation #1486 +- undo delete of downloads #1472 +- Download option in share menu #1498 +- Added share option to long tap menu #1454 +- Minimize main player on exit #1354 +- Library version update and database backup fix #1510 +- ExoPlayer 2.8.2 Update #1392 diff --git a/fastlane/metadata/android/cs/changelogs/68.txt b/fastlane/metadata/android/cs/changelogs/68.txt new file mode 100644 index 00000000000..528100d34f2 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/68.txt @@ -0,0 +1,15 @@ +# changes of v0.14.1 + +### Fixed +- Fixed failed to decrypt video url #1659 +- Fixed description link not extract well #1657 + +# changes of v0.14.0 + +### New +- New Drawer design #1461 +- New customizable front page #1461 + +### Improvements +- Reworked Gesture controls #1604 +- New way to close the popup player #1597 diff --git a/fastlane/metadata/android/cs/changelogs/69.txt b/fastlane/metadata/android/cs/changelogs/69.txt new file mode 100644 index 00000000000..48113281b12 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/69.txt @@ -0,0 +1,9 @@ +### New +- Odstranění a sdílení v odběrech dlouhým klepnutím #1516 +- Uživatelské rozhraní tabletu a rozvržení seznamu mřížky #1617 + +### Vylepšení +- ukládání a opětovné načítání naposledy použitého poměru stran #1748 +- Povolení lineárního rozložení v aktivitě Stahování s úplnými názvy videí #1771 +- Odstraňování a sdílení odběrů přímo z karty odběrů #1516 +- Enqueuing nyní spustí přehrávání videa, pokud fronta přehrávání již skončila #1783 diff --git a/fastlane/metadata/android/cs/changelogs/70.txt b/fastlane/metadata/android/cs/changelogs/70.txt new file mode 100644 index 00000000000..6a4f439762a --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/70.txt @@ -0,0 +1,7 @@ +UPOZORNĚNÍ: Tato verze je pravděpodobně plná chyb, stejně jako ta předchozí. Nicméně vzhledem k úplnému vypnutí od 17. je lepší rozbitá verze než žádná. Nebo ne? ¯\_(ツ)_/¯ + +### Vylepšení +* stažené soubory lze nyní otevřít jedním kliknutím #1879 +* podpora upuštění pro Android 4.1 - 4.3 #1884 +* odstranění starého přehrávače #1884 +* odstranění streamů z aktuální fronty přehrávání přejetím doprava #1915 diff --git a/fastlane/metadata/android/cs/changelogs/71.txt b/fastlane/metadata/android/cs/changelogs/71.txt new file mode 100644 index 00000000000..a0fdaa76fe3 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/71.txt @@ -0,0 +1,7 @@ +### Vylepšení +* Přidání oznámení o aktualizaci aplikace pro sestavení na GitHubu (#1608 by @krtkush) +* Různá vylepšení downloaderu (#1944 by @kapodamy): + * přidat chybějící bílé ikony a použít hardcorový způsob změny barev ikon + * kontrola, zda je iterátor inicializován (oprava #2031) + * umožnit opakování stahování při chybě "post-processing failed" v novém muxeru + * nový muxer MPEG-4 opravuje nesynchronní video a audio toky (#2039) diff --git a/fastlane/metadata/android/cs/changelogs/730.txt b/fastlane/metadata/android/cs/changelogs/730.txt new file mode 100644 index 00000000000..13d0879c898 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/730.txt @@ -0,0 +1,2 @@ +# Fixed +- Znovu opravte chybu funkce dešifrování. diff --git a/fastlane/metadata/android/cs/changelogs/740.txt b/fastlane/metadata/android/cs/changelogs/740.txt new file mode 100644 index 00000000000..479d25e25a2 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/740.txt @@ -0,0 +1,23 @@ +

Improvements

+
    +
  • make links in comments clickable, increase text size
  • +
  • seek on clicking timestamp links in comments
  • +
  • show preferred tab based on recently selected state
  • +
  • add playlist to queue when long clicking on 'Background' in playlist window
  • +
  • search for shared text when it is not an URL
  • +
  • add "share at current time" button to the main video player
  • +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- diff --git a/fastlane/metadata/android/cs/changelogs/750.txt b/fastlane/metadata/android/cs/changelogs/750.txt new file mode 100644 index 00000000000..325be0c281e --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/750.txt @@ -0,0 +1,14 @@ +New +Playback resume #2288 +• Resume streams where you stopped last time +Downloader Enhancements #2149 +• Use Storage Access Framework to store downloads on external SD-cards +• New mp4 muxer +• Optionally change the download directory before starting a download +• Respect metered networks + + +Improved +• Removed gema strings #2295 +• Handle (auto)rotation changes during activity lifecycle #2444 +• Make long-press menus consistent #2368 diff --git a/fastlane/metadata/android/cs/changelogs/760.txt b/fastlane/metadata/android/cs/changelogs/760.txt new file mode 100644 index 00000000000..d449ec53a37 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/760.txt @@ -0,0 +1,14 @@ +Změny ve verzi 0.17.1 + +Nové stránky +- Thajská lokalizace + + +Vylepšené stránky +- Znovu přidána akce "začít přehrávat zde" v nabídkách pro dlouhé stisknutí pro seznamy skladeb #2518 +- Přidání přepínače pro výběr souborů SAF / legacy #2521 + +Opraveno +- Oprava mizení tlačítek v zobrazení stahování při přepínání aplikací #2487 +- Oprava pozice přehrávání se ukládá, i když je vypnutá historie sledování +- Oprava sníženého výkonu způsobeného pozicí přehrávání v zobrazeních seznamu #2517 diff --git a/fastlane/metadata/android/cs/changelogs/770.txt b/fastlane/metadata/android/cs/changelogs/770.txt new file mode 100644 index 00000000000..0a07f6bdd95 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/770.txt @@ -0,0 +1,4 @@ +Změny ve verzi 0.17.2 + +Oprava +- Oprava nebylo k dispozici žádné video diff --git a/fastlane/metadata/android/cs/changelogs/780.txt b/fastlane/metadata/android/cs/changelogs/780.txt new file mode 100644 index 00000000000..32cc03d1f76 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/780.txt @@ -0,0 +1,11 @@ +Změny ve verzi 0.17.3 + +Vylepšené stránky +- Přidána možnost vymazat stavy přehrávání #2550 +- Zobrazení skrytých adresářů ve výběru souborů #2591 +- Podpora otevírání adres URL z instancí `invidio.us` pomocí NewPipe #2488 +- Přidána podpora pro adresy URL `music.youtube.com` TeamNewPipe/NewPipeExtractor#194 + +Opraveno +- YouTube] Opraveno 'java.lang.IllegalArgumentException #192 +- YouTube] Opraveno nefunkční živé vysílání TeamNewPipe/NewPipeExtractor#195 diff --git a/fastlane/metadata/android/cs/changelogs/790.txt b/fastlane/metadata/android/cs/changelogs/790.txt new file mode 100644 index 00000000000..04b5c576324 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/790.txt @@ -0,0 +1,9 @@ +Vylepšené stránky +- Přidání více nadpisů pro zlepšení přístupnosti pro nevidomé #2655 +- Udělejte jazyk nastavení složky pro stahování konzistentnější a méně nejednoznačný #2637 + +Opraveno +- Kontrola, zda je stažen poslední bajt v bloku #2646 +- Opraveno posouvání ve fragmentu detailu videa #2672 +- Odstranění dvojité animace vymazání vyhledávacího pole na jednu #2695 +- [SoundCloud] Oprava extrakce client_id #2745 diff --git a/fastlane/metadata/android/cs/changelogs/800.txt b/fastlane/metadata/android/cs/changelogs/800.txt new file mode 100644 index 00000000000..0a22893f19c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/800.txt @@ -0,0 +1,10 @@ +Nový +- Podpora PeerTube bez P2P (#2201) [Beta]: + ◦ Sledování a stahování videí z instancí PeerTube + ◦ Přidání instancí v nastavení pro přístup ke kompletnímu světu PeerTube + ◦ V systémech Android 4.4 a 7.1 mohou být při přístupu k některým instancím problémy s přenosem SSL, což může vést k chybě sítě. + +- Downloader (#2679): + ◦ Vypočítat předpokládaný čas stahování + ◦ Stáhnout opus (soubory webm) jako ogg + ◦ Obnovení vypršených odkazů ke stažení pro obnovení stahování po dlouhé pauze diff --git a/fastlane/metadata/android/cs/changelogs/810.txt b/fastlane/metadata/android/cs/changelogs/810.txt new file mode 100644 index 00000000000..c04a9cac9af --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/810.txt @@ -0,0 +1,8 @@ +Nový +- Zobrazení miniatury videa na zamykací obrazovce při přehrávání na pozadí + +Vylepšená stránka +- Přidání místního seznamu skladeb do fronty při dlouhém stisknutí tlačítka na pozadí / vyskakovacího tlačítka +- Umožnit posouvání karet hlavní stránky a jejich skrytí, pokud je k dispozici pouze jedna karta +- Omezit počet aktualizací miniatur oznámení v přehrávači na pozadí +- Přidání fiktivní miniatury pro prázdné místní seznamy skladeb diff --git a/fastlane/metadata/android/cs/changelogs/820.txt b/fastlane/metadata/android/cs/changelogs/820.txt new file mode 100644 index 00000000000..9dc52c6f517 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/820.txt @@ -0,0 +1 @@ +Opraven regex názvu dešifrovací funkce, který znemožňuje použití služby YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/830.txt b/fastlane/metadata/android/cs/changelogs/830.txt new file mode 100644 index 00000000000..1f666691264 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/830.txt @@ -0,0 +1 @@ +Aktualizováno klient_id služby SoundCloud pro opravu problémů se službou SoundCloud. diff --git a/fastlane/metadata/android/cs/changelogs/840.txt b/fastlane/metadata/android/cs/changelogs/840.txt new file mode 100644 index 00000000000..73da3bcc5c8 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/840.txt @@ -0,0 +1,8 @@ +Nový +- Přidán volič jazyka pro změnu jazyka aplikace +- Přidáno tlačítko odeslat do Kodi do skládací nabídky přehrávače +- Přidána možnost kopírování komentářů při dlouhém stisknutí + +Vylepšena stránka +- Oprava aktivity ReCaptcha a správné ukládání získaných souborů cookie +- Odstraněna nabídka s tečkami ve prospěch šuplíku a skrytí tlačítka historie, pokud není v nastavení povolena historie sledování diff --git a/fastlane/metadata/android/cs/changelogs/850.txt b/fastlane/metadata/android/cs/changelogs/850.txt new file mode 100644 index 00000000000..86fa0fc0f66 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/850.txt @@ -0,0 +1 @@ +V tomto vydání byla aktualizována verze webových stránek YouTube. Stará verze webových stránek bude v březnu ukončena, a proto je nutné provést aktualizaci NewPipe. diff --git a/fastlane/metadata/android/cs/changelogs/860.txt b/fastlane/metadata/android/cs/changelogs/860.txt new file mode 100644 index 00000000000..b1d6765f850 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/860.txt @@ -0,0 +1,7 @@ +Vylepšené stránky +- Uložení a obnovení, zda je výška tónu a tempo odpojeno, nebo ne +- Podpora výřezu displeje v přehrávači +- Kulaté zobrazení a počet účastníků +- Optimalizováno pro YouTube tak, aby využívalo méně dat + +V této verzi bylo opraveno více než 15 chyb souvisejících s YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/870.txt b/fastlane/metadata/android/cs/changelogs/870.txt new file mode 100644 index 00000000000..27cc41b5daa --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/870.txt @@ -0,0 +1,2 @@ +Jedná se o opravnou verzi, která aktualizuje NewPipe tak, aby opět umožňovala používání služby SoundCloud bez větších potíží. +V extraktoru se nyní používá rozhraní API SoundCloud v2 a byla vylepšena detekce neplatných ID klientů. diff --git a/fastlane/metadata/android/cs/changelogs/900.txt b/fastlane/metadata/android/cs/changelogs/900.txt new file mode 100644 index 00000000000..301ced05932 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/900.txt @@ -0,0 +1,13 @@ +Nový +- Skupiny předplatného a tříděné kanály +- Tlačítko ztlumení zvuku v přehrávačích + +Vylepšené stránky +- Povoleno otevírání odkazů na music.youtube.com a media.ccc.de v aplikaci NewPipe +- Přemístění dvou nastavení ze vzhledu do obsahu +- Skrytí možností vyhledávání po 5, 15 a 25 sekundách, pokud je povoleno nepřesné vyhledávání + +Opraveno +- některá videa WebM nelze zobrazit +- zálohování databáze v systému Android P +- pád při sdílení staženého souboru diff --git a/fastlane/metadata/android/cs/changelogs/910.txt b/fastlane/metadata/android/cs/changelogs/910.txt new file mode 100644 index 00000000000..130b5dc8707 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/910.txt @@ -0,0 +1 @@ +Opravena migrace databáze, která v některých vzácných případech znemožňovala spuštění aplikace NewPipe. diff --git a/fastlane/metadata/android/cs/changelogs/920.txt b/fastlane/metadata/android/cs/changelogs/920.txt new file mode 100644 index 00000000000..b5e3167ee3f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/920.txt @@ -0,0 +1,9 @@ +Vylepšeno + +- Přidáno datum nahrání a počet zobrazení na položkách mřížky streamu +- Vylepšení rozvržení záhlaví zásuvky + +Opraveno + +- Opraveno tlačítko ztlumení zvuku způsobující pády na rozhraní API 19 +- Opraveno stahování dlouhých videí 1080p 60fps diff --git a/fastlane/metadata/android/cs/changelogs/930.txt b/fastlane/metadata/android/cs/changelogs/930.txt new file mode 100644 index 00000000000..e72a3a61c47 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/930.txt @@ -0,0 +1,10 @@ +Nový +- Vyhledávání na YouTube Music +- Základní podpora Android TV + +Vylepšené stránky +- Přidána možnost odstranit všechna sledovaná videa z místního seznamu skladeb +- Zobrazení zprávy, když obsah ještě není podporován, místo pádu. +- Vylepšena změna velikosti vyskakovacího přehrávače pomocí gest štípnutí +- Enqueue streamy při dlouhém stisknutí tlačítek na pozadí a vyskakovacích tlačítek v kanálu +- Vylepšené zpracování velikosti záhlaví zásuvky diff --git a/fastlane/metadata/android/cs/changelogs/940.txt b/fastlane/metadata/android/cs/changelogs/940.txt new file mode 100644 index 00000000000..7988003d8d2 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/940.txt @@ -0,0 +1,9 @@ +Nový +- Přidání podpory pro komentáře SoundCloud +- Přidání nastavení omezeného režimu YouTube +- Zobrazení podrobností o nadřazeném kanálu PeerTube + +Vylepšené stránky +- Zobrazení tlačítka Kore pouze pro podporované služby +- Blokování gest přehrávače, která začínají na panelu NavigationBar nebo StatusBar +- Změna barvy pozadí tlačítek opakování a přihlášení k odběru na základě barvy služby diff --git a/fastlane/metadata/android/cs/changelogs/950.txt b/fastlane/metadata/android/cs/changelogs/950.txt new file mode 100644 index 00000000000..21e52a36556 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/950.txt @@ -0,0 +1,4 @@ +Tato verze přináší tři drobné opravy: +- Oprava přístupu k úložišti v systému Adroid 10+ +- Opraveno otevírání kiosků +- Opraveno rozbor trvání dlouhých videí diff --git a/fastlane/metadata/android/cs/changelogs/951.txt b/fastlane/metadata/android/cs/changelogs/951.txt new file mode 100644 index 00000000000..e5e3b3a6475 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/951.txt @@ -0,0 +1,6 @@ +Nový +- Přidání vyhledávání pro výběr odběru v dialogovém okně skupiny kanálů +- Přidání filtru do dialogového okna skupiny kanálů pro zobrazení pouze neseskupených odběrů +- Přidání karty seznamu skladeb na hlavní stránku +- Rychlé převíjení vpřed/vzad ve frontě přehrávačů na pozadí/vyskočení. +- Zobrazení návrhu vyhledávání: mysleli jste a zobrazení výsledku pro diff --git a/fastlane/metadata/android/cs/changelogs/953.txt b/fastlane/metadata/android/cs/changelogs/953.txt new file mode 100644 index 00000000000..cb762686232 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/953.txt @@ -0,0 +1 @@ +Oprava extrakce dešifrovací funkce YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/954.txt b/fastlane/metadata/android/cs/changelogs/954.txt new file mode 100644 index 00000000000..e07b70aa2e9 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/954.txt @@ -0,0 +1,6 @@ +- nový pracovní postup aplikace: přehrávání videí na stránce s detailem, přejetí prstem dolů pro minimalizaci přehrávače +- Oznámení MediaStyle: přizpůsobitelné akce v oznámeních, zlepšení výkonu +- základní změna velikosti při používání aplikace NewPipe jako aplikace pro stolní počítače + +- zobrazení dialogu s možnostmi otevření v případě přípitku nepodporované adresy URL +- Zlepšení zkušeností s návrhy vyhledávání, pokud nelze načíst ty vzdálené diff --git a/fastlane/metadata/android/cs/changelogs/955.txt b/fastlane/metadata/android/cs/changelogs/955.txt new file mode 100644 index 00000000000..ed158e34b8e --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/955.txt @@ -0,0 +1,3 @@ +[YouTube] Oprava vyhledávání pro některé uživatele +[YouTube] Oprava náhodných výjimek při dešifrování +[SoundCloud] Adresy URL, které končí lomítkem, jsou nyní zpracovávány správně diff --git a/fastlane/metadata/android/cs/changelogs/956.txt b/fastlane/metadata/android/cs/changelogs/956.txt new file mode 100644 index 00000000000..f3188257313 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/956.txt @@ -0,0 +1 @@ +[YouTube] Opraveno selhání při načítání jakéhokoli videa diff --git a/fastlane/metadata/android/cs/changelogs/957.txt b/fastlane/metadata/android/cs/changelogs/957.txt new file mode 100644 index 00000000000..666c5109725 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/957.txt @@ -0,0 +1,8 @@ +- Sjednocení specifických akcí enqueue do jedné +- Gesto dvěma prsty pro zavření přehrávače +- Povolení vymazání souborů cookie reCAPTCHA +- Možnost nezabarvovat oznámení +- Vylepšení způsobu otevírání detailů videa s cílem opravit nekonečné vyrovnávací paměti, chybné chování při sdílení do NewPipe a další nesrovnalosti +- Zrychlení videí na YouTube a oprava videí s věkovým omezením +- Oprava pádu při rychlém převíjení vpřed/vzad +- Nepřeuspořádávat seznamy přetahováním miniatur diff --git a/fastlane/metadata/android/cs/changelogs/958.txt b/fastlane/metadata/android/cs/changelogs/958.txt new file mode 100644 index 00000000000..989f9ad6125 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/958.txt @@ -0,0 +1,15 @@ +Nové a vylepšené: +- Znovu přidána možnost skrýt miniaturu na zamykací obrazovce +- Tažení pro obnovení kanálu +- Vylepšený výkon při načítání místních seznamů + +Opraveno: +- Opraven pád při spuštění aplikace NewPipe po jejím vyjmutí z paměti RAM +- Opraven pád při spuštění, když není připojení k internetu +- Opraveno: Respektování nastavení jasu a nastavení hlasitosti +- YouTube] Opraveny dlouhé seznamy skladeb + +Ostatní: +- Vyčištění kódu a několik interních vylepšení +- Aktualizace závislostí +- diff --git a/fastlane/metadata/android/cs/changelogs/959.txt b/fastlane/metadata/android/cs/changelogs/959.txt new file mode 100644 index 00000000000..18a25645b6d --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/959.txt @@ -0,0 +1,3 @@ +Opravena nekonečná smyčka pádů po otevření hlášení chyb. +Aktualizován seznam instancí PeerTube, které lze automaticky otevřít pomocí NewPipe. +Aktualizovány překlady. diff --git a/fastlane/metadata/android/cs/changelogs/960.txt b/fastlane/metadata/android/cs/changelogs/960.txt new file mode 100644 index 00000000000..c25277f6428 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/960.txt @@ -0,0 +1,4 @@ +- Vylepšený popis možnosti exportu databáze v nastavení. +- Opraveno zpracování komentářů na YouTube. +- Opraveno zobrazení názvu služby media.ccc.de. +- Aktualizovány překlady. diff --git a/fastlane/metadata/android/cs/changelogs/961.txt b/fastlane/metadata/android/cs/changelogs/961.txt new file mode 100644 index 00000000000..db711f0d4c2 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/961.txt @@ -0,0 +1,12 @@ +- [YouTube] Podpora mixu +- [YouTube] Zobrazení informací o veřejnoprávních vysílatelích a Covid-19 +- [media.ccc.de] Přidána nejnovější videa +- Přidán somálský překlad + +- Mnoho interních vylepšení + +- Opraveno sdílení videí z přehrávače +- Opraveno prázdné webové zobrazení ReCaptcha +- Opraven pád, ke kterému docházelo při odebírání streamu ze seznamu +- [PeerTube] Opraveny související streamy +- YouTube] Opraveno vyhledávání hudby na YouTube diff --git a/fastlane/metadata/android/cs/changelogs/963.txt b/fastlane/metadata/android/cs/changelogs/963.txt new file mode 100644 index 00000000000..e971418af19 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/963.txt @@ -0,0 +1 @@ +- [YouTube] Opraveno pokračování kanálu diff --git a/fastlane/metadata/android/cs/changelogs/964.txt b/fastlane/metadata/android/cs/changelogs/964.txt new file mode 100644 index 00000000000..11eacbcd80d --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/964.txt @@ -0,0 +1,8 @@ +- Přidána podpora kapitol v ovládání hráče +- [PeerTube] Přidáno vyhledávání v sépiové barvě +- Znovu přidáno tlačítko pro sdílení v zobrazení detailu videa a popis streamu přesunut do rozložení karet +- Zakázáno obnovení jasu, pokud je gesto jasu zakázáno +- Přidána položka seznamu pro přehrávání videa v Kodi +- Opraven pád v případě, že na některých zařízeních není nastaven výchozí prohlížeč, a vylepšeny dialogy sdílení +- +- diff --git a/fastlane/metadata/android/cs/changelogs/965.txt b/fastlane/metadata/android/cs/changelogs/965.txt new file mode 100644 index 00000000000..c62dfa65c46 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/965.txt @@ -0,0 +1,6 @@ +Opraven pád, ke kterému docházelo při změně pořadí skupin kanálů. +Opraveno získávání dalších videí YouTube z kanálů a seznamů skladeb. +Opraveno získávání komentářů YouTube. +Přidána podpora podcest /watch/, /v/ a /w/ v adresách URL YouTube. +Opraveno získávání id klienta služby SoundCloud a obsahu s geografickým omezením. +Přidána lokalizace do severní kurdštiny. diff --git a/fastlane/metadata/android/cs/changelogs/966.txt b/fastlane/metadata/android/cs/changelogs/966.txt new file mode 100644 index 00000000000..212687b51c4 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/966.txt @@ -0,0 +1,14 @@ +Novinka: +- Přidat novou službu: Bandcamp + +Vylepšeno: +- Přidána možnost, aby aplikace následovala motiv zařízení +- Předcházení některým pádům zobrazením vylepšeného panelu chyb +- Zobrazení více informací o tom, proč je obsah nedostupný +- Hardwarové tlačítko mezerníku spouští přehrávání/pauzu +- Zobrazení přípitku "Stahování zahájeno" + +Opraveno: +- Oprava velmi malé miniatury v detailech videa při přehrávání na pozadí +- Oprava prázdného názvu v minimalizovaném přehrávači +- diff --git a/fastlane/metadata/android/cs/changelogs/967.txt b/fastlane/metadata/android/cs/changelogs/967.txt new file mode 100644 index 00000000000..ba62e27eb8d --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/967.txt @@ -0,0 +1 @@ +Opraveno nesprávné fungování služby YouTube v EU. To bylo způsobeno novým systémem souborů cookie a souhlasu s ochranou osobních údajů, který vyžaduje, aby NewPipe nastavil soubor cookie CONSENT. diff --git a/fastlane/metadata/android/cs/changelogs/968.txt b/fastlane/metadata/android/cs/changelogs/968.txt new file mode 100644 index 00000000000..5c14d8a5ff1 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/968.txt @@ -0,0 +1,7 @@ +Do nabídky dlouhého stisknutí tlačítka byla přidána možnost Podrobnosti o kanálu. +Přidána funkce přejmenování názvu seznamu skladeb z rozhraní seznamu skladeb. +Umožňuje uživateli pozastavit video během jeho ukládání do vyrovnávací paměti. +Vyleštěn bílý motiv. +Opraveno překrývání písem při použití větší velikosti písma. +Opraveno chybějící video na zařízeních Formuler a Zephier. +Opraveny různé pády. diff --git a/fastlane/metadata/android/cs/changelogs/969.txt b/fastlane/metadata/android/cs/changelogs/969.txt new file mode 100644 index 00000000000..8c581404790 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/969.txt @@ -0,0 +1,8 @@ +- Povolení instalace na externí úložiště +- [Bandcamp] Přidána podpora pro zobrazení prvních tří komentářů u streamu +- Zobrazení přípitku "stahování zahájeno" pouze po zahájení stahování +- Nenastavovat soubor cookie reCaptcha, pokud není uložen žádný soubor cookie +- Přehrávač] Zlepšení výkonu mezipaměti +- Přehrávač] Opraveno automatické nepřehrávání přehrávače +- Zrušit předchozí Snackbary při mazání stahování +- Opraven pokus o odstranění objektu, který není v seznamu diff --git a/fastlane/metadata/android/cs/changelogs/970.txt b/fastlane/metadata/android/cs/changelogs/970.txt new file mode 100644 index 00000000000..f526adf8c96 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/970.txt @@ -0,0 +1,11 @@ +Nový +- Zobrazení metadat obsahu (značky, kategorie, licence, ...) pod popisem +- Přidána možnost "Zobrazit podrobnosti o kanálu" ve vzdálených (nelokálních) seznamech skladeb +- Přidána možnost "Otevřít v prohlížeči" do nabídky dlouhého stisknutí tlačítka + +Opravena stránka +- Opraven pád při otáčení na stránce s podrobnostmi o videu +- Opraveno tlačítko "Přehrát s Kodi" v přehrávači, které vždy vyzve k instalaci aplikace Kore +- Opraveno a vylepšeno nastavení cest pro import a export +- +- diff --git a/fastlane/metadata/android/cs/changelogs/971.txt b/fastlane/metadata/android/cs/changelogs/971.txt new file mode 100644 index 00000000000..481b9edf1ec --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/971.txt @@ -0,0 +1,3 @@ +Hotfix +- Zvětšení vyrovnávací paměti pro přehrávání po obnovení vyrovnávací paměti +- Opraven pád na tabletech a televizorech při kliknutí na ikonu přehrávání v přehrávači diff --git a/fastlane/metadata/android/cs/changelogs/972.txt b/fastlane/metadata/android/cs/changelogs/972.txt new file mode 100644 index 00000000000..d0989c7b937 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/972.txt @@ -0,0 +1,14 @@ +Nový +Rozpoznání časových razítek a hashtagů v popisu +Přidáno ruční nastavení režimu tabletu +Přidána možnost skrýt přehrávané položky ve zdroji + +Vylepšený +Správná podpora rozhraní Storage Access Framework +Lepší zpracování chyb nedostupných a ukončených kanálů +List sdílení Android pro uživatele Androidu 10+ nyní zobrazuje název obsahu. +Aktualizované instance Invidious a podpora předávaných odkazů. + +Stabilní +[YouTube] Obsah s věkovým omezením +- diff --git a/fastlane/metadata/android/cs/changelogs/973.txt b/fastlane/metadata/android/cs/changelogs/973.txt new file mode 100644 index 00000000000..2eba9ea7c4c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/973.txt @@ -0,0 +1,4 @@ +Hotfix +- Oprava ořezávání miniatur a názvů v mřížkovém rozložení kvůli špatnému výpočtu, kolik videí se vejde do jednoho řádku. +- Oprava dialogu stahování, který zmizí, aniž by cokoli provedl, pokud je otevřen z nabídky sdílení +- Aktualizace knihovny související s otevíráním externích činností, například výběrem souborů v rámci Storage Access Framework diff --git a/fastlane/metadata/android/cs/changelogs/974.txt b/fastlane/metadata/android/cs/changelogs/974.txt new file mode 100644 index 00000000000..3149e47375c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/974.txt @@ -0,0 +1,5 @@ +Hotfix +- Oprava problémů s vyrovnávací pamětí způsobených škrcením YouTube +- Oprava extrakce komentářů YouTube a pádů s vypnutými komentáři +- Oprava vyhledávání hudby na YouTube +- Oprava živých přenosů PeerTube diff --git a/fastlane/metadata/android/cs/changelogs/975.txt b/fastlane/metadata/android/cs/changelogs/975.txt new file mode 100644 index 00000000000..839bf0e3680 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/975.txt @@ -0,0 +1,17 @@ +Nový +- Zobrazení náhledu miniatur při hledání +- Rozpoznání zakázaných komentářů +- Umožňuje označit položku kanálu jako sledovanou +- Zobrazit srdíčka komentářů + +Vylepšené stránky +- Vylepšení rozvržení metadat a značek +- Použití barvy služby na součásti uživatelského rozhraní + +Opraveno +- Oprava miniatur v mini přehrávači +- Oprava nekonečného vyrovnávací paměti u duplicitních položek fronty +- Opravy některých přehrávačů, jako je otáčení a rychlejší zavírání +- +- +- diff --git a/fastlane/metadata/android/cs/changelogs/976.txt b/fastlane/metadata/android/cs/changelogs/976.txt new file mode 100644 index 00000000000..cc28400f559 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/976.txt @@ -0,0 +1,10 @@ +- Přidána možnost přímého otevření přehrávače ve fullscreenu +- Umožňuje vybrat, které typy návrhů vyhledávání se mají zobrazit +- Tmavé téma je nyní tmavší + přidána tmavá úvodní obrazovka +- Vylepšený nástroj pro výběr souborů, který šedě označuje nechtěné soubory +- Opraven import odběrů YouTube +- Opakované přehrávání streamu vyžaduje opětovné klepnutí na tlačítko přehrávání +- Opraveno ukončení zvukové relace +- +- +-. diff --git a/fastlane/metadata/android/cs/changelogs/977.txt b/fastlane/metadata/android/cs/changelogs/977.txt new file mode 100644 index 00000000000..3418d9d4356 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/977.txt @@ -0,0 +1,10 @@ +- Do nabídky dlouhého stisku bylo přidáno tlačítko "přehrát další". +- Do filtru záměrů byla přidána předpona cesty ke krátkým filmům YouTube +- Opraven import nastavení +- Výměna pozice panelu vyhledávání s tlačítky přehrávače na obrazovce Fronta +- Různé opravy související se správcem MediasessionManager +- Opraveno nedokončení panelu vyhledávání po skončení videa +- Zakázáno tunelování médií na RealtekATV +- Rozšířena klikatelná oblast minimalizovaných tlačítek přehrávače + +-. diff --git a/fastlane/metadata/android/cs/changelogs/978.txt b/fastlane/metadata/android/cs/changelogs/978.txt new file mode 100644 index 00000000000..caaf1ac5732 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/978.txt @@ -0,0 +1 @@ +Opraveno provádění kontroly nové verze NewPipe. Tato kontrola se někdy prováděla příliš brzy, a proto vedla k pádu aplikace. To by nyní mělo být opraveno. diff --git a/fastlane/metadata/android/cs/changelogs/979.txt b/fastlane/metadata/android/cs/changelogs/979.txt new file mode 100644 index 00000000000..8d3ee14924b --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/979.txt @@ -0,0 +1,2 @@ +- Opraveno obnovení přehrávání +- Vylepšení zajišťující, že služba, která určuje, zda má NewPipe kontrolovat nové verze, není spuštěna na pozadí diff --git a/fastlane/metadata/android/cs/changelogs/980.txt b/fastlane/metadata/android/cs/changelogs/980.txt new file mode 100644 index 00000000000..59139b3a2bd --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/980.txt @@ -0,0 +1,13 @@ +Nový +- Přidání možnosti "Přidat do seznamu skladeb" do nabídky sdílení +- Přidána podpora pro krátké odkazy na y2u.be a PeerTube + +Vylepšené stránky +- Kompaktnější ovládání rychlosti přehrávání +- Kanál nyní zvýrazňuje nové položky +- Možnost "Zobrazit sledované položky" ve feedu je nyní uložena + +Opraveno +- Opravena extrakce lajků a dislajků na YouTube +- Opraveno automatické přehrávání po návratu z pozadí +A mnoho dalšího diff --git a/fastlane/metadata/android/cs/changelogs/981.txt b/fastlane/metadata/android/cs/changelogs/981.txt new file mode 100644 index 00000000000..6dd389fd720 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/981.txt @@ -0,0 +1,2 @@ +Odstraněna podpora MediaParser, aby se opravilo selhání obnovení přehrávání po vyrovnávací paměti v systému Android 11+. +Zakázáno tunelování médií na přehrávači Philips QM16XE, aby se odstranily problémy s přehráváním. diff --git a/fastlane/metadata/android/cs/changelogs/982.txt b/fastlane/metadata/android/cs/changelogs/982.txt new file mode 100644 index 00000000000..c666499a41f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/982.txt @@ -0,0 +1 @@ +Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/983.txt b/fastlane/metadata/android/cs/changelogs/983.txt new file mode 100644 index 00000000000..004ccca65c0 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/983.txt @@ -0,0 +1,9 @@ +Přidání nového uživatelského rozhraní a chování při hledání dvojitým klepnutím +Možnost vyhledávání v nastavení +Zvýraznění připnutých komentářů jako takových +Přidat podporu open-with-app pro instanci FSFE PeerTube +Přidat oznámení o chybách +Oprava přehrávání první položky fronty při změně hráče +Při vyrovnávací paměti během živých přenosů čekat déle, než dojde k selhání +Oprava pořadí výsledků místního vyhledávání +Oprava prázdných políček položek ve frontě přehrávání diff --git a/fastlane/metadata/android/cs/changelogs/984.txt b/fastlane/metadata/android/cs/changelogs/984.txt new file mode 100644 index 00000000000..3b9eb35a4bf --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/984.txt @@ -0,0 +1,7 @@ +načtení dostatečného množství počátečních položek v seznamech, aby zaplnily celou obrazovku, a oprava posouvání na tabletech a televizorech. +Oprava náhodných pádů při procházení seznamů +Nechat překryvný oblouk rychlého vyhledávání hráče přejít pod uživatelské rozhraní systému +Vrátit změny výřezů při přehrávání ve více oknech, které způsobovaly regresi chybně umístěného přehrávače na některých telefonech +Zvýšit compileSdk z 30 na 31 +Aktualizovat knihovnu pro hlášení chyb +- diff --git a/fastlane/metadata/android/cs/changelogs/985.txt b/fastlane/metadata/android/cs/changelogs/985.txt new file mode 100644 index 00000000000..7035a111220 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/985.txt @@ -0,0 +1 @@ +Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube diff --git a/fastlane/metadata/android/cs/changelogs/986.txt b/fastlane/metadata/android/cs/changelogs/986.txt new file mode 100644 index 00000000000..0ffe8dabc51 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/986.txt @@ -0,0 +1,16 @@ +Nový +• Oznámení o nových streamech +• Bezproblémový přechod mezi přehrávači na pozadí a videem +• Změna výšky tónu podle půltónů +• Připojení fronty hlavního přehrávače k seznamu skladeb + +Vylepšený +• Zapamatujte si velikost kroku rychlosti / stoupání +• Zmírnění počátečního dlouhého ukládání do vyrovnávací paměti v přehrávači videa +• Vylepšete uživatelské rozhraní přehrávače pro Android TV +• Potvrďte před odstraněním všech stažených souborů + +Stabilní +• +• +• diff --git a/fastlane/metadata/android/cs/changelogs/987.txt b/fastlane/metadata/android/cs/changelogs/987.txt new file mode 100644 index 00000000000..51cd846c2a1 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/987.txt @@ -0,0 +1,12 @@ +Nový +- Podpora jiných způsobů doručování než progresivního HTTP: rychlejší načítání přehrávání, opravy pro PeerTube a SoundCloud, přehrávání nedávno ukončených živých přenosů na YouTube. +- Tlačítko Přidat pro přidání vzdáleného seznamu skladeb do místního seznamu skladeb +- Náhled obrázku ve sdíleném listu systému Android 10+ + +Vylepšená stránka +- Vylepšení dialogového okna s parametry přehrávání +- Přesunutí tlačítek pro import/export předplatného do nabídky se třemi tečkami + +Opraveno +- +- diff --git a/fastlane/metadata/android/cs/changelogs/988.txt b/fastlane/metadata/android/cs/changelogs/988.txt new file mode 100644 index 00000000000..76da8121ccb --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Oprava chyby "Nelze načíst žádný stream" při pokusu o přehrání jakéhokoli videa +[YouTube] Oprava zprávy "Následující obsah není v této aplikaci k dispozici." zobrazené místo požadovaného videa diff --git a/fastlane/metadata/android/cs/changelogs/989.txt b/fastlane/metadata/android/cs/changelogs/989.txt new file mode 100644 index 00000000000..08ce8dd95ae --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/989.txt @@ -0,0 +1,3 @@ +• [YouTube] Oprava nekonečného načítání při pokusu přehrát jakékoli video +• [YouTube] Oprava omezování výkonu u některých videí +• Aktualizace knihovny jsoup na verzi 1.15.3, která obsahuje bezpečnostní opravu diff --git a/fastlane/metadata/android/de/changelogs/987.txt b/fastlane/metadata/android/de/changelogs/987.txt index b6870154cb6..a857b1caac3 100644 --- a/fastlane/metadata/android/de/changelogs/987.txt +++ b/fastlane/metadata/android/de/changelogs/987.txt @@ -9,4 +9,4 @@ Verbesserte Behoben - Fix: Entfernen vollständig angesehener Videos aus der Wiedergabeliste -- Repariert das Thema des Freigabemenüs und den Eintrag "Zur Wiedergabeliste hinzufügen". +- Repariert das Thema des Freigabemenüs und den Eintrag "Zur Wiedergabeliste hinzufügen" diff --git a/fastlane/metadata/android/fr/changelogs/63.txt b/fastlane/metadata/android/fr/changelogs/63.txt index be078632bfb..b9abcd760aa 100644 --- a/fastlane/metadata/android/fr/changelogs/63.txt +++ b/fastlane/metadata/android/fr/changelogs/63.txt @@ -1,8 +1,8 @@ ### Améliorations -- Importation/exportation des paramètres #1333 +- Import/export des paramètres #1333 - Réduction overdraw (amélioration des performances) #1371 - Petites améliorations du code #1375 -- GDPR #1420 +- Ajout d'un popup RGPD #1420 ### Corrections - Téléchargeur : Correction d'un plantage lors du chargement de téléchargements inachevés de fichiers .giga #1407 diff --git a/fastlane/metadata/android/fr/changelogs/65.txt b/fastlane/metadata/android/fr/changelogs/65.txt new file mode 100644 index 00000000000..bb664a3cb18 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/65.txt @@ -0,0 +1,26 @@ +### Améliorations + +- L'animation de l'icône du burgermenu a été désactivé #1486 +- Annulation de la suppression des téléchargements #1472 +- Option de téléchargement dans le menu de partage #1498 +- Ajout d'une option de partage dans le menu "long tap" #1454 +- Réduction du lecteur principal à la sortie #1354 +- Mise à jour de la version de la bibliothèque et correction de la sauvegarde de la base de données #1510 +- Mise à jour de ExoPlayer 2.8.2 #1392 + - La boîte de dialogue de contrôle de la vitesse de lecture a été retravaillée pour prendre en charge différentes tailles de pas pour un changement de vitesse plus rapide. + - Ajout d'une option d'avance rapide pendant les silences dans le contrôle de la vitesse de lecture. Cela devrait être utile pour les livres audio et certains genres musicaux, et peut apporter une véritable expérience transparente (et peut casser une chanson avec beaucoup de silences =\\). + - Refonte de la résolution des sources de médias pour permettre le passage des métadonnées avec les médias en interne dans le lecteur, plutôt que de le faire manuellement. Maintenant, nous avons une seule source de métadonnées et elles sont directement disponibles lorsque la lecture commence. + - Correction des métadonnées des listes de lecture distantes qui ne sont pas mises à jour lorsque de nouvelles métadonnées sont disponibles lors de l'ouverture du fragment de liste de lecture. + - Diverses corrections de l'interface utilisateur : #1383, les contrôles de notification du lecteur en arrière-plan sont maintenant toujours blancs, il est plus facile de fermer le lecteur popup en le lançant. +- Utilisation d'un nouvel extracteur avec une architecture remaniée pour le multiservice. + +### Corrections + +- Correction #1440 Disposition des informations vidéo cassée #1491 +- Correction de l'historique des vues #1497 + - #1495, en mettant à jour les métadonnées (vignette, titre et nombre de vidéos) dès que l'utilisateur accède à la liste de lecture. + - #1475, en enregistrant une vue dans la base de données lorsque l'utilisateur lance une vidéo sur un lecteur externe sur le fragment de détail. +- Correction du timeout de la fenêtre en cas de mode popup. #1463 (Corrigé #640) +- Correction du lecteur vidéo principal #1509 + - Correction du mode répétition entraînant un NPE du lecteur lorsqu'une nouvelle intention est reçue alors que l'activité du lecteur est en arrière-plan. + - Correction de la réduction du lecteur en popup ne détruisant pas le lecteur lorsque la permission de popup n'est pas accordée. diff --git a/fastlane/metadata/android/fr/changelogs/66.txt b/fastlane/metadata/android/fr/changelogs/66.txt new file mode 100644 index 00000000000..3a94c81e03f --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/66.txt @@ -0,0 +1,28 @@ +# Journal des modifications de la v0.13.6 + +### Améliorations + +- L'animation de l'icône du menu « hamburger » a été désactivée #1486 +- Annulation de la suppression des téléchargements #1472 +- Option de téléchargement dans le menu de partage #1498 +- Ajout d'une option de partage dans le menu "long tap" #1454 +- Réduction du lecteur principal à la sortie #1354 +- Mise à jour de la version de la bibliothèque et correction de la sauvegarde de la base de données #1510 +- Mise à jour de ExoPlayer 2.8.2 #1392 + - La boîte de dialogue de contrôle de la vitesse de lecture a été retravaillée pour prendre en charge différentes tailles de pas pour un changement de vitesse plus rapide. + - Ajout d'une option d'avance rapide pendant les silences dans le contrôle de la vitesse de lecture. Cela devrait être utile pour les livres audio et certains genres musicaux, et peut apporter une véritable expérience transparente (et peut casser une chanson avec beaucoup de silences =\\). + - Refonte de la résolution des sources de médias pour permettre le passage des métadonnées avec les médias en interne dans le lecteur, plutôt que de le faire manuellement. Maintenant, nous avons une seule source de métadonnées et elles sont directement disponibles lorsque la lecture commence. + - Correction des métadonnées des listes de lecture distantes qui ne sont pas mises à jour lorsque de nouvelles métadonnées sont disponibles lors de l'ouverture du fragment de liste de lecture. + - Diverses corrections de l'interface utilisateur : #1383, les contrôles de notification du lecteur en arrière-plan sont maintenant toujours blancs, il est plus facile de fermer le lecteur popup en le lançant. +- Utilisation d'un nouvel extracteur avec une architecture remaniée pour le multiservice. + +### Corrections + +- Correction #1440 Disposition des informations vidéo cassée #1491 +- Correction de l'historique des vues #1497 + - #1495, en mettant à jour les métadonnées (vignette, titre et nombre de vidéos) dès que l'utilisateur accède à la liste de lecture. + - #1475, en enregistrant une vue dans la base de données lorsque l'utilisateur lance une vidéo sur un lecteur externe sur le fragment de détail. +- Correction du timeout de la fenêtre en cas de mode popup. #1463 (Corrigé #640) +- Correction du lecteur vidéo principal #1509 + - Correction du mode répétition entraînant un NPE du lecteur lorsqu'une nouvelle intention est reçue alors que l'activité du lecteur est en arrière-plan. + - Correction de la réduction du lecteur en popup ne détruisant pas le lecteur lorsque la permission de popup n'est pas accordée. diff --git a/fastlane/metadata/android/fr/changelogs/68.txt b/fastlane/metadata/android/fr/changelogs/68.txt new file mode 100644 index 00000000000..9b2760c35fd --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/68.txt @@ -0,0 +1,31 @@ +# Modifications v0.14.1 + +### Corrections +- Échec du décryptage de l'URL vidéo #1659 +- Lien de description, ne s'extrayait pas bien #1657 + +# Modifications v0.14.0 + +### Nouveautés +- Design du dossier #1461 +- Page d'accueil personnalisable #1461 + +### Améliorations +- Contrôles gestuels retravaillés #1604 +- Nouvelle façon de fermer le lecteur popup #1597 + +### Corrections +- Erreur lorsque le nombre d'abonnements n'est pas disponible. Ferme #1649. + - Affiche "le nombre d'abonnés non disponible" dans ces cas. +- NPE lorsqu'une liste de lecture YouTube est vide. +- Kiosques dans SoundCloud +- Refactor et correction du bug #1623 +- Résultat de recherche cyclique #1562 +- Barre de recherche qui n'est pas mise en page de manière statique +- Vidéos YT Premium qui ne sont pas bloquées correctement +- Vidéos qui ne se chargent pas toujours (à cause du parsing DASH) +- Liens dans la description des vidéos +- Afficher un avertissement lorsque quelqu'un essaie de télécharger vers une carte SD externe +- Exception "rien indiqué" qui déclenche un rapport +- La vignette ne s'affiche pas dans le lecteur de fond pour Android 8.1 [voir ici](https://github.com/TeamNewPipe/NewPipe/issues/943) +- Enregistrement du récepteur de diffusion. Ferme le dossier #1641. diff --git a/fastlane/metadata/android/fr/changelogs/69.txt b/fastlane/metadata/android/fr/changelogs/69.txt new file mode 100644 index 00000000000..c96b390d9f9 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/69.txt @@ -0,0 +1,19 @@ +### Nouveau +- Suppression et partage par appui long dans les abonnements #1516 +- Interface utilisateur pour tablettes et disposition de la liste en grille #1617 + +### Améliorations +- Stockage/recharge du dernier rapport d'aspect utilisé #1748 +- Activation de la disposition linéaire dans l'activité Téléchargements avec les noms complets des vidéos #1771 +- Suppression et partage des abonnements directement à partir de l'onglet abonnements #1516 +- La mise en file d'attente déclenche désormais la lecture de la vidéo si la file d'attente de lecture est déjà terminée #1783 +- Paramètres distincts pour les gestes de volume et de luminosité #1644 +- Ajout de la prise en charge de la localisation #1792 + +### Corrections +- Analyse de l'heure pour le format . , afin que NewPipe puisse être utilisé en Finlande. +- Compte d'abonnement +- Ajout permission de service de premier plan pour les appareils API 28+ #1830 + +### Bugs connus +- État de lecture ne peut être enregistré sur Android P diff --git a/fastlane/metadata/android/fr/changelogs/70.txt b/fastlane/metadata/android/fr/changelogs/70.txt new file mode 100644 index 00000000000..fccfccd2bfe --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/70.txt @@ -0,0 +1,25 @@ +ATTENTION : Cette version est probablement un festival de bugs, tout comme la dernière. Cependant, en raison de la fermeture complète depuis la 17. une version cassée est mieux que pas de version. N'est-ce pas ? ¯\_(ツ)_/¯ + +### Améliorations +* Les fichiers téléchargés peuvent maintenant être ouverts en un seul clic. +* Suppression du support pour Android 4.1 - 4.3 #1884 +* Suppression de l'ancien lecteur #1884 +* Suppression des flux de la file d'attente de lecture actuelle en les faisant glisser vers la droite #1915 +* Suppression du flux en file d'attente automatique lorsqu'un nouveau flux est mis en file d'attente manuellement #1878 +* Post-traitement pour les téléchargements et implémentation des fonctionnalités manquantes #1759 par @kapodamy + * Infrastructure de post-traitement + * Infrastructure de gestion des erreurs (pour le téléchargeur) + * File d'attente au lieu de téléchargements multiples + * Déplacer les téléchargements sérialisés en attente (fichiers `.giga`) vers les données de l'application. + * Implémentation de la répétition maximale des téléchargements + * Mise en pause des téléchargements multi-threads + * Arrêter les téléchargements lors du passage au réseau mobile (ne fonctionne jamais, voir 2ème point) + * Sauvegarder le nombre de threads pour les prochains téléchargements + * Beaucoup d'incohérences corrigées + +### Corrigé +* Correction d'un crash avec la résolution par défaut réglée sur la meilleure et la résolution limitée des données mobiles #1835 +* Correction du crash du lecteur de pop-up #1874 +* NPE lors de l'ouverture du lecteur de fond #1901 +* Correction de l'insertion de nouveaux flux lorsque la mise en file d'attente automatique est activée #1878 +* Correction du problème de décryptage de Shuttown diff --git a/fastlane/metadata/android/fr/changelogs/71.txt b/fastlane/metadata/android/fr/changelogs/71.txt index 0fa046111b4..4d1a5b1f69b 100644 --- a/fastlane/metadata/android/fr/changelogs/71.txt +++ b/fastlane/metadata/android/fr/changelogs/71.txt @@ -1,10 +1,10 @@ ### Améliorations -* Notification maj GitHub (#1608 par @krtkush) -* Améliorations téléchargeur (#1944 par @kapodamy) : -  * icônes blanches manquantes ; utilisation d'une méthode pour changer leurs couleurs -  * vérification si l'itérateur est initialisé (#2031) - * réessayer les téléchargements post-processing failed dans le nouveau muxer -  * nouveau muxer MPEG-4 corrigeant les flux non synchrones (#2039) +* Notification maj GitHub #1608 +* Améliorations téléchargeur #1944 : +  * Ajout des icônes blanches manquantes et utilisation d'une méthode hardcodé pour changer leurs couleurs +  * Vérification si l'itérateur est initialisé (#2031) + * Autoriser le ré-essai de téléchargement après une erreur "post-processing failed" dans le nouveau muxer +  * Nouveau muxer MPEG-4 corrigeant les flux non synchrones (#2039) ### Corrections -* Flux YouTube en direct s'arrêtent (#1996 par @yausername) +* Flux YouTube en direct s'arrêtent #1996 diff --git a/fastlane/metadata/android/fr/changelogs/964.txt b/fastlane/metadata/android/fr/changelogs/964.txt new file mode 100644 index 00000000000..4d65340fccd --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/964.txt @@ -0,0 +1,8 @@ +• Ajout des chapitres dans lecteur +• [PeerTube] Ajout recherche en sépia +• Ajout bouton de partage en vue détaillée de la vidéo, déplacement description du flux dans l'onglet +• Désactivation restauration de luminosité si le geste est désactivé +• Ajout élément de liste pour lire vidéos sur Kodi +• Correction crash si aucun navigateur par défaut défini, amélioration dialogues de partage +• Basculer lecture/pause avec bouton d'espace matériel en lecteur plein écran +• [media.ccc.de] Corrections diff --git a/fastlane/metadata/android/fr/changelogs/966.txt b/fastlane/metadata/android/fr/changelogs/966.txt new file mode 100644 index 00000000000..7cbe82fbfc3 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/966.txt @@ -0,0 +1,14 @@ +Nouveautés +• Ajout Bandcamp + +Améliorations +• Ajout option pour que application suive thème de l'appareil +• Prévention plantages par affichage panneau d'erreurs amélioré +• Plus d'informations sur raison indisponibilité contenu +• Bouton matériel espace déclenche lecture/pause +• Affichage toast "Téléchargement commencé" + +Corrections +• Très petite vignette dans détails de vidéo lors de lecture en arrière-plan +• Titre vide dans lecteur réduit +• Dernier mode redimensionnement pas restauré correctement diff --git a/fastlane/metadata/android/fr/changelogs/969.txt b/fastlane/metadata/android/fr/changelogs/969.txt new file mode 100644 index 00000000000..6ac4a9467d6 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/969.txt @@ -0,0 +1,8 @@ +• Autoriser installation sur un stockage externe +• [Bandcamp] Ajout fonction permettant d'afficher les trois premiers commentaires d'un flux +• Afficher 'download has started' uniquement lorsque téléchargement lancé +• Ne pas définir cookie reCaptcha lorsqu'aucun n'est stocké +• [Player] Amélioration performances cache +• [Player] Correction problème lecture automatique +• Désactiver barres d'état précédentes lors suppr. des téléchargements +• Correction suppression objet ne figurant pas dans la liste diff --git a/fastlane/metadata/android/fr/changelogs/970.txt b/fastlane/metadata/android/fr/changelogs/970.txt new file mode 100644 index 00000000000..928d3782222 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/970.txt @@ -0,0 +1,11 @@ +Nouveautés +• Affichage métadonnées du contenu sous la description +• Ajout option "Afficher les détails de la chaîne" dans les playlists distantes +• Ajout option "Ouvrir dans le navigateur" dans le menu de la touche longue + +Corrections +• Correction d'un crash de rotation sur la page de détails de la vidéo +• Correction du bouton "Jouer avec Kodi" qui demande toujours d'installer Kore +• Correction chemins d'import/export des paramètres +• Correction nombre de commentaires aimés +Et bien plus encore diff --git a/fastlane/metadata/android/fr/changelogs/971.txt b/fastlane/metadata/android/fr/changelogs/971.txt new file mode 100644 index 00000000000..3b302a06b1e --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/971.txt @@ -0,0 +1,3 @@ +Correctifs +• Augmentation de la mémoire tampon pour la lecture après le re-buffer +• Correction d'un crash sur les tablettes et les téléviseurs lors d'un clic sur l'icône de la file d'attente dans le lecteur diff --git a/fastlane/metadata/android/fr/changelogs/973.txt b/fastlane/metadata/android/fr/changelogs/973.txt new file mode 100644 index 00000000000..667279399e3 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/973.txt @@ -0,0 +1,4 @@ +Correctifs +• Correction des vignettes et des titres qui sont coupés dans la mise en page en vue grille, dû à un calcul erroné du nombre de vidéos pouvant tenir dans une rangée. +• Correction de la boîte de dialogue de téléchargement qui disparaît sans rien faire si elle est ouverte à partir du menu de partage +• Maj d'une bibliothèque liée à l'ouverture d'activités externes telles que le sélecteur de fichiers du framewok d'accès stockage diff --git a/fastlane/metadata/android/fr/changelogs/974.txt b/fastlane/metadata/android/fr/changelogs/974.txt new file mode 100644 index 00000000000..d963abe2410 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/974.txt @@ -0,0 +1,5 @@ +Correctifs +• Correction des problèmes de mise en mémoire tampon causés par la restriction de débit de YouTube +• Correction de l'extraction des commentaires de YouTube et des plantages avec les commentaires désactivés +• Correction de la recherche de musique sur YouTube +• Correction des directs PeerTube diff --git a/fastlane/metadata/android/fr/changelogs/977.txt b/fastlane/metadata/android/fr/changelogs/977.txt new file mode 100644 index 00000000000..6232ffa756d --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/977.txt @@ -0,0 +1,8 @@ +• Ajout bouton "lecture suivante" au menu de la pression longue +• Ajout préfixe du chemin des shorts YouTube au filtre d'intention +• Correction importation des paramètres +• Permutation position barre de recherche avec boutons du lecteur dans l'écran de la file d'attente +• Corrections liées à MediasessionManager +• Correction barre de progression qui ne se termine pas après fin de vidéo +• Désactivation tunneling média sur RealtekATV +• Élargissement zone cliquable des boutons de lecture minimisés diff --git a/fastlane/metadata/android/fr/changelogs/980.txt b/fastlane/metadata/android/fr/changelogs/980.txt new file mode 100644 index 00000000000..6835f70c8a3 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/980.txt @@ -0,0 +1,13 @@ +Nouveautés +• Ajout option "Ajouter à la liste de lecture" au menu de partage +• Ajout prise en charge des liens courts y2u.be et PeerTube + +Améliorations +• Commandes de vitesse de lecture plus compactes +• Le flux met désormais en évidence les nouveaux éléments +• L'option "Afficher les éléments surveillés" dans le flux est maintenant enregistrée + +Corrections +• Correction extraction des likes/dislikes de YouTube +• Correction relecture automatique après le retour de l'arrière-plan +Et bien d'autres diff --git a/fastlane/metadata/android/fr/changelogs/987.txt b/fastlane/metadata/android/fr/changelogs/987.txt index e0e1bd7bd15..1641d9a0016 100644 --- a/fastlane/metadata/android/fr/changelogs/987.txt +++ b/fastlane/metadata/android/fr/changelogs/987.txt @@ -1,8 +1,8 @@ Nouveautés -• Prise en charge de d'autres méthodes de diffusion que le HTTP progressif : temps de chargement plus rapide, corrections pour PeerTube et SoundCloud, lecture des nouveaux flux en directs de YouTube -• Boutton pour ajouter une liste de lecture distante à une locale +• Prise en charge d'autres méthodes de diffusion que le HTTP progressif : temps de chargement plus rapide, corrections pour PeerTube et SoundCloud, lecture des nouveaux flux en directs de YouTube +• Bouton pour ajouter une liste de lecture distante à une locale • Prévisualisation d'images lors d'un partage pour Andoid 10+ Améliorations • Amélioration de la boîte de dialogue des paramètres de la lecture -• Déplacement des bouttons importation/exportation vers le menu à trois points +• Déplacement des boutons importation/exportation vers le menu à trois points diff --git a/fastlane/metadata/android/fr/changelogs/988.txt b/fastlane/metadata/android/fr/changelogs/988.txt new file mode 100644 index 00000000000..7ac03bb2ac2 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Correction de l'erreur « Impossible d'obtenir un flux » lors de la lecture d'une vidéo +[YouTube] Correction du message « Le contenu suivant n'est pas disponible sur cette application. » affiché à la place de la vidéo demandée diff --git a/fastlane/metadata/android/fr/short_description.txt b/fastlane/metadata/android/fr/short_description.txt index a593ce32c93..70048c15a31 100644 --- a/fastlane/metadata/android/fr/short_description.txt +++ b/fastlane/metadata/android/fr/short_description.txt @@ -1 +1 @@ -Un lecteur multimédia libre et léger pour Android. +Une interface pour YouTube libre et légère sur Android. diff --git a/fastlane/metadata/android/he/changelogs/988.txt b/fastlane/metadata/android/he/changelogs/988.txt new file mode 100644 index 00000000000..fc69457f787 --- /dev/null +++ b/fastlane/metadata/android/he/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] תוקנה השגיאה „אי אפשר לקבל שום תזרים” בעת ניסיון לנגן סרטונים +[YouTube] תוקנה ההודעה „התוכן הבא אינו זמין ביישומון הזה” שמופיעה במקום הסרטון המבוקש diff --git a/fastlane/metadata/android/hu/changelogs/65.txt b/fastlane/metadata/android/hu/changelogs/65.txt new file mode 100644 index 00000000000..c3ee63eccdb --- /dev/null +++ b/fastlane/metadata/android/hu/changelogs/65.txt @@ -0,0 +1,26 @@ +### Fejlesztések + +- A burgermenu ikon animációjának letiltása #1486 +- a letöltések törlésének visszavonása #1472 +- Letöltési lehetőség a #1498 megosztás menüben +- Megosztási lehetőség hozzáadva a hosszú érintéssel #1454 +- A fő játékos minimalizálása a 1354-es kijáratnál +- A könyvtár verziójának frissítése és az adatbázis biztonsági mentésének javítása #1510 +- ExoPlayer 2.8.2 frissítés #1392 + - Átdolgoztuk a lejátszási sebesség-vezérlő párbeszédpanelt, hogy támogassa a különböző lépésméreteket a gyorsabb sebességváltás érdekében. + - Hozzáadott egy kapcsolót a gyors előretekeréshez a lejátszási sebesség szabályozásában a csendek alatt. Ez hasznos lehet hangoskönyvek és bizonyos zenei műfajok esetében, és valódi zökkenőmentes élményt nyújthat (és megszakíthat egy dalt sok csenddel =\\). + - Átdolgozott médiaforrás felbontás, amely lehetővé teszi a metaadatok továbbítását a média mellett a lejátszón belül, nem pedig manuálisan. Most már egyetlen metaadatforrásunk van, és közvetlenül elérhető a lejátszás megkezdésekor. + - Javítva a távoli lejátszási lista metaadatai, amelyek nem frissülnek, amikor új metaadatok állnak rendelkezésre a lejátszási lista töredékének megnyitásakor. + - Különféle felhasználói felület-javítások: #1383, a háttérben lévő lejátszó értesítési vezérlői mostantól mindig fehérek, a felugró lejátszót egyszerűbben le lehet állítani dobással +- Használjon új kivonatot refaktorált architektúrával a többszolgáltatáshoz + +### Javítások + +- Javítás: #1440 Sérült videó információs elrendezés #1491 +- Előzmények megtekintése #1497. javítás + - #1495, a metaadatok (bélyegkép, cím és videószám) frissítésével, amint a felhasználó hozzáfér a lejátszási listához. + - #1475, egy nézet regisztrálásával az adatbázisban, amikor a felhasználó elindít egy videót a külső lejátszón a részletrészleten. +- Javítsa ki a képernyő időtúllépését felugró mód esetén. #1463 (fix #640) +- Fő videólejátszó javítás #1509 + - [#1412] Javítva az ismétlési mód, ami a játékos NPE-jét okozza, ha új szándék érkezik, miközben a játékos tevékenysége a háttérben van. + - Javítva, hogy a lejátszó előugró ablakra minimalizálja, nem semmisíti meg a lejátszót ha a popup engedélyt nem adják meg. diff --git a/fastlane/metadata/android/hu/short_description.txt b/fastlane/metadata/android/hu/short_description.txt index 0a96f5e90a1..50752eeaf35 100644 --- a/fastlane/metadata/android/hu/short_description.txt +++ b/fastlane/metadata/android/hu/short_description.txt @@ -1 +1 @@ -Egy ingyenes és könnyű YouTube előtétprogram Androidra. +Ingyenes, könnyű YouTube felület Androidra. diff --git a/fastlane/metadata/android/it/changelogs/63.txt b/fastlane/metadata/android/it/changelogs/63.txt index dd8f7324dc7..a342392ad99 100644 --- a/fastlane/metadata/android/it/changelogs/63.txt +++ b/fastlane/metadata/android/it/changelogs/63.txt @@ -1,8 +1,8 @@ ### Miglioramenti -- Impostazioni di importazione / esportazione # 1333 -- Ridotto l'overdraw (miglioramento delle prestazioni) # 1371 -- Piccoli miglioramenti al codice # 1375 -- Aggiunto tutto ciò che riguarda il GDPR # 1420 +- Impostazioni di importazione / esportazione #1333 +- Ridotto l'overdraw (miglioramento delle prestazioni) #1371 +- Piccoli miglioramenti al codice #1375 +- Aggiunto tutto ciò che riguarda il GDPR #1420 ### Risolto -- Downloader: risolto il crash durante il caricamento di download incompleti dai file .giga # 1407 +- Downloader: risolto il crash durante il caricamento di download incompleti dai file .giga #1407 diff --git a/fastlane/metadata/android/it/changelogs/730.txt b/fastlane/metadata/android/it/changelogs/730.txt new file mode 100644 index 00000000000..3df41556f08 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/730.txt @@ -0,0 +1,2 @@ +# Risolto +- Sistemato di nuovo un errore nella funzione di decifrazione. diff --git a/fastlane/metadata/android/ko/changelogs/63.txt b/fastlane/metadata/android/ko/changelogs/63.txt new file mode 100644 index 00000000000..69ca21e9733 --- /dev/null +++ b/fastlane/metadata/android/ko/changelogs/63.txt @@ -0,0 +1,8 @@ +### 변경점 +- 불러오기/내보내기 세팅 #1333 +- 오버드로우 현상 개선 (성능 개선) #1371 +- 코드 일부분 개선 #1375 +- GDPR에 관한 모든것 업데이트 #1420 + +### 해결된것 +- 다운로더 : 다운로드가 완료되지 않은 .giga파일을 로딩할때 발생하는 에러 해결#1407 diff --git a/fastlane/metadata/android/ko/changelogs/64.txt b/fastlane/metadata/android/ko/changelogs/64.txt new file mode 100644 index 00000000000..4cef85a41cd --- /dev/null +++ b/fastlane/metadata/android/ko/changelogs/64.txt @@ -0,0 +1,8 @@ +### 변경점 +- 불러오기/내보내기 세팅 #1333 +- 오버드로우 현상 개선 (성능 개선) #1371 +- 코드 일부분 개선 #1375 +- GDPR에 관한 모든것 업데이트 #1420 + +### 고친것 +- 다운로더 : 다운로드가 완료되지 않은 .giga파일을 로딩할때 발생하는 문제 해결#1407 diff --git a/fastlane/metadata/android/pl/changelogs/964.txt b/fastlane/metadata/android/pl/changelogs/964.txt new file mode 100644 index 00000000000..1684d6985e2 --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/964.txt @@ -0,0 +1,8 @@ +• Dodano wsparcie rozdziałów w kontrolkach odtwarzacza +• [PeerTube] Dodano wyszukiwarkę Sepia +• Dodano ponownie przycisk udostępniania w sekcji szczegółów i przeniesiono informacje o strumieniu do układu karty +• Wyłączono przywracanie jasności przy wyłączonych gestach regulacji jasności +• Dodano element listy umożliwiający odtworzenie wideo w Kodi +• Naprawiono błąd przy braku domyślnej przeglądarki na niektórych urządzeniach i usprawniono menu udostępniania +• Przełączanie odtwarzanie/pauza poprzez wciśnięcie spacji na klawiaturze fizycznej w odtwarzaczu pełnoekranowym +• [media.cc.de] Różne poprawki i usprawnienia diff --git a/fastlane/metadata/android/pl/changelogs/965.txt b/fastlane/metadata/android/pl/changelogs/965.txt new file mode 100644 index 00000000000..d336d984d54 --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/965.txt @@ -0,0 +1,5 @@ +Naprawiono błąd przy zmianie kolejności grup kanałów. +Naprawiono pobieranie kolejnych wideo z kanałów i playlist. +Naprawiono pobieranie komentarzy w YouTube. Dodano wsparcie dla ścieżek /watch/, /v/ oraz /w/ w URL-ach YouTube. +Naprawiono pobieranie ID użytkownika SoundCloud i zawartości z ograniczeniami geograficznymi. +Dodano język północnokurdyjski. diff --git a/fastlane/metadata/android/pt/changelogs/955.txt b/fastlane/metadata/android/pt/changelogs/955.txt index cd70b41c9ba..98bed58fecd 100644 --- a/fastlane/metadata/android/pt/changelogs/955.txt +++ b/fastlane/metadata/android/pt/changelogs/955.txt @@ -1,3 +1,3 @@ -[YouTube] A procura por alguns utilizadores corrigida -[YouTube] Exceções de desencriptação aleatórias corrigidas -[SounCloud] URLs que terminam com uma barra são agora analisados corretamente +[YouTube] O problema com busca que afetava utilizadores foi corrigida +[YouTube] Exceções de desencriptação aleatórias foram corrigidas +[SounCloud] URLs que terminam com uma barra são analisadas corretamente diff --git a/fastlane/metadata/android/ru/changelogs/65.txt b/fastlane/metadata/android/ru/changelogs/65.txt new file mode 100644 index 00000000000..51993fef336 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/65.txt @@ -0,0 +1 @@ +эскиз видео diff --git a/fastlane/metadata/android/ru/changelogs/66.txt b/fastlane/metadata/android/ru/changelogs/66.txt new file mode 100644 index 00000000000..51993fef336 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/66.txt @@ -0,0 +1 @@ +эскиз видео diff --git a/fastlane/metadata/android/ru/changelogs/68.txt b/fastlane/metadata/android/ru/changelogs/68.txt new file mode 100644 index 00000000000..51993fef336 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/68.txt @@ -0,0 +1 @@ +эскиз видео diff --git a/fastlane/metadata/android/ru/changelogs/69.txt b/fastlane/metadata/android/ru/changelogs/69.txt new file mode 100644 index 00000000000..c081690f841 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/69.txt @@ -0,0 +1 @@ +настройки diff --git a/fastlane/metadata/android/ru/changelogs/70.txt b/fastlane/metadata/android/ru/changelogs/70.txt new file mode 100644 index 00000000000..da96c42fda6 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/70.txt @@ -0,0 +1 @@ +всплывающий diff --git a/fastlane/metadata/android/ru/changelogs/780.txt b/fastlane/metadata/android/ru/changelogs/780.txt new file mode 100644 index 00000000000..24cfa6e7fbe --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/780.txt @@ -0,0 +1,12 @@ +Изменения в 0.17.3 + +Улучшено +• Добавлена возможность очистки состояний воспроизведения #2550 +• Показ скрытых каталогов в средстве выбора файлов #2591 +• Поддержка URL-адресов из экземпляров `invidio.us`, открываемых с помощью NewPipe #2488 +• Добавлена поддержка URL-адресов `music.youtube.com` TeamNewPipe/NewPipeExtractor #194 + +Исправлено +• [YouTube] Исправлена ошибка java.lang.IllegalArgumentException #192 +• [YouTube] Исправлены неработающие прямые трансляции TeamNewPipe/NewPipeExtractor#195 +• Исправлена проблема с производительностью в Android Pie при загрузке потока #2592 diff --git a/fastlane/metadata/android/ru/changelogs/790.txt b/fastlane/metadata/android/ru/changelogs/790.txt new file mode 100644 index 00000000000..24d1c115af0 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/790.txt @@ -0,0 +1 @@ +папки diff --git a/fastlane/metadata/android/ru/changelogs/985.txt b/fastlane/metadata/android/ru/changelogs/985.txt new file mode 100644 index 00000000000..d3978869d59 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/985.txt @@ -0,0 +1 @@ +Исправлено: YouTube не воспроизводил никакие потоки diff --git a/fastlane/metadata/android/ru/changelogs/987.txt b/fastlane/metadata/android/ru/changelogs/987.txt new file mode 100644 index 00000000000..8e97023650f --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/987.txt @@ -0,0 +1,12 @@ +Новое +• Поддержка методов доставки, отличных от прогрессивного HTTP: ускорение времени загрузки воспроизведения, исправления PeerTube и SoundCloud, воспроизведение недавно закончившихся трансляций YouTube +• Кнопка «Добавить», чтобы добавить удаленный плейлист к локальному +• Предпросмотр изображения на странице общего доступа Android 10+ + +Улучшено +• Улучшения окна параметров воспроизведения +• Перемещение кнопки импорта/экспорта подписки в трехточечное меню + +Исправлено +• Исправлено удаление полностью просмотренных видео из плейлиста +• Исправлена тема меню «Поделиться» и пункт «Добавить в плейлист» diff --git a/fastlane/metadata/android/sk/changelogs/987.txt b/fastlane/metadata/android/sk/changelogs/987.txt new file mode 100644 index 00000000000..b291ba0d507 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/987.txt @@ -0,0 +1,12 @@ +Novinky +• Podpora spôsobov poskytovania iných ako progresívnych HTTP: rýchlejší čas načítania prehrávania, opravy pre PeerTube a SoundCloud, prehrávanie nedávno ukončených priamych prenosov YouTube +• Tlačidlo Pridať na pridanie vzdialeného zoznamu skladieb k lokálnemu +• Ukážka obrázka v zdieľanom hárku so systémom Android 10+ + +Vylepšenia +• Vylepšené dialógové okno parametrov prehrávania +• Presunuté tlačidlá importu/exportu odberov do ponuky (tri bodky) + +Opravy +• Oprava odstraňovania kompletne prehraných videí zo zoznamu videí +• Oprava témy v ponuke zdieľania a pri položke „pridať do zoznamu skladieb“ diff --git a/fastlane/metadata/android/tr/changelogs/63.txt b/fastlane/metadata/android/tr/changelogs/63.txt index b4ccdf68a82..9370c537a89 100644 --- a/fastlane/metadata/android/tr/changelogs/63.txt +++ b/fastlane/metadata/android/tr/changelogs/63.txt @@ -1,8 +1,8 @@ ### Geliştirmeler - İçe/Dışa aktarma ayarları #1333 -- Aşmalar azaltıldı(performance iyileştirmeleri) #1371 +- Aşmalar azaltıldı(performans iyileştirmeleri) #1371 - Küçük kod iyileştirmeleri #1375 - GPDR hakkında her şey eklendi #1420 ### Düzeltildi -- İndirici: .giga dosyalarından bitmeyen indirmeler yüklenirken çökmeler düzeltildi #1407 +- İndirici: .giga dosyalarından bitmemiş indirmeler yüklenirken çökmeler düzeltildi #1407 diff --git a/fastlane/metadata/android/tr/full_description.txt b/fastlane/metadata/android/tr/full_description.txt index e6ca6f1b468..11daef85b63 100644 --- a/fastlane/metadata/android/tr/full_description.txt +++ b/fastlane/metadata/android/tr/full_description.txt @@ -1 +1,2 @@ -NewPipe herhangi bir Google çerçeve kütüphanesi veya YouTube API'si kullanmaz. Gereksindiği bilgileri edinirken yalnızca web sitesini ayrıştırır. Bu nedenle Google hizmetlerinin kurulmadığı aygıtlarda kullanılabilir. Ayrıca, NewPipe'ı kullanırken YouTube hesabına gereksinmezsiniz, özgür ve açık kaynaklı yazılımdır. +NewPipe herhangi bir Google çerçeve kütüphanesi veya YouTube API'ı kullanmaz. Sadece, ihtiyaç duyduğu bilgiyi edinmek için web sitesini ayrıştırır. +Bu nedenle Google hizmetlerinin kurulmadığı aygıtlarda kullanılabilir. Ayrıca, NewPipe'ı kullanırken YouTube hesabına ihtiyacınız yok, ve bu özgür ve açık kaynaklı bir yazılımdır. diff --git a/fastlane/metadata/android/uk/changelogs/988.txt b/fastlane/metadata/android/uk/changelogs/988.txt new file mode 100644 index 00000000000..d882d430ab9 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Виправлено помилку «Не вдалося отримати жодного потоку» під час спроби відтворити будь-яке відео +[YouTube] Виправлено «Цей вміст недоступний у цьому застосунку.» замість запитаного відео diff --git a/fastlane/metadata/android/uk/changelogs/989.txt b/fastlane/metadata/android/uk/changelogs/989.txt new file mode 100644 index 00000000000..2ccd1653ae3 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/989.txt @@ -0,0 +1,3 @@ +• [YouTube] Виправлено нескінченне завантаження за спроби відтворити будь-яке відео +• [YouTube] Виправлено тротлінг на деяких відео +• Оновлено бібліотеку jsoup до версії 1.15.3, яка включає виправлення безпеки diff --git a/fastlane/metadata/android/zh-Hans/changelogs/988.txt b/fastlane/metadata/android/zh-Hans/changelogs/988.txt new file mode 100644 index 00000000000..67f53fc8fc3 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] 修复 试图播放任何视频时,显示"无法获得任何流 " +[YouTube] 修复 显示"以下内容在此应用中不可用 ",而不是所需要的视频 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/989.txt b/fastlane/metadata/android/zh-Hans/changelogs/989.txt new file mode 100644 index 00000000000..0c89cb98657 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/989.txt @@ -0,0 +1,3 @@ +- [YouTube] 修复 尝试播放任何视频时无限加载 +- [YouTube] 修复 某些视频的节流问题 +- 将jsoup库升级到1.15.3,其中包括一个安全修复 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/988.txt b/fastlane/metadata/android/zh-Hant/changelogs/988.txt new file mode 100644 index 00000000000..684c8844471 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] 修正嘗試播放任何影片時「無法取得任何串流」的錯誤 +[YouTube] 修正請求影片時顯示「以下內容不在此應用程式中可用」的訊息 diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt new file mode 100644 index 00000000000..bcc006d34c9 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] 修正播咩片都「攞唔到任何串流」嘅問題 +[YouTube] 修正出現「呢部內容喺呢個 app 欠奉」嘅訊息,睇唔到請求嘅影片 From ebce4c5b7e437d0d3fc7c3e82f9257f6825bdd03 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 27 Aug 2022 14:47:57 +0200 Subject: [PATCH 240/240] Add changelog for v0.24.0 (990) --- .../metadata/android/en-US/changelogs/990.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/990.txt diff --git a/fastlane/metadata/android/en-US/changelogs/990.txt b/fastlane/metadata/android/en-US/changelogs/990.txt new file mode 100644 index 00000000000..e12c20ba589 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/990.txt @@ -0,0 +1,15 @@ +This release drops support for Android 4.4 KitKat, now the minimum version is Android 5 Lollipop! + +New +• Download from long-press menu +• Hide future videos in feed +• Share local playlists + +Improved +• Refactor the player code into small components: less RAM used, less bugs +• Improve thumbnails' scale mode +• Vector-ize image placeholders + +Fixed +• Fix various issues with the player notification: outdated/missing media info, distorted thumbnail +• Fix fullscreen using 1/4 of screen