package org.schabi.newpipe.util; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.annotation.StringRes; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.VideoStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @SuppressWarnings("WeakerAccess") public final class ListHelper { private static final List HIGH_RESOLUTION_LIST = Arrays.asList("1440p", "2160p", "1440p60", "2160p60"); /** * Return the index of the default stream in the list, based on the parameters * defaultResolution and defaultFormat * * @return index of the default resolution&format */ public static int getDefaultResolutionIndex(String defaultResolution, String bestResolutionKey, MediaFormat defaultFormat, List videoStreams) { if (videoStreams == null || videoStreams.isEmpty()) return -1; sortStreamList(videoStreams, false); if (defaultResolution.equals(bestResolutionKey)) { return 0; } int defaultStreamIndex = getDefaultStreamIndex(defaultResolution, defaultFormat, videoStreams); if (defaultStreamIndex == -1 && defaultResolution.contains("p60")) { defaultStreamIndex = getDefaultStreamIndex(defaultResolution.replace("p60", "p"), defaultFormat, videoStreams); } // this is actually an error, // but maybe there is really no stream fitting to the default value. if (defaultStreamIndex == -1) return 0; return defaultStreamIndex; } /** * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ public static int getDefaultResolutionIndex(Context context, List videoStreams) { SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context); if (defaultPreferences == null) return 0; String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value)); return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); } /** * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ public static int getPopupDefaultResolutionIndex(Context context, List videoStreams) { SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context); if (defaultPreferences == null) return 0; String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value)); return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); } public static int getDefaultAudioFormat(Context context, List audioStreams) { MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key, R.string.default_audio_format_value); return getHighestQualityAudioIndex(defaultFormat, audioStreams); } public static int getHighestQualityAudioIndex(List audioStreams) { if (audioStreams == null || audioStreams.isEmpty()) return -1; int highestQualityIndex = 0; if (audioStreams.size() > 1) for (int i = 1; i < audioStreams.size(); i++) { AudioStream audioStream = audioStreams.get(i); if (audioStream.average_bitrate >= audioStreams.get(highestQualityIndex).average_bitrate) highestQualityIndex = i; } return highestQualityIndex; } /** * Get the audio from the list with the highest bitrate * * @param audioStreams list the audio streams * @return audio with highest average bitrate */ public static AudioStream getHighestQualityAudio(List audioStreams) { if (audioStreams == null || audioStreams.isEmpty()) return null; return audioStreams.get(getHighestQualityAudioIndex(audioStreams)); } /** * Get the audio from the list with the highest bitrate * * @param audioStreams list the audio streams * @return index of the audio with the highest average bitrate of the default format */ public static int getHighestQualityAudioIndex(MediaFormat defaultFormat, List audioStreams) { if (audioStreams == null || audioStreams.isEmpty() || defaultFormat == null) return -1; int highestQualityIndex = -1; for (int i = 0; i < audioStreams.size(); i++) { AudioStream audioStream = audioStreams.get(i); if (highestQualityIndex == -1 && audioStream.format == defaultFormat.id) highestQualityIndex = i; if (highestQualityIndex != -1 && audioStream.format == defaultFormat.id && audioStream.average_bitrate > audioStreams.get(highestQualityIndex).average_bitrate) { highestQualityIndex = i; } } if (highestQualityIndex == -1) highestQualityIndex = getHighestQualityAudioIndex(audioStreams); return highestQualityIndex; } /** * Join the two lists of video streams (video_only and normal videos), and sort them according with default format * chosen by the user * * @param context context to search for the format to give preference * @param videoStreams normal videos list * @param videoOnlyStreams video only stream list * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest * @return the sorted list */ public static List getSortedStreamVideosList(Context context, List videoStreams, List videoOnlyStreams, boolean ascendingOrder) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); boolean showHigherResolutions = preferences.getBoolean(context.getString(R.string.show_higher_resolutions_key), false); MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value); return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder); } /** * Join the two lists of video streams (video_only and normal videos), and sort them according with default format * chosen by the user * * @param defaultFormat format to give preference * @param showHigherResolutions show >1080p resolutions * @param videoStreams normal videos list * @param videoOnlyStreams video only stream list * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list * @return the sorted list */ public static List getSortedStreamVideosList(MediaFormat defaultFormat, boolean showHigherResolutions, List videoStreams, List videoOnlyStreams, boolean ascendingOrder) { ArrayList retList = new ArrayList<>(); HashMap hashMap = new HashMap<>(); if (videoOnlyStreams != null) { for (VideoStream stream : videoOnlyStreams) { if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue; retList.add(stream); } } if (videoStreams != null) { for (VideoStream stream : videoStreams) { if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.resolution)) continue; retList.add(stream); } } // Add all to the hashmap for (VideoStream videoStream : retList) hashMap.put(videoStream.resolution, videoStream); // Override the values when the key == resolution, with the defaultFormat for (VideoStream videoStream : retList) { if (videoStream.format == defaultFormat.id) hashMap.put(videoStream.resolution, videoStream); } retList.clear(); retList.addAll(hashMap.values()); sortStreamList(retList, ascendingOrder); return retList; } /** * Sort the streams list depending on the parameter ascendingOrder; *

* It works like that:
* - Take a string resolution, remove the letters, replace "0p60" (for 60fps videos) with "1" * and sort by the greatest:
*

     *      720p     ->  720
     *      720p60   ->  721
     *      360p     ->  360
     *      1080p    ->  1080
     *      1080p60  ->  1081
     * 
* ascendingOrder ? 360 < 720 < 721 < 1080 < 1081 * !ascendingOrder ? 1081 < 1080 < 721 < 720 < 360
* * @param videoStreams list that the sorting will be applied * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest */ public static void sortStreamList(List videoStreams, final boolean ascendingOrder) { Collections.sort(videoStreams, new Comparator() { @Override public int compare(VideoStream o1, VideoStream o2) { int res1 = Integer.parseInt(o1.resolution.replace("0p60", "1").replaceAll("[^\\d.]", "")); int res2 = Integer.parseInt(o2.resolution.replace("0p60", "1").replaceAll("[^\\d.]", "")); return ascendingOrder ? res1 - res2 : res2 - res1; } }); } /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ private static int getDefaultStreamIndex(String defaultResolution, MediaFormat defaultFormat, List videoStreams) { int defaultStreamIndex = -1; for (int i = 0; i < videoStreams.size(); i++) { VideoStream stream = videoStreams.get(i); if (defaultStreamIndex == -1 && stream.resolution.equals(defaultResolution)) defaultStreamIndex = i; if (stream.format == defaultFormat.id && stream.resolution.equals(defaultResolution)) { return i; } } return defaultStreamIndex; } private static int getDefaultResolutionWithDefaultFormat(Context context, String defaultResolution, List videoStreams) { MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value); return getDefaultResolutionIndex(defaultResolution, context.getString(R.string.best_resolution_key), defaultFormat, videoStreams); } private static MediaFormat getDefaultFormat(Context context, @StringRes int defaultFormatKey, @StringRes int defaultFormatValueKey) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); String defaultFormat = context.getString(defaultFormatValueKey); String defaultFormatString = preferences.getString(context.getString(defaultFormatKey), defaultFormat); MediaFormat defaultMediaFormat = getMediaFormatFromKey(context, defaultFormatString); if (defaultMediaFormat == null) { preferences.edit().putString(context.getString(defaultFormatKey), defaultFormat).apply(); defaultMediaFormat = getMediaFormatFromKey(context, defaultFormat); } return defaultMediaFormat; } private static MediaFormat getMediaFormatFromKey(Context context, String formatKey) { MediaFormat format = null; if (formatKey.equals(context.getString(R.string.video_webm_key))) { format = MediaFormat.WEBM; } else if (formatKey.equals(context.getString(R.string.video_mp4_key))) { format = MediaFormat.MPEG_4; } else if (formatKey.equals(context.getString(R.string.video_3gp_key))) { format = MediaFormat.v3GPP; } else if (formatKey.equals(context.getString(R.string.audio_webm_key))) { format = MediaFormat.WEBMA; } else if (formatKey.equals(context.getString(R.string.audio_m4a_key))) { format = MediaFormat.M4A; } return format; } }