feat: add prefer original option, improve audio stream ordering

This commit is contained in:
ThetaDev 2023-03-19 20:40:27 +01:00
parent 87a88e4df7
commit 7aed2eed8a
7 changed files with 113 additions and 123 deletions

View file

@ -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 {

View file

@ -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()

View file

@ -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));
} }
} }

View file

@ -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>

View file

@ -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>

View file

@ -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"

View file

@ -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"