Skip to content

Commit

Permalink
feat(YouTube): Add Change default audio track patch
Browse files Browse the repository at this point in the history
  • Loading branch information
LisoUseInAIKyrios committed Dec 14, 2024
1 parent b0cde78 commit 9fe88b9
Show file tree
Hide file tree
Showing 15 changed files with 470 additions and 235 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.ForceiOSAVCAvailability;

import app.revanced.extension.shared.spoof.AudioStreamLanguage;
import app.revanced.extension.shared.spoof.ClientType;

/**
Expand All @@ -22,7 +21,6 @@ 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 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());
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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,21 @@
import java.util.Locale;

public enum AudioStreamLanguage {
/**
* Change nothing, same as unpatched.
*/
DEFAULT,

/**
* Original language of the video.
*/
ORIGINAL,

/**
* Current language the app is set to.
*/
APP_LANGUAGE,

// Language codes found in locale_config.xml
// Region specific variants of Chinese/English/Spanish/French have been removed.
AF,
Expand Down Expand Up @@ -86,15 +99,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public enum ClientType {
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
"32", // Android 12.1
"1.56.21",
true,
true),
true
),
// Specific for kids videos.
IOS(5,
"IOS",
Expand All @@ -39,22 +39,8 @@ public enum ClientType {
// but 17.40 is the last version that supports iOS 13.
? "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);
false
);

private static boolean forceAVC() {
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
Expand Down Expand Up @@ -100,20 +86,14 @@ private static boolean forceAVC() {
*/
public final boolean canLogin;

/**
* If a language code should be used.
*/
public final boolean useLanguageCode;

ClientType(int id,
String clientName,
String deviceModel,
String osVersion,
String userAgent,
@Nullable String androidSdkVersion,
String clientVersion,
boolean canLogin,
boolean useLanguageCode) {
boolean canLogin) {
this.id = id;
this.clientName = clientName;
this.deviceModel = deviceModel;
Expand All @@ -122,6 +102,5 @@ private static boolean forceAVC() {
this.androidSdkVersion = androidSdkVersion;
this.clientVersion = clientVersion;
this.canLogin = canLogin;
this.useLanguageCode = useLanguageCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Locale;

import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;

final class PlayerRoutes {
Expand All @@ -35,9 +35,9 @@ static String createInnertubeBody(ClientType clientType) {
JSONObject context = new JSONObject();

JSONObject client = new JSONObject();
if (clientType.useLanguageCode) {
client.put("hl", BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().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.
client.put("hl", Locale.getDefault().toLanguageTag());
client.put("clientName", clientType.clientName);
client.put("clientVersion", clientType.clientVersion);
client.put("deviceModel", clientType.deviceModel);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package app.revanced.extension.youtube.patches;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
import app.revanced.extension.youtube.settings.Settings;

@SuppressWarnings("unused")
public class ChangeDefaultAudioLanguagePatch {

private static final String DEFAULT_AUDIO_TRACKS_IDENTIFIER = "original";

/**
* Audio track identifier.
*
* Examples:
* fr-FR.10
* it.10
*/
private static final Pattern AUDIO_TRACK_ID_PATTERN =
Pattern.compile("^([a-z]{2})(-[A-Z]{2})?(\\.\\d+)");

private static void printDebug(Logger.LogMessage message) {
// Do not log by default as it's spammy.
final boolean logAudioStreams = false;

//noinspection ConstantConditions
if (logAudioStreams) {
Logger.printDebug(message);
}
}

/**
* Injection point.
*/
public static boolean setAudioStreamAsDefault(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
try {
AudioStreamLanguage defaultLanguage = Settings.AUDIO_DEFAULT_LANGUAGE.get();
if (defaultLanguage == AudioStreamLanguage.DEFAULT) {
return isDefault; // Do nothing.
}

printDebug(() -> "isDefault: " + isDefault + " audioTrackId: " + audioTrackId
+ " audioTrackDisplayName:" + audioTrackDisplayName);

if (defaultLanguage == AudioStreamLanguage.ORIGINAL) {
final boolean isOriginal = audioTrackDisplayName.contains(DEFAULT_AUDIO_TRACKS_IDENTIFIER);
if (isOriginal) {
printDebug(() -> "Using original audio language: " + audioTrackId);
}

return isOriginal;
}

Matcher matcher = AUDIO_TRACK_ID_PATTERN.matcher(audioTrackId);
if (!matcher.matches()) {
Logger.printException(() -> "Cannot set default audio, unknown track: " + audioTrackId);
return isDefault;
}

String desiredIso639 = defaultLanguage.getIso639_1();
if (desiredIso639.equals(matcher.group(1))
|| desiredIso639.equals(matcher.group(2))) {
printDebug(() -> "Using preferred audio language: " + audioTrackId);
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "setAudioStreamAsDefault failure", ex);
}

return isDefault;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
Expand All @@ -52,6 +53,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 EnumSetting<AudioStreamLanguage> AUDIO_DEFAULT_LANGUAGE = new EnumSetting<>("revanced_audio_default_language", AudioStreamLanguage.DEFAULT);

// Ads
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,44 @@ public static Drawable getBackButtonDrawable() {

/**
* 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++;
Expand All @@ -100,9 +109,9 @@ protected void initialize() {
CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference);
}

preference = findPreference(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE.key);
preference = findPreference(Settings.AUDIO_DEFAULT_LANGUAGE.key);
if (preference instanceof ListPreference languagePreference) {
sortListPreferenceByValues(languagePreference);
sortListPreferenceByValues(languagePreference, 3);
}
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
Expand Down
4 changes: 4 additions & 0 deletions patches/api/patches.api
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,10 @@ public final class app/revanced/patches/youtube/shared/FingerprintsKt {
public static final fun getRollingNumberTextViewAnimationUpdateFingerprint ()Lapp/revanced/patcher/Fingerprint;
}

public final class app/revanced/patches/youtube/video/audio/ChangeDefaultAudioLanguagePatchKt {
public static final fun getChangeDefaultAudioLanguagePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

public final class app/revanced/patches/youtube/video/information/VideoInformationPatchKt {
public static final fun getVideoInformationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun userSelectedPlaybackSpeedHook (Ljava/lang/String;Ljava/lang/String;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
package app.revanced.patches.music.misc.spoof

import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
import app.revanced.patches.shared.misc.spoof.EXTENSION_CLASS_DESCRIPTOR
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch

val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
compatibleWith("com.google.android.apps.youtube.music")
}, {
musicActivityOnCreateFingerprint.method.addInstruction(
0,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientTypeToAndroidVrNoHl()V"
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
"revanced_spoof_video_streams_client",
summaryKey = null,
),
ListPreference(
"revanced_spoof_video_streams_language",
summaryKey = null
),
SwitchPreference("revanced_spoof_video_streams_ios_force_avc"),
// Preference requires a title but the actual text is chosen at runtime.
NonInteractivePreference(
Expand Down
Loading

0 comments on commit 9fe88b9

Please sign in to comment.