Skip to content
This repository has been archived by the owner on Dec 11, 2024. It is now read-only.

Commit

Permalink
fix(YouTube - Spoof streaming data): Playback issues can occur when t…
Browse files Browse the repository at this point in the history
…he data connection changes or when RVX has been open for a long time

fix(YouTube - Spoof streaming data): Wrong package name used in User-Agent

fix(YouTube - Spoof streaming data): `Authorization` key is always included when fetching an API, even if there is no `Authorization` in the header (e.g. the user is not logged in or using the Incognito Mode)

fix(YouTube - Spoof streaming data): Revert `reduce response timeout and cache size`
  • Loading branch information
inotia00 authored and anddea committed Oct 4, 2024
1 parent 47d53e2 commit c342d6f
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
@SuppressWarnings("unused")
public class SpoofStreamingDataPatch {
private static final boolean SPOOF_STREAMING_DATA = Settings.SPOOF_STREAMING_DATA.get();
private static final ClientType SPOOF_STREAMING_DATA_TYPE = Settings.SPOOF_STREAMING_DATA_TYPE.get();

/**
* Any unreachable ip address. Used to intentionally fail requests.
Expand Down Expand Up @@ -56,6 +55,10 @@ public static Uri blockGetWatchRequest(Uri playerRequestUri) {
* Injection point.
* <p>
* Blocks /initplayback requests.
* <p>
* In some cases, blocking all URLs containing the path `initplayback`
* using localhost can also cause playback issues.
* See <a href="https://github.com/inotia00/ReVanced_Extended/issues/2416">this GitHub Issue</a>.
*/
public static String blockInitPlaybackRequest(String originalUrlString) {
if (SPOOF_STREAMING_DATA) {
Expand All @@ -64,17 +67,9 @@ public static String blockInitPlaybackRequest(String originalUrlString) {
String path = originalUri.getPath();

if (path != null && path.contains("initplayback")) {
String replacementUriString = (SPOOF_STREAMING_DATA_TYPE == ClientType.IOS)
? UNREACHABLE_HOST_URI_STRING
// TODO: Ideally, a local proxy could be setup and block
// the request the same way as Burp Suite is capable of
// because that way the request is never sent to YouTube unnecessarily.
// Just using localhost unfortunately does not work.
: originalUri.buildUpon().clearQuery().build().toString();

Logger.printDebug(() -> "Blocking 'initplayback' by returning unreachable url");
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");

return replacementUriString;
return originalUri.buildUpon().clearQuery().build().toString();
}
} catch (Exception ex) {
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
Expand Down Expand Up @@ -158,7 +153,7 @@ public static byte[] removeVideoPlaybackPostBody(Uri uri, int method, byte[] pos
String path = uri.getPath();
String clientNameQueryKey = "c";
final boolean iosClient = "IOS".equals(uri.getQueryParameter(clientNameQueryKey));
if (iosClient && path != null && path.contains("videoplayback")) {
if (path != null && path.contains("videoplayback")) {
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public class AppClient {
* Package name for YouTube VR (Meta Quests): com.google.android.apps.youtube.vr.oculus
* Package name for YouTube VR (ByteDance Pico 4): com.google.android.apps.youtube.vr.pico
*/
private static final String USER_AGENT_ANDROID_VR = "com.google.android.youtube/" +
private static final String USER_AGENT_ANDROID_VR = "com.google.android.apps.youtube.vr.oculus/" +
CLIENT_VERSION_ANDROID_VR +
" (Linux; U; Android " +
OS_VERSION_ANDROID_VR +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ public static String getLastSpoofedClientName() {
: lastSpoofedClientType.friendlyName;
}

/**
* How long to keep fetches until they are expired.
*/
private static final long CACHE_RETENTION_TIME_MILLISECONDS = 5 * 60 * 1000; // 5 Minutes

/**
* TCP connection and HTTP read timeout.
*/
Expand All @@ -76,15 +71,15 @@ public static String getLastSpoofedClientName() {

@GuardedBy("itself")
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
new LinkedHashMap<>(30) {
new LinkedHashMap<>(100) {
/**
* Cache limit must be greater than the maximum number of videos open at once,
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
* But instead use a much larger value, to handle if a video viewed a while ago
* is somehow still referenced. Each stream is a small array of Strings
* so memory usage is not a concern.
*/
private static final int CACHE_LIMIT = 15;
private static final int CACHE_LIMIT = 50;

@Override
protected boolean removeEldestEntry(Entry eldest) {
Expand All @@ -93,29 +88,24 @@ protected boolean removeEldestEntry(Entry eldest) {
});

public static void fetchRequest(@NonNull String videoId, Map<String, String> fetchHeaders) {
synchronized (cache) {
// Remove any expired entries.
final long now = System.currentTimeMillis();
cache.values().removeIf(request -> {
final boolean expired = request.isExpired(now);
if (expired) Logger.printDebug(() -> "Removing expired stream: " + request.videoId);
return expired;
});
cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders));
}
cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders));
}

@Nullable
public static StreamingDataRequest getRequestForVideoId(@Nullable String videoId) {
synchronized (cache) {
return cache.get(videoId);
}
return cache.get(videoId);
}

private static void handleConnectionError(String toastMessage, @Nullable Exception ex) {
Logger.printInfo(() -> toastMessage, ex);
}

private static final String[] REQUEST_HEADER_KEYS = {
"Authorization", // Available only to logged in users.
"X-GOOG-API-FORMAT-VERSION",
"X-Goog-Visitor-Id"
};

@Nullable
private static HttpURLConnection send(ClientType clientType, String videoId,
Map<String, String> playerHeaders) {
Expand All @@ -132,10 +122,11 @@ private static HttpURLConnection send(ClientType clientType, String videoId,
connection.setConnectTimeout(HTTP_TIMEOUT_MILLISECONDS);
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);

String authHeader = playerHeaders.get("Authorization");
String visitorId = playerHeaders.get("X-Goog-Visitor-Id");
connection.setRequestProperty("Authorization", authHeader);
connection.setRequestProperty("X-Goog-Visitor-Id", visitorId);
for (String key : REQUEST_HEADER_KEYS) {
if (playerHeaders.containsKey(key)) {
connection.setRequestProperty(key, playerHeaders.get(key));
}
}

String innerTubeBody = PlayerRoutes.createInnertubeBody(clientType, videoId);
byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8);
Expand Down Expand Up @@ -195,27 +186,15 @@ private static ByteBuffer fetch(@NonNull String videoId, Map<String, String> pla
return null;
}

private final long timeFetched;
private final String videoId;
private final Future<ByteBuffer> future;

private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
Objects.requireNonNull(playerHeaders);
this.timeFetched = System.currentTimeMillis();
this.videoId = videoId;
this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playerHeaders));
}

public boolean isExpired(long now) {
final long timeSinceCreation = now - timeFetched;
if (timeSinceCreation > CACHE_RETENTION_TIME_MILLISECONDS) {
return true;
}

// Only expired if the fetch failed (API null response).
return (fetchCompleted() && getStream() == null);
}

public boolean fetchCompleted() {
return future.isDone();
}
Expand Down

0 comments on commit c342d6f

Please sign in to comment.