diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java index 9a62db4332..ad1741d9d9 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java @@ -3,7 +3,7 @@ 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 SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, parent(SPOOF_VIDEO_STREAMS)); + public static final EnumSetting 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 SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS)); } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/AudioStreamLanguage.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/AudioStreamLanguage.java index 0d9070e2fd..566b2379f4 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/AudioStreamLanguage.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/AudioStreamLanguage.java @@ -3,6 +3,11 @@ 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; diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java index f51779a15a..b29dce4318 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java @@ -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(); diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java index 5ad672f126..9e61eca990 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java @@ -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 static byte[] removeVideoPlaybackPostBody(Uri uri, int method, byte[] pos 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; } } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java index ca1a0eb897..5da5628e0b 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java @@ -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 */ diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java new file mode 100644 index 0000000000..4a34eb3cd6 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java @@ -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; + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 950f34be4d..eaad85188c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -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); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index ed4502aef4..6511fc00ce 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -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> firstPairs = new ArrayList<>(firstEntriesToPreserve); + List> pairsToSort = new ArrayList<>(entrySize); - List> 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 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 pair : firstPairs) { + sortedEntries[i] = pair.first; + sortedEntryValues[i] = pair.second; + i++; + } - int i = 1; - for (Pair pair : entryPairs) { + for (Pair pair : pairsToSort) { sortedEntries[i] = pair.first; sortedEntryValues[i] = pair.second; i++; @@ -102,7 +111,7 @@ protected void initialize() { 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); diff --git a/patches/api/patches.api b/patches/api/patches.api index 5f7861ff2f..53a61711ca 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -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/ForceOriginalAudioPatchKt { + public static final fun getForceOriginalAudioPatch ()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 diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt index 573ac0c154..21eb321569 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt @@ -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" - ) }) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index 9c0dc3e855..d2ef79a4f4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -110,22 +110,22 @@ val enableDebuggingPatch = bytecodePatch( """ ) } - } - experimentalStringFeatureFlagFingerprint.match( - experimentalFeatureFlagParentFingerprint.originalClassDef - ).method.apply { - val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT) + experimentalStringFeatureFlagFingerprint.match( + experimentalFeatureFlagParentFingerprint.originalClassDef + ).method.apply { + val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT) - addInstructions( - insertIndex, - """ - move-result-object v0 - invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String; - move-result-object v0 - return-object v0 - """ - ) + addInstructions( + insertIndex, + """ + move-result-object v0 + invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 + """ + ) + } } // There exists other experimental accessor methods for byte[] diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/Fingerprints.kt new file mode 100644 index 0000000000..a338ea6b7d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/Fingerprints.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.video.audio + +import app.revanced.patcher.fingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal val streamingModelBuilderFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("L") + strings("vprng") +} + +internal val menuItemAudioTrackFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + parameters("L") + returns("V") + strings("menu_item_audio_track") +} + +internal val audioStreamingTypeSelector = fingerprint { + accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL) + returns("L") + strings("raw") // String is not unique +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt new file mode 100644 index 0000000000..94a80d908d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt @@ -0,0 +1,159 @@ +package app.revanced.patches.youtube.video.audio + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.immutable.ImmutableField +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;" + +@Suppress("unused") +val forceOriginalAudioPatch = bytecodePatch( + name = "Force original audio", + description = "Adds an option to always use the original audio track.", +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + + compatibleWith( + "com.google.android.youtube"( + "18.38.44", + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + "19.43.41", + "19.45.38", + "19.46.42", + ), + ) + + execute { + addResources("youtube", "video.audio.forceOriginalAudioPatch") + + PreferenceScreen.VIDEO.addPreferences( + SwitchPreference("revanced_force_original_audio") + ) + + fun Method.firstFormatStreamingModelCall( + returnType: String = "Ljava/lang/String;" + ): MutableMethod { + val audioTrackIdIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;" + && reference.returnType == returnType + } + + return navigate(this).to(audioTrackIdIndex).stop() + } + + // Accessor methods of FormatStreamModel have no string constants and + // opcodes are identical to other methods in the same class, + // so must walk from another class that use the methods. + val isDefaultMethod = streamingModelBuilderFingerprint.originalMethod.firstFormatStreamingModelCall("Z") + val audioTrackIdMethod = menuItemAudioTrackFingerprint.originalMethod.firstFormatStreamingModelCall() + val audioTrackDisplayNameMethod = audioStreamingTypeSelector.originalMethod.firstFormatStreamingModelCall() + val formatStreamModelClass = proxy(classes.first { + it.type == audioTrackIdMethod.definingClass + }).mutableClass + + formatStreamModelClass.apply { + // Add a new field to store the override. + val helperFieldName = "isDefaultAudioTrackOverride" + fields.add( + ImmutableField( + type, + helperFieldName, + "Ljava/lang/Boolean;", + // Boolean is a 100% immutable class (all fields are final) + // and safe to write to a shared field without volatile/synchronization, + // but without volatile the field can show stale data + // and the same field is calculated more than once by different threads. + AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value, + null, + null, + null + ).toMutable() + ) + + // Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed. + val helperMethodClass = type + val helperMethodName = "extension_isDefaultAudioTrack" + val helperMethod = ImmutableMethod( + helperMethodClass, + helperMethodName, + listOf(ImmutableMethodParameter("Z", null, null)), + "Z", + AccessFlags.PRIVATE.value, + null, + null, + MutableMethodImplementation(6), + ).toMutable().apply { + addInstructionsWithLabels( + 0, + """ + iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean; + if-eqz v0, :call_extension + invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z + move-result v3 + return v3 + + :call_extension + invoke-virtual { p0 }, $audioTrackIdMethod + move-result-object v1 + + invoke-virtual { p0 }, $audioTrackDisplayNameMethod + move-result-object v2 + + invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z + move-result v3 + + invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean; + move-result-object v0 + iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean; + return v3 + """ + ) + } + methods.add(helperMethod) + + // Modify isDefaultAudioTrack() to call extension helper method. + isDefaultMethod.apply { + val index = indexOfFirstInstructionOrThrow(Opcode.RETURN) + val register = getInstruction(index).registerA + + addInstructions( + index, + """ + invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z + move-result v$register + """ + ) + } + } + } +} diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 56db255896..7f8cab4cf2 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1226,6 +1226,11 @@ Enabling this can unlock higher video qualities" Haptics are disabled Haptics are enabled + + Forced original audio + Using original audio + Using default audio + Auto @@ -1292,8 +1297,8 @@ AVC has a maximum resolution of 1080p, Opus audio codec is not available, and vi • Videos end 1 second early" Android VR spoofing side effects "• Kids videos may not play -• Livestreams start from the beginning -• Videos end 1 second early" +• Audio track menu is missing +• Stable volume is not available" Default audio stream language App language Arabic