mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-04-30 06:34:28 +02:00
fix(YouTube - Spoof video streams): Log out the iOS client to restore kids videos playback (#4000)
This commit is contained in:
parent
12ea26b10d
commit
cc2ac4e4cd
@ -8,6 +8,17 @@ import android.os.Build;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public enum ClientType {
|
public enum ClientType {
|
||||||
|
// Specific purpose for age restricted, or private videos, because the iOS client is not logged in.
|
||||||
|
ANDROID_VR(28,
|
||||||
|
"Quest 3",
|
||||||
|
"12",
|
||||||
|
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
|
||||||
|
"32", // Android 12.1
|
||||||
|
"1.56.21",
|
||||||
|
"ANDROID_VR",
|
||||||
|
true
|
||||||
|
),
|
||||||
|
// Specific for kids videos.
|
||||||
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
||||||
IOS(5,
|
IOS(5,
|
||||||
// iPhone 15 supports AV1 hardware decoding.
|
// iPhone 15 supports AV1 hardware decoding.
|
||||||
@ -25,14 +36,9 @@ public enum ClientType {
|
|||||||
null,
|
null,
|
||||||
// Version number should be a valid iOS release.
|
// Version number should be a valid iOS release.
|
||||||
// https://www.ipa4fun.com/history/185230
|
// https://www.ipa4fun.com/history/185230
|
||||||
"19.10.7"
|
"19.10.7",
|
||||||
),
|
"IOS",
|
||||||
ANDROID_VR(28,
|
false
|
||||||
"Quest 3",
|
|
||||||
"12",
|
|
||||||
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
|
|
||||||
"32", // Android 12.1
|
|
||||||
"1.56.21"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +50,7 @@ public enum ClientType {
|
|||||||
/**
|
/**
|
||||||
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
|
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
|
||||||
*/
|
*/
|
||||||
public final String model;
|
public final String deviceModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device OS version.
|
* Device OS version.
|
||||||
@ -63,17 +69,37 @@ public enum ClientType {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public final String androidSdkVersion;
|
public final String androidSdkVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client name.
|
||||||
|
*/
|
||||||
|
public final String clientName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App version.
|
* App version.
|
||||||
*/
|
*/
|
||||||
public final String appVersion;
|
public final String clientVersion;
|
||||||
|
|
||||||
ClientType(int id, String model, String osVersion, String userAgent, @Nullable String androidSdkVersion, String appVersion) {
|
/**
|
||||||
|
* If the client can access the API logged in.
|
||||||
|
*/
|
||||||
|
public final boolean canLogin;
|
||||||
|
|
||||||
|
ClientType(int id,
|
||||||
|
String deviceModel,
|
||||||
|
String osVersion,
|
||||||
|
String userAgent,
|
||||||
|
@Nullable String androidSdkVersion,
|
||||||
|
String clientVersion,
|
||||||
|
String clientName,
|
||||||
|
boolean canLogin
|
||||||
|
) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.model = model;
|
this.deviceModel = deviceModel;
|
||||||
this.osVersion = osVersion;
|
this.osVersion = osVersion;
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
this.androidSdkVersion = androidSdkVersion;
|
this.androidSdkVersion = androidSdkVersion;
|
||||||
this.appVersion = appVersion;
|
this.clientVersion = clientVersion;
|
||||||
|
this.clientName = clientName;
|
||||||
|
this.canLogin = canLogin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,13 @@ import app.revanced.extension.youtube.requests.Requester;
|
|||||||
import app.revanced.extension.youtube.requests.Route;
|
import app.revanced.extension.youtube.requests.Route;
|
||||||
|
|
||||||
final class PlayerRoutes {
|
final class PlayerRoutes {
|
||||||
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
|
||||||
|
|
||||||
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
|
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
|
||||||
Route.Method.POST,
|
Route.Method.POST,
|
||||||
"player" +
|
"player" +
|
||||||
"?fields=streamingData" +
|
"?fields=streamingData" +
|
||||||
"&alt=proto"
|
"&alt=proto"
|
||||||
).compile();
|
).compile();
|
||||||
|
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
||||||
/**
|
/**
|
||||||
* TCP connection and HTTP read timeout
|
* TCP connection and HTTP read timeout
|
||||||
*/
|
*/
|
||||||
@ -37,8 +35,8 @@ final class PlayerRoutes {
|
|||||||
|
|
||||||
JSONObject client = new JSONObject();
|
JSONObject client = new JSONObject();
|
||||||
client.put("clientName", clientType.name());
|
client.put("clientName", clientType.name());
|
||||||
client.put("clientVersion", clientType.appVersion);
|
client.put("clientVersion", clientType.clientVersion);
|
||||||
client.put("deviceModel", clientType.model);
|
client.put("deviceModel", clientType.deviceModel);
|
||||||
client.put("osVersion", clientType.osVersion);
|
client.put("osVersion", clientType.osVersion);
|
||||||
if (clientType.androidSdkVersion != null) {
|
if (clientType.androidSdkVersion != null) {
|
||||||
client.put("androidSdkVersion", clientType.androidSdkVersion);
|
client.put("androidSdkVersion", clientType.androidSdkVersion);
|
||||||
@ -57,7 +55,9 @@ final class PlayerRoutes {
|
|||||||
return innerTubeBody.toString();
|
return innerTubeBody.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @noinspection SameParameterValue*/
|
/**
|
||||||
|
* @noinspection SameParameterValue
|
||||||
|
*/
|
||||||
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException {
|
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException {
|
||||||
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);
|
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
/**
|
/**
|
||||||
* Video streaming data. Fetching is tied to the behavior YT uses,
|
* Video streaming data. Fetching is tied to the behavior YT uses,
|
||||||
* where this class fetches the streams only when YT fetches.
|
* where this class fetches the streams only when YT fetches.
|
||||||
*
|
* <p>
|
||||||
* Effectively the cache expiration of these fetches is the same as the stock app,
|
* Effectively the cache expiration of these fetches is the same as the stock app,
|
||||||
* since the stock app would not use expired streams and therefor
|
* since the stock app would not use expired streams and therefor
|
||||||
* the extension replace stream hook is called only if YT
|
* the extension replace stream hook is called only if YT
|
||||||
@ -37,38 +37,20 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
public class StreamingDataRequest {
|
public class StreamingDataRequest {
|
||||||
|
|
||||||
private static final ClientType[] CLIENT_ORDER_TO_USE;
|
private static final ClientType[] CLIENT_ORDER_TO_USE;
|
||||||
|
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
static {
|
|
||||||
ClientType[] allClientTypes = ClientType.values();
|
|
||||||
ClientType preferredClient = Settings.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[] REQUEST_HEADER_KEYS = {
|
private static final String[] REQUEST_HEADER_KEYS = {
|
||||||
"Authorization", // Available only to logged in users.
|
AUTHORIZATION_HEADER, // Available only to logged-in users.
|
||||||
"X-GOOG-API-FORMAT-VERSION",
|
"X-GOOG-API-FORMAT-VERSION",
|
||||||
"X-Goog-Visitor-Id"
|
"X-Goog-Visitor-Id"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TCP connection and HTTP read timeout.
|
* TCP connection and HTTP read timeout.
|
||||||
*/
|
*/
|
||||||
private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000;
|
private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS}
|
* Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS}
|
||||||
*/
|
*/
|
||||||
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
|
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
|
||||||
|
|
||||||
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
||||||
new LinkedHashMap<>(100) {
|
new LinkedHashMap<>(100) {
|
||||||
/**
|
/**
|
||||||
@ -86,8 +68,32 @@ public class StreamingDataRequest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static {
|
||||||
|
ClientType[] allClientTypes = ClientType.values();
|
||||||
|
ClientType preferredClient = Settings.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 final String videoId;
|
||||||
|
private final Future<ByteBuffer> future;
|
||||||
|
|
||||||
|
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
||||||
|
Objects.requireNonNull(playerHeaders);
|
||||||
|
this.videoId = videoId;
|
||||||
|
this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playerHeaders));
|
||||||
|
}
|
||||||
|
|
||||||
public static void fetchRequest(String videoId, Map<String, String> fetchHeaders) {
|
public static void fetchRequest(String videoId, Map<String, String> fetchHeaders) {
|
||||||
// Always fetch, even if there is a existing request for the same video.
|
// Always fetch, even if there is an existing request for the same video.
|
||||||
cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders));
|
cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +125,10 @@ public class StreamingDataRequest {
|
|||||||
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
||||||
|
|
||||||
for (String key : REQUEST_HEADER_KEYS) {
|
for (String key : REQUEST_HEADER_KEYS) {
|
||||||
|
if (!clientType.canLogin && key.equals(AUTHORIZATION_HEADER)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String value = playerHeaders.get(key);
|
String value = playerHeaders.get(key);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
connection.setRequestProperty(key, value);
|
connection.setRequestProperty(key, value);
|
||||||
@ -186,15 +196,6 @@ public class StreamingDataRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String videoId;
|
|
||||||
private final Future<ByteBuffer> future;
|
|
||||||
|
|
||||||
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
|
||||||
Objects.requireNonNull(playerHeaders);
|
|
||||||
this.videoId = videoId;
|
|
||||||
this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playerHeaders));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean fetchCompleted() {
|
public boolean fetchCompleted() {
|
||||||
return future.isDone();
|
return future.isDone();
|
||||||
}
|
}
|
||||||
|
@ -1225,9 +1225,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
|||||||
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Your device does not have VP9 hardware decoding, and this setting is always on when Client spoofing is enabled</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Your device does not have VP9 hardware decoding, and this setting is always on when Client spoofing is enabled</string>
|
||||||
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Enabling this might improve battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, and video playback will use more internet data than VP9 or AV1.</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Enabling this might improve battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, and video playback will use more internet data than VP9 or AV1.</string>
|
||||||
<string name="revanced_spoof_video_streams_about_ios_title">iOS spoofing side effects</string>
|
<string name="revanced_spoof_video_streams_about_ios_title">iOS spoofing side effects</string>
|
||||||
<string name="revanced_spoof_video_streams_about_ios_summary">• Movies or paid videos may not play\n• Livestreams start from the beginning\n• Videos may end 1 second early\n• No opus audio codec</string>
|
<string name="revanced_spoof_video_streams_about_ios_summary">• Private kids videos may not play\n• Livestreams start from the beginning\n• Videos may end 1 second early\n• No opus audio codec</string>
|
||||||
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing side effects</string>
|
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing side effects</string>
|
||||||
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Audio track menu is missing\n• Stable volume is not available</string>
|
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Kids videos may not play\n• Audio track menu is missing\n• Stable volume is not available</string>
|
||||||
</patch>
|
</patch>
|
||||||
</app>
|
</app>
|
||||||
<app id="twitch">
|
<app id="twitch">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user