feat: add prefer original option, improve audio stream ordering
This commit is contained in:
parent
87a88e4df7
commit
7aed2eed8a
7 changed files with 113 additions and 123 deletions
|
@ -52,18 +52,8 @@ public class AudioPlaybackResolver implements PlaybackResolver {
|
||||||
final MediaItemTag tag;
|
final MediaItemTag tag;
|
||||||
|
|
||||||
if (!audioStreams.isEmpty()) {
|
if (!audioStreams.isEmpty()) {
|
||||||
int audioIndex = 0;
|
final int audioIndex =
|
||||||
|
ListHelper.getAudioFormatIndex(context, audioStreams, audioTrack);
|
||||||
if (audioTrack != null) {
|
|
||||||
for (int i = 0; i < audioStreams.size(); i++) {
|
|
||||||
final AudioStream audioStream = audioStreams.get(i);
|
|
||||||
if (audioStream.getAudioTrackId() != null
|
|
||||||
&& audioStream.getAudioTrackId().equals(audioTrack)) {
|
|
||||||
audioIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream = getStreamForIndex(audioIndex, audioStreams);
|
stream = getStreamForIndex(audioIndex, audioStreams);
|
||||||
tag = StreamInfoTag.of(info, audioStreams, audioIndex);
|
tag = StreamInfoTag.of(info, audioStreams, audioIndex);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -90,18 +90,8 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||||
getPlaybackQuality());
|
getPlaybackQuality());
|
||||||
}
|
}
|
||||||
|
|
||||||
int audioIndex = 0;
|
final int audioIndex =
|
||||||
if (audioTrack != null) {
|
ListHelper.getAudioFormatIndex(context, audioStreamsList, audioTrack);
|
||||||
for (int i = 0; i < audioStreamsList.size(); i++) {
|
|
||||||
final AudioStream stream = audioStreamsList.get(i);
|
|
||||||
if (stream.getAudioTrackId() != null
|
|
||||||
&& stream.getAudioTrackId().equals(audioTrack)) {
|
|
||||||
audioIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final MediaItemTag tag =
|
final MediaItemTag tag =
|
||||||
StreamInfoTag.of(info, videoStreamsList, videoIndex, audioStreamsList, audioIndex);
|
StreamInfoTag.of(info, videoStreamsList, videoIndex, audioStreamsList, audioIndex);
|
||||||
@Nullable final VideoStream video = tag.getMaybeQuality()
|
@Nullable final VideoStream video = tag.getMaybeQuality()
|
||||||
|
|
|
@ -38,11 +38,17 @@ public final class ListHelper {
|
||||||
// Audio format in order of quality. 0=lowest quality, n=highest quality
|
// Audio format in order of quality. 0=lowest quality, n=highest quality
|
||||||
private static final List<MediaFormat> AUDIO_FORMAT_QUALITY_RANKING =
|
private static final List<MediaFormat> AUDIO_FORMAT_QUALITY_RANKING =
|
||||||
List.of(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A);
|
List.of(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A);
|
||||||
// Audio format in order of efficiency. 0=most efficient, n=least efficient
|
// Audio format in order of efficiency. 0=least efficient, n=most efficient
|
||||||
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
||||||
List.of(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
|
List.of(MediaFormat.MP3, MediaFormat.M4A, MediaFormat.WEBMA);
|
||||||
// Use a Set for better performance
|
// Use a Set for better performance
|
||||||
private static final Set<String> HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p");
|
private static final Set<String> HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p");
|
||||||
|
// Audio track types in order of priotity. 0=lowest, n=highest
|
||||||
|
private static final List<AudioTrackType> AUDIO_TRACK_TYPE_RANKING =
|
||||||
|
List.of(AudioTrackType.DESCRIPTIVE, AudioTrackType.DUBBED, AudioTrackType.ORIGINAL);
|
||||||
|
// Audio track types in order of priotity when descriptive audio is preferred.
|
||||||
|
private static final List<AudioTrackType> AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE =
|
||||||
|
List.of(AudioTrackType.ORIGINAL, AudioTrackType.DUBBED, AudioTrackType.DESCRIPTIVE);
|
||||||
|
|
||||||
private ListHelper() {
|
private ListHelper() {
|
||||||
}
|
}
|
||||||
|
@ -104,13 +110,23 @@ public final class ListHelper {
|
||||||
final MediaFormat defaultFormat = getDefaultFormat(context,
|
final MediaFormat defaultFormat = getDefaultFormat(context,
|
||||||
R.string.default_audio_format_key, R.string.default_audio_format_value);
|
R.string.default_audio_format_key, R.string.default_audio_format_value);
|
||||||
|
|
||||||
// If the user has chosen to limit resolution to conserve mobile data
|
return getAudioIndexByHighestRank(defaultFormat, audioStreams,
|
||||||
// usage then we should also limit our audio usage.
|
getAudioStreamComparator(context));
|
||||||
if (isLimitingDataUsage(context)) {
|
|
||||||
return getMostCompactAudioIndex(defaultFormat, audioStreams);
|
|
||||||
} else {
|
|
||||||
return getHighestQualityAudioIndex(defaultFormat, audioStreams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getAudioFormatIndex(final Context context,
|
||||||
|
final List<AudioStream> audioStreams,
|
||||||
|
@Nullable final String trackId) {
|
||||||
|
if (trackId != null) {
|
||||||
|
for (int i = 0; i < audioStreams.size(); i++) {
|
||||||
|
final AudioStream s = audioStreams.get(i);
|
||||||
|
if (s.getAudioTrackId() != null
|
||||||
|
&& s.getAudioTrackId().equals(trackId)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getDefaultAudioFormat(context, audioStreams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,15 +222,7 @@ public final class ListHelper {
|
||||||
|
|
||||||
final HashMap<String, AudioStream> collectedStreams = new HashMap<>();
|
final HashMap<String, AudioStream> collectedStreams = new HashMap<>();
|
||||||
|
|
||||||
final Comparator<AudioStream> cmp;
|
final Comparator<AudioStream> cmp = getAudioStreamFormatComparator(context);
|
||||||
if (isLimitingDataUsage(context)) {
|
|
||||||
cmp = getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING);
|
|
||||||
} else {
|
|
||||||
cmp = getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String preferredLanguage = Localization.getPreferredLocale(context).getISO3Language();
|
|
||||||
boolean hasPreferredLanguage = false;
|
|
||||||
|
|
||||||
for (final AudioStream stream : audioStreams) {
|
for (final AudioStream stream : audioStreams) {
|
||||||
if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) {
|
if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) {
|
||||||
|
@ -226,24 +234,8 @@ public final class ListHelper {
|
||||||
final AudioStream presentStream = collectedStreams.get(trackId);
|
final AudioStream presentStream = collectedStreams.get(trackId);
|
||||||
if (presentStream == null || cmp.compare(stream, presentStream) > 0) {
|
if (presentStream == null || cmp.compare(stream, presentStream) > 0) {
|
||||||
collectedStreams.put(trackId, stream);
|
collectedStreams.put(trackId, stream);
|
||||||
|
|
||||||
if (stream.getAudioLocale() != null
|
|
||||||
&& stream.getAudioLocale().getISO3Language().equals(preferredLanguage)) {
|
|
||||||
hasPreferredLanguage = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to English if the preferred language was not found
|
|
||||||
final String preferredLanguageOrEnglish =
|
|
||||||
hasPreferredLanguage ? preferredLanguage : Locale.ENGLISH.getISO3Language();
|
|
||||||
final SharedPreferences preferences =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
final boolean preferDescriptiveAudio =
|
|
||||||
preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key),
|
|
||||||
false);
|
|
||||||
final Comparator<AudioStream> trackCmp =
|
|
||||||
getAudioTrackComparator(preferredLanguageOrEnglish, preferDescriptiveAudio);
|
|
||||||
|
|
||||||
// Filter unknown audio tracks if there are multiple tracks
|
// Filter unknown audio tracks if there are multiple tracks
|
||||||
java.util.stream.Stream<AudioStream> cs = collectedStreams.values().stream();
|
java.util.stream.Stream<AudioStream> cs = collectedStreams.values().stream();
|
||||||
|
@ -251,8 +243,9 @@ public final class ListHelper {
|
||||||
cs = cs.filter(s -> s.getAudioTrackId() != null);
|
cs = cs.filter(s -> s.getAudioTrackId() != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort collected streams
|
// Sort collected streams by name
|
||||||
return cs.sorted(trackCmp).collect(Collectors.toList());
|
return cs.sorted(Comparator.comparing(audioStream ->
|
||||||
|
Localization.audioTrackName(context, audioStream))).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -420,42 +413,6 @@ public final class ListHelper {
|
||||||
return videoStreams;
|
return videoStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the audio from the list with the highest quality.
|
|
||||||
* Format will be ignored if it yields no results.
|
|
||||||
*
|
|
||||||
* @param format The target format type or null if it doesn't matter
|
|
||||||
* @param audioStreams List of audio streams
|
|
||||||
* @return Index of audio stream that produces the most compact results or -1 if not found
|
|
||||||
*/
|
|
||||||
static int getHighestQualityAudioIndex(@Nullable final MediaFormat format,
|
|
||||||
@Nullable final List<AudioStream> audioStreams) {
|
|
||||||
return getAudioIndexByHighestRank(format, audioStreams,
|
|
||||||
// Compares descending (last = highest rank)
|
|
||||||
getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the audio from the list with the lowest bitrate and most efficient format.
|
|
||||||
* Format will be ignored if it yields no results.
|
|
||||||
*
|
|
||||||
* @param format The target format type or null if it doesn't matter
|
|
||||||
* @param audioStreams List of audio streams
|
|
||||||
* @return Index of audio stream that produces the most compact results or -1 if not found
|
|
||||||
*/
|
|
||||||
static int getMostCompactAudioIndex(@Nullable final MediaFormat format,
|
|
||||||
@Nullable final List<AudioStream> audioStreams) {
|
|
||||||
return getAudioIndexByHighestRank(format, audioStreams,
|
|
||||||
// The "reversed()" is important -> Compares ascending (first = highest rank)
|
|
||||||
getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Comparator<AudioStream> getAudioStreamComparator(
|
|
||||||
final List<MediaFormat> formatRanking) {
|
|
||||||
return Comparator.nullsLast(Comparator.comparingInt(AudioStream::getAverageBitrate))
|
|
||||||
.thenComparingInt(stream -> formatRanking.indexOf(stream.getFormat()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the audio-stream from the list with the highest rank, depending on the comparator.
|
* Get the audio-stream from the list with the highest rank, depending on the comparator.
|
||||||
* Format will be ignored if it yields no results.
|
* Format will be ignored if it yields no results.
|
||||||
|
@ -674,23 +631,65 @@ public final class ListHelper {
|
||||||
return manager.isActiveNetworkMetered();
|
return manager.isActiveNetworkMetered();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Comparator<AudioStream> getAudioTrackComparator(
|
/**
|
||||||
final String preferredLanguage, final boolean preferDescriptiveAudio) {
|
* Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate.
|
||||||
return Comparator.comparing(AudioStream::getAudioLocale, (o1, o2) -> Boolean.compare(
|
*
|
||||||
o1 == null || !o1.getISO3Language().equals(preferredLanguage),
|
* @param context App context
|
||||||
o2 == null || !o2.getISO3Language().equals(preferredLanguage))
|
* @return Comparator
|
||||||
).thenComparing(s -> s.getAudioTrackType() == AudioTrackType.DESCRIPTIVE, (o1, o2) ->
|
*/
|
||||||
Boolean.compare(o1 ^ preferDescriptiveAudio, o2 ^ preferDescriptiveAudio)
|
private static Comparator<AudioStream> getAudioStreamFormatComparator(
|
||||||
).thenComparing(AudioStream::getAudioTrackName, (o1, o2) -> {
|
@NonNull final Context context) {
|
||||||
if (o1 != null) {
|
final boolean limitDataUsage = isLimitingDataUsage(context);
|
||||||
if (o2 != null) {
|
final List<MediaFormat> formatRanking = limitDataUsage
|
||||||
return o1.compareTo(o2);
|
? AUDIO_FORMAT_EFFICIENCY_RANKING : AUDIO_FORMAT_QUALITY_RANKING;
|
||||||
} else {
|
|
||||||
return -1;
|
Comparator<AudioStream> bitrateComparator =
|
||||||
|
Comparator.comparingInt(AudioStream::getAverageBitrate);
|
||||||
|
if (limitDataUsage) {
|
||||||
|
bitrateComparator = bitrateComparator.reversed();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return 1;
|
return bitrateComparator.thenComparingInt(
|
||||||
|
stream -> formatRanking.indexOf(stream.getFormat()));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
/**
|
||||||
|
* Get a {@link Comparator} to compare {@link AudioStream}s by their language, format
|
||||||
|
* and bitrate.
|
||||||
|
*
|
||||||
|
* @param context App context
|
||||||
|
* @return Comparator
|
||||||
|
*/
|
||||||
|
private static Comparator<AudioStream> getAudioStreamComparator(
|
||||||
|
@NonNull final Context context) {
|
||||||
|
final SharedPreferences preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
final boolean preferOriginalAudio =
|
||||||
|
preferences.getBoolean(context.getString(R.string.prefer_original_audio_key),
|
||||||
|
false);
|
||||||
|
final boolean preferDescriptiveAudio =
|
||||||
|
preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key),
|
||||||
|
false);
|
||||||
|
final String preferredLanguage = Localization.getPreferredLocale(context).getISO3Language();
|
||||||
|
|
||||||
|
final List<AudioTrackType> trackTypeRanking = preferDescriptiveAudio
|
||||||
|
? AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE : AUDIO_TRACK_TYPE_RANKING;
|
||||||
|
|
||||||
|
return Comparator.comparing(AudioStream::getAudioTrackType, (o1, o2) -> {
|
||||||
|
if (preferOriginalAudio) {
|
||||||
|
return Boolean.compare(
|
||||||
|
o1 == AudioTrackType.ORIGINAL, o2 == AudioTrackType.ORIGINAL);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}).thenComparing(AudioStream::getAudioLocale,
|
||||||
|
Comparator.nullsFirst(Comparator.comparing(
|
||||||
|
locale -> locale.getISO3Language().equals(preferredLanguage))))
|
||||||
|
.thenComparing(AudioStream::getAudioTrackType,
|
||||||
|
Comparator.nullsLast(Comparator.comparingInt(trackTypeRanking::indexOf)))
|
||||||
|
.thenComparing(AudioStream::getAudioLocale,
|
||||||
|
Comparator.nullsFirst(Comparator.comparing(
|
||||||
|
locale -> locale.getISO3Language().equals(
|
||||||
|
Locale.ENGLISH.getISO3Language()))))
|
||||||
|
.thenComparing(getAudioStreamFormatComparator(context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,6 +192,8 @@
|
||||||
<item>@string/audio_webm_key</item>
|
<item>@string/audio_webm_key</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string name="prefer_original_audio_key">prefer_original_audio</string>
|
||||||
|
<string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
|
||||||
<string name="last_resize_mode">last_resize_mode</string>
|
<string name="last_resize_mode">last_resize_mode</string>
|
||||||
|
|
||||||
<!-- DEBUG ONLY -->
|
<!-- DEBUG ONLY -->
|
||||||
|
@ -260,7 +262,6 @@
|
||||||
<string name="show_next_video_key">show_next_video</string>
|
<string name="show_next_video_key">show_next_video</string>
|
||||||
<string name="show_description_key">show_description</string>
|
<string name="show_description_key">show_description</string>
|
||||||
<string name="show_meta_info_key">show_meta_info</string>
|
<string name="show_meta_info_key">show_meta_info</string>
|
||||||
<string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
|
|
||||||
<string name="stream_info_selected_tab_key">stream_info_selected_tab</string>
|
<string name="stream_info_selected_tab_key">stream_info_selected_tab</string>
|
||||||
<string name="show_hold_to_append_key">show_hold_to_append</string>
|
<string name="show_hold_to_append_key">show_hold_to_append</string>
|
||||||
<string name="content_language_key">content_language</string>
|
<string name="content_language_key">content_language</string>
|
||||||
|
|
|
@ -94,6 +94,8 @@
|
||||||
<string name="show_description_summary">Turn off to hide video description and additional information</string>
|
<string name="show_description_summary">Turn off to hide video description and additional information</string>
|
||||||
<string name="show_meta_info_title">Show meta info</string>
|
<string name="show_meta_info_title">Show meta info</string>
|
||||||
<string name="show_meta_info_summary">Turn off to hide meta info boxes with additional information about the stream creator, stream content or a search request</string>
|
<string name="show_meta_info_summary">Turn off to hide meta info boxes with additional information about the stream creator, stream content or a search request</string>
|
||||||
|
<string name="prefer_original_audio_title">Prefer original audio</string>
|
||||||
|
<string name="prefer_original_audio_summary">Select the original audio track regardless of the language</string>
|
||||||
<string name="prefer_descriptive_audio_title">Prefer descriptive audio</string>
|
<string name="prefer_descriptive_audio_title">Prefer descriptive audio</string>
|
||||||
<string name="prefer_descriptive_audio_summary">Select an audio track with descriptions for visually impaired people if available</string>
|
<string name="prefer_descriptive_audio_summary">Select an audio track with descriptions for visually impaired people if available</string>
|
||||||
<string name="thumbnail_cache_wipe_complete_notice">Image cache wiped</string>
|
<string name="thumbnail_cache_wipe_complete_notice">Image cache wiped</string>
|
||||||
|
|
|
@ -114,14 +114,6 @@
|
||||||
app:singleLineTitle="false"
|
app:singleLineTitle="false"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="@string/prefer_descriptive_audio_key"
|
|
||||||
android:summary="@string/prefer_descriptive_audio_summary"
|
|
||||||
android:title="@string/prefer_descriptive_audio_title"
|
|
||||||
app:singleLineTitle="false"
|
|
||||||
app:iconSpaceReserved="false"/>
|
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/import_data"
|
android:key="@string/import_data"
|
||||||
android:summary="@string/import_data_summary"
|
android:summary="@string/import_data_summary"
|
||||||
|
|
|
@ -61,6 +61,22 @@
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="@string/prefer_original_audio_key"
|
||||||
|
android:summary="@string/prefer_original_audio_summary"
|
||||||
|
android:title="@string/prefer_original_audio_title"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="@string/prefer_descriptive_audio_key"
|
||||||
|
android:summary="@string/prefer_descriptive_audio_summary"
|
||||||
|
android:title="@string/prefer_descriptive_audio_title"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="@string/progressive_load_interval_default_value"
|
android:defaultValue="@string/progressive_load_interval_default_value"
|
||||||
android:entries="@array/progressive_load_interval_descriptions"
|
android:entries="@array/progressive_load_interval_descriptions"
|
||||||
|
|
Loading…
Reference in a new issue