mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-06-12 13:17:38 +02:00
feat(YouTube): Add Force original audio
patch (#4122)
This commit is contained in:

committed by
GitHub

parent
1e1863446a
commit
f4aa440608
@ -3,7 +3,7 @@ package app.revanced.extension.shared.settings;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.ForceiOSAVCAvailability;
|
||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
|
||||
|
||||
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
@ -22,9 +22,9 @@ public class BaseSettings {
|
||||
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
|
||||
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, parent(SPOOF_VIDEO_STREAMS));
|
||||
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, new SpoofiOSAvailability());
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
||||
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new ForceiOSAVCAvailability());
|
||||
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
|
||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,11 @@ package app.revanced.extension.shared.spoof;
|
||||
import java.util.Locale;
|
||||
|
||||
public enum AudioStreamLanguage {
|
||||
/**
|
||||
* YouTube default.
|
||||
* Can be the original language or can be app language,
|
||||
* depending on what YouTube decides to pick as the default.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
// Language codes found in locale_config.xml
|
||||
@ -86,15 +91,21 @@ public enum AudioStreamLanguage {
|
||||
private final String iso639_1;
|
||||
|
||||
AudioStreamLanguage() {
|
||||
iso639_1 = name().replace('_', '-');
|
||||
String name = name();
|
||||
final int regionSeparatorIndex = name.indexOf('_');
|
||||
if (regionSeparatorIndex >= 0) {
|
||||
iso639_1 = name.substring(0, regionSeparatorIndex).toLowerCase(Locale.US)
|
||||
+ name.substring(regionSeparatorIndex);
|
||||
} else {
|
||||
iso639_1 = name().toLowerCase(Locale.US);
|
||||
}
|
||||
}
|
||||
|
||||
public String getIso639_1() {
|
||||
// Changing the app language does not force the app to completely restart,
|
||||
// so the default needs to be the current language and not a static field.
|
||||
if (this == DEFAULT) {
|
||||
// Android VR requires uppercase language code.
|
||||
return Locale.getDefault().toLanguageTag().toUpperCase(Locale.US);
|
||||
return Locale.getDefault().toLanguageTag();
|
||||
}
|
||||
|
||||
return iso639_1;
|
||||
|
@ -17,7 +17,7 @@ public enum ClientType {
|
||||
"32", // Android 12.1
|
||||
"1.56.21",
|
||||
true,
|
||||
true),
|
||||
false),
|
||||
// Specific for kids videos.
|
||||
IOS(5,
|
||||
"IOS",
|
||||
@ -40,21 +40,8 @@ public enum ClientType {
|
||||
? "17.40.5"
|
||||
: "19.47.7",
|
||||
false,
|
||||
true),
|
||||
/**
|
||||
* Android VR with no language code.
|
||||
* Used for age restricted videos and YouTube Music to disable stable volume.
|
||||
*/
|
||||
ANDROID_VR_NO_HL(
|
||||
ANDROID_VR.id,
|
||||
ANDROID_VR.clientName,
|
||||
ANDROID_VR.deviceModel,
|
||||
ANDROID_VR.osVersion,
|
||||
ANDROID_VR.userAgent,
|
||||
ANDROID_VR.androidSdkVersion,
|
||||
ANDROID_VR.clientVersion,
|
||||
ANDROID_VR.canLogin,
|
||||
false);
|
||||
true
|
||||
);
|
||||
|
||||
private static boolean forceAVC() {
|
||||
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
|
||||
|
@ -22,14 +22,6 @@ public class SpoofVideoStreamsPatch {
|
||||
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
|
||||
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
|
||||
|
||||
/**
|
||||
* Injection point. Used by YT Music to disable stable volume.
|
||||
*/
|
||||
public static void setClientTypeToAndroidVrNoHl() {
|
||||
Logger.printDebug(() -> "Setting stream spoofing to: " + ClientType.ANDROID_VR_NO_HL);
|
||||
BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.save(ClientType.ANDROID_VR_NO_HL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Blocks /get_watch requests by returning an unreachable URI.
|
||||
@ -173,10 +165,11 @@ public class SpoofVideoStreamsPatch {
|
||||
return postData;
|
||||
}
|
||||
|
||||
public static final class ForceiOSAVCAvailability implements Setting.Availability {
|
||||
public static final class SpoofiOSAvailability implements Setting.Availability {
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS;
|
||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
||||
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,9 @@ final class PlayerRoutes {
|
||||
"?fields=streamingData" +
|
||||
"&alt=proto"
|
||||
).compile();
|
||||
|
||||
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
||||
|
||||
/**
|
||||
* TCP connection and HTTP read timeout
|
||||
*/
|
||||
|
@ -0,0 +1,41 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ForceOriginalAudioPatch {
|
||||
|
||||
private static final String DEFAULT_AUDIO_TRACKS_IDENTIFIER = "original";
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
|
||||
try {
|
||||
if (!Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
||||
return isDefault;
|
||||
}
|
||||
|
||||
if (audioTrackDisplayName.isEmpty()) {
|
||||
// Older app targets can have empty audio tracks and these might be placeholders.
|
||||
// The real audio tracks are called after these.
|
||||
return isDefault;
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: "
|
||||
+ String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName);
|
||||
|
||||
final boolean isOriginal = audioTrackDisplayName.contains(DEFAULT_AUDIO_TRACKS_IDENTIFIER);
|
||||
if (isOriginal) {
|
||||
Logger.printDebug(() -> "Using audio: " + audioTrackId);
|
||||
}
|
||||
|
||||
return isOriginal;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
||||
}
|
||||
|
||||
return isDefault;
|
||||
}
|
||||
}
|
@ -52,6 +52,8 @@ public class Settings extends BaseSettings {
|
||||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
||||
// Audio
|
||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE);
|
||||
|
||||
// Ads
|
||||
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
|
||||
|
@ -48,35 +48,44 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
|
||||
/**
|
||||
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
|
||||
*
|
||||
* @noinspection SameParameterValue
|
||||
*/
|
||||
private static void sortListPreferenceByValues(ListPreference listPreference) {
|
||||
private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) {
|
||||
CharSequence[] entries = listPreference.getEntries();
|
||||
CharSequence[] entryValues = listPreference.getEntryValues();
|
||||
final int entrySize = entries.length;
|
||||
|
||||
if (entrySize != entryValues.length) {
|
||||
// Xml array declaration has a missing/extra entry.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// Ensure the first entry remains the first after sorting.
|
||||
CharSequence firstEntry = entries[0];
|
||||
CharSequence firstEntryValue = entryValues[0];
|
||||
List<Pair<String, String>> firstPairs = new ArrayList<>(firstEntriesToPreserve);
|
||||
List<Pair<String, String>> pairsToSort = new ArrayList<>(entrySize);
|
||||
|
||||
List<Pair<String, String>> entryPairs = new ArrayList<>(entrySize);
|
||||
for (int i = 1; i < entrySize; i++) {
|
||||
entryPairs.add(new Pair<>(entries[i].toString(), entryValues[i].toString()));
|
||||
for (int i = 0; i < entrySize; i++) {
|
||||
Pair<String, String> pair = new Pair<>(entries[i].toString(), entryValues[i].toString());
|
||||
if (i < firstEntriesToPreserve) {
|
||||
firstPairs.add(pair);
|
||||
} else {
|
||||
pairsToSort.add(pair);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(entryPairs, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
|
||||
Collections.sort(pairsToSort, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
|
||||
|
||||
CharSequence[] sortedEntries = new CharSequence[entrySize];
|
||||
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
|
||||
|
||||
sortedEntries[0] = firstEntry;
|
||||
sortedEntryValues[0] = firstEntryValue;
|
||||
int i = 0;
|
||||
for (Pair<String, String> pair : firstPairs) {
|
||||
sortedEntries[i] = pair.first;
|
||||
sortedEntryValues[i] = pair.second;
|
||||
i++;
|
||||
}
|
||||
|
||||
int i = 1;
|
||||
for (Pair<String, String> pair : entryPairs) {
|
||||
for (Pair<String, String> pair : pairsToSort) {
|
||||
sortedEntries[i] = pair.first;
|
||||
sortedEntryValues[i] = pair.second;
|
||||
i++;
|
||||
@ -102,7 +111,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
|
||||
preference = findPreference(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE.key);
|
||||
if (preference instanceof ListPreference languagePreference) {
|
||||
sortListPreferenceByValues(languagePreference);
|
||||
sortListPreferenceByValues(languagePreference, 1);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "initialize failure", ex);
|
||||
|
Reference in New Issue
Block a user