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 4c0e79f21b..1f5c1113de 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,8 +3,11 @@ 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.AudioStreamLanguageOverrideAvailability; +import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability; import app.revanced.extension.shared.spoof.AudioStreamLanguage; +import app.revanced.extension.shared.spoof.ClientType; /** * Settings shared across multiple apps. @@ -20,5 +23,11 @@ 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 AudioStreamLanguageOverrideAvailability()); + public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE); + 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 SpoofiOSAvailability()); + // Client type must be last spoof setting due to cyclic references. + public static final EnumSetting SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", 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 ec9be62adb..d94b0069f6 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 @@ -2,18 +2,14 @@ import java.util.Locale; -import app.revanced.extension.shared.Utils; - 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. + * The current app language. */ DEFAULT, // Language codes found in locale_config.xml - // Region specific variants of Chinese/English/Spanish/French have been removed. + // All region specific variants have been removed. AF, AM, AR, @@ -67,6 +63,7 @@ public enum AudioStreamLanguage { OR, PA, PL, + PT, RO, RU, SI, @@ -94,6 +91,9 @@ public enum AudioStreamLanguage { language = name().toLowerCase(Locale.US); } + /** + * @return The 2 letter ISO 639_1 language code. + */ public String getLanguage() { // 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. 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 14240a7677..fde8a67fdf 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 @@ -4,9 +4,11 @@ import androidx.annotation.Nullable; +import app.revanced.extension.shared.settings.BaseSettings; + public enum ClientType { // https://dumps.tadiphone.dev/dumps/oculus/eureka - ANDROID_VR_NO_AUTH( // Must be first so a default audio language can be set. + ANDROID_VR_NO_AUTH( 28, "ANDROID_VR", "Quest 3", @@ -14,17 +16,9 @@ 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", - false), - // Fall over to authenticated ('hl' is ignored and audio is same as language set in users Google account). - ANDROID_VR( - ANDROID_VR_NO_AUTH.id, - ANDROID_VR_NO_AUTH.clientName, - ANDROID_VR_NO_AUTH.deviceModel, - ANDROID_VR_NO_AUTH.osVersion, - ANDROID_VR_NO_AUTH.userAgent, - ANDROID_VR_NO_AUTH.androidSdkVersion, - ANDROID_VR_NO_AUTH.clientVersion, - true), + false, + "Android VR No auth" + ), ANDROID_UNPLUGGED( 29, "ANDROID_UNPLUGGED", @@ -33,7 +27,49 @@ public enum ClientType { "com.google.android.apps.youtube.unplugged/8.49.0 (Linux; U; Android 14; GB) gzip", "34", "8.49.0", - true); // Requires login. + true, + "Android TV" + ), + ANDROID_VR( + ANDROID_VR_NO_AUTH.id, + ANDROID_VR_NO_AUTH.clientName, + ANDROID_VR_NO_AUTH.deviceModel, + ANDROID_VR_NO_AUTH.osVersion, + ANDROID_VR_NO_AUTH.userAgent, + ANDROID_VR_NO_AUTH.androidSdkVersion, + ANDROID_VR_NO_AUTH.clientVersion, + true, + "Android VR" + ), + IOS_UNPLUGGED(33, + "IOS_UNPLUGGED", + forceAVC() + ? "iPhone12,5" // 11 Pro Max (last device with iOS 13) + : "iPhone16,2", // 15 Pro Max + // iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1. + forceAVC() + ? "13.7.17H35" // Last release of iOS 13. + : "18.1.1.22B91", + forceAVC() + ? "com.google.ios.youtubeunplugged/6.45 (iPhone; U; CPU iOS 13_7 like Mac OS X)" + : "com.google.ios.youtubeunplugged/8.33 (iPhone; U; CPU iOS 18_1_1 like Mac OS X)", + null, + // Version number should be a valid iOS release. + // https://www.ipa4fun.com/history/152043/ + // Some newer versions can also force AVC, + // but 6.45 is the last version that supports iOS 13. + forceAVC() + ? "6.45" + : "8.33", + true, + forceAVC() + ? "iOS TV Force AVC" + : "iOS TV" + ); + + private static boolean forceAVC() { + return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get(); + } /** * YouTube @@ -75,6 +111,11 @@ public enum ClientType { */ public final boolean canLogin; + /** + * Friendly name displayed in stats for nerds. + */ + public final String friendlyName; + ClientType(int id, String clientName, String deviceModel, @@ -82,7 +123,8 @@ public enum ClientType { String userAgent, @Nullable String androidSdkVersion, String clientVersion, - boolean canLogin) { + boolean canLogin, + String friendlyName) { this.id = id; this.clientName = clientName; this.deviceModel = deviceModel; @@ -91,5 +133,7 @@ public enum ClientType { this.androidSdkVersion = androidSdkVersion; this.clientVersion = clientVersion; this.canLogin = canLogin; + this.friendlyName = friendlyName; } + } 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 e547baef6e..2595ca1661 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 @@ -1,6 +1,7 @@ package app.revanced.extension.shared.spoof; import android.net.Uri; +import android.text.TextUtils; import androidx.annotation.Nullable; @@ -17,6 +18,9 @@ public class SpoofVideoStreamsPatch { private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get(); + private static final boolean FIX_HLS_CURRENT_TIME = SPOOF_STREAMING_DATA + && BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED; + /** * Any unreachable ip address. Used to intentionally fail requests. */ @@ -30,17 +34,6 @@ private static boolean isPatchIncluded() { return false; // Modified during patching. } - public static final class NotSpoofingAndroidAvailability implements Setting.Availability { - @Override - public boolean isAvailable() { - if (SpoofVideoStreamsPatch.isPatchIncluded()) { - return !BaseSettings.SPOOF_VIDEO_STREAMS.get(); - } - - return true; - } - } - /** * Injection point. * Blocks /get_watch requests by returning an unreachable URI. @@ -97,6 +90,17 @@ public static boolean isSpoofingEnabled() { return SPOOF_STREAMING_DATA; } + /** + * Injection point. + * Only invoked when playing a livestream on an iOS client. + */ + public static boolean fixHLSCurrentTime(boolean original) { + if (!SPOOF_STREAMING_DATA) { + return original; + } + return false; + } + /** * Injection point. */ @@ -183,4 +187,50 @@ public static byte[] removeVideoPlaybackPostBody(Uri uri, int method, byte[] pos return postData; } + + /** + * Injection point. + */ + public static String appendSpoofedClient(String videoFormat) { + try { + if (SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_STATS_FOR_NERDS.get() + && !TextUtils.isEmpty(videoFormat)) { + // Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages. + return "\u202D" + videoFormat + "\u2009(" // u202D = left to right override + + StreamingDataRequest.getLastSpoofedClientName() + ")"; + } + } catch (Exception ex) { + Logger.printException(() -> "appendSpoofedClient failure", ex); + } + + return videoFormat; + } + + public static final class NotSpoofingAndroidAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + if (SpoofVideoStreamsPatch.isPatchIncluded()) { + return !BaseSettings.SPOOF_VIDEO_STREAMS.get() + || BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED; + } + + return true; + } + } + + public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + return !BaseSettings.SPOOF_VIDEO_STREAMS.get() + || BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH; + } + } + + 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_UNPLUGGED; + } + } } 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 b37daa3cf1..9974064e97 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 @@ -10,6 +10,7 @@ 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.AudioStreamLanguage; import app.revanced.extension.shared.spoof.ClientType; final class PlayerRoutes { @@ -36,8 +37,17 @@ static String createInnertubeBody(ClientType clientType) { try { JSONObject context = new JSONObject(); + // Can override default language only if no login is used. + // Could use preferred audio for all clients that do not login, + // but if this is a fall over client it will set the language even though + // the audio language is not selectable in the UI. + ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get(); + AudioStreamLanguage language = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH + ? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get() + : AudioStreamLanguage.DEFAULT; + JSONObject client = new JSONObject(); - client.put("hl", BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getLanguage()); + client.put("hl", language.getLanguage()); client.put("clientName", clientType.clientName); client.put("clientVersion", clientType.clientVersion); client.put("deviceModel", clientType.deviceModel); diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java index 67f687b79f..143dfdf3ad 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java @@ -22,7 +22,6 @@ import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.spoof.AudioStreamLanguage; import app.revanced.extension.shared.spoof.ClientType; /** @@ -36,7 +35,22 @@ */ public class StreamingDataRequest { - private static final ClientType[] CLIENT_ORDER_TO_USE = ClientType.values(); + private static final ClientType[] CLIENT_ORDER_TO_USE; + + static { + ClientType[] allClientTypes = ClientType.values(); + ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get(); + + CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length]; + CLIENT_ORDER_TO_USE[0] = preferredClient; + + int i = 1; + for (ClientType c : allClientTypes) { + if (c != preferredClient) { + CLIENT_ORDER_TO_USE[i++] = c; + } + } + } private static final String AUTHORIZATION_HEADER = "Authorization"; @@ -73,6 +87,13 @@ protected boolean removeEldestEntry(Entry eldest) { } }); + private static volatile ClientType lastSpoofedClientType; + + public static String getLastSpoofedClientName() { + ClientType client = lastSpoofedClientType; + return client == null ? "Unknown" : client.friendlyName; + } + private final String videoId; private final Future future; @@ -164,12 +185,6 @@ private static ByteBuffer fetch(String videoId, Map playerHeader // Show an error if the last client type fails, or if the debug is enabled then show for all attempts. final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled; - if (clientType == ClientType.ANDROID_VR_NO_AUTH - && BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get() == AudioStreamLanguage.DEFAULT) { - // Only use no auth Android VR if a non default audio language is selected. - continue; - } - HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast); if (connection != null) { try { @@ -177,7 +192,7 @@ private static ByteBuffer fetch(String videoId, Map playerHeader // but empty response body does. if (connection.getContentLength() == 0) { if (BaseSettings.DEBUG.get()) { - Logger.printException(() -> "Ignoring empty client response: " + clientType); + Logger.printException(() -> "Ignoring empty client: " + clientType); } } else { try (InputStream inputStream = new BufferedInputStream(connection.getInputStream()); @@ -188,6 +203,7 @@ private static ByteBuffer fetch(String videoId, Map playerHeader while ((bytesRead = inputStream.read(buffer)) >= 0) { baos.write(buffer, 0, bytesRead); } + lastSpoofedClientType = clientType; return ByteBuffer.wrap(baos.toByteArray()); } @@ -198,7 +214,8 @@ private static ByteBuffer fetch(String videoId, Map playerHeader } } - handleConnectionError("Could not fetch any client streams", null, debugEnabled); + lastSpoofedClientType = null; + handleConnectionError("Could not fetch any client streams", null, true); return null; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java new file mode 100644 index 0000000000..a7fef50f90 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java @@ -0,0 +1,87 @@ +package app.revanced.extension.youtube.settings.preference; + +import static app.revanced.extension.shared.StringRef.str; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.Setting; +import app.revanced.extension.shared.spoof.ClientType; + +@SuppressWarnings({"deprecation", "unused"}) +public class SpoofStreamingDataSideEffectsPreference extends Preference { + + @Nullable + private ClientType currentClientType; + + private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { + // Because this listener may run before the ReVanced settings fragment updates Settings, + // this could show the prior config and not the current. + // + // Push this call to the end of the main run queue, + // so all other listeners are done and Settings is up to date. + Utils.runOnMainThread(this::updateUI); + }; + + public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SpoofStreamingDataSideEffectsPreference(Context context) { + super(context); + } + + private void addChangeListener() { + Setting.preferences.preferences.registerOnSharedPreferenceChangeListener(listener); + } + + private void removeChangeListener() { + Setting.preferences.preferences.unregisterOnSharedPreferenceChangeListener(listener); + } + + @Override + protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { + super.onAttachedToHierarchy(preferenceManager); + updateUI(); + addChangeListener(); + } + + @Override + protected void onPrepareForRemoval() { + super.onPrepareForRemoval(); + removeChangeListener(); + } + + private void updateUI() { + ClientType clientType = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get(); + if (currentClientType == clientType) { + return; + } + + Logger.printDebug(() -> "Updating spoof stream side effects preference"); + setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get()); + + String key = "revanced_spoof_video_streams_about_" + + (clientType == ClientType.IOS_UNPLUGGED + ? "ios_tv" + : "android"); + setTitle(str(key + "_title")); + setSummary(str(key + "_summary")); + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt index 683529a71b..164d67b1ad 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt @@ -1,6 +1,7 @@ package app.revanced.patches.shared.misc.spoof import app.revanced.patcher.fingerprint +import app.revanced.util.literal import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -111,6 +112,23 @@ internal val buildMediaDataSourceFingerprint = fingerprint { ) } +internal const val HLS_CURRENT_TIME_FEATURE_FLAG = 45355374L + +internal val hlsCurrentTimeFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + parameters("Z", "L") + literal { + HLS_CURRENT_TIME_FEATURE_FLAG + } +} + +internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returns("Ljava/lang/String;") + parameters("L") + strings("codecs=\"") +} + internal val patchIncludedExtensionMethodFingerprint = fingerprint { accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) returns("Z") diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt index 30c7bf9c9b..c6628f5cbd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt @@ -10,8 +10,10 @@ import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.insertFeatureFlagBooleanOverride import app.revanced.util.returnEarly import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -206,6 +208,34 @@ fun spoofVideoStreamsPatch( """, ) } + + // endregion + + // region Append spoof info. + + nerdsStatsVideoFormatBuilderFingerprint.method.apply { + findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index -> + val register = getInstruction(index).registerA + + addInstructions( + index, + """ + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$register + """ + ) + } + } + + // endregion + + // region Fix iOS livestream current time. + + hlsCurrentTimeFingerprint.method.insertFeatureFlagBooleanOverride( + HLS_CURRENT_TIME_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z" + ) + // endregion executeBlock() diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt index f3275b2f40..a408815ca5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt @@ -37,11 +37,21 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({ sorting = PreferenceScreenPreference.Sorting.UNSORTED, preferences = setOf( SwitchPreference("revanced_spoof_video_streams"), + ListPreference( + "revanced_spoof_video_streams_client_type", + summaryKey = null, + ), + NonInteractivePreference( + // Requires a key and title but the actual text is chosen at runtime. + key = "revanced_spoof_video_streams_about_android", + tag = "app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference" + ), ListPreference( "revanced_spoof_video_streams_language", summaryKey = null ), - NonInteractivePreference("revanced_spoof_video_streams_about") + SwitchPreference("revanced_spoof_video_streams_ios_force_avc"), + SwitchPreference("revanced_spoof_streaming_data_stats_for_nerds"), ), ), ) 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 index 51286e662e..4a4f00ecf9 100644 --- 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 @@ -31,8 +31,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR = @Suppress("unused") val forceOriginalAudioPatch = bytecodePatch( name = "Force original audio", - description = "Adds an option to always use the original audio track. " + - "This patch does nothing if 'Spoof video streams' is enabled.", + description = "Adds an option to always use the original audio track.", ) { dependsOn( sharedExtensionPatch, diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index 04d95a7d62..50ac505a42 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -1,6 +1,19 @@ + + Android VR + @string/revanced_spoof_video_streams_client_type_android_vr_no_auth + Android TV + iOS TV + + + + ANDROID_VR + ANDROID_VR_NO_AUTH + ANDROID_UNPLUGGED + IOS_UNPLUGGED + @string/revanced_spoof_video_streams_language_DEFAULT @string/revanced_spoof_video_streams_language_AR @@ -38,6 +51,7 @@ @string/revanced_spoof_video_streams_language_OR @string/revanced_spoof_video_streams_language_PA @string/revanced_spoof_video_streams_language_PL + @string/revanced_spoof_video_streams_language_PT @string/revanced_spoof_video_streams_language_RO @string/revanced_spoof_video_streams_language_RU @string/revanced_spoof_video_streams_language_SK @@ -91,6 +105,7 @@ OR PA PL + PT RO RU SK diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index a10b426cee..e2650f1e5e 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1297,11 +1297,25 @@ Enabling this can unlock higher video qualities" Video playback may not work" Turning off this setting may cause video playback issues. Default client - Spoofing side effects - "• Audio track menu is missing + + Android VR (no auth) + Force iOS AVC (H.264) + Video codec is forced to AVC (H.264) + Video codec is determined automatically + "Enabling this might improve battery life and fix playback stuttering. + +AVC has a maximum resolution of 1080p, Opus audio codec is not available, and video playback will use more internet data than VP9 or AV1." + iOS TV spoofing side effects + "• Movies or paid videos may not play +• Videos end 1 second early" + Android spoofing side effects + "• Audio track menu is missing • Stable volume is not available • Force original audio is not available" - Default audio stream language + Show in Stats for nerds + Client type is shown in Stats for nerds + Client is hidden in Stats for nerds + VR default audio stream language Account language Arabic Azerbaijani @@ -1338,6 +1352,7 @@ Video playback may not work" Odia Punjabi Polish + Portuguese Romanian Russian Slovak