mirror of
https://github.com/revanced/revanced-patches
synced 2025-01-27 18:07:35 +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.FALSE;
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
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.AudioStreamLanguage;
|
||||||
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings shared across multiple apps.
|
* 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 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 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 java.util.Locale;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
|
|
||||||
public enum AudioStreamLanguage {
|
public enum AudioStreamLanguage {
|
||||||
/**
|
/**
|
||||||
* YouTube default.
|
* The current app language.
|
||||||
* Can be the original language or can be app language,
|
|
||||||
* depending on what YouTube decides to pick as the default.
|
|
||||||
*/
|
*/
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
|
||||||
// Language codes found in locale_config.xml
|
// 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,
|
AF,
|
||||||
AM,
|
AM,
|
||||||
AR,
|
AR,
|
||||||
@ -67,6 +63,7 @@ public enum AudioStreamLanguage {
|
|||||||
OR,
|
OR,
|
||||||
PA,
|
PA,
|
||||||
PL,
|
PL,
|
||||||
|
PT,
|
||||||
RO,
|
RO,
|
||||||
RU,
|
RU,
|
||||||
SI,
|
SI,
|
||||||
@ -94,6 +91,9 @@ public enum AudioStreamLanguage {
|
|||||||
language = name().toLowerCase(Locale.US);
|
language = name().toLowerCase(Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The 2 letter ISO 639_1 language code.
|
||||||
|
*/
|
||||||
public String getLanguage() {
|
public String getLanguage() {
|
||||||
// Changing the app language does not force the app to completely restart,
|
// 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.
|
// 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 androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
||||||
public enum ClientType {
|
public enum ClientType {
|
||||||
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
// 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,
|
28,
|
||||||
"ANDROID_VR",
|
"ANDROID_VR",
|
||||||
"Quest 3",
|
"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",
|
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
|
||||||
"32", // Android 12.1
|
"32", // Android 12.1
|
||||||
"1.56.21",
|
"1.56.21",
|
||||||
false),
|
false,
|
||||||
// Fall over to authenticated ('hl' is ignored and audio is same as language set in users Google account).
|
"Android VR No auth"
|
||||||
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_UNPLUGGED(
|
ANDROID_UNPLUGGED(
|
||||||
29,
|
29,
|
||||||
"ANDROID_UNPLUGGED",
|
"ANDROID_UNPLUGGED",
|
||||||
@ -33,7 +27,49 @@ public enum ClientType {
|
|||||||
"com.google.android.apps.youtube.unplugged/8.49.0 (Linux; U; Android 14; GB) gzip",
|
"com.google.android.apps.youtube.unplugged/8.49.0 (Linux; U; Android 14; GB) gzip",
|
||||||
"34",
|
"34",
|
||||||
"8.49.0",
|
"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
|
* YouTube
|
||||||
@ -75,6 +111,11 @@ public enum ClientType {
|
|||||||
*/
|
*/
|
||||||
public final boolean canLogin;
|
public final boolean canLogin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Friendly name displayed in stats for nerds.
|
||||||
|
*/
|
||||||
|
public final String friendlyName;
|
||||||
|
|
||||||
ClientType(int id,
|
ClientType(int id,
|
||||||
String clientName,
|
String clientName,
|
||||||
String deviceModel,
|
String deviceModel,
|
||||||
@ -82,7 +123,8 @@ public enum ClientType {
|
|||||||
String userAgent,
|
String userAgent,
|
||||||
@Nullable String androidSdkVersion,
|
@Nullable String androidSdkVersion,
|
||||||
String clientVersion,
|
String clientVersion,
|
||||||
boolean canLogin) {
|
boolean canLogin,
|
||||||
|
String friendlyName) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.clientName = clientName;
|
this.clientName = clientName;
|
||||||
this.deviceModel = deviceModel;
|
this.deviceModel = deviceModel;
|
||||||
@ -91,5 +133,7 @@ public enum ClientType {
|
|||||||
this.androidSdkVersion = androidSdkVersion;
|
this.androidSdkVersion = androidSdkVersion;
|
||||||
this.clientVersion = clientVersion;
|
this.clientVersion = clientVersion;
|
||||||
this.canLogin = canLogin;
|
this.canLogin = canLogin;
|
||||||
|
this.friendlyName = friendlyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app.revanced.extension.shared.spoof;
|
package app.revanced.extension.shared.spoof;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@ -17,6 +18,9 @@ import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
|||||||
public class SpoofVideoStreamsPatch {
|
public class SpoofVideoStreamsPatch {
|
||||||
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
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.
|
* Any unreachable ip address. Used to intentionally fail requests.
|
||||||
*/
|
*/
|
||||||
@ -30,17 +34,6 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return false; // Modified during patching.
|
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.
|
* Injection point.
|
||||||
* Blocks /get_watch requests by returning an unreachable URI.
|
* Blocks /get_watch requests by returning an unreachable URI.
|
||||||
@ -97,6 +90,17 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return SPOOF_STREAMING_DATA;
|
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.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@ -183,4 +187,50 @@ public class SpoofVideoStreamsPatch {
|
|||||||
|
|
||||||
return postData;
|
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.Requester;
|
||||||
import app.revanced.extension.shared.requests.Route;
|
import app.revanced.extension.shared.requests.Route;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
final class PlayerRoutes {
|
final class PlayerRoutes {
|
||||||
@ -36,8 +37,17 @@ final class PlayerRoutes {
|
|||||||
try {
|
try {
|
||||||
JSONObject context = new JSONObject();
|
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();
|
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("clientName", clientType.clientName);
|
||||||
client.put("clientVersion", clientType.clientVersion);
|
client.put("clientVersion", clientType.clientVersion);
|
||||||
client.put("deviceModel", clientType.deviceModel);
|
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.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
|
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +35,22 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||||||
*/
|
*/
|
||||||
public class StreamingDataRequest {
|
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";
|
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 String videoId;
|
||||||
|
|
||||||
private final Future<ByteBuffer> future;
|
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.
|
// 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;
|
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);
|
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
try {
|
try {
|
||||||
@ -177,7 +192,7 @@ public class StreamingDataRequest {
|
|||||||
// but empty response body does.
|
// but empty response body does.
|
||||||
if (connection.getContentLength() == 0) {
|
if (connection.getContentLength() == 0) {
|
||||||
if (BaseSettings.DEBUG.get()) {
|
if (BaseSettings.DEBUG.get()) {
|
||||||
Logger.printException(() -> "Ignoring empty client response: " + clientType);
|
Logger.printException(() -> "Ignoring empty client: " + clientType);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||||
@ -188,6 +203,7 @@ public class StreamingDataRequest {
|
|||||||
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||||
baos.write(buffer, 0, bytesRead);
|
baos.write(buffer, 0, bytesRead);
|
||||||
}
|
}
|
||||||
|
lastSpoofedClientType = clientType;
|
||||||
|
|
||||||
return ByteBuffer.wrap(baos.toByteArray());
|
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;
|
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
|
package app.revanced.patches.shared.misc.spoof
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.literal
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
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 {
|
internal val patchIncludedExtensionMethodFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
||||||
returns("Z")
|
returns("Z")
|
||||||
|
@ -10,8 +10,10 @@ import app.revanced.patcher.patch.BytecodePatchContext
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
|
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import app.revanced.util.insertFeatureFlagBooleanOverride
|
||||||
import app.revanced.util.returnEarly
|
import app.revanced.util.returnEarly
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
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
|
// endregion
|
||||||
|
|
||||||
executeBlock()
|
executeBlock()
|
||||||
|
@ -37,11 +37,21 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
|
|||||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||||
preferences = setOf(
|
preferences = setOf(
|
||||||
SwitchPreference("revanced_spoof_video_streams"),
|
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(
|
ListPreference(
|
||||||
"revanced_spoof_video_streams_language",
|
"revanced_spoof_video_streams_language",
|
||||||
summaryKey = null
|
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")
|
@Suppress("unused")
|
||||||
val forceOriginalAudioPatch = bytecodePatch(
|
val forceOriginalAudioPatch = bytecodePatch(
|
||||||
name = "Force original audio",
|
name = "Force original audio",
|
||||||
description = "Adds an option to always use the original audio track. " +
|
description = "Adds an option to always use the original audio track.",
|
||||||
"This patch does nothing if 'Spoof video streams' is enabled.",
|
|
||||||
) {
|
) {
|
||||||
dependsOn(
|
dependsOn(
|
||||||
sharedExtensionPatch,
|
sharedExtensionPatch,
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<app id="youtube">
|
<app id="youtube">
|
||||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
<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">
|
<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_DEFAULT</item>
|
||||||
<item>@string/revanced_spoof_video_streams_language_AR</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_OR</item>
|
||||||
<item>@string/revanced_spoof_video_streams_language_PA</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_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_RO</item>
|
||||||
<item>@string/revanced_spoof_video_streams_language_RU</item>
|
<item>@string/revanced_spoof_video_streams_language_RU</item>
|
||||||
<item>@string/revanced_spoof_video_streams_language_SK</item>
|
<item>@string/revanced_spoof_video_streams_language_SK</item>
|
||||||
@ -91,6 +105,7 @@
|
|||||||
<item>OR</item>
|
<item>OR</item>
|
||||||
<item>PA</item>
|
<item>PA</item>
|
||||||
<item>PL</item>
|
<item>PL</item>
|
||||||
|
<item>PT</item>
|
||||||
<item>RO</item>
|
<item>RO</item>
|
||||||
<item>RU</item>
|
<item>RU</item>
|
||||||
<item>SK</item>
|
<item>SK</item>
|
||||||
|
@ -1297,11 +1297,25 @@ Enabling this can unlock higher video qualities"</string>
|
|||||||
Video playback may not work"</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_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_client_type_title">Default client</string>
|
||||||
<string name="revanced_spoof_video_streams_about_title">Spoofing side effects</string>
|
<!-- 'no auth' means no authentication -->
|
||||||
<string name="revanced_spoof_video_streams_about_summary">"• Audio track menu is missing
|
<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
|
• Stable volume is not available
|
||||||
• Force original audio is not available"</string>
|
• 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_DEFAULT">Account language</string>
|
||||||
<string name="revanced_spoof_video_streams_language_AR">Arabic</string>
|
<string name="revanced_spoof_video_streams_language_AR">Arabic</string>
|
||||||
<string name="revanced_spoof_video_streams_language_AZ">Azerbaijani</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_OR">Odia</string>
|
||||||
<string name="revanced_spoof_video_streams_language_PA">Punjabi</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_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_RO">Romanian</string>
|
||||||
<string name="revanced_spoof_video_streams_language_RU">Russian</string>
|
<string name="revanced_spoof_video_streams_language_RU">Russian</string>
|
||||||
<string name="revanced_spoof_video_streams_language_SK">Slovak</string>
|
<string name="revanced_spoof_video_streams_language_SK">Slovak</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user