From d1b0cd74be873acc2a23b93836c41bbe10cd6a9f Mon Sep 17 00:00:00 2001 From: James Straub Date: Sat, 21 Apr 2018 12:35:04 -0400 Subject: [PATCH] Added the ability to limit video quality if using mobile data. * Added a dropdown to video & audio settings * Changes to ListHelper: ** Limits resolution when code requests the default video resolution ** Limits audio bitrate when code requests the default audio bitrate ** Removed some dead code and did some cleanup ** Make methods private/protected to help understand what was in use ** The code now chooses one format over an other using a simple raking system defined in array constants. I realized I needed to do this in order to choose the most efficient video stream. I did my best to evaluate the video and audio formats based on quality and efficiency. It's not an exact science. ** Made changes to the tests to support my changes --- .../newpipe/player/MainVideoPlayer.java | 2 +- .../newpipe/player/PopupVideoPlayer.java | 2 +- .../org/schabi/newpipe/util/ListHelper.java | 383 +++++++++++++----- app/src/main/res/values/settings_keys.xml | 28 ++ app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/video_audio_settings.xml | 10 +- .../schabi/newpipe/util/ListHelperTest.java | 124 +++++- 7 files changed, 432 insertions(+), 119 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index aedcdb791..33832b7a2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -645,7 +645,7 @@ public final class MainVideoPlayer extends AppCompatActivity @Override protected int getOverrideResolutionIndex(final List sortedVideos, final String playbackQuality) { - return ListHelper.getDefaultResolutionIndex(context, sortedVideos, playbackQuality); + return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 9528bf2bc..d7eee46d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -509,7 +509,7 @@ public final class PopupVideoPlayer extends Service { @Override protected int getOverrideResolutionIndex(final List sortedVideos, final String playbackQuality) { - return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos, playbackQuality); + return ListHelper.getPopupResolutionIndex(context, sortedVideos, playbackQuality); } /*////////////////////////////////////////////////////////////////////////// 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 79fd1e496..93d20e22e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.util; import android.content.Context; import android.content.SharedPreferences; +import android.net.ConnectivityManager; import android.preference.PreferenceManager; import android.support.annotation.StringRes; @@ -13,56 +14,38 @@ 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 { + // Video format in order of quality. 0=lowest quality, n=highest quality + private static final List VIDEO_FORMAT_QUALITY_RANKING = + Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); + + // Audio format in order of quality. 0=lowest quality, n=highest quality + private static final List AUDIO_FORMAT_QUALITY_RANKING = + Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); + // Audio format in order of efficiency. 0=most efficient, n=least efficient + private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = + Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); + 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 getDefaultResolutionIndex(context, videoStreams, defaultResolution); + String defaultResolution = computeDefaultResolution(context, + R.string.default_resolution_key, R.string.default_resolution_value); + return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); } /** * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ - public static int getDefaultResolutionIndex(Context context, List videoStreams, String defaultResolution) { + public static int getResolutionIndex(Context context, List videoStreams, String defaultResolution) { return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); } @@ -70,69 +53,33 @@ public final class ListHelper { * @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 getPopupDefaultResolutionIndex(context, videoStreams, defaultResolution); + String defaultResolution = computeDefaultResolution(context, + R.string.default_popup_resolution_key, R.string.default_popup_resolution_value); + return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); } /** * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) */ - public static int getPopupDefaultResolutionIndex(Context context, List videoStreams, String defaultResolution) { + public static int getPopupResolutionIndex(Context context, List videoStreams, String defaultResolution) { 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); - } + MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key, + R.string.default_audio_format_value); - 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.getAverageBitrate() >= audioStreams.get(highestQualityIndex).getAverageBitrate()) highestQualityIndex = i; + // If the user has chosen to limit resolution to conserve mobile data + // usage then we should also limit our audio usage. + int result; + if (isLimitingDataUsage(context)) { + result = getMostCompactAudioIndex(defaultFormat, audioStreams); } - 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.getFormat() == defaultFormat) highestQualityIndex = i; - - if (highestQualityIndex != -1 && audioStream.getFormat() == defaultFormat - && audioStream.getAverageBitrate() > audioStreams.get(highestQualityIndex).getAverageBitrate()) { - highestQualityIndex = i; - } + else { + result = getHighestQualityAudioIndex(defaultFormat, audioStreams); } - if (highestQualityIndex == -1) highestQualityIndex = getHighestQualityAudioIndex(audioStreams); - return highestQualityIndex; + + return result; } /** @@ -154,6 +101,49 @@ public final class ListHelper { return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder); } + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + + private static String computeDefaultResolution(Context context, int key, int value) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + + // Load the prefered resolution otherwise the best available + String resolution = preferences != null ? preferences.getString(context.getString(key), + context.getString(value)) : context.getString(R.string.best_resolution_key); + + String maxResolution = getResolutionLimit(context); + if (maxResolution != null && compareVideoStreamResolution(maxResolution, resolution) < 1){ + resolution = maxResolution; + } + return resolution; + } + + /** + * Return the index of the default stream in the list, based on the parameters + * defaultResolution and defaultFormat + * + * @return index of the default resolution&format + */ + 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 = getVideoStreamIndex(defaultResolution, 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; + } + /** * Join the two lists of video streams (video_only and normal videos), and sort them according with default format * chosen by the user @@ -165,7 +155,7 @@ public final class ListHelper { * @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) { + static List getSortedStreamVideosList(MediaFormat defaultFormat, boolean showHigherResolutions, List videoStreams, List videoOnlyStreams, boolean ascendingOrder) { ArrayList retList = new ArrayList<>(); HashMap hashMap = new HashMap<>(); @@ -215,36 +205,138 @@ public final class ListHelper { * @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.getResolution().replace("0p60", "1").replaceAll("[^\\d.]", "")); - int res2 = Integer.parseInt(o2.getResolution().replace("0p60", "1").replaceAll("[^\\d.]", "")); - - return ascendingOrder ? res1 - res2 : res2 - res1; - } + private static void sortStreamList(List videoStreams, final boolean ascendingOrder) { + Collections.sort(videoStreams, (o1, o2) -> { + int result = compareVideoStreamResolution(o1, o2, VIDEO_FORMAT_QUALITY_RANKING); + return result == 0 ? 0 : (ascendingOrder ? result : -result); }); } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ + /** + * Get the audio from the list with the highest quality. Format will be ignored if it yields + * no results. + * + * @param audioStreams list the audio streams + * @return index of the audio with the highest average bitrate of the default format + */ + static int getHighestQualityAudioIndex(MediaFormat format, List audioStreams) { + int result = -1; + if (audioStreams != null) { + while(result == -1) { + AudioStream prevStream = null; + for (int idx = 0; idx < audioStreams.size(); idx++) { + AudioStream stream = audioStreams.get(idx); + if ((format == null || stream.getFormat() == format) && + (prevStream == null || compareAudioStreamBitrate(prevStream, stream, + AUDIO_FORMAT_QUALITY_RANKING) < 0)) { + prevStream = stream; + result = idx; + } + } + if (result == -1 && format == null) { + break; + } + format = null; + } + } + return result; + } - 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.getResolution().equals(defaultResolution)) defaultStreamIndex = i; + /** + * Get the audio from the list with the lowest bitrate and 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 the audio streams + * @return index of the audio stream that can produce the most compact results or -1 if not found. + */ + static int getMostCompactAudioIndex(MediaFormat format, List audioStreams) { + int result = -1; + if (audioStreams != null) { + while(result == -1) { + AudioStream prevStream = null; + for (int idx = 0; idx < audioStreams.size(); idx++) { + AudioStream stream = audioStreams.get(idx); + if ((format == null || stream.getFormat() == format) && + (prevStream == null || compareAudioStreamBitrate(prevStream, stream, + AUDIO_FORMAT_EFFICIENCY_RANKING) > 0)) { + prevStream = stream; + result = idx; + } + } + if (result == -1 && format == null) { + break; + } + format = null; + } + } + return result; + } - if (stream.getFormat() == defaultFormat && stream.getResolution().equals(defaultResolution)) { - return i; + /** + * Locates a possible match for the given resolution and format in the provided list. + * In this order: + * 1. Find a format and resolution match + * 2. Find a format and resolution match and ignore the refresh + * 3. Find a resolution match + * 4. Find a resolution match and ignore the refresh + * 5. Find a resolution just below the requested resolution and ignore the refresh + * 6. Give up + */ + static int getVideoStreamIndex(String targetResolution, MediaFormat targetFormat, + List videoStreams) { + int fullMatchIndex = -1; + int fullMatchNoRefreshIndex = -1; + int resMatchOnlyIndex = -1; + int resMatchOnlyNoRefreshIndex = -1; + int lowerResMatchNoRefreshIndex = -1; + String targetResolutionNoRefresh = targetResolution.replaceAll("p\\d+$", "p"); + + for (int idx = 0; idx < videoStreams.size(); idx++) { + MediaFormat format = targetFormat == null ? null : videoStreams.get(idx).getFormat(); + String resolution = videoStreams.get(idx).getResolution(); + String resolutionNoRefresh = resolution.replaceAll("p\\d+$", "p"); + + if (format == targetFormat && resolution.equals(targetResolution)) { + fullMatchIndex = idx; + } + + if (format == targetFormat && resolutionNoRefresh.equals(targetResolutionNoRefresh)) { + fullMatchNoRefreshIndex = idx; + } + + if (resMatchOnlyIndex == -1 && resolution.equals(targetResolution)) { + resMatchOnlyIndex = idx; + } + + if (resMatchOnlyNoRefreshIndex == -1 && resolutionNoRefresh.equals(targetResolutionNoRefresh)) { + resMatchOnlyNoRefreshIndex = idx; + } + + if (lowerResMatchNoRefreshIndex == -1 && compareVideoStreamResolution(resolutionNoRefresh, targetResolutionNoRefresh) < 0) { + lowerResMatchNoRefreshIndex = idx; } } - return defaultStreamIndex; + if (fullMatchIndex != -1) { + return fullMatchIndex; + } + if (fullMatchNoRefreshIndex != -1) { + return fullMatchNoRefreshIndex; + } + if (resMatchOnlyIndex != -1) { + return resMatchOnlyIndex; + } + if (resMatchOnlyNoRefreshIndex != -1) { + return resMatchOnlyNoRefreshIndex; + } + return lowerResMatchNoRefreshIndex; } + /** + * Fetches the desired resolution or returns the default if it is not found. The resolution + * will be reduced if video chocking is active. + */ 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); @@ -280,4 +372,85 @@ public final class ListHelper { } return format; } + + // Compares the quality of two audio streams + private static int compareAudioStreamBitrate(AudioStream streamA, AudioStream streamB, + List formatRanking) { + if (streamA == null) { + return -1; + } + if (streamB == null) { + return 1; + } + if (streamA.getAverageBitrate() < streamB.getAverageBitrate()) { + return -1; + } + if (streamA.getAverageBitrate() > streamB.getAverageBitrate()) { + return 1; + } + + // Same bitrate and format + return formatRanking.indexOf(streamA.getFormat()) - formatRanking.indexOf(streamB.getFormat()); + } + + private static int compareVideoStreamResolution(String r1, String r2) { + int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1") + .replaceAll("[^\\d.]", "")); + int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1") + .replaceAll("[^\\d.]", "")); + return res1 - res2; + } + + // Compares the quality of two video streams. + private static int compareVideoStreamResolution(VideoStream streamA, VideoStream streamB, + List formatRanking) { + if (streamA == null) { + return -1; + } + if (streamB == null) { + return 1; + } + + int resComp = compareVideoStreamResolution(streamA.getResolution(), streamB.getResolution()); + if (resComp != 0) { + return resComp; + } + + // Same bitrate and format + return formatRanking.indexOf(streamA.getFormat()) - formatRanking.indexOf(streamB.getFormat()); + } + + + + private static boolean isLimitingDataUsage(Context context) { + return getResolutionLimit(context) != null; + } + + /** + * The maximum resolution allowed + * @param context App context + * @return maximum resolution allowed or null if there is no maximum + */ + private static String getResolutionLimit(Context context) { + String resolutionLimit = null; + if (!isWifiActive(context)) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + String defValue = context.getString(R.string.limit_data_usage_none_key); + String value = preferences.getString( + context.getString(R.string.limit_mobile_data_usage_key), defValue); + resolutionLimit = value.equals(defValue) ? null : value; + } + return resolutionLimit; + } + + /** + * Are we connected to wifi? + * @param context App context + * @return True if connected to wifi + */ + private static boolean isWifiActive(Context context) + { + ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + return manager.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI; + } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index befe17958..03be62431 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -851,4 +851,32 @@ ZM ZW + + + limit_mobile_data_usage + @string/limit_data_usage_none_key + + @string/limit_data_usage_none_description + 1080p60 + 1080p + 720p60 + 720p + 480p + 360p + 240p + 144p + + + @string/limit_data_usage_none_key + 1080p60 + 1080p + 720p60 + 720p + 480p + 360p + 240p + 144p + + limit_data_usage_none + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f3dc7d0a..9fa5c7eda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,6 +66,8 @@ Default video format WebM — free format M4A — better quality + No limit + Limit resolution when using mobile data Theme Light Dark diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml index 6a3b7a0b3..6ec0da215 100644 --- a/app/src/main/res/xml/video_audio_settings.xml +++ b/app/src/main/res/xml/video_audio_settings.xml @@ -19,6 +19,14 @@ android:summary="%s" android:title="@string/default_popup_resolution_title"/> + + + android:title="@string/default_audio_format_title" /> ())); } + @Test + public void getLowestQualityAudioFormatTest() throws Exception { + AudioStream stream = audioStreamsTestList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.M4A, audioStreamsTestList)); + assertEquals(128, stream.average_bitrate); + assertEquals(MediaFormat.M4A, stream.getFormat()); + stream = audioStreamsTestList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.WEBMA, audioStreamsTestList)); + assertEquals(64, stream.average_bitrate); + assertEquals(MediaFormat.WEBMA, stream.getFormat()); + + stream = audioStreamsTestList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, audioStreamsTestList)); + assertEquals(64, stream.average_bitrate); + assertEquals(MediaFormat.MP3, stream.getFormat()); + } + + @Test + public void getLowestQualityAudioFormatPreferredAbsent() throws Exception { + + ////////////////////////////////////////// + // Doesn't contain the preferred format // + //////////////////////////////////////// + + List testList = new ArrayList<>(Arrays.asList( + new AudioStream("", MediaFormat.M4A, /**/ 128), + new AudioStream("", 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)); + assertEquals(128, stream.average_bitrate); + assertEquals(MediaFormat.M4A, stream.getFormat()); + + // WEBMA is more compact than M4A + testList.add(new AudioStream("", MediaFormat.WEBMA, /**/ 128)); + stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList)); + assertEquals(128, stream.average_bitrate); + assertEquals(MediaFormat.WEBMA, stream.getFormat()); + + //////////////////////////////////////////////////////// + // Multiple not-preferred-formats and equal bitrates // + ////////////////////////////////////////////////////// + + testList = new ArrayList<>(Arrays.asList( + new AudioStream("", MediaFormat.WEBMA, /**/ 192), + new AudioStream("", MediaFormat.M4A, /**/ 192), + new AudioStream("", MediaFormat.WEBMA, /**/ 256), + new AudioStream("", MediaFormat.M4A, /**/ 192), + new AudioStream("", MediaFormat.WEBMA, /**/ 192), + new AudioStream("", MediaFormat.M4A, /**/ 192))); + // List doesn't contains this format, it should fallback to the most compact audio no matter what format it is. + stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList)); + assertEquals(192, stream.average_bitrate); + assertEquals(MediaFormat.WEBMA, stream.getFormat()); + + // Should be same as above + stream = testList.get(ListHelper.getMostCompactAudioIndex(null, testList)); + assertEquals(192, stream.average_bitrate); + assertEquals(MediaFormat.WEBMA, stream.getFormat()); + } + + @Test + public void getLowestQualityAudioNull() throws Exception { + assertEquals(-1, ListHelper.getMostCompactAudioIndex(null, null)); + assertEquals(-1, ListHelper.getMostCompactAudioIndex(null, new ArrayList())); + } + + @Test + public void getVideoDefaultStreamIndexCombinations() throws Exception { + List testList = Arrays.asList( + new VideoStream("", MediaFormat.MPEG_4, /**/ "1080p"), + new VideoStream("", MediaFormat.MPEG_4, /**/ "720p60"), + new VideoStream("", MediaFormat.MPEG_4, /**/ "720p"), + new VideoStream("", MediaFormat.WEBM, /**/ "480p"), + new VideoStream("", MediaFormat.MPEG_4, /**/ "360p"), + new VideoStream("", MediaFormat.WEBM, /**/ "360p"), + new VideoStream("", MediaFormat.v3GPP, /**/ "240p60"), + new VideoStream("", MediaFormat.WEBM, /**/ "144p")); + + // exact matches + assertEquals(1, ListHelper.getVideoStreamIndex("720p60", MediaFormat.MPEG_4, testList)); + assertEquals(2, ListHelper.getVideoStreamIndex("720p", MediaFormat.MPEG_4, testList)); + + // match but not refresh + assertEquals(0, ListHelper.getVideoStreamIndex("1080p60", MediaFormat.MPEG_4, testList)); + assertEquals(6, ListHelper.getVideoStreamIndex("240p", MediaFormat.v3GPP, testList)); + + // match but not format + assertEquals(1, ListHelper.getVideoStreamIndex("720p60", MediaFormat.WEBM, testList)); + assertEquals(2, ListHelper.getVideoStreamIndex("720p", MediaFormat.WEBM, testList)); + assertEquals(1, ListHelper.getVideoStreamIndex("720p60", null, testList)); + assertEquals(2, ListHelper.getVideoStreamIndex("720p", null, testList)); + + // match but not format and not refresh + assertEquals(0, ListHelper.getVideoStreamIndex("1080p60", MediaFormat.WEBM, testList)); + assertEquals(6, ListHelper.getVideoStreamIndex("240p", MediaFormat.WEBM, testList)); + assertEquals(0, ListHelper.getVideoStreamIndex("1080p60", null, testList)); + assertEquals(6, ListHelper.getVideoStreamIndex("240p", null, testList)); + + // match closest lower resolution + assertEquals(7, ListHelper.getVideoStreamIndex("200p", MediaFormat.WEBM, testList)); + assertEquals(7, ListHelper.getVideoStreamIndex("200p60", MediaFormat.WEBM, testList)); + assertEquals(7, ListHelper.getVideoStreamIndex("200p", MediaFormat.MPEG_4, testList)); + assertEquals(7, ListHelper.getVideoStreamIndex("200p60", MediaFormat.MPEG_4, testList)); + assertEquals(7, ListHelper.getVideoStreamIndex("200p", null, testList)); + assertEquals(7, ListHelper.getVideoStreamIndex("200p60", null, testList)); + + // Can't find a match + assertEquals(-1, ListHelper.getVideoStreamIndex("100p", null, testList)); + } } \ No newline at end of file