Skip to content

Commit

Permalink
fix(YouTube - Spoof video streams): Log out the iOS client to restore…
Browse files Browse the repository at this point in the history
… kids videos playback (#4000)
  • Loading branch information
oSumAtrIX authored Nov 27, 2024
1 parent 12ea26b commit cc2ac4e
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
import androidx.annotation.Nullable;

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
IOS(5,
// iPhone 15 supports AV1 hardware decoding.
Expand All @@ -25,14 +36,9 @@ public enum ClientType {
null,
// Version number should be a valid iOS release.
// https://www.ipa4fun.com/history/185230
"19.10.7"
),
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"
"19.10.7",
"IOS",
false
);

/**
Expand All @@ -44,7 +50,7 @@ public enum ClientType {
/**
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
*/
public final String model;
public final String deviceModel;

/**
* Device OS version.
Expand All @@ -63,17 +69,37 @@ public enum ClientType {
@Nullable
public final String androidSdkVersion;

/**
* Client name.
*/
public final String clientName;

/**
* App version.
*/
public final String appVersion;
public final String clientVersion;

/**
* If the client can access the API logged in.
*/
public final boolean canLogin;

ClientType(int id, String model, String osVersion, String userAgent, @Nullable String androidSdkVersion, String appVersion) {
ClientType(int id,
String deviceModel,
String osVersion,
String userAgent,
@Nullable String androidSdkVersion,
String clientVersion,
String clientName,
boolean canLogin
) {
this.id = id;
this.model = model;
this.deviceModel = deviceModel;
this.osVersion = osVersion;
this.userAgent = userAgent;
this.androidSdkVersion = androidSdkVersion;
this.appVersion = appVersion;
this.clientVersion = clientVersion;
this.clientName = clientName;
this.canLogin = canLogin;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@
import app.revanced.extension.youtube.requests.Route;

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(
Route.Method.POST,
"player" +
"?fields=streamingData" +
"&alt=proto"
).compile();

private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
/**
* TCP connection and HTTP read timeout
*/
Expand All @@ -30,15 +28,15 @@ private PlayerRoutes() {
}

static String createInnertubeBody(ClientType clientType) {
JSONObject innerTubeBody = new JSONObject();
JSONObject innerTubeBody = new JSONObject();

try {
JSONObject context = new JSONObject();

JSONObject client = new JSONObject();
client.put("clientName", clientType.name());
client.put("clientVersion", clientType.appVersion);
client.put("deviceModel", clientType.model);
client.put("clientVersion", clientType.clientVersion);
client.put("deviceModel", clientType.deviceModel);
client.put("osVersion", clientType.osVersion);
if (clientType.androidSdkVersion != null) {
client.put("androidSdkVersion", clientType.androidSdkVersion);
Expand All @@ -57,7 +55,9 @@ static String createInnertubeBody(ClientType clientType) {
return innerTubeBody.toString();
}

/** @noinspection SameParameterValue*/
/**
* @noinspection SameParameterValue
*/
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException {
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
/**
* Video streaming data. Fetching is tied to the behavior YT uses,
* 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,
* since the stock app would not use expired streams and therefor
* the extension replace stream hook is called only if YT
Expand All @@ -37,38 +37,20 @@
public class StreamingDataRequest {

private static final ClientType[] CLIENT_ORDER_TO_USE;

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 AUTHORIZATION_HEADER = "Authorization";
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-Visitor-Id"
};

/**
* TCP connection and HTTP read timeout.
*/
private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000;

/**
* 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 Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
new LinkedHashMap<>(100) {
/**
Expand All @@ -86,8 +68,32 @@ protected boolean removeEldestEntry(Entry eldest) {
}
});

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) {
// 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));
}

Expand Down Expand Up @@ -119,6 +125,10 @@ private static HttpURLConnection send(ClientType clientType, String videoId,
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);

for (String key : REQUEST_HEADER_KEYS) {
if (!clientType.canLogin && key.equals(AUTHORIZATION_HEADER)) {
continue;
}

String value = playerHeaders.get(key);
if (value != null) {
connection.setRequestProperty(key, value);
Expand Down Expand Up @@ -186,15 +196,6 @@ private static ByteBuffer fetch(String videoId, Map<String, String> playerHeader
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() {
return future.isDone();
}
Expand Down
2 changes: 1 addition & 1 deletion patches/src/main/resources/addresources/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
</patch>
<patch id="chat.autoclaim.autoClaimChannelPointsPatch">
</patch>
<patch id="ad.embedded.embeddedAdsPatch">
<patch id="ad.embedded.embeddedAdsPatch">
<string-array name="revanced_block_embedded_ads_entries">
<item>@string/revanced_block_embedded_ads_entry_1</item>
<item>@string/revanced_block_embedded_ads_entry_2</item>
Expand Down
4 changes: 2 additions & 2 deletions patches/src/main/resources/addresources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_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_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>
</app>
<app id="twitch">
Expand Down

0 comments on commit cc2ac4e

Please sign in to comment.