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
This commit is contained in:
parent
7d427b4cc4
commit
d1b0cd74be
7 changed files with 432 additions and 119 deletions
|
@ -645,7 +645,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
@Override
|
@Override
|
||||||
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||||
final String playbackQuality) {
|
final String playbackQuality) {
|
||||||
return ListHelper.getDefaultResolutionIndex(context, sortedVideos, playbackQuality);
|
return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -509,7 +509,7 @@ public final class PopupVideoPlayer extends Service {
|
||||||
@Override
|
@Override
|
||||||
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||||
final String playbackQuality) {
|
final String playbackQuality) {
|
||||||
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos, playbackQuality);
|
return ListHelper.getPopupResolutionIndex(context, sortedVideos, playbackQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
|
@ -13,56 +14,38 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public final class ListHelper {
|
public final class ListHelper {
|
||||||
|
|
||||||
|
// Video format in order of quality. 0=lowest quality, n=highest quality
|
||||||
|
private static final List<MediaFormat> 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<MediaFormat> 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<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
||||||
|
Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
|
||||||
|
|
||||||
private static final List<String> HIGH_RESOLUTION_LIST = Arrays.asList("1440p", "2160p", "1440p60", "2160p60");
|
private static final List<String> 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<VideoStream> 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)
|
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||||
*/
|
*/
|
||||||
public static int getDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
public static int getDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
||||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
String defaultResolution = computeDefaultResolution(context,
|
||||||
if (defaultPreferences == null) return 0;
|
R.string.default_resolution_key, R.string.default_resolution_value);
|
||||||
|
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||||
String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value));
|
|
||||||
return getDefaultResolutionIndex(context, videoStreams, defaultResolution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||||
*/
|
*/
|
||||||
public static int getDefaultResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
|
public static int getResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
|
||||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,69 +53,33 @@ public final class ListHelper {
|
||||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||||
*/
|
*/
|
||||||
public static int getPopupDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
public static int getPopupDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
||||||
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
String defaultResolution = computeDefaultResolution(context,
|
||||||
if (defaultPreferences == null) return 0;
|
R.string.default_popup_resolution_key, R.string.default_popup_resolution_value);
|
||||||
|
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||||
*/
|
*/
|
||||||
public static int getPopupDefaultResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
|
public static int getPopupResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
|
||||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDefaultAudioFormat(Context context, List<AudioStream> audioStreams) {
|
public static int getDefaultAudioFormat(Context context, List<AudioStream> audioStreams) {
|
||||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key, R.string.default_audio_format_value);
|
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key,
|
||||||
return getHighestQualityAudioIndex(defaultFormat, audioStreams);
|
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.
|
||||||
|
int result;
|
||||||
|
if (isLimitingDataUsage(context)) {
|
||||||
|
result = getMostCompactAudioIndex(defaultFormat, audioStreams);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = getHighestQualityAudioIndex(defaultFormat, audioStreams);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getHighestQualityAudioIndex(List<AudioStream> audioStreams) {
|
return result;
|
||||||
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;
|
|
||||||
}
|
|
||||||
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<AudioStream> 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<AudioStream> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (highestQualityIndex == -1) highestQualityIndex = getHighestQualityAudioIndex(audioStreams);
|
|
||||||
return highestQualityIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,6 +101,49 @@ public final class ListHelper {
|
||||||
return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
|
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<VideoStream> 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
|
* Join the two lists of video streams (video_only and normal videos), and sort them according with default format
|
||||||
* chosen by the user
|
* 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
|
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list
|
||||||
* @return the sorted list
|
* @return the sorted list
|
||||||
*/
|
*/
|
||||||
public static List<VideoStream> getSortedStreamVideosList(MediaFormat defaultFormat, boolean showHigherResolutions, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
static List<VideoStream> getSortedStreamVideosList(MediaFormat defaultFormat, boolean showHigherResolutions, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||||
ArrayList<VideoStream> retList = new ArrayList<>();
|
ArrayList<VideoStream> retList = new ArrayList<>();
|
||||||
HashMap<String, VideoStream> hashMap = new HashMap<>();
|
HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||||
|
|
||||||
|
@ -215,36 +205,138 @@ public final class ListHelper {
|
||||||
* @param videoStreams list that the sorting will be applied
|
* @param videoStreams list that the sorting will be applied
|
||||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||||
*/
|
*/
|
||||||
public static void sortStreamList(List<VideoStream> videoStreams, final boolean ascendingOrder) {
|
private static void sortStreamList(List<VideoStream> videoStreams, final boolean ascendingOrder) {
|
||||||
Collections.sort(videoStreams, new Comparator<VideoStream>() {
|
Collections.sort(videoStreams, (o1, o2) -> {
|
||||||
@Override
|
int result = compareVideoStreamResolution(o1, o2, VIDEO_FORMAT_QUALITY_RANKING);
|
||||||
public int compare(VideoStream o1, VideoStream o2) {
|
return result == 0 ? 0 : (ascendingOrder ? result : -result);
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/**
|
||||||
// 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<AudioStream> 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<VideoStream> videoStreams) {
|
/**
|
||||||
int defaultStreamIndex = -1;
|
* Get the audio from the list with the lowest bitrate and efficient format. Format will be
|
||||||
for (int i = 0; i < videoStreams.size(); i++) {
|
* ignored if it yields no results.
|
||||||
VideoStream stream = videoStreams.get(i);
|
*
|
||||||
if (defaultStreamIndex == -1 && stream.getResolution().equals(defaultResolution)) defaultStreamIndex = i;
|
* @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<AudioStream> 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<VideoStream> 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<VideoStream> videoStreams) {
|
private static int getDefaultResolutionWithDefaultFormat(Context context, String defaultResolution, List<VideoStream> videoStreams) {
|
||||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value);
|
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);
|
return getDefaultResolutionIndex(defaultResolution, context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
|
||||||
|
@ -280,4 +372,85 @@ public final class ListHelper {
|
||||||
}
|
}
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compares the quality of two audio streams
|
||||||
|
private static int compareAudioStreamBitrate(AudioStream streamA, AudioStream streamB,
|
||||||
|
List<MediaFormat> 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<MediaFormat> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -851,4 +851,32 @@
|
||||||
<item>ZM</item>
|
<item>ZM</item>
|
||||||
<item>ZW</item>
|
<item>ZW</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- Limit mobile data usage -->
|
||||||
|
<string name="limit_mobile_data_usage_key" translatable="false">limit_mobile_data_usage</string>
|
||||||
|
<string name="limit_mobile_data_usage_value" translatable="false">@string/limit_data_usage_none_key</string>
|
||||||
|
<string-array name="limit_data_usage_description_list">
|
||||||
|
<item>@string/limit_data_usage_none_description</item>
|
||||||
|
<item>1080p60</item>
|
||||||
|
<item>1080p</item>
|
||||||
|
<item>720p60</item>
|
||||||
|
<item>720p</item>
|
||||||
|
<item>480p</item>
|
||||||
|
<item>360p</item>
|
||||||
|
<item>240p</item>
|
||||||
|
<item>144p</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="limit_data_usage_values_list">
|
||||||
|
<item>@string/limit_data_usage_none_key</item>
|
||||||
|
<item>1080p60</item>
|
||||||
|
<item>1080p</item>
|
||||||
|
<item>720p60</item>
|
||||||
|
<item>720p</item>
|
||||||
|
<item>480p</item>
|
||||||
|
<item>360p</item>
|
||||||
|
<item>240p</item>
|
||||||
|
<item>144p</item>
|
||||||
|
</string-array>
|
||||||
|
<string name="limit_data_usage_none_key" translatable="false">limit_data_usage_none</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -66,6 +66,8 @@
|
||||||
<string name="default_video_format_title">Default video format</string>
|
<string name="default_video_format_title">Default video format</string>
|
||||||
<string name="webm_description">WebM — free format</string>
|
<string name="webm_description">WebM — free format</string>
|
||||||
<string name="m4a_description">M4A — better quality</string>
|
<string name="m4a_description">M4A — better quality</string>
|
||||||
|
<string name="limit_data_usage_none_description">No limit</string>
|
||||||
|
<string name="limit_mobile_data_usage_title">Limit resolution when using mobile data</string>
|
||||||
<string name="theme_title">Theme</string>
|
<string name="theme_title">Theme</string>
|
||||||
<string name="light_theme_title">Light</string>
|
<string name="light_theme_title">Light</string>
|
||||||
<string name="dark_theme_title">Dark</string>
|
<string name="dark_theme_title">Dark</string>
|
||||||
|
|
|
@ -19,6 +19,14 @@
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/default_popup_resolution_title"/>
|
android:title="@string/default_popup_resolution_title"/>
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="@string/limit_mobile_data_usage_value"
|
||||||
|
android:entries="@array/limit_data_usage_description_list"
|
||||||
|
android:entryValues="@array/limit_data_usage_values_list"
|
||||||
|
android:key="@string/limit_mobile_data_usage_key"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/limit_mobile_data_usage_title" />
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="@string/show_higher_resolutions_key"
|
android:key="@string/show_higher_resolutions_key"
|
||||||
|
@ -39,7 +47,7 @@
|
||||||
android:entryValues="@array/audio_format_values_list"
|
android:entryValues="@array/audio_format_values_list"
|
||||||
android:key="@string/default_audio_format_key"
|
android:key="@string/default_audio_format_key"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/default_audio_format_title"/>
|
android:title="@string/default_audio_format_title" />
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:layout="@layout/settings_category_header_layout"
|
android:layout="@layout/settings_category_header_layout"
|
||||||
|
|
|
@ -129,11 +129,6 @@ public class ListHelperTest {
|
||||||
assertEquals(MediaFormat.MPEG_4, result.getFormat());
|
assertEquals(MediaFormat.MPEG_4, result.getFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getHighestQualityAudioTest() throws Exception {
|
|
||||||
assertEquals(320, ListHelper.getHighestQualityAudio(audioStreamsTestList).average_bitrate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getHighestQualityAudioFormatTest() throws Exception {
|
public void getHighestQualityAudioFormatTest() throws Exception {
|
||||||
AudioStream stream = audioStreamsTestList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.M4A, audioStreamsTestList));
|
AudioStream stream = audioStreamsTestList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.M4A, audioStreamsTestList));
|
||||||
|
@ -174,19 +169,20 @@ public class ListHelperTest {
|
||||||
new AudioStream("", MediaFormat.WEBMA, /**/ 192),
|
new AudioStream("", MediaFormat.WEBMA, /**/ 192),
|
||||||
new AudioStream("", MediaFormat.M4A, /**/ 192),
|
new AudioStream("", MediaFormat.M4A, /**/ 192),
|
||||||
new AudioStream("", MediaFormat.WEBMA, /**/ 192),
|
new AudioStream("", MediaFormat.WEBMA, /**/ 192),
|
||||||
new AudioStream("", MediaFormat.M4A, /**/ 192)));
|
new AudioStream("", MediaFormat.M4A, /**/ 192),
|
||||||
// List doesn't contains this format, it should fallback to the highest bitrate audio no matter what format it is
|
new AudioStream("", MediaFormat.WEBMA, /**/ 192)));
|
||||||
// and as it have multiple with the same high value, the last one wins
|
// 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.getHighestQualityAudioIndex(MediaFormat.MP3, testList));
|
||||||
assertEquals(192, stream.average_bitrate);
|
assertEquals(192, stream.average_bitrate);
|
||||||
assertEquals(MediaFormat.M4A, stream.getFormat());
|
assertEquals(MediaFormat.M4A, stream.getFormat());
|
||||||
|
|
||||||
|
// Adding a new format and bitrate. Adding another stream will have no impact since
|
||||||
// Again with a new element
|
// it's not a prefered format.
|
||||||
testList.add(new AudioStream("", MediaFormat.WEBMA, /**/ 192));
|
testList.add(new AudioStream("", MediaFormat.WEBMA, /**/ 192));
|
||||||
stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList));
|
stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList));
|
||||||
assertEquals(192, stream.average_bitrate);
|
assertEquals(192, stream.average_bitrate);
|
||||||
assertEquals(MediaFormat.WEBMA, stream.getFormat());
|
assertEquals(MediaFormat.M4A, stream.getFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -195,5 +191,111 @@ public class ListHelperTest {
|
||||||
assertEquals(-1, ListHelper.getHighestQualityAudioIndex(null, new ArrayList<AudioStream>()));
|
assertEquals(-1, ListHelper.getHighestQualityAudioIndex(null, new ArrayList<AudioStream>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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<AudioStream> 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<AudioStream>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getVideoDefaultStreamIndexCombinations() throws Exception {
|
||||||
|
List<VideoStream> 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));
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue