From de7872d8f2301e7d95d8b6dd362a12cedc80c278 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 17 Mar 2023 21:51:40 +0100 Subject: [PATCH 01/20] feat: add audio language selector --- app/build.gradle | 2 +- .../org/schabi/newpipe/player/Player.java | 11 +++ .../player/mediaitem/MediaItemTag.java | 39 ++++++++ .../player/mediaitem/StreamInfoTag.java | 23 ++++- .../player/resolver/PlaybackResolver.java | 5 + .../resolver/VideoPlaybackResolver.java | 55 ++++++++--- .../newpipe/player/ui/VideoPlayerUi.java | 76 ++++++++++++++- .../org/schabi/newpipe/util/ListHelper.java | 94 +++++++++++++++++-- app/src/main/res/layout/player.xml | 16 ++++ 9 files changed, 295 insertions(+), 26 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a5d63f429..c0d423817 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -191,7 +191,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:7e793c11aec46358ccbfd8bcfcf521105f4f093a' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:5a9b6ed2e3306b9152cc6689dd61dbbe43483845' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ 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 4243c233b..69b161631 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -88,6 +88,7 @@ import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; @@ -1886,6 +1887,12 @@ public final class Player implements PlaybackListener, Listener { .map(quality -> quality.getSortedVideoStreams() .get(quality.getSelectedVideoStreamIndex())); } + + public Optional getSelectedAudioStream() { + return Optional.ofNullable(currentMetadata) + .flatMap(MediaItemTag::getMaybeAudioLanguage) + .map(MediaItemTag.AudioLanguage::getSelectedAudioStream); + } //endregion @@ -2178,6 +2185,10 @@ public final class Player implements PlaybackListener, Listener { videoResolver.setPlaybackQuality(quality); } + public void setAudioLanguage(@Nullable final String language) { + videoResolver.setAudioLanguage(language); + } + @NonNull public Context getContext() { diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java index f08086287..0ef1eaaf1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java @@ -7,6 +7,7 @@ import com.google.android.exoplayer2.MediaItem.RequestMetadata; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.Player; +import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; @@ -55,6 +56,11 @@ public interface MediaItemTag { return Optional.empty(); } + @NonNull + default Optional getMaybeAudioLanguage() { + return Optional.empty(); + } + Optional getMaybeExtras(@NonNull Class type); MediaItemTag withExtras(@NonNull T extra); @@ -128,4 +134,37 @@ public interface MediaItemTag { ? null : sortedVideoStreams.get(selectedVideoStreamIndex); } } + + final class AudioLanguage { + @NonNull + private final List audioStreams; + private final int selectedAudioStreamIndex; + + private AudioLanguage(@NonNull final List audioStreams, + final int selectedAudioStreamIndex) { + this.audioStreams = audioStreams; + this.selectedAudioStreamIndex = selectedAudioStreamIndex; + } + + static AudioLanguage of(@NonNull final List audioStreams, + final int selectedAudioStreamIndex) { + return new AudioLanguage(audioStreams, selectedAudioStreamIndex); + } + + @NonNull + public List getAudioStreams() { + return audioStreams; + } + + public int getSelectedAudioStreamIndex() { + return selectedAudioStreamIndex; + } + + @Nullable + public AudioStream getSelectedAudioStream() { + return selectedAudioStreamIndex < 0 + || selectedAudioStreamIndex >= audioStreams.size() + ? null : audioStreams.get(selectedAudioStreamIndex); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java index 4095f2bc8..5379fc5a6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.player.mediaitem; import com.google.android.exoplayer2.MediaItem; +import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; @@ -25,25 +26,33 @@ public final class StreamInfoTag implements MediaItemTag { @Nullable private final MediaItemTag.Quality quality; @Nullable + private final MediaItemTag.AudioLanguage audioLanguage; + @Nullable private final Object extras; private StreamInfoTag(@NonNull final StreamInfo streamInfo, @Nullable final MediaItemTag.Quality quality, + @Nullable final MediaItemTag.AudioLanguage audioLanguage, @Nullable final Object extras) { this.streamInfo = streamInfo; this.quality = quality; + this.audioLanguage = audioLanguage; this.extras = extras; } public static StreamInfoTag of(@NonNull final StreamInfo streamInfo, @NonNull final List sortedVideoStreams, - final int selectedVideoStreamIndex) { + final int selectedVideoStreamIndex, + @NonNull final List audioStreams, + final int selectedAudioStreamIndex) { final Quality quality = Quality.of(sortedVideoStreams, selectedVideoStreamIndex); - return new StreamInfoTag(streamInfo, quality, null); + final AudioLanguage audioLanguage = + AudioLanguage.of(audioStreams, selectedAudioStreamIndex); + return new StreamInfoTag(streamInfo, quality, audioLanguage, null); } public static StreamInfoTag of(@NonNull final StreamInfo streamInfo) { - return new StreamInfoTag(streamInfo, null, null); + return new StreamInfoTag(streamInfo, null, null, null); } @Override @@ -103,6 +112,12 @@ public final class StreamInfoTag implements MediaItemTag { return Optional.ofNullable(quality); } + @NonNull + @Override + public Optional getMaybeAudioLanguage() { + return Optional.ofNullable(audioLanguage); + } + @Override public Optional getMaybeExtras(@NonNull final Class type) { return Optional.ofNullable(extras).map(type::cast); @@ -110,6 +125,6 @@ public final class StreamInfoTag implements MediaItemTag { @Override public StreamInfoTag withExtras(@NonNull final Object extra) { - return new StreamInfoTag(streamInfo, quality, extra); + return new StreamInfoTag(streamInfo, quality, audioLanguage, extra); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java index 9c8cbb8f6..c15447418 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java @@ -156,6 +156,11 @@ public interface PlaybackResolver extends Resolver { cacheKey.append(audioStream.getAverageBitrate()); } + if (audioStream.getAudioTrackId() != null) { + cacheKey.append(" "); + cacheKey.append(audioStream.getAudioTrackId()); + } + return cacheKey.toString(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java index cf7d73558..2f0c59325 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Optional; import static com.google.android.exoplayer2.C.TIME_UNSET; +import static org.schabi.newpipe.util.ListHelper.getFilteredAudioStreams; import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams; import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams; @@ -44,6 +45,8 @@ public class VideoPlaybackResolver implements PlaybackResolver { @Nullable private String playbackQuality; + @Nullable + private String audioLanguage; public enum SourceType { LIVE_STREAM, @@ -74,19 +77,39 @@ public class VideoPlaybackResolver implements PlaybackResolver { final List videoStreamsList = ListHelper.getSortedStreamVideosList(context, getNonTorrentStreams(info.getVideoStreams()), getNonTorrentStreams(info.getVideoOnlyStreams()), false, true); - final int index; + final List audioStreamsList = + getFilteredAudioStreams(context, info.getAudioStreams()); + + final int videoIndex; if (videoStreamsList.isEmpty()) { - index = -1; + videoIndex = -1; } else if (playbackQuality == null) { - index = qualityResolver.getDefaultResolutionIndex(videoStreamsList); + videoIndex = qualityResolver.getDefaultResolutionIndex(videoStreamsList); } else { - index = qualityResolver.getOverrideResolutionIndex(videoStreamsList, + videoIndex = qualityResolver.getOverrideResolutionIndex(videoStreamsList, getPlaybackQuality()); } - final MediaItemTag tag = StreamInfoTag.of(info, videoStreamsList, index); + + int audioIndex = 0; + if (audioLanguage != null) { + for (int i = 0; i < audioStreamsList.size(); i++) { + final AudioStream stream = audioStreamsList.get(i); + if (stream.getAudioTrackId() != null + && stream.getAudioTrackId().equals(audioLanguage)) { + audioIndex = i; + break; + } + } + } + + final MediaItemTag tag = + StreamInfoTag.of(info, videoStreamsList, videoIndex, audioStreamsList, audioIndex); @Nullable final VideoStream video = tag.getMaybeQuality() .map(MediaItemTag.Quality::getSelectedVideoStream) .orElse(null); + @Nullable final AudioStream audio = tag.getMaybeAudioLanguage() + .map(MediaItemTag.AudioLanguage::getSelectedAudioStream) + .orElse(null); if (video != null) { try { @@ -99,14 +122,9 @@ public class VideoPlaybackResolver implements PlaybackResolver { } } - // Create optional audio stream source - final List audioStreams = getNonTorrentStreams(info.getAudioStreams()); - final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get( - ListHelper.getDefaultAudioFormat(context, audioStreams)); - // Use the audio stream if there is no video stream, or // merge with audio stream in case if video does not contain audio - if (audio != null && (video == null || video.isVideoOnly())) { + if (audio != null && (video == null || video.isVideoOnly() || audioLanguage != null)) { try { final MediaSource audioSource = PlaybackResolver.buildMediaSource( dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag); @@ -179,9 +197,24 @@ public class VideoPlaybackResolver implements PlaybackResolver { this.playbackQuality = playbackQuality; } + @Nullable + public String getAudioLanguage() { + return audioLanguage; + } + + public void setAudioLanguage(@Nullable final String audioLanguage) { + this.audioLanguage = audioLanguage; + } + public interface QualityResolver { int getDefaultResolutionIndex(List sortedVideos); int getOverrideResolutionIndex(List sortedVideos, String playbackQuality); } + + public interface AudioLanguageResolver { + int getDefaultLanguageIndex(List audioStreams); + + int getOverrideLanguageIndex(List audioStreams, String audioLanguage); + } } 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 9afd1bf24..3365ade72 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 @@ -63,6 +63,7 @@ import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; @@ -117,11 +118,13 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa //////////////////////////////////////////////////////////////////////////*/ private static final int POPUP_MENU_ID_QUALITY = 69; + private static final int POPUP_MENU_ID_LANGUAGE = 70; 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; + private PopupMenu languagePopupMenu; protected PopupMenu playbackSpeedPopupMenu; private PopupMenu captionPopupMenu; @@ -146,7 +149,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa //region Constructor, setup, destroy protected VideoPlayerUi(@NonNull final Player player, - @NonNull final PlayerBinding playerBinding) { + @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; setupFromView(); @@ -173,6 +176,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa R.style.DarkPopupMenu); qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); + languagePopupMenu = new PopupMenu(themeWrapper, binding.languageTextView); playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); @@ -190,6 +194,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa protected void initListeners() { binding.qualityTextView.setOnClickListener(makeOnClickListener(this::onQualityClicked)); + binding.languageTextView.setOnClickListener( + makeOnClickListener(this::onAudioLanguageClicked)); binding.playbackSpeed.setOnClickListener(makeOnClickListener(this::onPlaybackSpeedClicked)); binding.playbackSeekBar.setOnSeekBarChangeListener(this); @@ -266,6 +272,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa protected void deinitListeners() { binding.qualityTextView.setOnClickListener(null); + binding.languageTextView.setOnClickListener(null); binding.playbackSpeed.setOnClickListener(null); binding.playbackSeekBar.setOnSeekBarChangeListener(null); binding.captionTextView.setOnClickListener(null); @@ -419,6 +426,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.languageTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); @@ -984,6 +992,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa private void updateStreamRelatedViews() { player.getCurrentStreamInfo().ifPresent(info -> { binding.qualityTextView.setVisibility(View.GONE); + binding.languageTextView.setVisibility(View.GONE); binding.playbackSpeed.setVisibility(View.GONE); binding.playbackEndTime.setVisibility(View.GONE); @@ -1019,6 +1028,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa } buildQualityMenu(); + buildLanguageMenu(); binding.qualityTextView.setVisibility(View.VISIBLE); binding.surfaceView.setVisibility(View.VISIBLE); @@ -1067,6 +1077,37 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa .ifPresent(s -> binding.qualityTextView.setText(s.getResolution())); } + private void buildLanguageMenu() { + if (languagePopupMenu == null) { + return; + } + languagePopupMenu.getMenu().removeGroup(POPUP_MENU_ID_LANGUAGE); + + final List availableStreams = Optional.ofNullable(player.getCurrentMetadata()) + .flatMap(MediaItemTag::getMaybeAudioLanguage) + .map(MediaItemTag.AudioLanguage::getAudioStreams) + .orElse(null); + if (availableStreams == null || availableStreams.size() < 2) { + return; + } + + for (int i = 0; i < availableStreams.size(); i++) { + final AudioStream audioStream = availableStreams.get(i); + // TODO: ensure that audio streams have track names + if (audioStream.getAudioTrackName() == null) { + continue; + } + languagePopupMenu.getMenu().add(POPUP_MENU_ID_LANGUAGE, i, Menu.NONE, + audioStream.getAudioTrackName()); + } + + player.getSelectedAudioStream() + .ifPresent(s -> binding.languageTextView.setText(s.getAudioTrackName())); + binding.languageTextView.setVisibility(View.VISIBLE); + languagePopupMenu.setOnMenuItemClickListener(this); + languagePopupMenu.setOnDismissListener(this); + } + private void buildPlaybackSpeedMenu() { if (playbackSpeedPopupMenu == null) { return; @@ -1175,6 +1216,15 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa .ifPresent(binding.qualityTextView::setText); } + private void onAudioLanguageClicked() { + languagePopupMenu.show(); + isSomePopupMenuVisible = true; + + player.getSelectedAudioStream() + .map(AudioStream::getAudioTrackName) + .ifPresent(binding.languageTextView::setText); + } + /** * Called when an item of the quality selector or the playback speed selector is selected. */ @@ -1208,6 +1258,30 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa binding.qualityTextView.setText(menuItem.getTitle()); return true; + } else if (menuItem.getGroupId() == POPUP_MENU_ID_LANGUAGE) { + final int menuItemIndex = menuItem.getItemId(); + @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); + //noinspection SimplifyOptionalCallChains + if (currentMetadata == null || !currentMetadata.getMaybeAudioLanguage().isPresent()) { + return true; + } + + final MediaItemTag.AudioLanguage language = + currentMetadata.getMaybeAudioLanguage().get(); + final List availableStreams = language.getAudioStreams(); + final int selectedStreamIndex = language.getSelectedAudioStreamIndex(); + if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { + return true; + } + + player.saveStreamProgressState(); + final String newLanguage = availableStreams.get(menuItemIndex).getAudioTrackId(); + player.setRecovery(); + player.setAudioLanguage(newLanguage); + player.reloadPlayQueueManager(); + + binding.languageTextView.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]; 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 b3b7c1792..ed1e1e801 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.function.Predicate; @@ -42,13 +43,14 @@ public final class ListHelper { // Use a Set for better performance private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); - private ListHelper() { } + private ListHelper() { + } /** - * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) * @param context Android app context * @param videoStreams list of the video streams to check * @return index of the video stream with the default index + * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ public static int getDefaultResolutionIndex(final Context context, final List videoStreams) { @@ -58,11 +60,11 @@ public final class ListHelper { } /** - * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) * @param context Android app context * @param videoStreams list of the video streams to check * @param defaultResolution the default resolution to look for * @return index of the video stream with the default index + * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ public static int getResolutionIndex(final Context context, final List videoStreams, @@ -71,10 +73,10 @@ public final class ListHelper { } /** - * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) - * @param context Android app context - * @param videoStreams list of the video streams to check + * @param context Android app context + * @param videoStreams list of the video streams to check * @return index of the video stream with the default index + * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ public static int getPopupDefaultResolutionIndex(final Context context, final List videoStreams) { @@ -84,11 +86,11 @@ public final class ListHelper { } /** - * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) * @param context Android app context * @param videoStreams list of the video streams to check * @param defaultResolution the default resolution to look for * @return index of the video stream with the default index + * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ public static int getPopupResolutionIndex(final Context context, final List videoStreams, @@ -186,6 +188,80 @@ public final class ListHelper { videoOnlyStreams, ascendingOrder, preferVideoOnlyStreams); } + public static List getFilteredAudioStreams( + @NonNull final Context context, + @Nullable final List audioStreams) { + if (audioStreams == null) { + return Collections.emptyList(); + } + + final HashMap collectedStreams = new HashMap<>(); + + final Comparator cmp; + if (isLimitingDataUsage(context)) { + cmp = getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING); + } else { + cmp = getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING); + } + + final String preferredLanguage = Localization.getPreferredLocale(context).getISO3Language(); + boolean hasPreferredLanguage = false; + + for (final AudioStream stream : audioStreams) { + if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) { + continue; + } + + final String trackId; + if (stream.getAudioTrackId() != null) { + trackId = stream.getAudioTrackId(); + } else { + trackId = ""; + } + + final AudioStream presentStream = collectedStreams.get(trackId); + if (presentStream == null || cmp.compare(stream, presentStream) > 0) { + collectedStreams.put(trackId, stream); + + if (stream.getAudioLocale() != null + && stream.getAudioLocale().getISO3Language().equals(preferredLanguage)) { + hasPreferredLanguage = true; + } + } + } + + // Fall back to English if the preferred language was not found + final String preferredLanguageOrEnglish = + hasPreferredLanguage ? preferredLanguage : Locale.ENGLISH.getISO3Language(); + + // Sort collected streams + return collectedStreams.values().stream() + .sorted((s1, s2) -> { + // Preferred language comes first + if (s1.getAudioLocale() != null + && s1.getAudioLocale().getISO3Language() + .equals(preferredLanguageOrEnglish)) { + return -1; + } + if (s2.getAudioLocale() != null + && s2.getAudioLocale().getISO3Language() + .equals(preferredLanguageOrEnglish)) { + return 1; + } + + // Sort audio tracks alphabetically + if (s1.getAudioTrackName() != null) { + if (s2.getAudioTrackName() != null) { + return s1.getAudioTrackName().compareTo(s2.getAudioTrackName()); + } else { + return -1; + } + } + return 1; + }) + .collect(Collectors.toList()); + } + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -300,8 +376,8 @@ public final class ListHelper { // Filter out higher resolutions (or not if high resolutions should always be shown) .filter(stream -> showHigherResolutions || !HIGH_RESOLUTION_LIST.contains(stream.getResolution() - // Replace any frame rate with nothing - .replaceAll("p\\d+$", "p"))) + // Replace any frame rate with nothing + .replaceAll("p\\d+$", "p"))) .collect(Collectors.toList()); final HashMap hashMap = new HashMap<>(); diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index b528e4e9b..82760be3a 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -157,6 +157,22 @@ tools:text="The Video Artist LONG very LONG very Long" /> + + Date: Sat, 18 Mar 2023 14:50:19 +0100 Subject: [PATCH 02/20] feat: improve audio track sorting, add prefer_descriptive_audio option --- .../org/schabi/newpipe/util/ListHelper.java | 53 ++++++++++--------- app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/content_settings.xml | 8 +++ 4 files changed, 39 insertions(+), 25 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 ed1e1e801..2714337c2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -233,33 +233,16 @@ public final class ListHelper { // Fall back to English if the preferred language was not found final String preferredLanguageOrEnglish = hasPreferredLanguage ? preferredLanguage : Locale.ENGLISH.getISO3Language(); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); + final boolean preferDescriptiveAudio = + preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key), + false); + final Comparator trackCmp = + getAudioTrackComparator(preferredLanguageOrEnglish, preferDescriptiveAudio); // Sort collected streams - return collectedStreams.values().stream() - .sorted((s1, s2) -> { - // Preferred language comes first - if (s1.getAudioLocale() != null - && s1.getAudioLocale().getISO3Language() - .equals(preferredLanguageOrEnglish)) { - return -1; - } - if (s2.getAudioLocale() != null - && s2.getAudioLocale().getISO3Language() - .equals(preferredLanguageOrEnglish)) { - return 1; - } - - // Sort audio tracks alphabetically - if (s1.getAudioTrackName() != null) { - if (s2.getAudioTrackName() != null) { - return s1.getAudioTrackName().compareTo(s2.getAudioTrackName()); - } else { - return -1; - } - } - return 1; - }) - .collect(Collectors.toList()); + return collectedStreams.values().stream().sorted(trackCmp).collect(Collectors.toList()); } /*////////////////////////////////////////////////////////////////////////// @@ -680,4 +663,24 @@ public final class ListHelper { return manager.isActiveNetworkMetered(); } + + private static Comparator getAudioTrackComparator( + final String preferredLanguage, final boolean preferDescriptiveAudio) { + return Comparator.comparing(AudioStream::getAudioLocale, (o1, o2) -> Boolean.compare( + o1 == null || !o1.getISO3Language().equals(preferredLanguage), + o2 == null || !o2.getISO3Language().equals(preferredLanguage)) + ).thenComparing(AudioStream::isDescriptive, (o1, o2) -> + Boolean.compare(o1 ^ preferDescriptiveAudio, o2 ^ preferDescriptiveAudio) + ).thenComparing(AudioStream::getAudioTrackName, (o1, o2) -> { + if (o1 != null) { + if (o2 != null) { + return o1.compareTo(o2); + } else { + return -1; + } + } else { + return 1; + } + }); + } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 6a1d5cd45..9ed34bf26 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -260,6 +260,7 @@ show_next_video show_description show_meta_info + prefer_descriptive_audio stream_info_selected_tab show_hold_to_append content_language diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e1c75bba4..9867a8f21 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -94,6 +94,8 @@ Turn off to hide video description and additional information Show meta info Turn off to hide meta info boxes with additional information about the stream creator, stream content or a search request + Prefer descriptive audio + Select an audio track with descriptions for visually impaired people if available Image cache wiped Wipe cached metadata Remove all cached webpage data diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index fddb966c8..684b9e558 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -114,6 +114,14 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + Date: Sat, 18 Mar 2023 16:14:07 +0100 Subject: [PATCH 03/20] fix: remove todo --- .../main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java | 1 - 1 file changed, 1 deletion(-) 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 3365ade72..72d2f0b26 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 @@ -1093,7 +1093,6 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa for (int i = 0; i < availableStreams.size(); i++) { final AudioStream audioStream = availableStreams.get(i); - // TODO: ensure that audio streams have track names if (audioStream.getAudioTrackName() == null) { continue; } From 77649d388c01c0fbca3a1fad2399eb79aa5a8cd2 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 18 Mar 2023 16:28:09 +0100 Subject: [PATCH 04/20] fix: reduce complexity --- .../newpipe/player/ui/VideoPlayerUi.java | 91 ++++++++++--------- .../org/schabi/newpipe/util/ListHelper.java | 7 +- 2 files changed, 50 insertions(+), 48 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 72d2f0b26..13cbc9fd3 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 @@ -1236,50 +1236,10 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa } if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { - final int menuItemIndex = menuItem.getItemId(); - @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); - if (currentMetadata == null || currentMetadata.getMaybeQuality().isEmpty()) { - 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()); + onQualityItemClick(menuItem); return true; } else if (menuItem.getGroupId() == POPUP_MENU_ID_LANGUAGE) { - final int menuItemIndex = menuItem.getItemId(); - @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); - //noinspection SimplifyOptionalCallChains - if (currentMetadata == null || !currentMetadata.getMaybeAudioLanguage().isPresent()) { - return true; - } - - final MediaItemTag.AudioLanguage language = - currentMetadata.getMaybeAudioLanguage().get(); - final List availableStreams = language.getAudioStreams(); - final int selectedStreamIndex = language.getSelectedAudioStreamIndex(); - if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { - return true; - } - - player.saveStreamProgressState(); - final String newLanguage = availableStreams.get(menuItemIndex).getAudioTrackId(); - player.setRecovery(); - player.setAudioLanguage(newLanguage); - player.reloadPlayQueueManager(); - - binding.languageTextView.setText(menuItem.getTitle()); + onLanguageItemClick(menuItem); return true; } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { final int speedIndex = menuItem.getItemId(); @@ -1292,6 +1252,53 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa return false; } + private void onQualityItemClick(@NonNull final MenuItem menuItem) { + final int menuItemIndex = menuItem.getItemId(); + @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); + if (currentMetadata == null || currentMetadata.getMaybeQuality().isEmpty()) { + return; + } + + final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get(); + final List availableStreams = quality.getSortedVideoStreams(); + final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); + if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { + return; + } + + player.saveStreamProgressState(); + final String newResolution = availableStreams.get(menuItemIndex).getResolution(); + player.setRecovery(); + player.setPlaybackQuality(newResolution); + player.reloadPlayQueueManager(); + + binding.qualityTextView.setText(menuItem.getTitle()); + } + + private void onLanguageItemClick(@NonNull final MenuItem menuItem) { + final int menuItemIndex = menuItem.getItemId(); + @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); + if (currentMetadata == null || currentMetadata.getMaybeAudioLanguage().isEmpty()) { + return; + } + + final MediaItemTag.AudioLanguage language = + currentMetadata.getMaybeAudioLanguage().get(); + final List availableStreams = language.getAudioStreams(); + final int selectedStreamIndex = language.getSelectedAudioStreamIndex(); + if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { + return; + } + + player.saveStreamProgressState(); + final String newLanguage = availableStreams.get(menuItemIndex).getAudioTrackId(); + player.setRecovery(); + player.setAudioLanguage(newLanguage); + player.reloadPlayQueueManager(); + + binding.languageTextView.setText(menuItem.getTitle()); + } + /** * Called when some popup menu is dismissed. */ 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 2714337c2..ef40692d7 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -212,12 +212,7 @@ public final class ListHelper { continue; } - final String trackId; - if (stream.getAudioTrackId() != null) { - trackId = stream.getAudioTrackId(); - } else { - trackId = ""; - } + final String trackId = Objects.toString(stream.getAudioTrackId(), ""); final AudioStream presentStream = collectedStreams.get(trackId); if (presentStream == null || cmp.compare(stream, presentStream) > 0) { From 366c39d4c6e52d1c3a9875421d99d38b300bfd0a Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 19 Mar 2023 01:15:36 +0100 Subject: [PATCH 05/20] feat: add language selector to audio player --- .../newpipe/player/PlayQueueActivity.java | 89 +++++++++++++++++++ .../org/schabi/newpipe/player/Player.java | 28 +++++- .../player/event/PlayerEventListener.java | 1 + .../player/mediaitem/MediaItemTag.java | 14 +-- .../player/mediaitem/StreamInfoTag.java | 26 ++++-- .../resolver/AudioPlaybackResolver.java | 69 ++++++++------ .../resolver/VideoPlaybackResolver.java | 26 +++--- .../newpipe/player/ui/VideoPlayerUi.java | 66 +++++++------- .../org/schabi/newpipe/util/ListHelper.java | 8 ++ app/src/main/res/layout/player.xml | 2 +- app/src/main/res/menu/menu_play_queue.xml | 8 ++ app/src/main/res/values/strings.xml | 2 + 12 files changed, 241 insertions(+), 98 deletions(-) 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 9ce99c15b..fa3668e8b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -13,6 +13,7 @@ import android.provider.Settings; import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; import android.widget.SeekBar; @@ -27,11 +28,13 @@ import com.google.android.exoplayer2.PlaybackParameters; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; +import org.schabi.newpipe.extractor.stream.AudioStream; 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.mediaitem.MediaItemTag; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; @@ -44,6 +47,10 @@ import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + public final class PlayQueueActivity extends AppCompatActivity implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, PlaybackParameterDialog.Callback { @@ -52,6 +59,8 @@ public final class PlayQueueActivity extends AppCompatActivity private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; + private static final int MENU_ID_AUDIO_TRACK = 71; + private Player player; private boolean serviceBound; @@ -97,6 +106,7 @@ public final class PlayQueueActivity extends AppCompatActivity this.menu = m; getMenuInflater().inflate(R.menu.menu_play_queue, m); getMenuInflater().inflate(R.menu.menu_play_queue_bg, m); + buildAudioTrackMenu(); onMaybeMuteChanged(); // to avoid null reference if (player != null) { @@ -153,6 +163,12 @@ public final class PlayQueueActivity extends AppCompatActivity NavigationHelper.playOnBackgroundPlayer(this, player.getPlayQueue(), true); return true; } + + if (item.getGroupId() == MENU_ID_AUDIO_TRACK) { + onAudioTrackClick(item.getItemId()); + return true; + } + return super.onOptionsItemSelected(item); } @@ -591,4 +607,77 @@ public final class PlayQueueActivity extends AppCompatActivity item.setIcon(player.isMuted() ? R.drawable.ic_volume_off : R.drawable.ic_volume_up); } } + + @Override + public void onAudioTrackUpdate() { + buildAudioTrackMenu(); + } + + private void buildAudioTrackMenu() { + if (menu == null) { + return; + } + + final MenuItem audioTrackSelector = menu.findItem(R.id.action_audio_track); + final List availableStreams = + Optional.ofNullable(player.getCurrentMetadata()) + .flatMap(MediaItemTag::getMaybeAudioTrack) + .map(MediaItemTag.AudioTrack::getAudioStreams) + .orElse(null); + + if (availableStreams == null || availableStreams.size() < 2) { + audioTrackSelector.setVisible(false); + } else { + final SubMenu audioTrackMenu = audioTrackSelector.getSubMenu(); + audioTrackMenu.clear(); + + for (int i = 0; i < availableStreams.size(); i++) { + final AudioStream audioStream = availableStreams.get(i); + if (audioStream.getAudioTrackName() == null) { + continue; + } + + audioTrackMenu.add(MENU_ID_AUDIO_TRACK, i, Menu.NONE, + audioStream.getAudioTrackName()); + } + + player.getSelectedAudioStream().ifPresent(s -> { + final String trackName = Objects.toString(s.getAudioTrackName(), ""); + audioTrackSelector.setTitle(getString(R.string.play_queue_audio_track) + trackName); + + final String shortName = s.getAudioLocale() != null + ? s.getAudioLocale().getLanguage() : trackName; + audioTrackSelector.setTitleCondensed( + shortName.substring(0, Math.min(shortName.length(), 2))); + audioTrackSelector.setVisible(true); + }); + + } + } + + /** + * Called when an item from the audio track selector is selected. + * + * @param itemId index of the selected item + */ + private void onAudioTrackClick(final int itemId) { + @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); + if (currentMetadata == null || currentMetadata.getMaybeAudioTrack().isEmpty()) { + return; + } + + final MediaItemTag.AudioTrack audioTrack = + currentMetadata.getMaybeAudioTrack().get(); + final List availableStreams = audioTrack.getAudioStreams(); + final int selectedStreamIndex = audioTrack.getSelectedAudioStreamIndex(); + if (selectedStreamIndex == itemId || availableStreams.size() <= itemId) { + return; + } + + player.saveStreamProgressState(); + final String newAudioTrack = availableStreams.get(itemId).getAudioTrackId(); + player.setRecovery(); + player.setAudioTrack(newAudioTrack); + player.reloadPlayQueueManager(); + } } 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 69b161631..2d6d23a23 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1243,6 +1243,9 @@ public final class Player implements PlaybackListener, Listener { } final StreamInfo previousInfo = Optional.ofNullable(currentMetadata) .flatMap(MediaItemTag::getMaybeStreamInfo).orElse(null); + final MediaItemTag.AudioTrack previousAudioTrack = + Optional.ofNullable(currentMetadata) + .flatMap(MediaItemTag::getMaybeAudioTrack).orElse(null); currentMetadata = tag; if (!currentMetadata.getErrors().isEmpty()) { @@ -1263,6 +1266,12 @@ public final class Player implements PlaybackListener, Listener { if (previousInfo == null || !previousInfo.getUrl().equals(info.getUrl())) { // only update with the new stream info if it has actually changed updateMetadataWith(info); + } else if (previousAudioTrack == null + || tag.getMaybeAudioTrack() + .map(t -> t.getSelectedAudioStreamIndex() + != previousAudioTrack.getSelectedAudioStreamIndex()) + .orElse(false)) { + notifyAudioTrackUpdateToListeners(); } }); }); @@ -1759,6 +1768,7 @@ public final class Player implements PlaybackListener, Listener { registerStreamViewed(); notifyMetadataUpdateToListeners(); + notifyAudioTrackUpdateToListeners(); UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @@ -1890,8 +1900,8 @@ public final class Player implements PlaybackListener, Listener { public Optional getSelectedAudioStream() { return Optional.ofNullable(currentMetadata) - .flatMap(MediaItemTag::getMaybeAudioLanguage) - .map(MediaItemTag.AudioLanguage::getSelectedAudioStream); + .flatMap(MediaItemTag::getMaybeAudioTrack) + .map(MediaItemTag.AudioTrack::getSelectedAudioStream); } //endregion @@ -2024,6 +2034,15 @@ public final class Player implements PlaybackListener, Listener { } } + private void notifyAudioTrackUpdateToListeners() { + if (fragmentListener != null) { + fragmentListener.onAudioTrackUpdate(); + } + if (activityListener != null) { + activityListener.onAudioTrackUpdate(); + } + } + public void useVideoSource(final boolean videoEnabled) { if (playQueue == null || isAudioOnly == !videoEnabled || audioPlayerSelected()) { return; @@ -2185,8 +2204,9 @@ public final class Player implements PlaybackListener, Listener { videoResolver.setPlaybackQuality(quality); } - public void setAudioLanguage(@Nullable final String language) { - videoResolver.setAudioLanguage(language); + public void setAudioTrack(@Nullable final String audioTrackId) { + videoResolver.setAudioTrack(audioTrackId); + audioResolver.setAudioTrack(audioTrackId); } 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 84bd9d277..2cca259c2 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 @@ -11,5 +11,6 @@ public interface PlayerEventListener { PlaybackParameters parameters); void onProgressUpdate(int currentProgress, int duration, int bufferPercent); void onMetadataUpdate(StreamInfo info, PlayQueue queue); + default void onAudioTrackUpdate() { } void onServiceStopped(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java index 0ef1eaaf1..1119fb903 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java @@ -57,7 +57,7 @@ public interface MediaItemTag { } @NonNull - default Optional getMaybeAudioLanguage() { + default Optional getMaybeAudioTrack() { return Optional.empty(); } @@ -135,20 +135,20 @@ public interface MediaItemTag { } } - final class AudioLanguage { + final class AudioTrack { @NonNull private final List audioStreams; private final int selectedAudioStreamIndex; - private AudioLanguage(@NonNull final List audioStreams, - final int selectedAudioStreamIndex) { + private AudioTrack(@NonNull final List audioStreams, + final int selectedAudioStreamIndex) { this.audioStreams = audioStreams; this.selectedAudioStreamIndex = selectedAudioStreamIndex; } - static AudioLanguage of(@NonNull final List audioStreams, - final int selectedAudioStreamIndex) { - return new AudioLanguage(audioStreams, selectedAudioStreamIndex); + static AudioTrack of(@NonNull final List audioStreams, + final int selectedAudioStreamIndex) { + return new AudioTrack(audioStreams, selectedAudioStreamIndex); } @NonNull diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java index 5379fc5a6..689f5c72b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java @@ -26,17 +26,17 @@ public final class StreamInfoTag implements MediaItemTag { @Nullable private final MediaItemTag.Quality quality; @Nullable - private final MediaItemTag.AudioLanguage audioLanguage; + private final MediaItemTag.AudioTrack audioTrack; @Nullable private final Object extras; private StreamInfoTag(@NonNull final StreamInfo streamInfo, @Nullable final MediaItemTag.Quality quality, - @Nullable final MediaItemTag.AudioLanguage audioLanguage, + @Nullable final MediaItemTag.AudioTrack audioTrack, @Nullable final Object extras) { this.streamInfo = streamInfo; this.quality = quality; - this.audioLanguage = audioLanguage; + this.audioTrack = audioTrack; this.extras = extras; } @@ -46,9 +46,17 @@ public final class StreamInfoTag implements MediaItemTag { @NonNull final List audioStreams, final int selectedAudioStreamIndex) { final Quality quality = Quality.of(sortedVideoStreams, selectedVideoStreamIndex); - final AudioLanguage audioLanguage = - AudioLanguage.of(audioStreams, selectedAudioStreamIndex); - return new StreamInfoTag(streamInfo, quality, audioLanguage, null); + final AudioTrack audioTrack = + AudioTrack.of(audioStreams, selectedAudioStreamIndex); + return new StreamInfoTag(streamInfo, quality, audioTrack, null); + } + + public static StreamInfoTag of(@NonNull final StreamInfo streamInfo, + @NonNull final List audioStreams, + final int selectedAudioStreamIndex) { + final AudioTrack audioTrack = + AudioTrack.of(audioStreams, selectedAudioStreamIndex); + return new StreamInfoTag(streamInfo, null, audioTrack, null); } public static StreamInfoTag of(@NonNull final StreamInfo streamInfo) { @@ -114,8 +122,8 @@ public final class StreamInfoTag implements MediaItemTag { @NonNull @Override - public Optional getMaybeAudioLanguage() { - return Optional.ofNullable(audioLanguage); + public Optional getMaybeAudioTrack() { + return Optional.ofNullable(audioTrack); } @Override @@ -125,6 +133,6 @@ public final class StreamInfoTag implements MediaItemTag { @Override public StreamInfoTag withExtras(@NonNull final Object extra) { - return new StreamInfoTag(streamInfo, quality, audioLanguage, extra); + return new StreamInfoTag(streamInfo, quality, audioTrack, extra); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java index e87c93114..2e7b5c7d5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.resolver; +import static org.schabi.newpipe.util.ListHelper.getFilteredAudioStreams; import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams; import android.content.Context; @@ -28,6 +29,8 @@ public class AudioPlaybackResolver implements PlaybackResolver { private final Context context; @NonNull private final PlayerDataSource dataSource; + @Nullable + private String audioTrack; public AudioPlaybackResolver(@NonNull final Context context, @NonNull final PlayerDataSource dataSource) { @@ -43,12 +46,36 @@ public class AudioPlaybackResolver implements PlaybackResolver { return liveSource; } - final Stream stream = getAudioSource(info); - if (stream == null) { - return null; - } + final List audioStreams = + getFilteredAudioStreams(context, info.getAudioStreams()); + final Stream stream; + final MediaItemTag tag; - final MediaItemTag tag = StreamInfoTag.of(info); + if (!audioStreams.isEmpty()) { + int audioIndex = 0; + + if (audioTrack != null) { + for (int i = 0; i < audioStreams.size(); i++) { + final AudioStream audioStream = audioStreams.get(i); + if (audioStream.getAudioTrackId() != null + && audioStream.getAudioTrackId().equals(audioTrack)) { + audioIndex = i; + break; + } + } + } + stream = getStreamForIndex(audioIndex, audioStreams); + tag = StreamInfoTag.of(info, audioStreams, audioIndex); + } else { + final List videoStreams = getNonTorrentStreams(info.getVideoStreams()); + if (!videoStreams.isEmpty()) { + final int index = ListHelper.getDefaultResolutionIndex(context, videoStreams); + stream = getStreamForIndex(index, videoStreams); + tag = StreamInfoTag.of(info); + } else { + return null; + } + } try { return PlaybackResolver.buildMediaSource( @@ -59,29 +86,6 @@ public class AudioPlaybackResolver implements PlaybackResolver { } } - /** - * Get a stream to be played as audio. If a service has no separate {@link AudioStream}s we - * use a video stream as audio source to support audio background playback. - * - * @param info of the stream - * @return the audio source to use or null if none could be found - */ - @Nullable - private Stream getAudioSource(@NonNull final StreamInfo info) { - final List audioStreams = getNonTorrentStreams(info.getAudioStreams()); - if (!audioStreams.isEmpty()) { - final int index = ListHelper.getDefaultAudioFormat(context, audioStreams); - return getStreamForIndex(index, audioStreams); - } else { - final List videoStreams = getNonTorrentStreams(info.getVideoStreams()); - if (!videoStreams.isEmpty()) { - final int index = ListHelper.getDefaultResolutionIndex(context, videoStreams); - return getStreamForIndex(index, videoStreams); - } - } - return null; - } - @Nullable Stream getStreamForIndex(final int index, @NonNull final List streams) { if (index >= 0 && index < streams.size()) { @@ -89,4 +93,13 @@ public class AudioPlaybackResolver implements PlaybackResolver { } return null; } + + @Nullable + public String getAudioTrack() { + return audioTrack; + } + + public void setAudioTrack(@Nullable final String audioLanguage) { + this.audioTrack = audioLanguage; + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java index 2f0c59325..64349409d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -46,7 +46,7 @@ public class VideoPlaybackResolver implements PlaybackResolver { @Nullable private String playbackQuality; @Nullable - private String audioLanguage; + private String audioTrack; public enum SourceType { LIVE_STREAM, @@ -91,11 +91,11 @@ public class VideoPlaybackResolver implements PlaybackResolver { } int audioIndex = 0; - if (audioLanguage != null) { + if (audioTrack != null) { for (int i = 0; i < audioStreamsList.size(); i++) { final AudioStream stream = audioStreamsList.get(i); if (stream.getAudioTrackId() != null - && stream.getAudioTrackId().equals(audioLanguage)) { + && stream.getAudioTrackId().equals(audioTrack)) { audioIndex = i; break; } @@ -107,8 +107,8 @@ public class VideoPlaybackResolver implements PlaybackResolver { @Nullable final VideoStream video = tag.getMaybeQuality() .map(MediaItemTag.Quality::getSelectedVideoStream) .orElse(null); - @Nullable final AudioStream audio = tag.getMaybeAudioLanguage() - .map(MediaItemTag.AudioLanguage::getSelectedAudioStream) + @Nullable final AudioStream audio = tag.getMaybeAudioTrack() + .map(MediaItemTag.AudioTrack::getSelectedAudioStream) .orElse(null); if (video != null) { @@ -124,7 +124,7 @@ public class VideoPlaybackResolver implements PlaybackResolver { // Use the audio stream if there is no video stream, or // merge with audio stream in case if video does not contain audio - if (audio != null && (video == null || video.isVideoOnly() || audioLanguage != null)) { + if (audio != null && (video == null || video.isVideoOnly() || audioTrack != null)) { try { final MediaSource audioSource = PlaybackResolver.buildMediaSource( dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag); @@ -198,12 +198,12 @@ public class VideoPlaybackResolver implements PlaybackResolver { } @Nullable - public String getAudioLanguage() { - return audioLanguage; + public String getAudioTrack() { + return audioTrack; } - public void setAudioLanguage(@Nullable final String audioLanguage) { - this.audioLanguage = audioLanguage; + public void setAudioTrack(@Nullable final String audioLanguage) { + this.audioTrack = audioLanguage; } public interface QualityResolver { @@ -211,10 +211,4 @@ public class VideoPlaybackResolver implements PlaybackResolver { int getOverrideResolutionIndex(List sortedVideos, String playbackQuality); } - - public interface AudioLanguageResolver { - int getDefaultLanguageIndex(List audioStreams); - - int getOverrideLanguageIndex(List audioStreams, String audioLanguage); - } } 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 13cbc9fd3..df966d591 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 @@ -118,13 +118,13 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa //////////////////////////////////////////////////////////////////////////*/ private static final int POPUP_MENU_ID_QUALITY = 69; - private static final int POPUP_MENU_ID_LANGUAGE = 70; + private static final int POPUP_MENU_ID_AUDIO_TRACK = 70; 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; - private PopupMenu languagePopupMenu; + private PopupMenu audioTrackPopupMenu; protected PopupMenu playbackSpeedPopupMenu; private PopupMenu captionPopupMenu; @@ -176,7 +176,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa R.style.DarkPopupMenu); qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); - languagePopupMenu = new PopupMenu(themeWrapper, binding.languageTextView); + audioTrackPopupMenu = new PopupMenu(themeWrapper, binding.audioTrackTextView); playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); @@ -194,8 +194,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa protected void initListeners() { binding.qualityTextView.setOnClickListener(makeOnClickListener(this::onQualityClicked)); - binding.languageTextView.setOnClickListener( - makeOnClickListener(this::onAudioLanguageClicked)); + binding.audioTrackTextView.setOnClickListener( + makeOnClickListener(this::onAudioTracksClicked)); binding.playbackSpeed.setOnClickListener(makeOnClickListener(this::onPlaybackSpeedClicked)); binding.playbackSeekBar.setOnSeekBarChangeListener(this); @@ -272,7 +272,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa protected void deinitListeners() { binding.qualityTextView.setOnClickListener(null); - binding.languageTextView.setOnClickListener(null); + binding.audioTrackTextView.setOnClickListener(null); binding.playbackSpeed.setOnClickListener(null); binding.playbackSeekBar.setOnSeekBarChangeListener(null); binding.captionTextView.setOnClickListener(null); @@ -426,7 +426,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - binding.languageTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.audioTrackTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); @@ -992,7 +992,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa private void updateStreamRelatedViews() { player.getCurrentStreamInfo().ifPresent(info -> { binding.qualityTextView.setVisibility(View.GONE); - binding.languageTextView.setVisibility(View.GONE); + binding.audioTrackTextView.setVisibility(View.GONE); binding.playbackSpeed.setVisibility(View.GONE); binding.playbackEndTime.setVisibility(View.GONE); @@ -1028,7 +1028,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa } buildQualityMenu(); - buildLanguageMenu(); + buildAudioTrackMenu(); binding.qualityTextView.setVisibility(View.VISIBLE); binding.surfaceView.setVisibility(View.VISIBLE); @@ -1077,15 +1077,15 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa .ifPresent(s -> binding.qualityTextView.setText(s.getResolution())); } - private void buildLanguageMenu() { - if (languagePopupMenu == null) { + private void buildAudioTrackMenu() { + if (audioTrackPopupMenu == null) { return; } - languagePopupMenu.getMenu().removeGroup(POPUP_MENU_ID_LANGUAGE); + audioTrackPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_AUDIO_TRACK); final List availableStreams = Optional.ofNullable(player.getCurrentMetadata()) - .flatMap(MediaItemTag::getMaybeAudioLanguage) - .map(MediaItemTag.AudioLanguage::getAudioStreams) + .flatMap(MediaItemTag::getMaybeAudioTrack) + .map(MediaItemTag.AudioTrack::getAudioStreams) .orElse(null); if (availableStreams == null || availableStreams.size() < 2) { return; @@ -1096,15 +1096,15 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa if (audioStream.getAudioTrackName() == null) { continue; } - languagePopupMenu.getMenu().add(POPUP_MENU_ID_LANGUAGE, i, Menu.NONE, + audioTrackPopupMenu.getMenu().add(POPUP_MENU_ID_AUDIO_TRACK, i, Menu.NONE, audioStream.getAudioTrackName()); } player.getSelectedAudioStream() - .ifPresent(s -> binding.languageTextView.setText(s.getAudioTrackName())); - binding.languageTextView.setVisibility(View.VISIBLE); - languagePopupMenu.setOnMenuItemClickListener(this); - languagePopupMenu.setOnDismissListener(this); + .ifPresent(s -> binding.audioTrackTextView.setText(s.getAudioTrackName())); + binding.audioTrackTextView.setVisibility(View.VISIBLE); + audioTrackPopupMenu.setOnMenuItemClickListener(this); + audioTrackPopupMenu.setOnDismissListener(this); } private void buildPlaybackSpeedMenu() { @@ -1215,13 +1215,13 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa .ifPresent(binding.qualityTextView::setText); } - private void onAudioLanguageClicked() { - languagePopupMenu.show(); + private void onAudioTracksClicked() { + audioTrackPopupMenu.show(); isSomePopupMenuVisible = true; player.getSelectedAudioStream() .map(AudioStream::getAudioTrackName) - .ifPresent(binding.languageTextView::setText); + .ifPresent(binding.audioTrackTextView::setText); } /** @@ -1238,8 +1238,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { onQualityItemClick(menuItem); return true; - } else if (menuItem.getGroupId() == POPUP_MENU_ID_LANGUAGE) { - onLanguageItemClick(menuItem); + } else if (menuItem.getGroupId() == POPUP_MENU_ID_AUDIO_TRACK) { + onAudioTrackItemClick(menuItem); return true; } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { final int speedIndex = menuItem.getItemId(); @@ -1275,28 +1275,28 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa binding.qualityTextView.setText(menuItem.getTitle()); } - private void onLanguageItemClick(@NonNull final MenuItem menuItem) { + private void onAudioTrackItemClick(@NonNull final MenuItem menuItem) { final int menuItemIndex = menuItem.getItemId(); @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); - if (currentMetadata == null || currentMetadata.getMaybeAudioLanguage().isEmpty()) { + if (currentMetadata == null || currentMetadata.getMaybeAudioTrack().isEmpty()) { return; } - final MediaItemTag.AudioLanguage language = - currentMetadata.getMaybeAudioLanguage().get(); - final List availableStreams = language.getAudioStreams(); - final int selectedStreamIndex = language.getSelectedAudioStreamIndex(); + final MediaItemTag.AudioTrack audioTrack = + currentMetadata.getMaybeAudioTrack().get(); + final List availableStreams = audioTrack.getAudioStreams(); + final int selectedStreamIndex = audioTrack.getSelectedAudioStreamIndex(); if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { return; } player.saveStreamProgressState(); - final String newLanguage = availableStreams.get(menuItemIndex).getAudioTrackId(); + final String newAudioTrack = availableStreams.get(menuItemIndex).getAudioTrackId(); player.setRecovery(); - player.setAudioLanguage(newLanguage); + player.setAudioTrack(newAudioTrack); player.reloadPlayQueueManager(); - binding.languageTextView.setText(menuItem.getTitle()); + binding.audioTrackTextView.setText(menuItem.getTitle()); } /** 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 ef40692d7..a2e8216a3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -188,6 +188,14 @@ public final class ListHelper { videoOnlyStreams, ascendingOrder, preferVideoOnlyStreams); } + /** + * Filter the list of audio streams and return a list with the preferred stream for + * each audio track. Streams are sorted with the preferred language in the first position. + * + * @param context the context to search for the track to give preference + * @param audioStreams the list of audio streams + * @return the sorted, filtered list + */ public static List getFilteredAudioStreams( @NonNull final Context context, @Nullable final List audioStreams) { diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index 82760be3a..89f1ed88e 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -158,7 +158,7 @@ + + + + Remove Details Audio Settings + Audio: + Audio track Hold to enqueue Show channel details Enqueue From 87a88e4df7f6c0040e5bfce735a24d7f8034b20d Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 19 Mar 2023 03:06:29 +0100 Subject: [PATCH 06/20] feat: localized audio track names --- app/build.gradle | 2 +- .../newpipe/player/PlayQueueActivity.java | 32 +++++++------- .../newpipe/player/ui/VideoPlayerUi.java | 13 ++---- .../org/schabi/newpipe/util/ListHelper.java | 11 ++++- .../org/schabi/newpipe/util/Localization.java | 42 +++++++++++++++++++ app/src/main/res/values/strings.xml | 6 ++- 6 files changed, 76 insertions(+), 30 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c0d423817..a766b16a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -191,7 +191,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:5a9b6ed2e3306b9152cc6689dd61dbbe43483845' + implementation 'com.github.Theta-Dev:NewPipeExtractor:3fb356a7065c75909ee3856a29be92317c295bb9' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ 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 fa3668e8b..8c56627ec 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -5,6 +5,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; @@ -48,7 +49,6 @@ import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; import java.util.List; -import java.util.Objects; import java.util.Optional; public final class PlayQueueActivity extends AppCompatActivity @@ -618,14 +618,17 @@ public final class PlayQueueActivity extends AppCompatActivity return; } + final Context context = player.getContext(); final MenuItem audioTrackSelector = menu.findItem(R.id.action_audio_track); final List availableStreams = Optional.ofNullable(player.getCurrentMetadata()) .flatMap(MediaItemTag::getMaybeAudioTrack) .map(MediaItemTag.AudioTrack::getAudioStreams) .orElse(null); + final Optional selectedAudioStream = player.getSelectedAudioStream(); - if (availableStreams == null || availableStreams.size() < 2) { + if (availableStreams == null || availableStreams.size() < 2 + || selectedAudioStream.isEmpty()) { audioTrackSelector.setVisible(false); } else { final SubMenu audioTrackMenu = audioTrackSelector.getSubMenu(); @@ -633,25 +636,20 @@ public final class PlayQueueActivity extends AppCompatActivity for (int i = 0; i < availableStreams.size(); i++) { final AudioStream audioStream = availableStreams.get(i); - if (audioStream.getAudioTrackName() == null) { - continue; - } - audioTrackMenu.add(MENU_ID_AUDIO_TRACK, i, Menu.NONE, - audioStream.getAudioTrackName()); + Localization.audioTrackName(context, audioStream)); } - player.getSelectedAudioStream().ifPresent(s -> { - final String trackName = Objects.toString(s.getAudioTrackName(), ""); - audioTrackSelector.setTitle(getString(R.string.play_queue_audio_track) + trackName); - - final String shortName = s.getAudioLocale() != null - ? s.getAudioLocale().getLanguage() : trackName; - audioTrackSelector.setTitleCondensed( - shortName.substring(0, Math.min(shortName.length(), 2))); - audioTrackSelector.setVisible(true); - }); + final AudioStream s = selectedAudioStream.get(); + final String trackName = Localization.audioTrackName(context, s); + audioTrackSelector.setTitle( + context.getString(R.string.play_queue_audio_track, trackName)); + final String shortName = s.getAudioLocale() != null + ? s.getAudioLocale().getLanguage() : trackName; + audioTrackSelector.setTitleCondensed( + shortName.substring(0, Math.min(shortName.length(), 2))); + audioTrackSelector.setVisible(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 df966d591..8aff0af87 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 @@ -79,6 +79,7 @@ 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.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; @@ -1093,15 +1094,13 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa for (int i = 0; i < availableStreams.size(); i++) { final AudioStream audioStream = availableStreams.get(i); - if (audioStream.getAudioTrackName() == null) { - continue; - } audioTrackPopupMenu.getMenu().add(POPUP_MENU_ID_AUDIO_TRACK, i, Menu.NONE, - audioStream.getAudioTrackName()); + Localization.audioTrackName(context, audioStream)); } player.getSelectedAudioStream() - .ifPresent(s -> binding.audioTrackTextView.setText(s.getAudioTrackName())); + .ifPresent(s -> binding.audioTrackTextView.setText( + Localization.audioTrackName(context, s))); binding.audioTrackTextView.setVisibility(View.VISIBLE); audioTrackPopupMenu.setOnMenuItemClickListener(this); audioTrackPopupMenu.setOnDismissListener(this); @@ -1218,10 +1217,6 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa private void onAudioTracksClicked() { audioTrackPopupMenu.show(); isSomePopupMenuVisible = true; - - player.getSelectedAudioStream() - .map(AudioStream::getAudioTrackName) - .ifPresent(binding.audioTrackTextView::setText); } /** 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 a2e8216a3..971ee2759 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -13,6 +13,7 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.AudioTrackType; import org.schabi.newpipe.extractor.stream.DeliveryMethod; import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.VideoStream; @@ -244,8 +245,14 @@ public final class ListHelper { final Comparator trackCmp = getAudioTrackComparator(preferredLanguageOrEnglish, preferDescriptiveAudio); + // Filter unknown audio tracks if there are multiple tracks + java.util.stream.Stream cs = collectedStreams.values().stream(); + if (collectedStreams.size() > 1) { + cs = cs.filter(s -> s.getAudioTrackId() != null); + } + // Sort collected streams - return collectedStreams.values().stream().sorted(trackCmp).collect(Collectors.toList()); + return cs.sorted(trackCmp).collect(Collectors.toList()); } /*////////////////////////////////////////////////////////////////////////// @@ -672,7 +679,7 @@ public final class ListHelper { return Comparator.comparing(AudioStream::getAudioLocale, (o1, o2) -> Boolean.compare( o1 == null || !o1.getISO3Language().equals(preferredLanguage), o2 == null || !o2.getISO3Language().equals(preferredLanguage)) - ).thenComparing(AudioStream::isDescriptive, (o1, o2) -> + ).thenComparing(s -> s.getAudioTrackType() == AudioTrackType.DESCRIPTIVE, (o1, o2) -> Boolean.compare(o1 ^ preferDescriptiveAudio, o2 ^ preferDescriptiveAudio) ).thenComparing(AudioStream::getAudioTrackName, (o1, o2) -> { if (o1 != null) { 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 916b902f0..b4745560c 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -21,6 +21,7 @@ import org.ocpsoft.prettytime.units.Decade; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.localization.ContentCountry; +import org.schabi.newpipe.extractor.stream.AudioStream; import java.math.BigDecimal; import java.math.RoundingMode; @@ -261,6 +262,47 @@ public final class Localization { } } + /** + * Get the localized name of an audio track. + *

