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

Commit

Permalink
fix(Spoof Player Parameter): Fail to fetch storyboard from Trailer of…
Browse files Browse the repository at this point in the history
… Premieres Video
  • Loading branch information
YT-Advanced committed Feb 5, 2024
1 parent 629eb05 commit f45c888
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,22 @@ public final class PlayerRoutes {
"?fields=videoDetails.channelId," +
"videoDetails.author"
).compile();

public static final Route.CompiledRoute GET_STORYBOARD_SPEC_RENDERER = new Route(
Route.Method.POST,
"player" +
"?fields=storyboards.playerStoryboardSpecRenderer," +
"storyboards.playerLiveStoryboardSpecRenderer," +
"playabilityStatus.status"
"playabilityStatus.status," +
"playabilityStatus.errorScreen"
).compile();

public static final String WEB_INNER_TUBE_BODY;
public static final String ANDROID_INNER_TUBE_BODY;
public static final String TV_EMBED_INNER_TUBE_BODY;

private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";

/**
* TCP connection and HTTP read timeout
*/
Expand All @@ -52,7 +58,6 @@ public final class PlayerRoutes {
} catch (JSONException e) {
LogHelper.printException(() -> "Failed to create innerTubeBody", e);
}

ANDROID_INNER_TUBE_BODY = innerTubeBody.toString();

JSONObject tvEmbedInnerTubeBody = new JSONObject();
Expand All @@ -77,8 +82,26 @@ public final class PlayerRoutes {
} catch (JSONException e) {
LogHelper.printException(() -> "Failed to create tvEmbedInnerTubeBody", e);
}

TV_EMBED_INNER_TUBE_BODY = tvEmbedInnerTubeBody.toString();

JSONObject webInnerTubeBody = new JSONObject();

try {
JSONObject context = new JSONObject();

JSONObject client = new JSONObject();
client.put("clientName", "WEB");
client.put("clientVersion", "2.20240201.01.00");
client.put("clientScreen", "WATCH");

context.put("client", client);

webInnerTubeBody.put("context", context);
webInnerTubeBody.put("videoId", "%s");
} catch (JSONException e) {
LogHelper.printException(() -> "Failed to create webInnerTubeBody", e);
}
WEB_INNER_TUBE_BODY = webInnerTubeBody.toString();
}

private PlayerRoutes() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.ANDROID_INNER_TUBE_BODY;
import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.GET_STORYBOARD_SPEC_RENDERER;
import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.TV_EMBED_INNER_TUBE_BODY;
import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.WEB_INNER_TUBE_BODY;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -74,14 +75,14 @@ private static JSONObject fetchPlayerResponse(@NonNull String requestBody, boole
return null;
}

private static boolean isPlayabilityStatusOk(@NonNull JSONObject playerResponse) {
private static String GetPlayabilityStatus(@NonNull JSONObject playerResponse) {
try {
return playerResponse.getJSONObject("playabilityStatus").getString("status").equals("OK");
return playerResponse.getJSONObject("playabilityStatus").getString("status");
} catch (JSONException e) {
LogHelper.printDebug(() -> "Failed to get playabilityStatus for response: " + playerResponse);
}

return false;
// Prevent NullPointerException
return "";
}

/**
Expand All @@ -92,18 +93,48 @@ private static boolean isPlayabilityStatusOk(@NonNull JSONObject playerResponse)
*/
@Nullable
private static StoryboardRenderer getStoryboardRendererUsingBody(@NonNull String innerTubeBody,
boolean showToastOnIOException) {
boolean showToastOnIOException,
@NonNull String videoId) {
final JSONObject playerResponse = fetchPlayerResponse(innerTubeBody, showToastOnIOException);
if (playerResponse != null && isPlayabilityStatusOk(playerResponse))
if (playerResponse == null)
return null;

final String playabilityStatus = GetPlayabilityStatus(playerResponse);

if (playabilityStatus.equals("OK"))
return getStoryboardRendererUsingResponse(playerResponse);

// Get the StoryboardRenderer from Premieres Video.
// In Android client, YouTube used weird base64-like encoding for PlayerResponse.
// So additational fetching with WEB client is required for getting unserialized ones.
if (playabilityStatus.equals("LIVE_STREAM_OFFLINE"))
return getTrailerStoryboardRenderer(videoId);
return null;
}

@Nullable
private static StoryboardRenderer getTrailerStoryboardRenderer(@NonNull String videoId) {
try {
final JSONObject playerResponse = fetchPlayerResponse(
String.format(WEB_INNER_TUBE_BODY, videoId), false);

JSONObject unserializedPlayerResponse = playerResponse.getJSONObject("playabilityStatus")
.getJSONObject("errorScreen").getJSONObject("ypcTrailerRenderer").getJSONObject("unserializedPlayerResponse");

if (GetPlayabilityStatus(unserializedPlayerResponse).equals("OK"))
return getStoryboardRendererUsingResponse(unserializedPlayerResponse);
return null;
} catch (JSONException e) {
LogHelper.printException(() -> "Failed to get unserializedPlayerResponse", e);
}

return null;
}

@Nullable
private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) {
try {
LogHelper.printDebug(() -> "Parsing response: " + playerResponse);
LogHelper.printDebug(() -> "Parsing storyboardRenderer from response: " + playerResponse);
if (!playerResponse.has("storyboards")) {
LogHelper.printDebug(() -> "Using empty storyboard");
return emptyStoryboard;
Expand Down Expand Up @@ -138,16 +169,16 @@ public static StoryboardRenderer getStoryboardRenderer(@NonNull String videoId)
Objects.requireNonNull(videoId);

StoryboardRenderer renderer = getStoryboardRendererUsingBody(
String.format(ANDROID_INNER_TUBE_BODY, videoId), false);
String.format(ANDROID_INNER_TUBE_BODY, videoId), false, videoId);
if (renderer == null) {
LogHelper.printDebug(() -> videoId + " not available using Android client");
renderer = getStoryboardRendererUsingBody(
String.format(TV_EMBED_INNER_TUBE_BODY, videoId, videoId), true);
String.format(TV_EMBED_INNER_TUBE_BODY, videoId, videoId), true, videoId);
if (renderer == null) {
LogHelper.printDebug(() -> videoId + " not available using TV embedded client");
}
}

return renderer;
}
}
}

0 comments on commit f45c888

Please sign in to comment.