mirror of
https://github.com/revanced/revanced-patches
synced 2025-01-25 02:17:33 +01:00
fix(YouTube - Spoof video streams): Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds (#4202)
This commit is contained in:
parent
fea8cab737
commit
ab29f808a9
@ -3,8 +3,11 @@ package app.revanced.extension.shared.settings;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.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<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, parent(SPOOF_VIDEO_STREAMS));
|
||||
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, new 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<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
}
|
||||
|
@ -2,18 +2,14 @@ package app.revanced.extension.shared.spoof;
|
||||
|
||||
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.
|
||||
|
@ -4,9 +4,11 @@ import android.os.Build;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 @@ import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||
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 @@ public class SpoofVideoStreamsPatch {
|
||||
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 class SpoofVideoStreamsPatch {
|
||||
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 class SpoofVideoStreamsPatch {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ 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.AudioStreamLanguage;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
|
||||
final class PlayerRoutes {
|
||||
@ -36,8 +37,17 @@ final class PlayerRoutes {
|
||||
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);
|
||||
|
@ -22,7 +22,6 @@ import java.util.concurrent.TimeoutException;
|
||||
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 @@ import app.revanced.extension.shared.spoof.ClientType;
|
||||
*/
|
||||
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 @@ public class StreamingDataRequest {
|
||||
}
|
||||
});
|
||||
|
||||
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<ByteBuffer> future;
|
||||
@ -164,12 +185,6 @@ public class StreamingDataRequest {
|
||||
// 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 @@ public class StreamingDataRequest {
|
||||
// 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 @@ public class StreamingDataRequest {
|
||||
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||
baos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
lastSpoofedClientType = clientType;
|
||||
|
||||
return ByteBuffer.wrap(baos.toByteArray());
|
||||
}
|
||||
@ -198,7 +214,8 @@ public class StreamingDataRequest {
|
||||
}
|
||||
}
|
||||
|
||||
handleConnectionError("Could not fetch any client streams", null, debugEnabled);
|
||||
lastSpoofedClientType = null;
|
||||
handleConnectionError("Could not fetch any client streams", null, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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<OneRegisterInstruction>(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()
|
||||
|
@ -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"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -1,6 +1,19 @@
|
||||
<resources>
|
||||
<app id="youtube">
|
||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
||||
<string-array name="revanced_spoof_video_streams_client_type_entries">
|
||||
<item>Android VR</item>
|
||||
<item>@string/revanced_spoof_video_streams_client_type_android_vr_no_auth</item>
|
||||
<item>Android TV</item>
|
||||
<item>iOS TV</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_spoof_video_streams_client_type_entry_values">
|
||||
<!-- Enum names from extension -->
|
||||
<item>ANDROID_VR</item>
|
||||
<item>ANDROID_VR_NO_AUTH</item>
|
||||
<item>ANDROID_UNPLUGGED</item>
|
||||
<item>IOS_UNPLUGGED</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_spoof_video_streams_language_entries">
|
||||
<item>@string/revanced_spoof_video_streams_language_DEFAULT</item>
|
||||
<item>@string/revanced_spoof_video_streams_language_AR</item>
|
||||
@ -38,6 +51,7 @@
|
||||
<item>@string/revanced_spoof_video_streams_language_OR</item>
|
||||
<item>@string/revanced_spoof_video_streams_language_PA</item>
|
||||
<item>@string/revanced_spoof_video_streams_language_PL</item>
|
||||
<item>@string/revanced_spoof_video_streams_language_PT</item>
|
||||
<item>@string/revanced_spoof_video_streams_language_RO</item>
|
||||
<item>@string/revanced_spoof_video_streams_language_RU</item>
|
||||
<item>@string/revanced_spoof_video_streams_language_SK</item>
|
||||
@ -91,6 +105,7 @@
|
||||
<item>OR</item>
|
||||
<item>PA</item>
|
||||
<item>PL</item>
|
||||
<item>PT</item>
|
||||
<item>RO</item>
|
||||
<item>RU</item>
|
||||
<item>SK</item>
|
||||
|
@ -1297,11 +1297,25 @@ Enabling this can unlock higher video qualities"</string>
|
||||
Video playback may not work"</string>
|
||||
<string name="revanced_spoof_video_streams_user_dialog_message">Turning off this setting may cause video playback issues.</string>
|
||||
<string name="revanced_spoof_video_streams_client_type_title">Default client</string>
|
||||
<string name="revanced_spoof_video_streams_about_title">Spoofing side effects</string>
|
||||
<string name="revanced_spoof_video_streams_about_summary">"• Audio track menu is missing
|
||||
<!-- 'no auth' means no authentication -->
|
||||
<string name="revanced_spoof_video_streams_client_type_android_vr_no_auth">Android VR (no auth)</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_title">Force iOS AVC (H.264)</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Video codec is forced to AVC (H.264)</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Video codec is determined automatically</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">"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."</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_tv_title">iOS TV spoofing side effects</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_tv_summary">"• Movies or paid videos may not play
|
||||
• Videos end 1 second early"</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_title">Android spoofing side effects</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_summary">"• Audio track menu is missing
|
||||
• Stable volume is not available
|
||||
• Force original audio is not available"</string>
|
||||
<string name="revanced_spoof_video_streams_language_title">Default audio stream language</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Show in Stats for nerds</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">Client type is shown in Stats for nerds</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">Client is hidden in Stats for nerds</string>
|
||||
<string name="revanced_spoof_video_streams_language_title">VR default audio stream language</string>
|
||||
<string name="revanced_spoof_video_streams_language_DEFAULT">Account language</string>
|
||||
<string name="revanced_spoof_video_streams_language_AR">Arabic</string>
|
||||
<string name="revanced_spoof_video_streams_language_AZ">Azerbaijani</string>
|
||||
@ -1338,6 +1352,7 @@ Video playback may not work"</string>
|
||||
<string name="revanced_spoof_video_streams_language_OR">Odia</string>
|
||||
<string name="revanced_spoof_video_streams_language_PA">Punjabi</string>
|
||||
<string name="revanced_spoof_video_streams_language_PL">Polish</string>
|
||||
<string name="revanced_spoof_video_streams_language_PT">Portuguese</string>
|
||||
<string name="revanced_spoof_video_streams_language_RO">Romanian</string>
|
||||
<string name="revanced_spoof_video_streams_language_RU">Russian</string>
|
||||
<string name="revanced_spoof_video_streams_language_SK">Slovak</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user