From 036196a48747682bcad3831fae36db2916e9beb0 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Thu, 16 Jun 2022 11:14:02 +0200 Subject: [PATCH] Filter streams using Java 8 Stream's API instead of removing streams with list iterators and add a better toast when there is no audio stream for external players This ensures to not remove streams from the StreamInfo lists themselves, and so to not have to create list copies. The toast shown in RouterActivity, when there is no audio stream available for external players, is now shown, in the same case, when pressing the background button in VideoDetailFragment. --- .../newpipe/download/DownloadDialog.java | 24 +++--- .../fragments/detail/VideoDetailFragment.java | 26 ++++-- .../resolver/AudioPlaybackResolver.java | 6 +- .../resolver/VideoPlaybackResolver.java | 29 +++---- .../org/schabi/newpipe/util/ListHelper.java | 84 ++++++++---------- .../schabi/newpipe/util/NavigationHelper.java | 86 ++++++++++--------- 6 files changed, 123 insertions(+), 132 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 9f46f7f6b..4fb47496b 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -69,7 +69,6 @@ import org.schabi.newpipe.util.ThemeHelper; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -84,7 +83,7 @@ import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; import us.shandian.giga.service.MissionState; -import static org.schabi.newpipe.util.ListHelper.keepStreamsWithDelivery; +import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; public class DownloadDialog extends DialogFragment @@ -149,25 +148,24 @@ public class DownloadDialog extends DialogFragment public static DownloadDialog newInstance(final Context context, @NonNull final StreamInfo info) { // TODO: Adapt this code when the downloader support other types of stream deliveries - final List videoStreams = new ArrayList<>(info.getVideoStreams()); final List progressiveHttpVideoStreams = - keepStreamsWithDelivery(videoStreams, DeliveryMethod.PROGRESSIVE_HTTP); + getStreamsOfSpecifiedDelivery(info.getVideoStreams(), + DeliveryMethod.PROGRESSIVE_HTTP); - final List videoOnlyStreams = new ArrayList<>(info.getVideoOnlyStreams()); final List progressiveHttpVideoOnlyStreams = - keepStreamsWithDelivery(videoOnlyStreams, DeliveryMethod.PROGRESSIVE_HTTP); + getStreamsOfSpecifiedDelivery(info.getVideoOnlyStreams(), + DeliveryMethod.PROGRESSIVE_HTTP); - final List audioStreams = new ArrayList<>(info.getAudioStreams()); final List progressiveHttpAudioStreams = - keepStreamsWithDelivery(audioStreams, DeliveryMethod.PROGRESSIVE_HTTP); + getStreamsOfSpecifiedDelivery(info.getAudioStreams(), + DeliveryMethod.PROGRESSIVE_HTTP); - final List subtitlesStreams = new ArrayList<>(info.getSubtitles()); final List progressiveHttpSubtitlesStreams = - keepStreamsWithDelivery(subtitlesStreams, DeliveryMethod.PROGRESSIVE_HTTP); + getStreamsOfSpecifiedDelivery(info.getSubtitles(), + DeliveryMethod.PROGRESSIVE_HTTP); - final List videoStreamsList = new ArrayList<>( - ListHelper.getSortedStreamVideosList(context, progressiveHttpVideoStreams, - progressiveHttpVideoOnlyStreams, false, false)); + final List videoStreamsList = ListHelper.getSortedStreamVideosList(context, + progressiveHttpVideoStreams, progressiveHttpVideoOnlyStreams, false, false); final DownloadDialog instance = new DownloadDialog(); instance.setInfo(info); 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 bb09681f5..ff2114b83 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 @@ -31,6 +31,7 @@ import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.RelativeLayout; +import android.widget.Toast; import androidx.annotation.AttrRes; import androidx.annotation.NonNull; @@ -122,7 +123,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientat import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; -import static org.schabi.newpipe.util.ListHelper.removeNonUrlAndTorrentStreams; +import static org.schabi.newpipe.util.ListHelper.getNonUrlAndNonTorrentStreams; public final class VideoDetailFragment extends BaseStateFragment @@ -1092,9 +1093,6 @@ public final class VideoDetailFragment } private void openBackgroundPlayer(final boolean append) { - final AudioStream audioStream = currentInfo.getAudioStreams() - .get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams())); - final boolean useExternalAudioPlayer = PreferenceManager .getDefaultSharedPreferences(activity) .getBoolean(activity.getString(R.string.use_external_audio_player_key), false); @@ -1109,7 +1107,17 @@ public final class VideoDetailFragment if (!useExternalAudioPlayer) { openNormalBackgroundPlayer(append); } else { - startOnExternalPlayer(activity, currentInfo, audioStream); + final List audioStreams = getNonUrlAndNonTorrentStreams( + 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)); } } @@ -2147,10 +2155,10 @@ public final class VideoDetailFragment return; } - final List videoStreams = removeNonUrlAndTorrentStreams( - new ArrayList<>(currentInfo.getVideoStreams())); - final List videoOnlyStreams = removeNonUrlAndTorrentStreams( - new ArrayList<>(currentInfo.getVideoOnlyStreams())); + final List videoStreams = getNonUrlAndNonTorrentStreams( + currentInfo.getVideoStreams()); + final List videoOnlyStreams = getNonUrlAndNonTorrentStreams( + currentInfo.getVideoOnlyStreams()); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.select_quality_external_players); 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 85c15faf1..3e166c339 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,6 +1,6 @@ package org.schabi.newpipe.player.resolver; -import static org.schabi.newpipe.util.ListHelper.removeTorrentStreams; +import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams; import android.content.Context; import android.util.Log; @@ -18,7 +18,6 @@ import org.schabi.newpipe.player.mediaitem.StreamInfoTag; import org.schabi.newpipe.util.ListHelper; import java.io.IOException; -import java.util.ArrayList; import java.util.List; public class AudioPlaybackResolver implements PlaybackResolver { @@ -43,8 +42,7 @@ public class AudioPlaybackResolver implements PlaybackResolver { return liveSource; } - final List audioStreams = new ArrayList<>(info.getAudioStreams()); - removeTorrentStreams(audioStreams); + final List audioStreams = getNonTorrentStreams(info.getAudioStreams()); final int index = ListHelper.getDefaultAudioFormat(context, audioStreams); if (index < 0 || index >= info.getAudioStreams().size()) { 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 317c49fc9..fd00d0ed9 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 @@ -29,8 +29,8 @@ import java.util.List; import java.util.Optional; import static com.google.android.exoplayer2.C.TIME_UNSET; -import static org.schabi.newpipe.util.ListHelper.removeNonUrlAndTorrentStreams; -import static org.schabi.newpipe.util.ListHelper.removeTorrentStreams; +import static org.schabi.newpipe.util.ListHelper.getNonUrlAndNonTorrentStreams; +import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams; public class VideoPlaybackResolver implements PlaybackResolver { private static final String TAG = VideoPlaybackResolver.class.getSimpleName(); @@ -70,24 +70,21 @@ public class VideoPlaybackResolver implements PlaybackResolver { } final List mediaSources = new ArrayList<>(); - final List videoStreams = new ArrayList<>(info.getVideoStreams()); - final List videoOnlyStreams = new ArrayList<>(info.getVideoOnlyStreams()); - - removeTorrentStreams(videoStreams); - removeTorrentStreams(videoOnlyStreams); // Create video stream source - final List videos = ListHelper.getSortedStreamVideosList(context, - videoStreams, videoOnlyStreams, false, true); + final List videoStreamsList = ListHelper.getSortedStreamVideosList(context, + getNonTorrentStreams(info.getVideoStreams()), + getNonTorrentStreams(info.getVideoOnlyStreams()), false, true); final int index; - if (videos.isEmpty()) { + if (videoStreamsList.isEmpty()) { index = -1; } else if (playbackQuality == null) { - index = qualityResolver.getDefaultResolutionIndex(videos); + index = qualityResolver.getDefaultResolutionIndex(videoStreamsList); } else { - index = qualityResolver.getOverrideResolutionIndex(videos, getPlaybackQuality()); + index = qualityResolver.getOverrideResolutionIndex(videoStreamsList, + getPlaybackQuality()); } - final MediaItemTag tag = StreamInfoTag.of(info, videos, index); + final MediaItemTag tag = StreamInfoTag.of(info, videoStreamsList, index); @Nullable final VideoStream video = tag.getMaybeQuality() .map(MediaItemTag.Quality::getSelectedVideoStream) .orElse(null); @@ -104,8 +101,7 @@ public class VideoPlaybackResolver implements PlaybackResolver { } // Create optional audio stream source - final List audioStreams = info.getAudioStreams(); - removeTorrentStreams(audioStreams); + final List audioStreams = getNonTorrentStreams(info.getAudioStreams()); final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get( ListHelper.getDefaultAudioFormat(context, audioStreams)); @@ -129,13 +125,14 @@ public class VideoPlaybackResolver implements PlaybackResolver { if (mediaSources.isEmpty()) { return null; } + // Below are auxiliary media sources // Create subtitle sources final List subtitlesStreams = info.getSubtitles(); if (subtitlesStreams != null) { // Torrent and non URL subtitles are not supported by ExoPlayer - final List nonTorrentAndUrlStreams = removeNonUrlAndTorrentStreams( + final List nonTorrentAndUrlStreams = getNonUrlAndNonTorrentStreams( subtitlesStreams); for (final SubtitlesStream subtitle : nonTorrentAndUrlStreams) { final MediaFormat mediaFormat = subtitle.getFormat(); 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 3a03e0b30..33c7a2f49 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -23,10 +23,10 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; public final class ListHelper { @@ -116,27 +116,17 @@ public final class ListHelper { * Return a {@link Stream} list which uses the given delivery method from a {@link Stream} * list. * - * @param streamList the original stream list - * @param deliveryMethod the delivery method + * @param streamList the original {@link Stream stream} list + * @param deliveryMethod the {@link DeliveryMethod delivery method} * @param the item type's class that extends {@link Stream} - * @return a stream list which uses the given delivery method + * @return a {@link Stream stream} list which uses the given delivery method */ @NonNull - public static List keepStreamsWithDelivery( - @NonNull final List streamList, + public static List getStreamsOfSpecifiedDelivery( + final List streamList, final DeliveryMethod deliveryMethod) { - if (streamList.isEmpty()) { - return Collections.emptyList(); - } - - final Iterator streamListIterator = streamList.iterator(); - while (streamListIterator.hasNext()) { - if (streamListIterator.next().getDeliveryMethod() != deliveryMethod) { - streamListIterator.remove(); - } - } - - return streamList; + return getFilteredStreamList(streamList, + stream -> stream.getDeliveryMethod() == deliveryMethod); } /** @@ -147,21 +137,10 @@ public final class ListHelper { * @return a stream list which only contains URL streams and non-torrent streams */ @NonNull - public static List removeNonUrlAndTorrentStreams( - @NonNull final List streamList) { - if (streamList.isEmpty()) { - return Collections.emptyList(); - } - - final Iterator streamListIterator = streamList.iterator(); - while (streamListIterator.hasNext()) { - final S stream = streamListIterator.next(); - if (!stream.isUrl() || stream.getDeliveryMethod() == DeliveryMethod.TORRENT) { - streamListIterator.remove(); - } - } - - return streamList; + public static List getNonUrlAndNonTorrentStreams( + final List streamList) { + return getFilteredStreamList(streamList, + stream -> stream.isUrl() && stream.getDeliveryMethod() != DeliveryMethod.TORRENT); } /** @@ -172,21 +151,10 @@ public final class ListHelper { * @return a stream list which only contains non-torrent streams */ @NonNull - public static List removeTorrentStreams( - @NonNull final List streamList) { - if (streamList.isEmpty()) { - return Collections.emptyList(); - } - - final Iterator streamListIterator = streamList.iterator(); - while (streamListIterator.hasNext()) { - final S stream = streamListIterator.next(); - if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) { - streamListIterator.remove(); - } - } - - return streamList; + public static List getNonTorrentStreams( + final List streamList) { + return getFilteredStreamList(streamList, + stream -> stream.getDeliveryMethod() != DeliveryMethod.TORRENT); } /** @@ -224,6 +192,26 @@ public final class ListHelper { // Utils //////////////////////////////////////////////////////////////////////////*/ + /** + * Get a filtered stream list, by using Java 8 Stream's API and the given predicate. + * + * @param streamList the stream list to filter + * @param streamListPredicate the predicate which will be used to filter streams + * @param the item type's class that extends {@link Stream} + * @return a new stream list filtered using the given predicate + */ + private static List getFilteredStreamList( + final List streamList, + final Predicate streamListPredicate) { + if (streamList == null) { + return Collections.emptyList(); + } + + return streamList.stream() + .filter(streamListPredicate) + .collect(Collectors.toList()); + } + private static String computeDefaultResolution(final Context context, final int key, final int value) { final SharedPreferences preferences diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index c3246857e..ffc7433a0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -63,7 +63,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.List; -import static org.schabi.newpipe.util.ListHelper.removeNonUrlAndTorrentStreams; +import static org.schabi.newpipe.util.ListHelper.getNonUrlAndNonTorrentStreams; public final class NavigationHelper { public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; @@ -221,39 +221,42 @@ public final class NavigationHelper { public static void playOnExternalAudioPlayer(@NonNull final Context context, @NonNull final StreamInfo info) { final List audioStreams = info.getAudioStreams(); - if (audioStreams.isEmpty()) { + if (audioStreams == null || audioStreams.isEmpty()) { Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show(); return; } - final List audioStreamsForExternalPlayers = removeNonUrlAndTorrentStreams( - audioStreams); + + final List audioStreamsForExternalPlayers = + getNonUrlAndNonTorrentStreams(audioStreams); if (audioStreamsForExternalPlayers.isEmpty()) { Toast.makeText(context, R.string.no_audio_streams_available_for_external_players, Toast.LENGTH_SHORT).show(); return; } - final int index = ListHelper.getDefaultAudioFormat(context, - audioStreamsForExternalPlayers); + final int index = ListHelper.getDefaultAudioFormat(context, audioStreamsForExternalPlayers); final AudioStream audioStream = audioStreamsForExternalPlayers.get(index); + playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream); } public static void playOnExternalVideoPlayer(final Context context, @NonNull final StreamInfo info) { final List videoStreams = info.getVideoStreams(); - if (videoStreams.isEmpty()) { + if (videoStreams == null || videoStreams.isEmpty()) { Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show(); return; } + final List videoStreamsForExternalPlayers = ListHelper.getSortedStreamVideosList(context, - removeNonUrlAndTorrentStreams(videoStreams), null, false, false); + getNonUrlAndNonTorrentStreams(videoStreams), null, false, false); if (videoStreamsForExternalPlayers.isEmpty()) { Toast.makeText(context, R.string.no_video_streams_available_for_external_players, Toast.LENGTH_SHORT).show(); return; } + final int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsForExternalPlayers); @@ -267,42 +270,41 @@ public final class NavigationHelper { @NonNull final Stream stream) { final DeliveryMethod deliveryMethod = stream.getDeliveryMethod(); final String mimeType; - if (deliveryMethod == DeliveryMethod.PROGRESSIVE_HTTP) { - if (stream.getFormat() != null) { - mimeType = stream.getFormat().getMimeType(); - } else { - if (stream instanceof AudioStream) { - mimeType = "audio/*"; - } else if (stream instanceof VideoStream) { - mimeType = "video/*"; + + if (!stream.isUrl() || deliveryMethod == DeliveryMethod.TORRENT) { + Toast.makeText(context, R.string.selected_stream_external_player_not_supported, + Toast.LENGTH_SHORT).show(); + return; + } + + switch (deliveryMethod) { + case PROGRESSIVE_HTTP: + if (stream.getFormat() == null) { + if (stream instanceof AudioStream) { + mimeType = "audio/*"; + } else if (stream instanceof VideoStream) { + mimeType = "video/*"; + } else { + // This should never be reached, because subtitles are not opened in + // external players + return; + } } else { - // This should never be reached, because subtitles are not opened in external - // players - return; + mimeType = stream.getFormat().getMimeType(); } - } - } else { - if (!stream.isUrl() || deliveryMethod == DeliveryMethod.TORRENT) { - Toast.makeText(context, R.string.selected_stream_external_player_not_supported, - Toast.LENGTH_SHORT).show(); - return; - } else { - switch (deliveryMethod) { - case HLS: - mimeType = "application/x-mpegURL"; - break; - case DASH: - mimeType = "application/dash+xml"; - break; - case SS: - mimeType = "application/vnd.ms-sstr+xml"; - break; - default: - // Progressive HTTP streams are handled above and torrents streams are not - // exposed to external players - mimeType = ""; - } - } + break; + case HLS: + mimeType = "application/x-mpegURL"; + break; + case DASH: + mimeType = "application/dash+xml"; + break; + case SS: + mimeType = "application/vnd.ms-sstr+xml"; + break; + default: + // Torrent streams are not exposed to external players + mimeType = ""; } final Intent intent = new Intent();