mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-09 02:43:57 +01:00
feat(YouTube Music): Add Force original audio patch (#6036)
This commit is contained in:
committed by
GitHub
parent
9d6731660b
commit
d0d53d109e
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.extension.music.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.music.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ForceOriginalAudioPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setPreferredLanguage() {
|
||||||
|
app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled(
|
||||||
|
Settings.FORCE_ORIGINAL_AUDIO.get(),
|
||||||
|
Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,4 +32,6 @@ public class Settings extends BaseSettings {
|
|||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
|
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
|
||||||
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
|
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package app.revanced.extension.shared.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ForceOriginalAudioPatch {
|
||||||
|
|
||||||
|
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
|
||||||
|
|
||||||
|
private static volatile boolean enabled = false;
|
||||||
|
|
||||||
|
public static void setEnabled(boolean isEnabled, ClientType client) {
|
||||||
|
enabled = isEnabled;
|
||||||
|
|
||||||
|
if (isEnabled
|
||||||
|
&& SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams()
|
||||||
|
&& !client.useAuth) {
|
||||||
|
// If client spoofing does not use authentication and lacks multi-audio streams,
|
||||||
|
// then can use any language code for the request and if that requested language is
|
||||||
|
// not available YT uses the original audio language. Authenticated requests ignore
|
||||||
|
// the language code and always use the account language. Use a language that is
|
||||||
|
// not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972
|
||||||
|
// but the language is also supported natively by the Meta Quest device that
|
||||||
|
// Android VR is spoofing.
|
||||||
|
AppLanguage override = AppLanguage.NB; // Norwegian Bokmal.
|
||||||
|
Logger.printDebug(() -> "Setting language override: " + override);
|
||||||
|
SpoofVideoStreamsPatch.setLanguageOverride(override);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean ignoreDefaultAudioStream(boolean original) {
|
||||||
|
if (enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
|
||||||
|
try {
|
||||||
|
if (!enabled) {
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioTrackId.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 = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX);
|
||||||
|
if (isOriginal) {
|
||||||
|
Logger.printDebug(() -> "Using audio: " + audioTrackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOriginal;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.youtube.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
@@ -6,17 +6,17 @@ import android.content.Context;
|
|||||||
import android.preference.SwitchPreference;
|
import android.preference.SwitchPreference;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
|
||||||
|
|
||||||
@SuppressWarnings({"deprecation", "unused"})
|
@SuppressWarnings({"deprecation", "unused"})
|
||||||
public class ForceOriginalAudioSwitchPreference extends SwitchPreference {
|
public class ForceOriginalAudioSwitchPreference extends SwitchPreference {
|
||||||
|
|
||||||
// Spoof stream patch is not included, or is not currently spoofing to Android Studio.
|
// Spoof stream patch is not included, or is not currently spoofing to Android Studio.
|
||||||
private static final boolean available = !SpoofVideoStreamsPatch.isPatchIncluded()
|
private static final boolean available = !SpoofVideoStreamsPatch.isPatchIncluded()
|
||||||
|| !(Settings.SPOOF_VIDEO_STREAMS.get()
|
|| !(BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
||||||
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_CREATOR);
|
&& SpoofVideoStreamsPatch.getPreferredClient() == ClientType.ANDROID_CREATOR);
|
||||||
|
|
||||||
{
|
{
|
||||||
if (!available) {
|
if (!available) {
|
||||||
@@ -66,6 +66,10 @@ public class SpoofVideoStreamsPatch {
|
|||||||
StreamingDataRequest.setClientOrderToUse(availableClients, client);
|
StreamingDataRequest.setClientOrderToUse(availableClients, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ClientType getPreferredClient() {
|
||||||
|
return preferredClient;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
||||||
return isPatchIncluded()
|
return isPatchIncluded()
|
||||||
&& SPOOF_STREAMING_DATA
|
&& SPOOF_STREAMING_DATA
|
||||||
|
|||||||
@@ -1,72 +1,17 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.shared.settings.AppLanguage;
|
|
||||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ForceOriginalAudioPatch {
|
public class ForceOriginalAudioPatch {
|
||||||
|
|
||||||
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void setPreferredLanguage() {
|
public static void setPreferredLanguage() {
|
||||||
if (Settings.FORCE_ORIGINAL_AUDIO.get()
|
app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled(
|
||||||
&& SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams()
|
Settings.FORCE_ORIGINAL_AUDIO.get(),
|
||||||
&& !Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get().useAuth) {
|
Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get()
|
||||||
// If client spoofing does not use authentication and lacks multi-audio streams,
|
);
|
||||||
// then can use any language code for the request and if that requested language is
|
|
||||||
// not available YT uses the original audio language. Authenticated requests ignore
|
|
||||||
// the language code and always use the account language. Use a language that is
|
|
||||||
// not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972
|
|
||||||
// but the language is also supported natively by the Meta Quest device that
|
|
||||||
// Android VR is spoofing.
|
|
||||||
AppLanguage override = AppLanguage.NB; // Norwegian Bokmal.
|
|
||||||
Logger.printDebug(() -> "Setting language override: " + override);
|
|
||||||
SpoofVideoStreamsPatch.setLanguageOverride(override);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean ignoreDefaultAudioStream(boolean original) {
|
|
||||||
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
|
|
||||||
try {
|
|
||||||
if (!Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
|
||||||
return isDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioTrackId.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 = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX);
|
|
||||||
if (isOriginal) {
|
|
||||||
Logger.printDebug(() -> "Using audio: " + audioTrackId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return isOriginal;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
|
||||||
return isDefault;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
|
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
|
||||||
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
||||||
public static final BooleanSetting FORCE_AVC_CODEC = new BooleanSetting("revanced_force_avc_codec", FALSE, true, "revanced_force_avc_codec_user_dialog_message");
|
public static final BooleanSetting FORCE_AVC_CODEC = new BooleanSetting("revanced_force_avc_codec", FALSE, true, "revanced_force_avc_codec_user_dialog_message");
|
||||||
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, true);
|
||||||
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
|
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
|
||||||
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
|
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
|
||||||
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
|
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
|
||||||
@@ -75,9 +76,6 @@ public class Settings extends BaseSettings {
|
|||||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||||
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
||||||
|
|
||||||
// Audio
|
|
||||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, true);
|
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
|
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
|
||||||
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
|
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
|
||||||
|
|||||||
@@ -459,9 +459,14 @@ public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPat
|
|||||||
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatchKt {
|
||||||
|
public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/playservice/VersionCheckPatchKt {
|
public final class app/revanced/patches/music/playservice/VersionCheckPatchKt {
|
||||||
public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||||
public static final fun is_7_33_or_greater ()Z
|
public static final fun is_7_33_or_greater ()Z
|
||||||
|
public static final fun is_8_10_or_greater ()Z
|
||||||
public static final fun is_8_11_or_greater ()Z
|
public static final fun is_8_11_or_greater ()Z
|
||||||
public static final fun is_8_15_or_greater ()Z
|
public static final fun is_8_15_or_greater ()Z
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package app.revanced.patches.music.misc.tracks
|
||||||
|
|
||||||
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||||
|
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||||
|
import app.revanced.patches.music.playservice.is_8_10_or_greater
|
||||||
|
import app.revanced.patches.music.playservice.versionCheckPatch
|
||||||
|
import app.revanced.patches.music.shared.mainActivityOnCreateFingerprint
|
||||||
|
import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"Lapp/revanced/extension/music/patches/ForceOriginalAudioPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val forceOriginalAudioPatch = forceOriginalAudioPatch(
|
||||||
|
block = {
|
||||||
|
dependsOn(
|
||||||
|
sharedExtensionPatch,
|
||||||
|
settingsPatch,
|
||||||
|
versionCheckPatch
|
||||||
|
)
|
||||||
|
|
||||||
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52",
|
||||||
|
"8.10.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
fixUseLocalizedAudioTrackFlag = is_8_10_or_greater,
|
||||||
|
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
|
||||||
|
subclassExtensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR,
|
||||||
|
preferenceScreen = PreferenceScreen.MISC,
|
||||||
|
)
|
||||||
@@ -7,6 +7,8 @@ import app.revanced.util.findPlayStoreServicesVersion
|
|||||||
|
|
||||||
var is_7_33_or_greater = false
|
var is_7_33_or_greater = false
|
||||||
private set
|
private set
|
||||||
|
var is_8_10_or_greater = false
|
||||||
|
private set
|
||||||
var is_8_11_or_greater = false
|
var is_8_11_or_greater = false
|
||||||
private set
|
private set
|
||||||
var is_8_15_or_greater = false
|
var is_8_15_or_greater = false
|
||||||
@@ -22,6 +24,7 @@ val versionCheckPatch = resourcePatch(
|
|||||||
|
|
||||||
// All bug fix releases always seem to use the same play store version as the minor version.
|
// All bug fix releases always seem to use the same play store version as the minor version.
|
||||||
is_7_33_or_greater = 245199000 <= playStoreServicesVersion
|
is_7_33_or_greater = 245199000 <= playStoreServicesVersion
|
||||||
|
is_8_10_or_greater = 244799000 <= playStoreServicesVersion
|
||||||
is_8_11_or_greater = 251199000 <= playStoreServicesVersion
|
is_8_11_or_greater = 251199000 <= playStoreServicesVersion
|
||||||
is_8_15_or_greater = 251530000 <= playStoreServicesVersion
|
is_8_15_or_greater = 251530000 <= playStoreServicesVersion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package app.revanced.patches.reddit.customclients.boostforreddit.fix.redgifs
|
package app.revanced.patches.reddit.customclients.boostforreddit.fix.redgifs
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patches.reddit.customclients.CREATE_NEW_CLIENT_METHOD
|
import app.revanced.patches.reddit.customclients.CREATE_NEW_CLIENT_METHOD
|
||||||
import app.revanced.patches.reddit.customclients.boostforreddit.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.reddit.customclients.boostforreddit.misc.extension.sharedExtensionPatch
|
||||||
@@ -27,9 +26,7 @@ val fixRedgifsApi = fixRedgifsApiPatch(
|
|||||||
}
|
}
|
||||||
replaceInstruction(
|
replaceInstruction(
|
||||||
index,
|
index,
|
||||||
"""
|
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->$CREATE_NEW_CLIENT_METHOD"
|
||||||
invoke-static { }, ${EXTENSION_CLASS_DESCRIPTOR}->$CREATE_NEW_CLIENT_METHOD
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.patches.youtube.video.audio
|
package app.revanced.patches.shared.misc.audio
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
import app.revanced.util.containsLiteralInstruction
|
import app.revanced.util.containsLiteralInstruction
|
||||||
@@ -7,10 +7,14 @@ import com.android.tools.smali.dexlib2.AccessFlags
|
|||||||
internal val formatStreamModelToStringFingerprint = fingerprint {
|
internal val formatStreamModelToStringFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
returns("Ljava/lang/String;")
|
returns("Ljava/lang/String;")
|
||||||
custom { method, classDef ->
|
custom { method, _ ->
|
||||||
method.name == "toString" && classDef.type ==
|
method.name == "toString"
|
||||||
"Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
|
||||||
}
|
}
|
||||||
|
strings(
|
||||||
|
// Strings are partial matches.
|
||||||
|
"isDefaultAudioTrack=",
|
||||||
|
"audioTrackId="
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
|
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
|
||||||
@@ -20,7 +24,6 @@ internal val selectAudioStreamFingerprint = fingerprint {
|
|||||||
returns("L")
|
returns("L")
|
||||||
custom { method, _ ->
|
custom { method, _ ->
|
||||||
method.parameters.size > 2 // Method has a large number of parameters and may change.
|
method.parameters.size > 2 // Method has a large number of parameters and may change.
|
||||||
&& method.parameters[1].type == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
|
|
||||||
&& method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG)
|
&& method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package app.revanced.patches.shared.misc.audio
|
||||||
|
|
||||||
|
import app.revanced.patcher.Fingerprint
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
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.BytecodePatchBuilder
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
|
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.BasePreferenceScreen
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
|
import app.revanced.util.findMethodFromToString
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import app.revanced.util.insertLiteralOverride
|
||||||
|
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.instruction.OneRegisterInstruction
|
||||||
|
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/shared/patches/ForceOriginalAudioPatch;"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch shared with YouTube and YT Music.
|
||||||
|
*/
|
||||||
|
internal fun forceOriginalAudioPatch(
|
||||||
|
block: BytecodePatchBuilder.() -> Unit = {},
|
||||||
|
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||||
|
fixUseLocalizedAudioTrackFlag: Boolean,
|
||||||
|
mainActivityOnCreateFingerprint: Fingerprint,
|
||||||
|
subclassExtensionClassDescriptor: String,
|
||||||
|
preferenceScreen: BasePreferenceScreen.Screen
|
||||||
|
) = bytecodePatch(
|
||||||
|
name = "Force original audio",
|
||||||
|
description = "Adds an option to always use the original audio track.",
|
||||||
|
) {
|
||||||
|
|
||||||
|
block()
|
||||||
|
|
||||||
|
dependsOn(addResourcesPatch)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
addResources("shared", "misc.audio.forceOriginalAudioPatch")
|
||||||
|
|
||||||
|
preferenceScreen.addPreferences(
|
||||||
|
SwitchPreference(
|
||||||
|
key = "revanced_force_original_audio",
|
||||||
|
tag = "app.revanced.extension.shared.settings.preference.ForceOriginalAudioSwitchPreference"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static { }, $subclassExtensionClassDescriptor->setPreferredLanguage()V"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Disable feature flag that ignores the default track flag
|
||||||
|
// and instead overrides to the user region language.
|
||||||
|
if (fixUseLocalizedAudioTrackFlag) {
|
||||||
|
selectAudioStreamFingerprint.method.insertLiteralOverride(
|
||||||
|
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
|
||||||
|
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
formatStreamModelToStringFingerprint.let {
|
||||||
|
val isDefaultAudioTrackMethod = it.originalMethod.findMethodFromToString("isDefaultAudioTrack=")
|
||||||
|
val audioTrackDisplayNameMethod = it.originalMethod.findMethodFromToString("audioTrackDisplayName=")
|
||||||
|
val audioTrackIdMethod = it.originalMethod.findMethodFromToString("audioTrackId=")
|
||||||
|
|
||||||
|
it.classDef.apply {
|
||||||
|
// Add a new field to store the override.
|
||||||
|
val helperFieldName = "patch_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 = "patch_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.
|
||||||
|
isDefaultAudioTrackMethod.apply {
|
||||||
|
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
index,
|
||||||
|
"""
|
||||||
|
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
|
||||||
|
move-result v$register
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeBlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -161,7 +161,7 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
|
|||||||
addInstructions(
|
addInstructions(
|
||||||
index + 1,
|
index + 1,
|
||||||
"""
|
"""
|
||||||
invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->overrideBackPressToExit(Z)Z
|
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->overrideBackPressToExit(Z)Z
|
||||||
move-result v$register
|
move-result v$register
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
|
|||||||
|
|
||||||
addInstruction(
|
addInstruction(
|
||||||
index + 1,
|
index + 1,
|
||||||
"invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->setToolbar(Landroid/widget/FrameLayout;)V"
|
"invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->setToolbar(Landroid/widget/FrameLayout;)V"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,159 +1,36 @@
|
|||||||
package app.revanced.patches.youtube.video.audio
|
package app.revanced.patches.youtube.video.audio
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch
|
||||||
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.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.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
||||||
import app.revanced.util.findMethodFromToString
|
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
|
||||||
import app.revanced.util.insertLiteralOverride
|
|
||||||
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.instruction.OneRegisterInstruction
|
|
||||||
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 =
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;"
|
"Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;"
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val forceOriginalAudioPatch = bytecodePatch(
|
val forceOriginalAudioPatch = forceOriginalAudioPatch(
|
||||||
name = "Force original audio",
|
block = {
|
||||||
description = "Adds an option to always use the original audio track.",
|
dependsOn(
|
||||||
) {
|
sharedExtensionPatch,
|
||||||
dependsOn(
|
settingsPatch,
|
||||||
sharedExtensionPatch,
|
versionCheckPatch
|
||||||
settingsPatch,
|
|
||||||
addResourcesPatch,
|
|
||||||
versionCheckPatch
|
|
||||||
)
|
|
||||||
|
|
||||||
compatibleWith(
|
|
||||||
"com.google.android.youtube"(
|
|
||||||
"19.34.42",
|
|
||||||
"20.07.39",
|
|
||||||
"20.13.41",
|
|
||||||
"20.14.43",
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
execute {
|
compatibleWith(
|
||||||
addResources("youtube", "video.audio.forceOriginalAudioPatch")
|
"com.google.android.youtube"(
|
||||||
|
"19.34.42",
|
||||||
PreferenceScreen.VIDEO.addPreferences(
|
"20.07.39",
|
||||||
SwitchPreference(
|
"20.13.41",
|
||||||
key = "revanced_force_original_audio",
|
"20.14.43",
|
||||||
tag = "app.revanced.extension.youtube.settings.preference.ForceOriginalAudioSwitchPreference"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
fixUseLocalizedAudioTrackFlag = is_20_07_or_greater,
|
||||||
0,
|
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
|
||||||
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setPreferredLanguage()V"
|
subclassExtensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR,
|
||||||
)
|
preferenceScreen = PreferenceScreen.VIDEO,
|
||||||
|
)
|
||||||
// Disable feature flag that ignores the default track flag
|
|
||||||
// and instead overrides to the user region language.
|
|
||||||
if (is_20_07_or_greater) {
|
|
||||||
selectAudioStreamFingerprint.method.insertLiteralOverride(
|
|
||||||
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
|
|
||||||
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatStreamModelToStringFingerprint.let {
|
|
||||||
val isDefaultAudioTrackMethod = it.originalMethod.findMethodFromToString("isDefaultAudioTrack=")
|
|
||||||
val audioTrackDisplayNameMethod = it.originalMethod.findMethodFromToString("audioTrackDisplayName=")
|
|
||||||
val audioTrackIdMethod = it.originalMethod.findMethodFromToString("audioTrackId=")
|
|
||||||
|
|
||||||
it.classDef.apply {
|
|
||||||
// Add a new field to store the override.
|
|
||||||
val helperFieldName = "patch_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 = "patch_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.
|
|
||||||
isDefaultAudioTrackMethod.apply {
|
|
||||||
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
|
|
||||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
index,
|
|
||||||
"""
|
|
||||||
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
|
|
||||||
move-result v$register
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -176,6 +176,13 @@ Playback may not work"</string>
|
|||||||
<string name="revanced_spoof_video_streams_user_dialog_message">Turning off this setting may cause playback issues.</string>
|
<string name="revanced_spoof_video_streams_user_dialog_message">Turning off this setting may cause playback issues.</string>
|
||||||
<string name="revanced_spoof_video_streams_client_type_title">Default client</string>
|
<string name="revanced_spoof_video_streams_client_type_title">Default client</string>
|
||||||
</patch>
|
</patch>
|
||||||
|
<patch id="misc.audio.forceOriginalAudioPatch">
|
||||||
|
<string name="revanced_force_original_audio_title">Force original audio language</string>
|
||||||
|
<string name="revanced_force_original_audio_summary_on">Using original audio language</string>
|
||||||
|
<string name="revanced_force_original_audio_summary_off">Using default audio</string>
|
||||||
|
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
||||||
|
<string name="revanced_force_original_audio_not_available">To use this feature, change \'Spoof video streams\' to any client except Android Studio</string>
|
||||||
|
</patch>
|
||||||
<patch id="misc.debugging.enableDebuggingPatch">
|
<patch id="misc.debugging.enableDebuggingPatch">
|
||||||
<string name="revanced_debug_screen_title">Debugging</string>
|
<string name="revanced_debug_screen_title">Debugging</string>
|
||||||
<string name="revanced_debug_screen_summary">Enable or disable debugging options</string>
|
<string name="revanced_debug_screen_summary">Enable or disable debugging options</string>
|
||||||
@@ -1590,13 +1597,6 @@ Enabling this can unlock higher video qualities"</string>
|
|||||||
<string name="revanced_external_browser_summary_on">Opening links in external browser</string>
|
<string name="revanced_external_browser_summary_on">Opening links in external browser</string>
|
||||||
<string name="revanced_external_browser_summary_off">Opening links in in-app browser</string>
|
<string name="revanced_external_browser_summary_off">Opening links in in-app browser</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="video.audio.forceOriginalAudioPatch">
|
|
||||||
<string name="revanced_force_original_audio_title">Force original audio language</string>
|
|
||||||
<string name="revanced_force_original_audio_summary_on">Using original audio language</string>
|
|
||||||
<string name="revanced_force_original_audio_summary_off">Using default audio</string>
|
|
||||||
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
|
||||||
<string name="revanced_force_original_audio_not_available">To use this feature, change \'Spoof video streams\' to any client except Android Studio</string>
|
|
||||||
</patch>
|
|
||||||
<patch id="video.quality.rememberVideoQualityPatch">
|
<patch id="video.quality.rememberVideoQualityPatch">
|
||||||
<!-- Translations should use the same text as 'revanced_custom_playback_speeds_auto'. -->
|
<!-- Translations should use the same text as 'revanced_custom_playback_speeds_auto'. -->
|
||||||
<string name="revanced_video_quality_default_entry_1">Auto</string>
|
<string name="revanced_video_quality_default_entry_1">Auto</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user