Example:

+ *

English (original)

+ *

English (descriptive)

+ *

Spanish (dubbed)

+ * + * @param context used to get app language + * @param track a {@link AudioStream} of the track + * @return localized track name + */ + public static String audioTrackName(final Context context, final AudioStream track) { + String res; + + if (track.getAudioLocale() != null) { + res = track.getAudioLocale().getDisplayLanguage(getAppLocale(context)); + } else if (track.getAudioTrackName() != null) { + res = track.getAudioTrackName(); + } else { + res = context.getString(R.string.unknown_audio_track); + } + + if (track.getAudioTrackType() != null) { + res += " ("; + switch (track.getAudioTrackType()) { + case ORIGINAL: + res += context.getString(R.string.track_type_original); + break; + case DUBBED: + res += context.getString(R.string.track_type_dubbed); + break; + case DESCRIPTIVE: + res += context.getString(R.string.track_type_descriptive); + break; + } + res += ")"; + } + + return res; + } + /*////////////////////////////////////////////////////////////////////////// // Pretty Time //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8c578eeb2..91d618852 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -413,7 +413,7 @@ Remove Details Audio Settings - Audio: + Audio: %s Audio track Hold to enqueue Show channel details @@ -768,10 +768,14 @@ Select quality for external players Unknown format Unknown quality + Unknown Show future items Hide future items Fully watched Partially watched Upcoming Sort + original + dubbed + descriptive \ No newline at end of file From 7aed2eed8af152edd2713c2ce55c6319d9d31046 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 19 Mar 2023 20:40:27 +0100 Subject: [PATCH 07/20] feat: add prefer original option, improve audio stream ordering --- .../resolver/AudioPlaybackResolver.java | 14 +- .../resolver/VideoPlaybackResolver.java | 14 +- .../org/schabi/newpipe/util/ListHelper.java | 179 +++++++++--------- app/src/main/res/values/settings_keys.xml | 3 +- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/content_settings.xml | 8 - app/src/main/res/xml/video_audio_settings.xml | 16 ++ 7 files changed, 113 insertions(+), 123 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java index 2e7b5c7d5..e94295724 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java @@ -52,18 +52,8 @@ public class AudioPlaybackResolver implements PlaybackResolver { final MediaItemTag tag; if (!audioStreams.isEmpty()) { - int audioIndex = 0; - - if (audioTrack != null) { - for (int i = 0; i < audioStreams.size(); i++) { - final AudioStream audioStream = audioStreams.get(i); - if (audioStream.getAudioTrackId() != null - && audioStream.getAudioTrackId().equals(audioTrack)) { - audioIndex = i; - break; - } - } - } + final int audioIndex = + ListHelper.getAudioFormatIndex(context, audioStreams, audioTrack); stream = getStreamForIndex(audioIndex, audioStreams); tag = StreamInfoTag.of(info, audioStreams, audioIndex); } else { diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java index 64349409d..4f5465751 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -90,18 +90,8 @@ public class VideoPlaybackResolver implements PlaybackResolver { getPlaybackQuality()); } - int audioIndex = 0; - if (audioTrack != null) { - for (int i = 0; i < audioStreamsList.size(); i++) { - final AudioStream stream = audioStreamsList.get(i); - if (stream.getAudioTrackId() != null - && stream.getAudioTrackId().equals(audioTrack)) { - audioIndex = i; - break; - } - } - } - + final int audioIndex = + ListHelper.getAudioFormatIndex(context, audioStreamsList, audioTrack); final MediaItemTag tag = StreamInfoTag.of(info, videoStreamsList, videoIndex, audioStreamsList, audioIndex); @Nullable final VideoStream video = tag.getMaybeQuality() 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 971ee2759..1be020eae 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -38,11 +38,17 @@ public final class ListHelper { // Audio format in order of quality. 0=lowest quality, n=highest quality private static final List AUDIO_FORMAT_QUALITY_RANKING = List.of(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); - // Audio format in order of efficiency. 0=most efficient, n=least efficient + // Audio format in order of efficiency. 0=least efficient, n=most efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = - List.of(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); + List.of(MediaFormat.MP3, MediaFormat.M4A, MediaFormat.WEBMA); // Use a Set for better performance private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); + // Audio track types in order of priotity. 0=lowest, n=highest + private static final List AUDIO_TRACK_TYPE_RANKING = + List.of(AudioTrackType.DESCRIPTIVE, AudioTrackType.DUBBED, AudioTrackType.ORIGINAL); + // Audio track types in order of priotity when descriptive audio is preferred. + private static final List AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE = + List.of(AudioTrackType.ORIGINAL, AudioTrackType.DUBBED, AudioTrackType.DESCRIPTIVE); private ListHelper() { } @@ -104,13 +110,23 @@ public final class ListHelper { final MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key, R.string.default_audio_format_value); - // If the user has chosen to limit resolution to conserve mobile data - // usage then we should also limit our audio usage. - if (isLimitingDataUsage(context)) { - return getMostCompactAudioIndex(defaultFormat, audioStreams); - } else { - return getHighestQualityAudioIndex(defaultFormat, audioStreams); + return getAudioIndexByHighestRank(defaultFormat, audioStreams, + getAudioStreamComparator(context)); + } + + public static int getAudioFormatIndex(final Context context, + final List audioStreams, + @Nullable final String trackId) { + if (trackId != null) { + for (int i = 0; i < audioStreams.size(); i++) { + final AudioStream s = audioStreams.get(i); + if (s.getAudioTrackId() != null + && s.getAudioTrackId().equals(trackId)) { + return i; + } + } } + return getDefaultAudioFormat(context, audioStreams); } /** @@ -193,7 +209,7 @@ public final class ListHelper { * Filter the list of audio streams and return a list with the preferred stream for * each audio track. Streams are sorted with the preferred language in the first position. * - * @param context the context to search for the track to give preference + * @param context the context to search for the track to give preference * @param audioStreams the list of audio streams * @return the sorted, filtered list */ @@ -206,15 +222,7 @@ public final class ListHelper { final HashMap collectedStreams = new HashMap<>(); - final Comparator cmp; - if (isLimitingDataUsage(context)) { - cmp = getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING); - } else { - cmp = getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING); - } - - final String preferredLanguage = Localization.getPreferredLocale(context).getISO3Language(); - boolean hasPreferredLanguage = false; + final Comparator cmp = getAudioStreamFormatComparator(context); for (final AudioStream stream : audioStreams) { if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) { @@ -226,33 +234,18 @@ public final class ListHelper { final AudioStream presentStream = collectedStreams.get(trackId); if (presentStream == null || cmp.compare(stream, presentStream) > 0) { collectedStreams.put(trackId, stream); - - if (stream.getAudioLocale() != null - && stream.getAudioLocale().getISO3Language().equals(preferredLanguage)) { - hasPreferredLanguage = true; - } } } - // Fall back to English if the preferred language was not found - final String preferredLanguageOrEnglish = - hasPreferredLanguage ? preferredLanguage : Locale.ENGLISH.getISO3Language(); - final SharedPreferences preferences = - PreferenceManager.getDefaultSharedPreferences(context); - final boolean preferDescriptiveAudio = - preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key), - false); - final Comparator trackCmp = - getAudioTrackComparator(preferredLanguageOrEnglish, preferDescriptiveAudio); - // Filter unknown audio tracks if there are multiple tracks java.util.stream.Stream cs = collectedStreams.values().stream(); if (collectedStreams.size() > 1) { cs = cs.filter(s -> s.getAudioTrackId() != null); } - // Sort collected streams - return cs.sorted(trackCmp).collect(Collectors.toList()); + // Sort collected streams by name + return cs.sorted(Comparator.comparing(audioStream -> + Localization.audioTrackName(context, audioStream))).collect(Collectors.toList()); } /*////////////////////////////////////////////////////////////////////////// @@ -420,42 +413,6 @@ public final class ListHelper { return videoStreams; } - /** - * Get the audio from the list with the highest quality. - * Format will be ignored if it yields no results. - * - * @param format The target format type or null if it doesn't matter - * @param audioStreams List of audio streams - * @return Index of audio stream that produces the most compact results or -1 if not found - */ - static int getHighestQualityAudioIndex(@Nullable final MediaFormat format, - @Nullable final List audioStreams) { - return getAudioIndexByHighestRank(format, audioStreams, - // Compares descending (last = highest rank) - getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING)); - } - - /** - * Get the audio from the list with the lowest bitrate and most efficient format. - * Format will be ignored if it yields no results. - * - * @param format The target format type or null if it doesn't matter - * @param audioStreams List of audio streams - * @return Index of audio stream that produces the most compact results or -1 if not found - */ - static int getMostCompactAudioIndex(@Nullable final MediaFormat format, - @Nullable final List audioStreams) { - return getAudioIndexByHighestRank(format, audioStreams, - // The "reversed()" is important -> Compares ascending (first = highest rank) - 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())); - } - /** * Get the audio-stream from the list with the highest rank, depending on the comparator. * Format will be ignored if it yields no results. @@ -674,23 +631,65 @@ public final class ListHelper { return manager.isActiveNetworkMetered(); } - private static Comparator getAudioTrackComparator( - final String preferredLanguage, final boolean preferDescriptiveAudio) { - return Comparator.comparing(AudioStream::getAudioLocale, (o1, o2) -> Boolean.compare( - o1 == null || !o1.getISO3Language().equals(preferredLanguage), - o2 == null || !o2.getISO3Language().equals(preferredLanguage)) - ).thenComparing(s -> s.getAudioTrackType() == AudioTrackType.DESCRIPTIVE, (o1, o2) -> - Boolean.compare(o1 ^ preferDescriptiveAudio, o2 ^ preferDescriptiveAudio) - ).thenComparing(AudioStream::getAudioTrackName, (o1, o2) -> { - if (o1 != null) { - if (o2 != null) { - return o1.compareTo(o2); - } else { - return -1; - } - } else { - return 1; - } - }); + /** + * Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate. + * + * @param context App context + * @return Comparator + */ + private static Comparator getAudioStreamFormatComparator( + @NonNull final Context context) { + final boolean limitDataUsage = isLimitingDataUsage(context); + final List formatRanking = limitDataUsage + ? AUDIO_FORMAT_EFFICIENCY_RANKING : AUDIO_FORMAT_QUALITY_RANKING; + + Comparator bitrateComparator = + Comparator.comparingInt(AudioStream::getAverageBitrate); + if (limitDataUsage) { + bitrateComparator = bitrateComparator.reversed(); + } + + return bitrateComparator.thenComparingInt( + stream -> formatRanking.indexOf(stream.getFormat())); + } + + /** + * Get a {@link Comparator} to compare {@link AudioStream}s by their language, format + * and bitrate. + * + * @param context App context + * @return Comparator + */ + private static Comparator getAudioStreamComparator( + @NonNull final Context context) { + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); + final boolean preferOriginalAudio = + preferences.getBoolean(context.getString(R.string.prefer_original_audio_key), + false); + final boolean preferDescriptiveAudio = + preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key), + false); + final String preferredLanguage = Localization.getPreferredLocale(context).getISO3Language(); + + final List trackTypeRanking = preferDescriptiveAudio + ? AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE : AUDIO_TRACK_TYPE_RANKING; + + return Comparator.comparing(AudioStream::getAudioTrackType, (o1, o2) -> { + if (preferOriginalAudio) { + return Boolean.compare( + o1 == AudioTrackType.ORIGINAL, o2 == AudioTrackType.ORIGINAL); + } + return 0; + }).thenComparing(AudioStream::getAudioLocale, + Comparator.nullsFirst(Comparator.comparing( + locale -> locale.getISO3Language().equals(preferredLanguage)))) + .thenComparing(AudioStream::getAudioTrackType, + Comparator.nullsLast(Comparator.comparingInt(trackTypeRanking::indexOf))) + .thenComparing(AudioStream::getAudioLocale, + Comparator.nullsFirst(Comparator.comparing( + locale -> locale.getISO3Language().equals( + Locale.ENGLISH.getISO3Language())))) + .thenComparing(getAudioStreamFormatComparator(context)); } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 9ed34bf26..36dcf2b33 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -192,6 +192,8 @@ @string/audio_webm_key + prefer_original_audio + prefer_descriptive_audio last_resize_mode @@ -260,7 +262,6 @@ show_next_video show_description show_meta_info - prefer_descriptive_audio stream_info_selected_tab show_hold_to_append content_language diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 91d618852..475585879 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -94,6 +94,8 @@ Turn off to hide video description and additional information Show meta info Turn off to hide meta info boxes with additional information about the stream creator, stream content or a search request + Prefer original audio + Select the original audio track regardless of the language Prefer descriptive audio Select an audio track with descriptions for visually impaired people if available Image cache wiped diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 684b9e558..fddb966c8 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -114,14 +114,6 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> - - + + + + Date: Sun, 19 Mar 2023 21:05:48 +0100 Subject: [PATCH 08/20] feat: add external audio playback language selector --- .../fragments/detail/VideoDetailFragment.java | 65 ++++++++++++++----- .../newpipe/player/PlayQueueActivity.java | 8 +-- app/src/main/res/values/strings.xml | 1 + 3 files changed, 52 insertions(+), 22 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 7278716c0..4bb418e80 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 @@ -164,8 +164,12 @@ public final class VideoDetailFragment private boolean showRelatedItems; private boolean showDescription; private String selectedTabTag; - @AttrRes @NonNull final List tabIcons = new ArrayList<>(); - @StringRes @NonNull final List tabContentDescriptions = new ArrayList<>(); + @AttrRes + @NonNull + final List tabIcons = new ArrayList<>(); + @StringRes + @NonNull + final List tabContentDescriptions = new ArrayList<>(); private boolean tabSettingsChanged = false; private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates @@ -1042,20 +1046,10 @@ public final class VideoDetailFragment player.setRecovery(); } - if (!useExternalAudioPlayer) { - openNormalBackgroundPlayer(append); + if (useExternalAudioPlayer) { + showExternalAudioPlaybackDialog(); } else { - final List audioStreams = getUrlAndNonTorrentStreams( - currentInfo.getAudioStreams()); - final int index = ListHelper.getDefaultAudioFormat(activity, audioStreams); - - if (index == -1) { - Toast.makeText(activity, R.string.no_audio_streams_available_for_external_players, - Toast.LENGTH_SHORT).show(); - return; - } - - startOnExternalPlayer(activity, currentInfo, audioStreams.get(index)); + openNormalBackgroundPlayer(append); } } @@ -1108,7 +1102,7 @@ public final class VideoDetailFragment if (PreferenceManager.getDefaultSharedPreferences(activity) .getBoolean(this.getString(R.string.use_external_video_player_key), false)) { - showExternalPlaybackDialog(); + showExternalVideoPlaybackDialog(); } else { replaceQueueIfUserConfirms(this::openMainPlayer); } @@ -2102,7 +2096,7 @@ public final class VideoDetailFragment }).show(); } - private void showExternalPlaybackDialog() { + private void showExternalVideoPlaybackDialog() { if (currentInfo == null) { return; } @@ -2149,6 +2143,43 @@ public final class VideoDetailFragment builder.show(); } + private void showExternalAudioPlaybackDialog() { + if (currentInfo == null) { + return; + } + + final List audioStreams = getUrlAndNonTorrentStreams( + currentInfo.getAudioStreams()); + final List audioTracks = + ListHelper.getFilteredAudioStreams(activity, audioStreams); + + if (audioTracks.isEmpty()) { + Toast.makeText(activity, R.string.no_audio_streams_available_for_external_players, + Toast.LENGTH_SHORT).show(); + + } else if (audioTracks.size() == 1) { + startOnExternalPlayer(activity, currentInfo, audioTracks.get(0)); + } else { + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(R.string.select_audio_track_external_players); + + final int selectedAudioStream = + ListHelper.getDefaultAudioFormat(activity, audioTracks); + final CharSequence[] trackNames = audioTracks.stream() + .map(audioStream -> Localization.audioTrackName(activity, audioStream)) + .toArray(CharSequence[]::new); + + builder.setSingleChoiceItems(trackNames, selectedAudioStream, null); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, (dialog, i) -> { + final int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + startOnExternalPlayer(activity, currentInfo, + audioTracks.get(index)); + }); + builder.show(); + } + } + /* * Remove unneeded information while waiting for a next task * */ 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 8c56627ec..fd2128216 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -5,7 +5,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; @@ -618,7 +617,6 @@ public final class PlayQueueActivity extends AppCompatActivity return; } - final Context context = player.getContext(); final MenuItem audioTrackSelector = menu.findItem(R.id.action_audio_track); final List availableStreams = Optional.ofNullable(player.getCurrentMetadata()) @@ -637,13 +635,13 @@ public final class PlayQueueActivity extends AppCompatActivity for (int i = 0; i < availableStreams.size(); i++) { final AudioStream audioStream = availableStreams.get(i); audioTrackMenu.add(MENU_ID_AUDIO_TRACK, i, Menu.NONE, - Localization.audioTrackName(context, audioStream)); + Localization.audioTrackName(this, audioStream)); } final AudioStream s = selectedAudioStream.get(); - final String trackName = Localization.audioTrackName(context, s); + final String trackName = Localization.audioTrackName(this, s); audioTrackSelector.setTitle( - context.getString(R.string.play_queue_audio_track, trackName)); + getString(R.string.play_queue_audio_track, trackName)); final String shortName = s.getAudioLocale() != null ? s.getAudioLocale().getLanguage() : trackName; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 475585879..a4fbba67b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -768,6 +768,7 @@ No audio streams are available for external players No video streams are available for external players Select quality for external players + Select audio track for external players Unknown format Unknown quality Unknown From 9b8ffdd2aa9a0c74d6bdd247ada98a9376df802a Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 19 Mar 2023 21:20:21 +0100 Subject: [PATCH 09/20] fix: improve track name localization --- .../org/schabi/newpipe/util/Localization.java | 40 ++++++++++--------- app/src/main/res/values/strings.xml | 7 ++-- 2 files changed, 26 insertions(+), 21 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 b4745560c..9123d377c 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -11,6 +11,7 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; import androidx.core.math.MathUtils; @@ -22,6 +23,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.AudioTrackType; import java.math.BigDecimal; import java.math.RoundingMode; @@ -274,33 +276,35 @@ public final class Localization { * @return localized track name */ public static String audioTrackName(final Context context, final AudioStream track) { - String res; - + final String name; if (track.getAudioLocale() != null) { - res = track.getAudioLocale().getDisplayLanguage(getAppLocale(context)); + name = track.getAudioLocale().getDisplayLanguage(getAppLocale(context)); } else if (track.getAudioTrackName() != null) { - res = track.getAudioTrackName(); + name = track.getAudioTrackName(); } else { - res = context.getString(R.string.unknown_audio_track); + name = context.getString(R.string.unknown_audio_track); } if (track.getAudioTrackType() != null) { - res += " ("; - switch (track.getAudioTrackType()) { - case ORIGINAL: - res += context.getString(R.string.track_type_original); - break; - case DUBBED: - res += context.getString(R.string.track_type_dubbed); - break; - case DESCRIPTIVE: - res += context.getString(R.string.track_type_descriptive); - break; + final String trackType = audioTrackType(context, track.getAudioTrackType()); + if (trackType != null) { + return context.getString(R.string.audio_track_name, name, trackType); } - res += ")"; } + return name; + } - return res; + @Nullable + private static String audioTrackType(final Context context, final AudioTrackType trackType) { + switch (trackType) { + case ORIGINAL: + return context.getString(R.string.audio_track_type_original); + case DUBBED: + return context.getString(R.string.audio_track_type_dubbed); + case DESCRIPTIVE: + return context.getString(R.string.audio_track_type_descriptive); + } + return null; } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a4fbba67b..cc340e99c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -778,7 +778,8 @@ Partially watched Upcoming Sort - original - dubbed - descriptive + %s %s + original + dubbed + descriptive \ No newline at end of file From 61a14765f3bc23c93a004df03b063c4c6014706e Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 19 Mar 2023 22:31:31 +0100 Subject: [PATCH 10/20] fix: ListHelper tests --- .../org/schabi/newpipe/util/ListHelper.java | 34 +++-- .../schabi/newpipe/util/ListHelperTest.java | 122 ++++++++++++++---- 2 files changed, 123 insertions(+), 33 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 1be020eae..002a8014e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -222,7 +222,8 @@ public final class ListHelper { final HashMap collectedStreams = new HashMap<>(); - final Comparator cmp = getAudioStreamFormatComparator(context); + final Comparator cmp = + getAudioStreamFormatComparator(isLimitingDataUsage(context)); for (final AudioStream stream : audioStreams) { if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) { @@ -422,7 +423,7 @@ public final class ListHelper { * @param comparator The comparator used for determining the max/best/highest ranked value * @return Index of audio stream that produces the highest ranked result or -1 if not found */ - private static int getAudioIndexByHighestRank(@Nullable final MediaFormat targetedFormat, + static int getAudioIndexByHighestRank(@Nullable final MediaFormat targetedFormat, @Nullable final List audioStreams, final Comparator comparator) { if (audioStreams == null || audioStreams.isEmpty()) { @@ -634,12 +635,11 @@ public final class ListHelper { /** * Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate. * - * @param context App context + * @param limitDataUsage choose low bitrate audio stream * @return Comparator */ private static Comparator getAudioStreamFormatComparator( - @NonNull final Context context) { - final boolean limitDataUsage = isLimitingDataUsage(context); + final boolean limitDataUsage) { final List formatRanking = limitDataUsage ? AUDIO_FORMAT_EFFICIENCY_RANKING : AUDIO_FORMAT_QUALITY_RANKING; @@ -664,14 +664,32 @@ public final class ListHelper { @NonNull final Context context) { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + final Locale preferredLanguage = Localization.getPreferredLocale(context); final boolean preferOriginalAudio = preferences.getBoolean(context.getString(R.string.prefer_original_audio_key), false); final boolean preferDescriptiveAudio = preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key), false); - final String preferredLanguage = Localization.getPreferredLocale(context).getISO3Language(); + return getAudioStreamComparator(preferredLanguage, preferOriginalAudio, + preferDescriptiveAudio, isLimitingDataUsage(context)); + } + + /** + * Get a {@link Comparator} to compare {@link AudioStream}s by their language, format + * and bitrate. + * @param preferredLanguage Preferred audio stream language + * @param preferOriginalAudio Get the original audio track regardless of its language + * @param preferDescriptiveAudio Prefer the descriptive audio track if available + * @param limitDataUsage choose low bitrate audio stream + * @return Comparator + */ + static Comparator getAudioStreamComparator(final Locale preferredLanguage, + final boolean preferOriginalAudio, + final boolean preferDescriptiveAudio, + final boolean limitDataUsage) { + final String langCode = preferredLanguage.getISO3Language(); final List trackTypeRanking = preferDescriptiveAudio ? AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE : AUDIO_TRACK_TYPE_RANKING; @@ -683,13 +701,13 @@ public final class ListHelper { return 0; }).thenComparing(AudioStream::getAudioLocale, Comparator.nullsFirst(Comparator.comparing( - locale -> locale.getISO3Language().equals(preferredLanguage)))) + locale -> locale.getISO3Language().equals(langCode)))) .thenComparing(AudioStream::getAudioTrackType, Comparator.nullsLast(Comparator.comparingInt(trackTypeRanking::indexOf))) .thenComparing(AudioStream::getAudioLocale, Comparator.nullsFirst(Comparator.comparing( locale -> locale.getISO3Language().equals( Locale.ENGLISH.getISO3Language())))) - .thenComparing(getAudioStreamFormatComparator(context)); + .thenComparing(getAudioStreamFormatComparator(limitDataUsage)); } } 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 8a75b1b4e..173892e5a 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -3,10 +3,13 @@ package org.schabi.newpipe.util; import org.junit.Test; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.AudioTrackType; import org.schabi.newpipe.extractor.stream.VideoStream; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Locale; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -29,6 +32,15 @@ public class ListHelperTest { generateAudioStream("mp3-192", MediaFormat.MP3, 192), generateAudioStream("webma-320", MediaFormat.WEBMA, 320)); + private static final List AUDIO_TRACKS_TEST_LIST = List.of( + generateAudioTrack("en.or", "en.or", Locale.ENGLISH, AudioTrackType.ORIGINAL), + generateAudioTrack("en.du", "en.du", Locale.ENGLISH, AudioTrackType.DUBBED), + generateAudioTrack("en.ds", "en.ds", Locale.ENGLISH, AudioTrackType.DESCRIPTIVE), + generateAudioTrack("unknown", null, null, null), + generateAudioTrack("de.du", "de.du", Locale.GERMAN, AudioTrackType.DUBBED), + generateAudioTrack("de.ds", "de.ds", Locale.GERMAN, AudioTrackType.DESCRIPTIVE) + ); + 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), @@ -199,24 +211,28 @@ public class ListHelperTest { @Test public void getHighestQualityAudioFormatTest() { - AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex( - MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST)); + final Comparator cmp = + ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); + AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(320, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); - stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex( - MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(320, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); - stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex( - MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.MP3, stream.getFormat()); } @Test public void getHighestQualityAudioFormatPreferredAbsent() { + final Comparator cmp = + ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); ////////////////////////////////////////// // Doesn't contain the preferred format // @@ -227,8 +243,8 @@ public class ListHelperTest { generateAudioStream("webma-192", MediaFormat.WEBMA, 192)); // List doesn't contains this format // It should fallback to the highest bitrate audio no matter what format it is - AudioStream stream = testList.get(ListHelper.getHighestQualityAudioIndex( - MediaFormat.MP3, testList)); + AudioStream stream = testList.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.MP3, testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); @@ -246,44 +262,53 @@ public class ListHelperTest { generateAudioStream("webma-192-4", MediaFormat.WEBMA, 192))); // List doesn't contains this format, it should fallback to the highest bitrate audio and // the highest quality format. - stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList)); + stream = + testList.get(ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); // Adding a new format and bitrate. Adding another stream will have no impact since // it's not a preferred format. testList.add(generateAudioStream("webma-192-5", MediaFormat.WEBMA, 192)); - stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList)); + stream = + testList.get(ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); } @Test public void getHighestQualityAudioNull() { - assertEquals(-1, ListHelper.getHighestQualityAudioIndex(null, null)); - assertEquals(-1, ListHelper.getHighestQualityAudioIndex(null, new ArrayList<>())); + final Comparator cmp = + ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, null, cmp)); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, new ArrayList<>(), cmp)); } @Test public void getLowestQualityAudioFormatTest() { - AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex( - MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST)); + final Comparator cmp = + ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, true); + + AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(128, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); - stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex( - MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(64, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); - stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex( - MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(64, stream.getAverageBitrate()); assertEquals(MediaFormat.MP3, stream.getFormat()); } @Test public void getLowestQualityAudioFormatPreferredAbsent() { + final Comparator cmp = + ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, true); ////////////////////////////////////////// // Doesn't contain the preferred format // @@ -294,14 +319,15 @@ public class ListHelperTest { generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192))); // List doesn't contains this format // It should fallback to the most compact audio no matter what format it is. - AudioStream stream = testList.get(ListHelper.getMostCompactAudioIndex( - MediaFormat.MP3, testList)); + AudioStream stream = testList.get(ListHelper.getAudioIndexByHighestRank( + MediaFormat.MP3, testList, cmp)); assertEquals(128, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); // WEBMA is more compact than M4A testList.add(generateAudioStream("webma-192-2", MediaFormat.WEBMA, 128)); - stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList)); + stream = + testList.get(ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); assertEquals(128, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); @@ -318,20 +344,52 @@ public class ListHelperTest { generateAudioStream("m4a-192-3", MediaFormat.M4A, 192))); // List doesn't contain this format // It should fallback to the most compact audio no matter what format it is. - stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList)); + stream = testList.get( + ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); // Should be same as above - stream = testList.get(ListHelper.getMostCompactAudioIndex(null, testList)); + stream = testList.get( + ListHelper.getAudioIndexByHighestRank(null, testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); } @Test public void getLowestQualityAudioNull() { - assertEquals(-1, ListHelper.getMostCompactAudioIndex(null, null)); - assertEquals(-1, ListHelper.getMostCompactAudioIndex(null, new ArrayList<>())); + final Comparator cmp = + ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, null, cmp)); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, new ArrayList<>(), cmp)); + } + + @Test + public void getAudioTrack() { + // English language + Comparator cmp = + ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); + AudioStream stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + null, AUDIO_TRACKS_TEST_LIST, cmp)); + assertEquals("en.or", stream.getId()); + + // German language + cmp = ListHelper.getAudioStreamComparator(Locale.GERMAN, false, false, false); + stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + null, AUDIO_TRACKS_TEST_LIST, cmp)); + assertEquals("de.du", stream.getId()); + + // German language, but prefer original + cmp = ListHelper.getAudioStreamComparator(Locale.GERMAN, true, false, false); + stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + null, AUDIO_TRACKS_TEST_LIST, cmp)); + assertEquals("en.or", stream.getId()); + + // Prefer descriptive audio + cmp = ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, true, false); + stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + null, AUDIO_TRACKS_TEST_LIST, cmp)); + assertEquals("en.ds", stream.getId()); } @Test @@ -390,6 +448,20 @@ public class ListHelperTest { .build(); } + private static AudioStream generateAudioTrack( + @NonNull final String id, @Nullable final String trackId, + @Nullable final Locale locale, @Nullable final AudioTrackType trackType) { + return new AudioStream.Builder() + .setId(id) + .setContent("", true) + .setMediaFormat(MediaFormat.M4A) + .setAverageBitrate(128) + .setAudioTrackId(trackId) + .setAudioLocale(locale) + .setAudioTrackType(trackType) + .build(); + } + @NonNull private static VideoStream generateVideoStream(@NonNull final String id, @Nullable final MediaFormat mediaFormat, From dbd6e4d11f43e5b4cf69a7db18d11f1605a7cc32 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 19 Mar 2023 22:55:37 +0100 Subject: [PATCH 11/20] fix: sonarcloud lint --- .../newpipe/player/PlayQueueActivity.java | 28 +++++++++---------- .../org/schabi/newpipe/util/ListHelper.java | 5 ++-- .../schabi/newpipe/util/ListHelperTest.java | 6 ++++ 3 files changed, 21 insertions(+), 18 deletions(-) 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 fd2128216..bf0dc4a56 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -657,23 +657,21 @@ public final class PlayQueueActivity extends AppCompatActivity * @param itemId index of the selected item */ private void onAudioTrackClick(final int itemId) { - @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); - if (currentMetadata == null || currentMetadata.getMaybeAudioTrack().isEmpty()) { + if (player.getCurrentMetadata() == null) { return; } + player.getCurrentMetadata().getMaybeAudioTrack().ifPresent(audioTrack -> { + final List availableStreams = audioTrack.getAudioStreams(); + final int selectedStreamIndex = audioTrack.getSelectedAudioStreamIndex(); + if (selectedStreamIndex == itemId || availableStreams.size() <= itemId) { + return; + } - final MediaItemTag.AudioTrack audioTrack = - currentMetadata.getMaybeAudioTrack().get(); - final List availableStreams = audioTrack.getAudioStreams(); - final int selectedStreamIndex = audioTrack.getSelectedAudioStreamIndex(); - if (selectedStreamIndex == itemId || availableStreams.size() <= itemId) { - return; - } - - player.saveStreamProgressState(); - final String newAudioTrack = availableStreams.get(itemId).getAudioTrackId(); - player.setRecovery(); - player.setAudioTrack(newAudioTrack); - player.reloadPlayQueueManager(); + player.saveStreamProgressState(); + final String newAudioTrack = availableStreams.get(itemId).getAudioTrackId(); + player.setRecovery(); + player.setAudioTrack(newAudioTrack); + player.reloadPlayQueueManager(); + }); } } 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 002a8014e..68e50b9c1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -239,13 +239,12 @@ public final class ListHelper { } // Filter unknown audio tracks if there are multiple tracks - java.util.stream.Stream cs = collectedStreams.values().stream(); if (collectedStreams.size() > 1) { - cs = cs.filter(s -> s.getAudioTrackId() != null); + collectedStreams.remove(""); } // Sort collected streams by name - return cs.sorted(Comparator.comparing(audioStream -> + return collectedStreams.values().stream().sorted(Comparator.comparing(audioStream -> Localization.audioTrackName(context, audioStream))).collect(Collectors.toList()); } 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 173892e5a..4619f8ad7 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -390,6 +390,12 @@ public class ListHelperTest { stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( null, AUDIO_TRACKS_TEST_LIST, cmp)); assertEquals("en.ds", stream.getId()); + + // Japanese language, fall back to original + cmp = ListHelper.getAudioStreamComparator(Locale.JAPANESE, true, false, false); + stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( + null, AUDIO_TRACKS_TEST_LIST, cmp)); + assertEquals("en.or", stream.getId()); } @Test From fdd3b03fe5b1848ccf1765d9eabca49b01a98f01 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 19 Mar 2023 23:37:52 +0100 Subject: [PATCH 12/20] fix: audio stream format selection --- .../org/schabi/newpipe/util/ListHelper.java | 78 +++++++++--------- .../schabi/newpipe/util/ListHelperTest.java | 80 +++++++++---------- 2 files changed, 75 insertions(+), 83 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 68e50b9c1..0164b708f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -107,11 +107,8 @@ public final class ListHelper { public static int getDefaultAudioFormat(final Context context, final List audioStreams) { - final MediaFormat defaultFormat = getDefaultFormat(context, - R.string.default_audio_format_key, R.string.default_audio_format_value); - - return getAudioIndexByHighestRank(defaultFormat, audioStreams, - getAudioStreamComparator(context)); + return getAudioIndexByHighestRank(audioStreams, + getAudioTrackComparator(context).thenComparing(getAudioFormatComparator(context))); } public static int getAudioFormatIndex(final Context context, @@ -222,8 +219,7 @@ public final class ListHelper { final HashMap collectedStreams = new HashMap<>(); - final Comparator cmp = - getAudioStreamFormatComparator(isLimitingDataUsage(context)); + final Comparator cmp = getAudioFormatComparator(context); for (final AudioStream stream : audioStreams) { if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) { @@ -417,32 +413,18 @@ public final class ListHelper { * Get the audio-stream from the list with the highest rank, depending on the comparator. * Format will be ignored if it yields no results. * - * @param targetedFormat The target format type or null if it doesn't matter * @param audioStreams List of audio streams * @param comparator The comparator used for determining the max/best/highest ranked value * @return Index of audio stream that produces the highest ranked result or -1 if not found */ - static int getAudioIndexByHighestRank(@Nullable final MediaFormat targetedFormat, - @Nullable final List audioStreams, - final Comparator comparator) { + static int getAudioIndexByHighestRank(@Nullable final List audioStreams, + final Comparator comparator) { if (audioStreams == null || audioStreams.isEmpty()) { return -1; } final AudioStream highestRankedAudioStream = audioStreams.stream() - .filter(audioStream -> targetedFormat == null - || audioStream.getFormat() == targetedFormat) - .max(comparator) - .orElse(null); - - if (highestRankedAudioStream == null) { - // Fallback: Ignore targetedFormat if not null - if (targetedFormat != null) { - return getAudioIndexByHighestRank(null, audioStreams, comparator); - } - // targetedFormat is already null -> return -1 - return -1; - } + .max(comparator).orElse(null); return audioStreams.indexOf(highestRankedAudioStream); } @@ -631,14 +613,27 @@ public final class ListHelper { return manager.isActiveNetworkMetered(); } + /** + * Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate. + * @param context app context + * @return Comparator + */ + private static Comparator getAudioFormatComparator( + final @NonNull Context context) { + final MediaFormat defaultFormat = getDefaultFormat(context, + R.string.default_audio_format_key, R.string.default_audio_format_value); + return getAudioFormatComparator(defaultFormat, isLimitingDataUsage(context)); + } + /** * Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate. * + * @param defaultFormat the default format to look for * @param limitDataUsage choose low bitrate audio stream * @return Comparator */ - private static Comparator getAudioStreamFormatComparator( - final boolean limitDataUsage) { + static Comparator getAudioFormatComparator( + @Nullable final MediaFormat defaultFormat, final boolean limitDataUsage) { final List formatRanking = limitDataUsage ? AUDIO_FORMAT_EFFICIENCY_RANKING : AUDIO_FORMAT_QUALITY_RANKING; @@ -648,18 +643,22 @@ public final class ListHelper { bitrateComparator = bitrateComparator.reversed(); } - return bitrateComparator.thenComparingInt( + return Comparator.comparing(AudioStream::getFormat, (o1, o2) -> { + if (defaultFormat != null) { + return Boolean.compare(o1 == defaultFormat, o2 == defaultFormat); + } + return 0; + }).thenComparing(bitrateComparator).thenComparingInt( stream -> formatRanking.indexOf(stream.getFormat())); } /** - * Get a {@link Comparator} to compare {@link AudioStream}s by their language, format - * and bitrate. + * Get a {@link Comparator} to compare {@link AudioStream}s by their tracks. * * @param context App context * @return Comparator */ - private static Comparator getAudioStreamComparator( + private static Comparator getAudioTrackComparator( @NonNull final Context context) { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); @@ -671,23 +670,21 @@ public final class ListHelper { preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key), false); - return getAudioStreamComparator(preferredLanguage, preferOriginalAudio, - preferDescriptiveAudio, isLimitingDataUsage(context)); + return getAudioTrackComparator(preferredLanguage, preferOriginalAudio, + preferDescriptiveAudio); } /** - * Get a {@link Comparator} to compare {@link AudioStream}s by their language, format - * and bitrate. + * Get a {@link Comparator} to compare {@link AudioStream}s by their tracks. + * * @param preferredLanguage Preferred audio stream language * @param preferOriginalAudio Get the original audio track regardless of its language * @param preferDescriptiveAudio Prefer the descriptive audio track if available - * @param limitDataUsage choose low bitrate audio stream * @return Comparator */ - static Comparator getAudioStreamComparator(final Locale preferredLanguage, - final boolean preferOriginalAudio, - final boolean preferDescriptiveAudio, - final boolean limitDataUsage) { + static Comparator getAudioTrackComparator( + final Locale preferredLanguage, final boolean preferOriginalAudio, + final boolean preferDescriptiveAudio) { final String langCode = preferredLanguage.getISO3Language(); final List trackTypeRanking = preferDescriptiveAudio ? AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE : AUDIO_TRACK_TYPE_RANKING; @@ -706,7 +703,6 @@ public final class ListHelper { .thenComparing(AudioStream::getAudioLocale, Comparator.nullsFirst(Comparator.comparing( locale -> locale.getISO3Language().equals( - Locale.ENGLISH.getISO3Language())))) - .thenComparing(getAudioStreamFormatComparator(limitDataUsage)); + Locale.ENGLISH.getISO3Language())))); } } 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 4619f8ad7..b4a4167cf 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -211,20 +211,21 @@ public class ListHelperTest { @Test public void getHighestQualityAudioFormatTest() { - final Comparator cmp = - ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); + Comparator cmp = ListHelper.getAudioFormatComparator(MediaFormat.M4A, false); AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST, cmp)); + AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(320, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); + cmp = ListHelper.getAudioFormatComparator(MediaFormat.WEBMA, false); stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST, cmp)); + AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(320, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); + cmp = ListHelper.getAudioFormatComparator(MediaFormat.MP3, false); stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST, cmp)); + AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.MP3, stream.getFormat()); } @@ -232,7 +233,7 @@ public class ListHelperTest { @Test public void getHighestQualityAudioFormatPreferredAbsent() { final Comparator cmp = - ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); + ListHelper.getAudioFormatComparator(MediaFormat.MP3, false); ////////////////////////////////////////// // Doesn't contain the preferred format // @@ -243,8 +244,7 @@ public class ListHelperTest { generateAudioStream("webma-192", MediaFormat.WEBMA, 192)); // List doesn't contains this format // It should fallback to the highest bitrate audio no matter what format it is - AudioStream stream = testList.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.MP3, testList, cmp)); + AudioStream stream = testList.get(ListHelper.getAudioIndexByHighestRank(testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); @@ -263,7 +263,7 @@ public class ListHelperTest { // List doesn't contains this format, it should fallback to the highest bitrate audio and // the highest quality format. stream = - testList.get(ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); + testList.get(ListHelper.getAudioIndexByHighestRank(testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); @@ -271,44 +271,42 @@ public class ListHelperTest { // it's not a preferred format. testList.add(generateAudioStream("webma-192-5", MediaFormat.WEBMA, 192)); stream = - testList.get(ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); + testList.get(ListHelper.getAudioIndexByHighestRank(testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); } @Test public void getHighestQualityAudioNull() { - final Comparator cmp = - ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); - assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, null, cmp)); - assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, new ArrayList<>(), cmp)); + final Comparator cmp = ListHelper.getAudioFormatComparator(null, false); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, cmp)); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(new ArrayList<>(), cmp)); } @Test public void getLowestQualityAudioFormatTest() { - final Comparator cmp = - ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, true); - + Comparator cmp = ListHelper.getAudioFormatComparator(MediaFormat.M4A, true); AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST, cmp)); + AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(128, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); + cmp = ListHelper.getAudioFormatComparator(MediaFormat.WEBMA, true); stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST, cmp)); + AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(64, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); + cmp = ListHelper.getAudioFormatComparator(MediaFormat.MP3, true); stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST, cmp)); + AUDIO_STREAMS_TEST_LIST, cmp)); assertEquals(64, stream.getAverageBitrate()); assertEquals(MediaFormat.MP3, stream.getFormat()); } @Test public void getLowestQualityAudioFormatPreferredAbsent() { - final Comparator cmp = - ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, true); + Comparator cmp = ListHelper.getAudioFormatComparator(MediaFormat.MP3, true); ////////////////////////////////////////// // Doesn't contain the preferred format // @@ -319,15 +317,13 @@ public class ListHelperTest { generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192))); // List doesn't contains this format // It should fallback to the most compact audio no matter what format it is. - AudioStream stream = testList.get(ListHelper.getAudioIndexByHighestRank( - MediaFormat.MP3, testList, cmp)); + AudioStream stream = testList.get(ListHelper.getAudioIndexByHighestRank(testList, cmp)); assertEquals(128, stream.getAverageBitrate()); assertEquals(MediaFormat.M4A, stream.getFormat()); // WEBMA is more compact than M4A testList.add(generateAudioStream("webma-192-2", MediaFormat.WEBMA, 128)); - stream = - testList.get(ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); + stream = testList.get(ListHelper.getAudioIndexByHighestRank(testList, cmp)); assertEquals(128, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); @@ -345,56 +341,56 @@ public class ListHelperTest { // List doesn't contain this format // It should fallback to the most compact audio no matter what format it is. stream = testList.get( - ListHelper.getAudioIndexByHighestRank(MediaFormat.MP3, testList, cmp)); + ListHelper.getAudioIndexByHighestRank(testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); // Should be same as above + cmp = ListHelper.getAudioFormatComparator(null, true); stream = testList.get( - ListHelper.getAudioIndexByHighestRank(null, testList, cmp)); + ListHelper.getAudioIndexByHighestRank(testList, cmp)); assertEquals(192, stream.getAverageBitrate()); assertEquals(MediaFormat.WEBMA, stream.getFormat()); } @Test public void getLowestQualityAudioNull() { - final Comparator cmp = - ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); - assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, null, cmp)); - assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, new ArrayList<>(), cmp)); + final Comparator cmp = ListHelper.getAudioFormatComparator(null, false); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(null, cmp)); + assertEquals(-1, ListHelper.getAudioIndexByHighestRank(new ArrayList<>(), cmp)); } @Test public void getAudioTrack() { // English language Comparator cmp = - ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, false, false); + ListHelper.getAudioTrackComparator(Locale.ENGLISH, false, false); AudioStream stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - null, AUDIO_TRACKS_TEST_LIST, cmp)); + AUDIO_TRACKS_TEST_LIST, cmp)); assertEquals("en.or", stream.getId()); // German language - cmp = ListHelper.getAudioStreamComparator(Locale.GERMAN, false, false, false); + cmp = ListHelper.getAudioTrackComparator(Locale.GERMAN, false, false); stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - null, AUDIO_TRACKS_TEST_LIST, cmp)); + AUDIO_TRACKS_TEST_LIST, cmp)); assertEquals("de.du", stream.getId()); // German language, but prefer original - cmp = ListHelper.getAudioStreamComparator(Locale.GERMAN, true, false, false); + cmp = ListHelper.getAudioTrackComparator(Locale.GERMAN, true, false); stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - null, AUDIO_TRACKS_TEST_LIST, cmp)); + AUDIO_TRACKS_TEST_LIST, cmp)); assertEquals("en.or", stream.getId()); // Prefer descriptive audio - cmp = ListHelper.getAudioStreamComparator(Locale.ENGLISH, false, true, false); + cmp = ListHelper.getAudioTrackComparator(Locale.ENGLISH, false, true); stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - null, AUDIO_TRACKS_TEST_LIST, cmp)); + AUDIO_TRACKS_TEST_LIST, cmp)); assertEquals("en.ds", stream.getId()); // Japanese language, fall back to original - cmp = ListHelper.getAudioStreamComparator(Locale.JAPANESE, true, false, false); + cmp = ListHelper.getAudioTrackComparator(Locale.JAPANESE, true, false); stream = AUDIO_TRACKS_TEST_LIST.get(ListHelper.getAudioIndexByHighestRank( - null, AUDIO_TRACKS_TEST_LIST, cmp)); + AUDIO_TRACKS_TEST_LIST, cmp)); assertEquals("en.or", stream.getId()); } From ed06f559aed1a466c2b659474a5f0b5719f224db Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 20 Mar 2023 14:12:08 +0100 Subject: [PATCH 13/20] feat: add track selection to downloader --- app/build.gradle | 2 +- .../newpipe/download/DownloadDialog.java | 166 +++++++++++++----- .../newpipe/util/AudioTrackAdapter.java | 94 ++++++++++ .../org/schabi/newpipe/util/ListHelper.java | 122 ++++++++++++- app/src/main/res/layout/download_dialog.xml | 36 +++- app/src/main/res/values/strings.xml | 1 + 6 files changed, 363 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/AudioTrackAdapter.java diff --git a/app/build.gradle b/app/build.gradle index a766b16a8..e9e1ea124 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -191,7 +191,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.Theta-Dev:NewPipeExtractor:3fb356a7065c75909ee3856a29be92317c295bb9' + implementation 'com.github.Theta-Dev:NewPipeExtractor:1aa232475e957ce5d2c036406a983db4190ebf2b' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ 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 d1ee0ee88..5d3679471 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -68,6 +68,8 @@ import org.schabi.newpipe.util.SecondaryStreamHelper; import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.StreamItemAdapter; import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; +import org.schabi.newpipe.util.AudioTrackAdapter; +import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper; import org.schabi.newpipe.util.ThemeHelper; import java.io.File; @@ -95,12 +97,14 @@ public class DownloadDialog extends DialogFragment @State StreamInfo currentInfo; @State - StreamSizeWrapper wrappedAudioStreams; - @State StreamSizeWrapper wrappedVideoStreams; @State StreamSizeWrapper wrappedSubtitleStreams; @State + AudioTracksWrapper wrappedAudioTracks; + @State + int selectedAudioStreamIndex; + @State int selectedVideoIndex; // set in the constructor @State int selectedAudioIndex = 0; // default to the first item @@ -117,6 +121,7 @@ public class DownloadDialog extends DialogFragment private Context context; private boolean askForSavePath; + private AudioTrackAdapter audioTrackAdapter; private StreamItemAdapter audioStreamsAdapter; private StreamItemAdapter videoStreamsAdapter; private StreamItemAdapter subtitleStreamsAdapter; @@ -163,18 +168,26 @@ public class DownloadDialog extends DialogFragment public DownloadDialog(@NonNull final Context context, @NonNull final StreamInfo info) { this.currentInfo = info; + final List audioStreams = + getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP); + final List> groupedAudioStreams = + ListHelper.getGroupedAudioStreams(context, audioStreams); + this.wrappedAudioTracks = new AudioTracksWrapper(groupedAudioStreams, context); + this.selectedAudioStreamIndex = + ListHelper.getDefaultAudioTrackGroup(context, groupedAudioStreams); + // TODO: Adapt this code when the downloader support other types of stream deliveries final List videoStreams = ListHelper.getSortedStreamVideosList( context, getStreamsOfSpecifiedDelivery(info.getVideoStreams(), PROGRESSIVE_HTTP), getStreamsOfSpecifiedDelivery(info.getVideoOnlyStreams(), PROGRESSIVE_HTTP), false, - false + // If there are multiple languages available, prefer streams without audio + // to allow language selection + wrappedAudioTracks.size() > 1 ); this.wrappedVideoStreams = new StreamSizeWrapper<>(videoStreams, context); - this.wrappedAudioStreams = new StreamSizeWrapper<>( - getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP), context); this.wrappedSubtitleStreams = new StreamSizeWrapper<>( getStreamsOfSpecifiedDelivery(info.getSubtitles(), PROGRESSIVE_HTTP), context); @@ -212,33 +225,9 @@ public class DownloadDialog extends DialogFragment setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context)); Icepick.restoreInstanceState(this, savedInstanceState); - final var secondaryStreams = new SparseArrayCompat>(4); - final List videoStreams = wrappedVideoStreams.getStreamsList(); - - for (int i = 0; i < videoStreams.size(); i++) { - if (!videoStreams.get(i).isVideoOnly()) { - continue; - } - final AudioStream audioStream = SecondaryStreamHelper - .getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i)); - - if (audioStream != null) { - secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, - audioStream)); - } else if (DEBUG) { - final MediaFormat mediaFormat = videoStreams.get(i).getFormat(); - if (mediaFormat != null) { - Log.w(TAG, "No audio stream candidates for video format " - + mediaFormat.name()); - } else { - Log.w(TAG, "No audio stream candidates for unknown video format"); - } - } - } - - this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams); - this.audioStreamsAdapter = new StreamItemAdapter<>(wrappedAudioStreams); + this.audioTrackAdapter = new AudioTrackAdapter(wrappedAudioTracks); this.subtitleStreamsAdapter = new StreamItemAdapter<>(wrappedSubtitleStreams); + updateSecondaryStreams(); final Intent intent = new Intent(context, DownloadManagerService.class); context.startService(intent); @@ -265,6 +254,38 @@ public class DownloadDialog extends DialogFragment }, Context.BIND_AUTO_CREATE); } + /** + * Update the displayed video streams based on the selected audio track. + */ + private void updateSecondaryStreams() { + final StreamSizeWrapper audioStreams = getWrappedAudioStreams(); + final var secondaryStreams = new SparseArrayCompat>(4); + final List videoStreams = wrappedVideoStreams.getStreamsList(); + + for (int i = 0; i < videoStreams.size(); i++) { + if (!videoStreams.get(i).isVideoOnly()) { + continue; + } + final AudioStream audioStream = SecondaryStreamHelper + .getAudioStreamFor(audioStreams.getStreamsList(), videoStreams.get(i)); + + if (audioStream != null) { + secondaryStreams.append(i, new SecondaryStreamHelper<>(audioStreams, audioStream)); + } else if (DEBUG) { + final MediaFormat mediaFormat = videoStreams.get(i).getFormat(); + if (mediaFormat != null) { + Log.w(TAG, "No audio stream candidates for video format " + + mediaFormat.name()); + } else { + Log.w(TAG, "No audio stream candidates for unknown video format"); + } + } + } + + this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams); + this.audioStreamsAdapter = new StreamItemAdapter<>(audioStreams); + } + @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, @@ -285,13 +306,13 @@ public class DownloadDialog extends DialogFragment dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName())); - selectedAudioIndex = ListHelper - .getDefaultAudioFormat(getContext(), wrappedAudioStreams.getStreamsList()); + selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), + getWrappedAudioStreams().getStreamsList()); selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll()); dialogBinding.qualitySpinner.setOnItemSelectedListener(this); - + dialogBinding.audioTrackSpinner.setOnItemSelectedListener(this); dialogBinding.videoAudioGroup.setOnCheckedChangeListener(this); initToolbar(dialogBinding.toolbarLayout.toolbar); @@ -383,7 +404,7 @@ public class DownloadDialog extends DialogFragment new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG, "Downloading video stream size", currentInfo.getServiceId())))); - disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams) + disposables.add(StreamSizeWrapper.fetchSizeForWrapper(getWrappedAudioStreams()) .subscribe(result -> { if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) { @@ -405,14 +426,29 @@ public class DownloadDialog extends DialogFragment currentInfo.getServiceId())))); } + private void setupAudioTrackSpinner() { + if (getContext() == null) { + return; + } + + dialogBinding.audioTrackSpinner.setAdapter(audioTrackAdapter); + dialogBinding.audioTrackSpinner.setSelection(selectedAudioStreamIndex); + + dialogBinding.audioStreamSpinner.setAdapter(audioStreamsAdapter); + dialogBinding.audioStreamSpinner.setSelection(selectedAudioIndex); + } + private void setupAudioSpinner() { if (getContext() == null) { return; } - dialogBinding.qualitySpinner.setAdapter(audioStreamsAdapter); - dialogBinding.qualitySpinner.setSelection(selectedAudioIndex); + dialogBinding.qualitySpinner.setVisibility(View.GONE); setRadioButtonsState(true); + dialogBinding.audioStreamSpinner.setVisibility(View.VISIBLE); + dialogBinding.audioTrackSpinner.setVisibility( + wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); + dialogBinding.defaultAudioTrackPresentText.setVisibility(View.GONE); } private void setupVideoSpinner() { @@ -422,7 +458,21 @@ public class DownloadDialog extends DialogFragment dialogBinding.qualitySpinner.setAdapter(videoStreamsAdapter); dialogBinding.qualitySpinner.setSelection(selectedVideoIndex); + dialogBinding.qualitySpinner.setVisibility(View.VISIBLE); setRadioButtonsState(true); + dialogBinding.audioStreamSpinner.setVisibility(View.GONE); + onVideoStreamSelected(); + } + + private void onVideoStreamSelected() { + final boolean isVideoOnly = videoStreamsAdapter.getItem(selectedVideoIndex).isVideoOnly(); + + dialogBinding.audioTrackSpinner.setVisibility( + isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); + dialogBinding.defaultAudioTrackPresentText.setVisibility( + !isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE + + ); } private void setupSubtitleSpinner() { @@ -432,7 +482,11 @@ public class DownloadDialog extends DialogFragment dialogBinding.qualitySpinner.setAdapter(subtitleStreamsAdapter); dialogBinding.qualitySpinner.setSelection(selectedSubtitleIndex); + dialogBinding.qualitySpinner.setVisibility(View.VISIBLE); setRadioButtonsState(true); + dialogBinding.audioStreamSpinner.setVisibility(View.GONE); + dialogBinding.audioTrackSpinner.setVisibility(View.GONE); + dialogBinding.defaultAudioTrackPresentText.setVisibility(View.GONE); } @@ -550,18 +604,27 @@ public class DownloadDialog extends DialogFragment + "parent = [" + parent + "], view = [" + view + "], " + "position = [" + position + "], id = [" + id + "]"); } - switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) { - case R.id.audio_button: + + switch (parent.getId()) { + case R.id.quality_spinner: + switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) { + case R.id.video_button: + selectedVideoIndex = position; + onVideoStreamSelected(); + break; + case R.id.subtitle_button: + selectedSubtitleIndex = position; + break; + } + onItemSelectedSetFileName(); + break; + case R.id.audio_track_spinner: + selectedAudioStreamIndex = position; + updateSecondaryStreams(); + break; + case R.id.audio_stream_spinner: selectedAudioIndex = position; - break; - case R.id.video_button: - selectedVideoIndex = position; - break; - case R.id.subtitle_button: - selectedSubtitleIndex = position; - break; } - onItemSelectedSetFileName(); } private void onItemSelectedSetFileName() { @@ -607,6 +670,7 @@ public class DownloadDialog extends DialogFragment protected void setupDownloadOptions() { setRadioButtonsState(false); + setupAudioTrackSpinner(); final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0; final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0; @@ -657,6 +721,13 @@ public class DownloadDialog extends DialogFragment dialogBinding.subtitleButton.setEnabled(enabled); } + private StreamSizeWrapper getWrappedAudioStreams() { + if (selectedAudioStreamIndex < 0 || selectedAudioStreamIndex > wrappedAudioTracks.size()) { + return StreamSizeWrapper.empty(); + } + return wrappedAudioTracks.getTracksList().get(selectedAudioStreamIndex); + } + private int getSubtitleIndexBy(@NonNull final List streams) { final Localization preferredLocalization = NewPipe.getPreferredLocalization(); @@ -1013,7 +1084,6 @@ public class DownloadDialog extends DialogFragment psName = Postprocessing.ALGORITHM_WEBM_MUXER; } - psArgs = null; final long videoSize = wrappedVideoStreams.getSizeInBytes( (VideoStream) selectedStream); diff --git a/app/src/main/java/org/schabi/newpipe/util/AudioTrackAdapter.java b/app/src/main/java/org/schabi/newpipe/util/AudioTrackAdapter.java new file mode 100644 index 000000000..39a05acb3 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/AudioTrackAdapter.java @@ -0,0 +1,94 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A list adapter for groups of {@link AudioStream}s (audio tracks). + */ +public class AudioTrackAdapter extends BaseAdapter { + private final AudioTracksWrapper tracksWrapper; + + public AudioTrackAdapter(final AudioTracksWrapper tracksWrapper) { + this.tracksWrapper = tracksWrapper; + } + + @Override + public int getCount() { + return tracksWrapper.size(); + } + + @Override + public List getItem(final int position) { + return tracksWrapper.getTracksList().get(position).getStreamsList(); + } + + @Override + public long getItemId(final int position) { + return position; + } + + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + final var context = parent.getContext(); + final View view; + if (convertView == null) { + view = LayoutInflater.from(context).inflate( + R.layout.stream_quality_item, parent, false); + } else { + view = convertView; + } + + final ImageView woSoundIconView = view.findViewById(R.id.wo_sound_icon); + final TextView formatNameView = view.findViewById(R.id.stream_format_name); + final TextView qualityView = view.findViewById(R.id.stream_quality); + final TextView sizeView = view.findViewById(R.id.stream_size); + + final List streams = getItem(position); + final AudioStream stream = streams.get(0); + + woSoundIconView.setVisibility(View.GONE); + sizeView.setVisibility(View.VISIBLE); + + if (stream.getAudioTrackId() != null) { + formatNameView.setText(stream.getAudioTrackId()); + } + qualityView.setText(Localization.audioTrackName(context, stream)); + + return view; + } + + public static class AudioTracksWrapper implements Serializable { + private final List> tracksList; + + public AudioTracksWrapper(@NonNull final List> groupedAudioStreams, + @Nullable final Context context) { + this.tracksList = groupedAudioStreams.stream().map(streams -> + new StreamSizeWrapper<>(streams, context)).collect(Collectors.toList()); + } + + public List> getTracksList() { + return tracksList; + } + + public int size() { + return tracksList.size(); + } + } +} 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 0164b708f..f8a800b0e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -111,6 +111,19 @@ public final class ListHelper { getAudioTrackComparator(context).thenComparing(getAudioFormatComparator(context))); } + public static int getDefaultAudioTrackGroup(final Context context, + final List> groupedAudioStreams) { + if (groupedAudioStreams == null || groupedAudioStreams.isEmpty()) { + return -1; + } + + final Comparator cmp = getAudioTrackComparator(context); + final List highestRanked = groupedAudioStreams.stream() + .max((o1, o2) -> cmp.compare(o1.get(0), o2.get(0))) + .orElse(null); + return groupedAudioStreams.indexOf(highestRanked); + } + public static int getAudioFormatIndex(final Context context, final List audioStreams, @Nullable final String trackId) { @@ -240,8 +253,50 @@ public final class ListHelper { } // Sort collected streams by name - return collectedStreams.values().stream().sorted(Comparator.comparing(audioStream -> - Localization.audioTrackName(context, audioStream))).collect(Collectors.toList()); + return collectedStreams.values().stream().sorted(getAudioTrackNameComparator(context)) + .collect(Collectors.toList()); + } + + /** + * Group the list of audioStreams by their track ID and sort the resulting list by track name. + * + * @param context app context to get track names for sorting + * @param audioStreams list of audio streams + * @return list of audio streams lists representing individual tracks + */ + public static List> getGroupedAudioStreams( + @NonNull final Context context, + @Nullable final List audioStreams) { + if (audioStreams == null) { + return Collections.emptyList(); + } + + final HashMap> collectedStreams = new HashMap<>(); + + for (final AudioStream stream : audioStreams) { + final String trackId = Objects.toString(stream.getAudioTrackId(), ""); + if (collectedStreams.containsKey(trackId)) { + collectedStreams.get(trackId).add(stream); + } else { + final List list = new ArrayList<>(); + list.add(stream); + collectedStreams.put(trackId, list); + } + } + + // Filter unknown audio tracks if there are multiple tracks + if (collectedStreams.size() > 1) { + collectedStreams.remove(""); + } + + // Sort tracks alphabetically, sort track streams by quality + final Comparator nameCmp = getAudioTrackNameComparator(context); + final Comparator formatCmp = getAudioFormatComparator(context); + + return collectedStreams.values().stream() + .sorted((o1, o2) -> nameCmp.compare(o1.get(0), o2.get(0))) + .map(streams -> streams.stream().sorted(formatCmp).collect(Collectors.toList())) + .collect(Collectors.toList()); } /*////////////////////////////////////////////////////////////////////////// @@ -413,8 +468,8 @@ public final class ListHelper { * Get the audio-stream from the list with the highest rank, depending on the comparator. * Format will be ignored if it yields no results. * - * @param audioStreams List of audio streams - * @param comparator The comparator used for determining the max/best/highest ranked value + * @param audioStreams List of audio streams + * @param comparator The comparator used for determining the max/best/highest ranked value * @return Index of audio stream that produces the highest ranked result or -1 if not found */ static int getAudioIndexByHighestRank(@Nullable final List audioStreams, @@ -615,6 +670,9 @@ public final class ListHelper { /** * Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate. + * + *

The prefered stream will be ordered last.

+ * * @param context app context * @return Comparator */ @@ -628,7 +686,9 @@ public final class ListHelper { /** * Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate. * - * @param defaultFormat the default format to look for + *

The prefered stream will be ordered last.

+ * + * @param defaultFormat the default format to look for * @param limitDataUsage choose low bitrate audio stream * @return Comparator */ @@ -655,6 +715,21 @@ public final class ListHelper { /** * Get a {@link Comparator} to compare {@link AudioStream}s by their tracks. * + *

In this order:

+ *
    + *
  1. If {@code preferOriginalAudio}: is original audio
  2. + *
  3. Language matches {@code preferredLanguage}
  4. + *
  5. + * Track type ranks highest in this order: + * Original > Dubbed > Descriptive + *

    If {@code preferDescriptiveAudio}: + * Descriptive > Dubbed > Original

    + *
  6. + *
  7. Language is English
  8. + *
+ * + *

The prefered track will be ordered last.

+ * * @param context App context * @return Comparator */ @@ -677,8 +752,23 @@ public final class ListHelper { /** * Get a {@link Comparator} to compare {@link AudioStream}s by their tracks. * - * @param preferredLanguage Preferred audio stream language - * @param preferOriginalAudio Get the original audio track regardless of its language + *

In this order:

+ *
    + *
  1. If {@code preferOriginalAudio}: is original audio
  2. + *
  3. Language matches {@code preferredLanguage}
  4. + *
  5. + * Track type ranks highest in this order: + * Original > Dubbed > Descriptive + *

    If {@code preferDescriptiveAudio}: + * Descriptive > Dubbed > Original

    + *
  6. + *
  7. Language is English
  8. + *
+ * + *

The prefered track will be ordered last.

+ * + * @param preferredLanguage Preferred audio stream language + * @param preferOriginalAudio Get the original audio track regardless of its language * @param preferDescriptiveAudio Prefer the descriptive audio track if available * @return Comparator */ @@ -699,10 +789,26 @@ public final class ListHelper { Comparator.nullsFirst(Comparator.comparing( locale -> locale.getISO3Language().equals(langCode)))) .thenComparing(AudioStream::getAudioTrackType, - Comparator.nullsLast(Comparator.comparingInt(trackTypeRanking::indexOf))) + Comparator.nullsFirst(Comparator.comparingInt(trackTypeRanking::indexOf))) .thenComparing(AudioStream::getAudioLocale, Comparator.nullsFirst(Comparator.comparing( locale -> locale.getISO3Language().equals( Locale.ENGLISH.getISO3Language())))); } + + /** + * Get a {@link Comparator} to compare {@link AudioStream}s by their languages and track types + * for alphabetical sorting. + * + * @param context app context for localization + * @return Comparator + */ + private static Comparator getAudioTrackNameComparator( + @NonNull final Context context) { + final Locale appLoc = Localization.getAppLocale(context); + + return Comparator.comparing(AudioStream::getAudioLocale, Comparator.nullsLast( + Comparator.comparing(locale -> locale.getDisplayName(appLoc)))) + .thenComparing(AudioStream::getAudioTrackType); + } } diff --git a/app/src/main/res/layout/download_dialog.xml b/app/src/main/res/layout/download_dialog.xml index 37bbf2b03..6b0a36cc8 100644 --- a/app/src/main/res/layout/download_dialog.xml +++ b/app/src/main/res/layout/download_dialog.xml @@ -71,11 +71,45 @@ android:minWidth="150dp" tools:listitem="@layout/stream_quality_item" /> + + + + + + ,
Toggle all Streams which are not yet supported by the downloader are not shown + The default audio track should be already present in this stream The selected stream is not supported by external players No audio streams are available for external players No video streams are available for external players From 694418d30dd3bedd817a56687096f892c0904db8 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 21 Mar 2023 16:58:36 +0100 Subject: [PATCH 14/20] fix: update stream sizes when audio track changed --- .../org/schabi/newpipe/download/DownloadDialog.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 5d3679471..2295c3c7a 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -433,9 +433,6 @@ public class DownloadDialog extends DialogFragment dialogBinding.audioTrackSpinner.setAdapter(audioTrackAdapter); dialogBinding.audioTrackSpinner.setSelection(selectedAudioStreamIndex); - - dialogBinding.audioStreamSpinner.setAdapter(audioStreamsAdapter); - dialogBinding.audioStreamSpinner.setSelection(selectedAudioIndex); } private void setupAudioSpinner() { @@ -445,6 +442,8 @@ public class DownloadDialog extends DialogFragment dialogBinding.qualitySpinner.setVisibility(View.GONE); setRadioButtonsState(true); + dialogBinding.audioStreamSpinner.setAdapter(audioStreamsAdapter); + dialogBinding.audioStreamSpinner.setSelection(selectedAudioIndex); dialogBinding.audioStreamSpinner.setVisibility(View.VISIBLE); dialogBinding.audioTrackSpinner.setVisibility( wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); @@ -619,8 +618,12 @@ public class DownloadDialog extends DialogFragment onItemSelectedSetFileName(); break; case R.id.audio_track_spinner: + final boolean trackChanged = selectedAudioStreamIndex != position; selectedAudioStreamIndex = position; - updateSecondaryStreams(); + if (trackChanged) { + updateSecondaryStreams(); + fetchStreamsSize(); + } break; case R.id.audio_stream_spinner: selectedAudioIndex = position; From 39a5c8bdfb08a9e5b60b479dbd9968efd331687a Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Wed, 29 Mar 2023 13:39:29 +0200 Subject: [PATCH 15/20] fix: reset video stream sizes on audio track selection --- .../java/org/schabi/newpipe/download/DownloadDialog.java | 1 + .../java/org/schabi/newpipe/util/StreamItemAdapter.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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 2295c3c7a..7dd482b21 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -261,6 +261,7 @@ public class DownloadDialog extends DialogFragment final StreamSizeWrapper audioStreams = getWrappedAudioStreams(); final var secondaryStreams = new SparseArrayCompat>(4); final List videoStreams = wrappedVideoStreams.getStreamsList(); + wrappedVideoStreams.resetSizes(); for (int i = 0; i < videoStreams.size(); i++) { if (!videoStreams.get(i).isVideoOnly()) { diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java index 74de45720..4bcdfd02a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java @@ -235,7 +235,7 @@ public class StreamItemAdapter extends BaseA this.unknownSize = context == null ? "--.-" : context.getString(R.string.unknown_content); - Arrays.fill(streamSizes, -2); + resetSizes(); } /** @@ -269,6 +269,10 @@ public class StreamItemAdapter extends BaseA .onErrorReturnItem(true); } + public void resetSizes() { + Arrays.fill(streamSizes, -2); + } + public static StreamSizeWrapper empty() { //noinspection unchecked return (StreamSizeWrapper) EMPTY; From b567d428ad4617e80a8f085e1833a45b71c3e5e5 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 21 Apr 2023 23:15:37 +0200 Subject: [PATCH 16/20] fix: small codestyle fixes --- .../newpipe/download/DownloadDialog.java | 14 ++-- .../newpipe/player/PlayQueueActivity.java | 3 - .../org/schabi/newpipe/player/Player.java | 83 ++++++++++++------- .../resolver/AudioPlaybackResolver.java | 7 ++ .../newpipe/player/ui/VideoPlayerUi.java | 11 +-- .../org/schabi/newpipe/util/ListHelper.java | 8 +- .../newpipe/util/StreamItemAdapter.java | 6 +- 7 files changed, 80 insertions(+), 52 deletions(-) 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 7dd482b21..e0b1606dc 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -103,7 +103,7 @@ public class DownloadDialog extends DialogFragment @State AudioTracksWrapper wrappedAudioTracks; @State - int selectedAudioStreamIndex; + int selectedAudioTrackIndex; @State int selectedVideoIndex; // set in the constructor @State @@ -173,7 +173,7 @@ public class DownloadDialog extends DialogFragment final List> groupedAudioStreams = ListHelper.getGroupedAudioStreams(context, audioStreams); this.wrappedAudioTracks = new AudioTracksWrapper(groupedAudioStreams, context); - this.selectedAudioStreamIndex = + this.selectedAudioTrackIndex = ListHelper.getDefaultAudioTrackGroup(context, groupedAudioStreams); // TODO: Adapt this code when the downloader support other types of stream deliveries @@ -433,7 +433,7 @@ public class DownloadDialog extends DialogFragment } dialogBinding.audioTrackSpinner.setAdapter(audioTrackAdapter); - dialogBinding.audioTrackSpinner.setSelection(selectedAudioStreamIndex); + dialogBinding.audioTrackSpinner.setSelection(selectedAudioTrackIndex); } private void setupAudioSpinner() { @@ -619,8 +619,8 @@ public class DownloadDialog extends DialogFragment onItemSelectedSetFileName(); break; case R.id.audio_track_spinner: - final boolean trackChanged = selectedAudioStreamIndex != position; - selectedAudioStreamIndex = position; + final boolean trackChanged = selectedAudioTrackIndex != position; + selectedAudioTrackIndex = position; if (trackChanged) { updateSecondaryStreams(); fetchStreamsSize(); @@ -726,10 +726,10 @@ public class DownloadDialog extends DialogFragment } private StreamSizeWrapper getWrappedAudioStreams() { - if (selectedAudioStreamIndex < 0 || selectedAudioStreamIndex > wrappedAudioTracks.size()) { + if (selectedAudioTrackIndex < 0 || selectedAudioTrackIndex > wrappedAudioTracks.size()) { return StreamSizeWrapper.empty(); } - return wrappedAudioTracks.getTracksList().get(selectedAudioStreamIndex); + return wrappedAudioTracks.getTracksList().get(selectedAudioTrackIndex); } private int getSubtitleIndexBy(@NonNull final List streams) { 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 bf0dc4a56..cd71c64e9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -667,11 +667,8 @@ public final class PlayQueueActivity extends AppCompatActivity return; } - player.saveStreamProgressState(); final String newAudioTrack = availableStreams.get(itemId).getAudioTrackId(); - player.setRecovery(); player.setAudioTrack(newAudioTrack); - player.reloadPlayQueueManager(); }); } } 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 144c754a9..89bdd9d69 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -180,13 +180,18 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ // play queue might be null e.g. while player is starting - @Nullable private PlayQueue playQueue; + @Nullable + private PlayQueue playQueue; - @Nullable private MediaSourceManager playQueueManager; + @Nullable + private MediaSourceManager playQueueManager; - @Nullable private PlayQueueItem currentItem; - @Nullable private MediaItemTag currentMetadata; - @Nullable private Bitmap currentThumbnail; + @Nullable + private PlayQueueItem currentItem; + @Nullable + private MediaItemTag currentMetadata; + @Nullable + private Bitmap currentThumbnail; /*////////////////////////////////////////////////////////////////////////// // Player @@ -195,12 +200,17 @@ public final class Player implements PlaybackListener, Listener { private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; - @NonNull private final DefaultTrackSelector trackSelector; - @NonNull private final LoadController loadController; - @NonNull private final DefaultRenderersFactory renderFactory; + @NonNull + private final DefaultTrackSelector trackSelector; + @NonNull + private final LoadController loadController; + @NonNull + private final DefaultRenderersFactory renderFactory; - @NonNull private final VideoPlaybackResolver videoResolver; - @NonNull private final AudioPlaybackResolver audioResolver; + @NonNull + private final VideoPlaybackResolver videoResolver; + @NonNull + private final AudioPlaybackResolver audioResolver; private final PlayerService service; //TODO try to remove and replace everything with context @@ -225,24 +235,32 @@ public final class Player implements PlaybackListener, Listener { private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; - @Nullable private PlayerServiceEventListener fragmentListener = null; - @Nullable private PlayerEventListener activityListener = null; + @Nullable + private PlayerServiceEventListener fragmentListener = null; + @Nullable + private PlayerEventListener activityListener = null; - @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); - @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); + @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; + @NonNull + private final Target currentThumbnailTarget; /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ - @NonNull private final Context context; - @NonNull private final SharedPreferences prefs; - @NonNull private final HistoryRecordManager recordManager; + @NonNull + private final Context context; + @NonNull + private final SharedPreferences prefs; + @NonNull + private final HistoryRecordManager recordManager; /*////////////////////////////////////////////////////////////////////////// @@ -334,7 +352,7 @@ public final class Player implements PlaybackListener, Listener { isAudioOnly = audioPlayerSelected(); if (intent.hasExtra(PLAYBACK_QUALITY)) { - setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY)); + videoResolver.setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY)); } // Resolve enqueue intents @@ -342,7 +360,7 @@ public final class Player implements PlaybackListener, Listener { playQueue.append(newQueue.getStreams()); return; - // Resolve enqueue next intents + // Resolve enqueue next intents } else if (intent.getBooleanExtra(ENQUEUE_NEXT, false) && playQueue != null) { final int currentIndex = playQueue.getIndex(); playQueue.append(newQueue.getStreams()); @@ -914,7 +932,7 @@ public final class Player implements PlaybackListener, Listener { private Disposable getProgressUpdateDisposable() { return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS, - AndroidSchedulers.mainThread()) + AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(ignored -> triggerProgressUpdate(), error -> Log.e(TAG, "Progress update failure: ", error)); @@ -923,7 +941,6 @@ public final class Player implements PlaybackListener, Listener { //endregion - /*////////////////////////////////////////////////////////////////////////// // Playback states //////////////////////////////////////////////////////////////////////////*/ @@ -1247,7 +1264,7 @@ public final class Player implements PlaybackListener, Listener { .flatMap(MediaItemTag::getMaybeStreamInfo).orElse(null); final MediaItemTag.AudioTrack previousAudioTrack = Optional.ofNullable(currentMetadata) - .flatMap(MediaItemTag::getMaybeAudioTrack).orElse(null); + .flatMap(MediaItemTag::getMaybeAudioTrack).orElse(null); currentMetadata = tag; if (!currentMetadata.getErrors().isEmpty()) { @@ -1270,9 +1287,9 @@ public final class Player implements PlaybackListener, Listener { updateMetadataWith(info); } else if (previousAudioTrack == null || tag.getMaybeAudioTrack() - .map(t -> t.getSelectedAudioStreamIndex() - != previousAudioTrack.getSelectedAudioStreamIndex()) - .orElse(false)) { + .map(t -> t.getSelectedAudioStreamIndex() + != previousAudioTrack.getSelectedAudioStreamIndex()) + .orElse(false)) { notifyAudioTrackUpdateToListeners(); } }); @@ -1361,6 +1378,7 @@ public final class Player implements PlaybackListener, Listener { // Errors //////////////////////////////////////////////////////////////////////////*/ //region Errors + /** * Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}. *

There are multiple types of errors:

@@ -1387,8 +1405,9 @@ public final class Player implements PlaybackListener, Listener { * For any error above that is not explicitly catchable, the player will * create a notification so users are aware. * + * * @see com.google.android.exoplayer2.Player.Listener#onPlayerError(PlaybackException) - * */ + */ // 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. @@ -2141,7 +2160,7 @@ public final class Player implements PlaybackListener, Listener { // because the stream source will be probably the same as the current played if (sourceType == SourceType.VIDEO_WITH_SEPARATED_AUDIO || (sourceType == SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY - && isNullOrEmpty(streamInfo.getAudioStreams()))) { + && isNullOrEmpty(streamInfo.getAudioStreams()))) { // It's not needed to reload the play queue manager only if the content's stream type // is a video stream, a live stream or an ended live stream return !StreamTypeUtil.isVideo(streamType); @@ -2203,12 +2222,18 @@ public final class Player implements PlaybackListener, Listener { } public void setPlaybackQuality(@Nullable final String quality) { + saveStreamProgressState(); + setRecovery(); videoResolver.setPlaybackQuality(quality); + reloadPlayQueueManager(); } public void setAudioTrack(@Nullable final String audioTrackId) { + saveStreamProgressState(); + setRecovery(); videoResolver.setAudioTrack(audioTrackId); audioResolver.setAudioTrack(audioTrackId); + reloadPlayQueueManager(); } @@ -2286,7 +2311,7 @@ public final class Player implements PlaybackListener, Listener { /** * Get the video renderer index of the current playing stream. - * + *

* This method returns the video renderer index of the current * {@link MappingTrackSelector.MappedTrackInfo} or {@link #RENDERER_UNAVAILABLE} if the current * {@link MappingTrackSelector.MappedTrackInfo} is null or if there is no video renderer index. diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java index e94295724..063a2a297 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java @@ -38,6 +38,13 @@ public class AudioPlaybackResolver implements PlaybackResolver { this.dataSource = dataSource; } + /** + * Get a media source providing audio. If a service has no separate {@link AudioStream}s we + * use a video stream as audio source to support audio background playback. + * + * @param info of the stream + * @return the audio source to use or null if none could be found + */ @Override @Nullable public MediaSource resolve(@NonNull final StreamInfo info) { 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 8aff0af87..2638ff041 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 @@ -110,7 +110,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa protected PlayerBinding binding; private final Handler controlsVisibilityHandler = new Handler(Looper.getMainLooper()); - @Nullable private SurfaceHolderCallback surfaceHolderCallback; + @Nullable + private SurfaceHolderCallback surfaceHolderCallback; boolean surfaceIsSetup = false; @@ -533,6 +534,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa /** * Sets the current duration into the corresponding elements. + * * @param currentProgress the current progress, in milliseconds */ private void updatePlayBackElementsCurrentDuration(final int currentProgress) { @@ -545,6 +547,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa /** * 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) { @@ -1261,11 +1264,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa return; } - player.saveStreamProgressState(); final String newResolution = availableStreams.get(menuItemIndex).getResolution(); - player.setRecovery(); player.setPlaybackQuality(newResolution); - player.reloadPlayQueueManager(); binding.qualityTextView.setText(menuItem.getTitle()); } @@ -1285,11 +1285,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa return; } - player.saveStreamProgressState(); final String newAudioTrack = availableStreams.get(menuItemIndex).getAudioTrackId(); - player.setRecovery(); player.setAudioTrack(newAudioTrack); - player.reloadPlayQueueManager(); binding.audioTrackTextView.setText(menuItem.getTitle()); } 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 f8a800b0e..b036d55c5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -715,9 +715,9 @@ public final class ListHelper { /** * Get a {@link Comparator} to compare {@link AudioStream}s by their tracks. * - *

In this order:

+ *

Tracks will be compared this order:

*
    - *
  1. If {@code preferOriginalAudio}: is original audio
  2. + *
  3. If {@code preferOriginalAudio}: use original audio
  4. *
  5. Language matches {@code preferredLanguage}
  6. *
  7. * Track type ranks highest in this order: @@ -752,9 +752,9 @@ public final class ListHelper { /** * Get a {@link Comparator} to compare {@link AudioStream}s by their tracks. * - *

    In this order:

    + *

    Tracks will be compared this order:

    *
      - *
    1. If {@code preferOriginalAudio}: is original audio
    2. + *
    3. If {@code preferOriginalAudio}: use original audio
    4. *
    5. Language matches {@code preferredLanguage}
    6. *
    7. * Track type ranks highest in this order: diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java index 4bcdfd02a..2eb63ff41 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java @@ -224,6 +224,8 @@ public class StreamItemAdapter extends BaseA public static class StreamSizeWrapper implements Serializable { private static final StreamSizeWrapper EMPTY = new StreamSizeWrapper<>(Collections.emptyList(), null); + private static final int SIZE_UNSET = -2; + private final List streamsList; private final long[] streamSizes; private final String unknownSize; @@ -251,7 +253,7 @@ public class StreamItemAdapter extends BaseA final Callable fetchAndSet = () -> { boolean hasChanged = false; for (final X stream : streamsWrapper.getStreamsList()) { - if (streamsWrapper.getSizeInBytes(stream) > -2) { + if (streamsWrapper.getSizeInBytes(stream) > SIZE_UNSET) { continue; } @@ -270,7 +272,7 @@ public class StreamItemAdapter extends BaseA } public void resetSizes() { - Arrays.fill(streamSizes, -2); + Arrays.fill(streamSizes, SIZE_UNSET); } public static StreamSizeWrapper empty() { From 4e837e838d08d5447051798dd15e575690d8bcc1 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 1 May 2023 00:02:37 +0200 Subject: [PATCH 17/20] fix docs in app/src/main/java/org/schabi/newpipe/util/Localization.java Co-authored-by: Audric V. <74829229+AudricV@users.noreply.github.com> --- .../org/schabi/newpipe/util/Localization.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 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 9123d377c..c4034252d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -266,14 +266,17 @@ public final class Localization { /** * Get the localized name of an audio track. - *

      Example:

      - *

      English (original)

      - *

      English (descriptive)

      - *

      Spanish (dubbed)

      * - * @param context used to get app language - * @param track a {@link AudioStream} of the track - * @return localized track name + *

      Examples of results returned by this method:

      + *
        + *
      • English (original)
      • + *
      • English (descriptive)
      • + *
      • Spanish (dubbed)
      • + *
      + * + * @param context the context used to get the app language + * @param track an {@link AudioStream} of the track + * @return the localized name of the audio track */ public static String audioTrackName(final Context context, final AudioStream track) { final String name; From 22671ca16c6d4e2904116d1faebfb6f95b8103ea Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 1 May 2023 00:04:04 +0200 Subject: [PATCH 18/20] fix: audio stream cache key, code fmt --- .../java/org/schabi/newpipe/download/DownloadDialog.java | 4 +--- .../schabi/newpipe/player/resolver/PlaybackResolver.java | 5 +++++ app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 3 ++- .../test/java/org/schabi/newpipe/util/ListHelperTest.java | 6 ++++-- 4 files changed, 12 insertions(+), 6 deletions(-) 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 e0b1606dc..0a92efb93 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -470,9 +470,7 @@ public class DownloadDialog extends DialogFragment dialogBinding.audioTrackSpinner.setVisibility( isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); dialogBinding.defaultAudioTrackPresentText.setVisibility( - !isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE - - ); + !isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); } private void setupSubtitleSpinner() { diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java index c15447418..e204b8372 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java @@ -161,6 +161,11 @@ public interface PlaybackResolver extends Resolver { cacheKey.append(audioStream.getAudioTrackId()); } + if (audioStream.getAudioLocale() != null) { + cacheKey.append(" "); + cacheKey.append(audioStream.getAudioLocale().getISO3Language()); + } + return cacheKey.toString(); } 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 9c58ed75e..f45f3786d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -797,7 +797,8 @@ public final class ListHelper { * @return Comparator */ static Comparator getAudioTrackComparator( - final Locale preferredLanguage, final boolean preferOriginalAudio, + final Locale preferredLanguage, + final boolean preferOriginalAudio, final boolean preferDescriptiveAudio) { final String langCode = preferredLanguage.getISO3Language(); final List trackTypeRanking = preferDescriptiveAudio 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 b4a4167cf..c7c36eadc 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -451,8 +451,10 @@ public class ListHelperTest { } private static AudioStream generateAudioTrack( - @NonNull final String id, @Nullable final String trackId, - @Nullable final Locale locale, @Nullable final AudioTrackType trackType) { + @NonNull final String id, + @Nullable final String trackId, + @Nullable final Locale locale, + @Nullable final AudioTrackType trackType) { return new AudioStream.Builder() .setId(id) .setContent("", true) From d89a3c6c4d6f3bd4045e7a55732f2249b0e6275b Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Tue, 2 May 2023 00:11:09 +0200 Subject: [PATCH 19/20] Remove "default" from audio track already present message We don't know if, on muxed video streams we get for all services which support multiple audio languages, that the audio language returned is the original one or not, even if it should be the case. In order to avoid saying potential false information, this word has been removed from the string resource (ID and value) and the corresponding layout ID in the download dialog. --- .../java/org/schabi/newpipe/download/DownloadDialog.java | 6 +++--- app/src/main/res/layout/download_dialog.xml | 6 +++--- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) 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 0a92efb93..88a3484d9 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -448,7 +448,7 @@ public class DownloadDialog extends DialogFragment dialogBinding.audioStreamSpinner.setVisibility(View.VISIBLE); dialogBinding.audioTrackSpinner.setVisibility( wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); - dialogBinding.defaultAudioTrackPresentText.setVisibility(View.GONE); + dialogBinding.audioTrackPresentInVideoText.setVisibility(View.GONE); } private void setupVideoSpinner() { @@ -469,7 +469,7 @@ public class DownloadDialog extends DialogFragment dialogBinding.audioTrackSpinner.setVisibility( isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); - dialogBinding.defaultAudioTrackPresentText.setVisibility( + dialogBinding.audioTrackPresentInVideoText.setVisibility( !isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE); } @@ -484,7 +484,7 @@ public class DownloadDialog extends DialogFragment setRadioButtonsState(true); dialogBinding.audioStreamSpinner.setVisibility(View.GONE); dialogBinding.audioTrackSpinner.setVisibility(View.GONE); - dialogBinding.defaultAudioTrackPresentText.setVisibility(View.GONE); + dialogBinding.audioTrackPresentInVideoText.setVisibility(View.GONE); } diff --git a/app/src/main/res/layout/download_dialog.xml b/app/src/main/res/layout/download_dialog.xml index 6b0a36cc8..67aa1577c 100644 --- a/app/src/main/res/layout/download_dialog.xml +++ b/app/src/main/res/layout/download_dialog.xml @@ -94,7 +94,7 @@ tools:visibility="gone" /> , Toggle all Streams which are not yet supported by the downloader are not shown - The default audio track should be already present in this stream + An audio track should be already present in this stream The selected stream is not supported by external players No audio streams are available for external players No video streams are available for external players From 023f6166abf7e566662ccf88e5f11164cc838a3e Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Tue, 2 May 2023 00:18:46 +0200 Subject: [PATCH 20/20] Add Open in browser button to audio external players dialog This change makes the dialog consistent with the video one. --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 3 ++- 1 file changed, 2 insertions(+), 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 d8b2b43a5..8227f1c69 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 @@ -2166,12 +2166,13 @@ public final class VideoDetailFragment if (audioTracks.isEmpty()) { Toast.makeText(activity, R.string.no_audio_streams_available_for_external_players, Toast.LENGTH_SHORT).show(); - } else if (audioTracks.size() == 1) { startOnExternalPlayer(activity, currentInfo, audioTracks.get(0)); } else { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.select_audio_track_external_players); + builder.setNeutralButton(R.string.open_in_browser, (dialog, i) -> + ShareUtils.openUrlInBrowser(requireActivity(), url)); final int selectedAudioStream = ListHelper.getDefaultAudioFormat(activity, audioTracks);