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 49aff657ac2..96c983ac694 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -592,7 +592,7 @@ private void onPlayModeChanged(final int repeatMode, final boolean shuffled) { private void onPlaybackParameterChanged(@Nullable final PlaybackParameters parameters) { if (parameters != null && menu != null && player != null) { final MenuItem item = menu.findItem(R.id.action_playback_speed); - item.setTitle(formatSpeed(parameters.speed)); + item.setTitle(formatSpeed(getApplicationContext(), parameters.speed)); } } 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 7e74c38480b..7648c5fe2d4 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 @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.DoubleConsumer; import java.util.function.DoubleFunction; @@ -283,8 +284,16 @@ private void initUI() { private void setText( final TextView textView, - final DoubleFunction formatter, + final BiFunction formatter, final double value + ) { + Objects.requireNonNull(textView).setText(formatter.apply(requireContext(), value)); + } + + private void setText( + final TextView textView, + final DoubleFunction formatter, + final double value ) { Objects.requireNonNull(textView).setText(formatter.apply(value)); } @@ -392,7 +401,7 @@ private void setupStepTextView( final double stepSizeValue, final TextView textView ) { - setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue); + setText(textView, this::getPercentString, stepSizeValue); textView.setOnClickListener(view -> { PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() @@ -576,18 +585,18 @@ private void updateCallback() { } @NonNull - private static String getStepUpPercentString(final double percent) { + private String getStepUpPercentString(final double percent) { return '+' + getPercentString(percent); } @NonNull - private static String getStepDownPercentString(final double percent) { + private String getStepDownPercentString(final double percent) { return '-' + getPercentString(percent); } @NonNull - private static String getPercentString(final double percent) { - return PlayerHelper.formatPitch(percent); + private String getPercentString(final double percent) { + return PlayerHelper.formatPitch(requireContext(), percent); } public interface Callback { 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 a110a80d676..1fc7746bfe0 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 @@ -33,11 +33,9 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.ui.CaptionStyleCompat; -import com.google.android.exoplayer2.util.MimeTypes; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; @@ -47,13 +45,14 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.ListHelper; +import org.schabi.newpipe.util.Localization; import java.lang.annotation.Retention; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; -import java.util.Formatter; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -62,11 +61,14 @@ import java.util.concurrent.TimeUnit; 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 NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); - private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); + private static PlayerHelperFormatters formattersInstance; + + private static PlayerHelperFormatters formatters(final Context context) { + if (formattersInstance == null) { + formattersInstance = PlayerHelperFormatters.create(context); + } + return formattersInstance; + } @Retention(SOURCE) @IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI, @@ -89,46 +91,33 @@ public final class PlayerHelper { private PlayerHelper() { } - //////////////////////////////////////////////////////////////////////////// - // Exposed helpers - //////////////////////////////////////////////////////////////////////////// + // region Exposed helpers @NonNull - public static String getTimeString(final int milliSeconds) { + public static String getTimeString(@NonNull final Context context, final int milliSeconds) { final int seconds = (milliSeconds % 60000) / 1000; final int minutes = (milliSeconds % 3600000) / 60000; final int hours = (milliSeconds % 86400000) / 3600000; final int days = (milliSeconds % (86400000 * 7)) / 86400000; - STRING_BUILDER.setLength(0); - return (days > 0 - ? STRING_FORMATTER.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds) - : hours > 0 - ? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds) - : STRING_FORMATTER.format("%02d:%02d", minutes, seconds) - ).toString(); - } + final PlayerHelperFormatters formatters = formatters(context); + if (days > 0) { + return formatters.stringFormat("%d:%02d:%02d:%02d", days, hours, minutes, seconds); + } - @NonNull - public static String formatSpeed(final double speed) { - return SPEED_FORMATTER.format(speed); + return hours > 0 + ? formatters.stringFormat("%d:%02d:%02d", hours, minutes, seconds) + : formatters.stringFormat("%02d:%02d", minutes, seconds); } @NonNull - public static String formatPitch(final double pitch) { - return PITCH_FORMATTER.format(pitch); + public static String formatSpeed(@NonNull final Context context, final double speed) { + return formatters(context).speed().format(speed); } @NonNull - public static String subtitleMimeTypesOf(@NonNull final MediaFormat format) { - switch (format) { - case VTT: - return MimeTypes.TEXT_VTT; - case TTML: - return MimeTypes.APPLICATION_TTML; - default: - throw new IllegalArgumentException("Unrecognized mime type: " + format.name()); - } + public static String formatPitch(@NonNull final Context context, final double pitch) { + return formatters(context).pitch().format(pitch); } @NonNull @@ -219,9 +208,8 @@ public static PlayQueue autoQueueOf(@NonNull final StreamInfo info, ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0)); } - //////////////////////////////////////////////////////////////////////////// - // Settings Resolution - //////////////////////////////////////////////////////////////////////////// + // endregion + // region Resolution public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) { return getPreferences(context) @@ -405,9 +393,8 @@ public static int getProgressiveLoadIntervalBytes(@NonNull final Context context return Integer.parseInt(preferredIntervalBytes) * 1024; } - //////////////////////////////////////////////////////////////////////////// - // Private helpers - //////////////////////////////////////////////////////////////////////////// + // endregion + // region Private helpers @NonNull private static SharedPreferences getPreferences(@NonNull final Context context) { @@ -426,10 +413,8 @@ private static SinglePlayQueue getAutoQueuedSinglePlayQueue( return singlePlayQueue; } - - //////////////////////////////////////////////////////////////////////////// - // Utils used by player - //////////////////////////////////////////////////////////////////////////// + // endregion + // region Utils used by player @RepeatMode public static int nextRepeatMode(@RepeatMode final int repeatMode) { @@ -503,4 +488,26 @@ public static int retrieveSeekDurationFromPreferences(final Player player) { player.getContext().getString(R.string.seek_duration_key), player.getContext().getString(R.string.seek_duration_default_value)))); } + + // endregion + + record PlayerHelperFormatters( + Locale locale, + NumberFormat speed, + NumberFormat pitch) { + + static PlayerHelperFormatters create(final Context context) { + final Locale locale = Localization.getAppLocale(context); + + final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale); + return new PlayerHelperFormatters( + locale, + new DecimalFormat("0.##x", dfs), + new DecimalFormat("##%", dfs)); + } + + String stringFormat(final String format, final Object... args) { + return String.format(locale, format, args); + } + } } 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 d8efb30df7d..d6bfca517b7 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 @@ -829,8 +829,8 @@ private void updateQueueTime(final int currentTime) { binding.itemsListHeaderDuration.setText( String.format("%s/%s", - getTimeString(currentTime + before), - getTimeString(before + after) + getTimeString(context, currentTime + before), + getTimeString(context, before + after) )); } 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 7157d6af22f..8a90d9b32d3 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 @@ -546,7 +546,7 @@ private void updatePlayBackElementsCurrentDuration(final int currentProgress) { if (player.getCurrentState() != STATE_PAUSED_SEEK) { binding.playbackSeekBar.setProgress(currentProgress); } - binding.playbackCurrentTime.setText(getTimeString(currentProgress)); + binding.playbackCurrentTime.setText(getTimeString(context, currentProgress)); } /** @@ -555,7 +555,7 @@ private void updatePlayBackElementsCurrentDuration(final int currentProgress) { * @param duration the video duration, in milliseconds */ private void setVideoDurationToControls(final int duration) { - binding.playbackEndTime.setText(getTimeString(duration)); + binding.playbackEndTime.setText(getTimeString(context, duration)); binding.playbackSeekBar.setMax(duration); // This is important for Android TVs otherwise it would apply the default from @@ -576,7 +576,7 @@ public void onProgressChanged(final SeekBar seekBar, final int progress, + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); } - binding.currentDisplaySeek.setText(getTimeString(progress)); + binding.currentDisplaySeek.setText(getTimeString(context, progress)); // Seekbar Preview Thumbnail SeekbarPreviewThumbnailHelper @@ -652,7 +652,7 @@ public void onStopTrackingTouch(final SeekBar seekBar) { player.getExoPlayer().play(); } - binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); + binding.playbackCurrentTime.setText(getTimeString(context, seekBar.getProgress())); animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA); @@ -794,7 +794,7 @@ private void updatePlayPauseButton(final PlayButtonAction action) { public void onPrepared() { super.onPrepared(); setVideoDurationToControls((int) player.getExoPlayer().getDuration()); - binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + binding.playbackSpeed.setText(formatSpeed(context, player.getPlaybackSpeed())); } @Override @@ -994,7 +994,7 @@ private void setShuffleButton(final boolean shuffled) { @Override public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { super.onPlaybackParametersChanged(playbackParameters); - binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed)); + binding.playbackSpeed.setText(formatSpeed(context, playbackParameters.speed)); } @Override @@ -1147,9 +1147,9 @@ private void buildPlaybackSpeedMenu() { for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { playbackSpeedPopupMenu.getMenu().add(POPUP_MENU_ID_PLAYBACK_SPEED, i, Menu.NONE, - formatSpeed(PLAYBACK_SPEEDS[i])); + formatSpeed(context, PLAYBACK_SPEEDS[i])); } - binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + binding.playbackSpeed.setText(formatSpeed(context, player.getPlaybackSpeed())); playbackSpeedPopupMenu.setOnMenuItemClickListener(this); playbackSpeedPopupMenu.setOnDismissListener(this); } @@ -1274,7 +1274,7 @@ public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { final float speed = PLAYBACK_SPEEDS[speedIndex]; player.setPlaybackSpeed(speed); - binding.playbackSpeed.setText(formatSpeed(speed)); + binding.playbackSpeed.setText(formatSpeed(context, speed)); } return false; 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 65cfec930d5..f1743cc5aec 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -9,9 +9,9 @@ import android.content.res.Resources; import android.icu.text.CompactDecimalFormat; import android.os.Build; +import android.text.BidiFormatter; import android.text.TextUtils; import android.text.format.DateUtils; -import android.text.BidiFormatter; import android.util.DisplayMetrics; import android.util.Log; @@ -133,15 +133,14 @@ public static String localizeNumber(@NonNull final Context context, final long n } public static String localizeNumber(@NonNull final Context context, final double number) { - final NumberFormat nf = NumberFormat.getInstance(getAppLocale(context)); - return nf.format(number); + return NumberFormat.getInstance(getAppLocale(context)).format(number); } public static String formatDate(@NonNull final Context context, @NonNull final OffsetDateTime offsetDateTime) { return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) - .withLocale(getAppLocale(context)).format(offsetDateTime - .atZoneSameInstant(ZoneId.systemDefault())); + .withLocale(getAppLocale(context)) + .format(offsetDateTime.atZoneSameInstant(ZoneId.systemDefault())); } @SuppressLint("StringFormatInvalid")