Skip to content

Commit

Permalink
fix(YouTube - Client spoof): Restore seekbar thumbnails
Browse files Browse the repository at this point in the history
This works around the issue, but causes seekbar thumbnails to be in low quality.
  • Loading branch information
oSumAtrIX committed Sep 25, 2023
1 parent bfae6b5 commit 978f630
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package app.revanced.integrations.patches;
package app.revanced.integrations.patches.spoof;

import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import app.revanced.integrations.patches.VideoInformation;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.shared.PlayerType;
import app.revanced.integrations.utils.LogHelper;

import static app.revanced.integrations.patches.spoof.requests.StoryBoardRendererRequester.fetchStoryboardsRenderer;
import static app.revanced.integrations.utils.ReVancedUtils.containsAny;

/** @noinspection unused*/
Expand Down Expand Up @@ -40,6 +39,8 @@ public class SpoofSignaturePatch {

private static boolean isPlayingShorts;

private static String storyboardRendererSpec = "";

/**
* Injection point.
*
Expand All @@ -63,6 +64,7 @@ public static String spoofParameter(String parameters) {
// This will cause playback issues in the feed, but it's better than manipulating the history.
parameters;

fetchStoryboardsRenderer(VideoInformation.getVideoId());
return INCOGNITO_PARAMETERS;
}

Expand All @@ -75,16 +77,15 @@ public static boolean getSeekbarThumbnailOverrideValue() {

/**
* Injection point.
*
* @param view seekbar thumbnail view. Includes both shorts and regular videos.
*/
public static void seekbarImageViewCreated(ImageView view) {
if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) return;
if (isPlayingShorts) return;

view.setVisibility(View.GONE);
// Also hide the border around the thumbnail (otherwise a 1 pixel wide bordered frame is visible).
ViewGroup parentLayout = (ViewGroup) view.getParent();
parentLayout.setPadding(0, 0, 0, 0);
public static String getStoryboardRendererSpec() {
return storyboardRendererSpec;
}

public static void setStoryboardRendererSpec(String newlyLoadedStoryboardRendererSpec) {
if (storyboardRendererSpec.equals(newlyLoadedStoryboardRendererSpec))
return;

storyboardRendererSpec = newlyLoadedStoryboardRendererSpec;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package app.revanced.integrations.patches.spoof.requests;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.integrations.patches.spoof.SpoofSignaturePatch;
import app.revanced.integrations.requests.Requester;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
import org.json.JSONObject;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;

public class StoryBoardRendererRequester {
private static final String INNER_TUBE_BODY =
"{" +
"\"context\": " +
"{" +
"\"client\": " +
"{ " +
"\"clientName\": \"ANDROID\", \"clientVersion\": \"18.37.36\", \"platform\": \"MOBILE\", " +
"\"osName\": \"Android\", \"osVersion\": \"12\", \"androidSdkVersion\": 31 " +
"} " +
"}, " +
"\"videoId\": \"%s\"" +
"}";

private StoryBoardRendererRequester() {
}

// TODO: Find a way to increase the quality of SeekBar thumbnail previews
public static void fetchStoryboardsRenderer(@NonNull String videoId) {
ReVancedUtils.verifyOffMainThread();

try {
final byte[] innerTubeBody = String.format(INNER_TUBE_BODY, videoId).getBytes(StandardCharsets.UTF_8);

HttpURLConnection connection = StoryBoardRendererRoutes.getPlayerResponseConnectionFromRoute();
connection.getOutputStream().write(innerTubeBody, 0, innerTubeBody.length);

final int responseCode = connection.getResponseCode();

if (responseCode == 200) {
final JSONObject playerResponse = Requester.parseJSONObject(connection);

final JSONObject storyboards = playerResponse.getJSONObject("storyboards");
final String storyboardsRendererTag = storyboards.has("playerLiveStoryboardSpecRenderer")
? "playerLiveStoryboardSpecRenderer"
: "playerStoryboardSpecRenderer";
final JSONObject storyboardsRenderer = storyboards.getJSONObject(storyboardsRendererTag);
final String storyboardsRendererSpec = storyboardsRenderer.getString("spec");

SpoofSignaturePatch.setStoryboardRendererSpec(storyboardsRendererSpec);
LogHelper.printDebug(() -> "StoryBoard renderer spec: " + storyboardsRendererSpec);

} else {
handleConnectionError("API not available: " + responseCode, null);
}
connection.disconnect();
} catch (SocketTimeoutException ex) {
handleConnectionError("API timed out", ex);
} catch (IOException ex) {
handleConnectionError(String.format("Failed to fetch StoryBoard URL (%s)", ex.getMessage()), ex);
} catch (Exception ex) {
handleConnectionError("Failed to fetch StoryBoard URL", ex);
}
}

private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) {
if (ex != null)
LogHelper.printException(() -> toastMessage, ex);
else
LogHelper.printException(() -> toastMessage);

SpoofSignaturePatch.setStoryboardRendererSpec("");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package app.revanced.integrations.patches.spoof.requests;

import app.revanced.integrations.requests.Requester;
import app.revanced.integrations.requests.Route;

import java.io.IOException;
import java.net.HttpURLConnection;

/** @noinspection unused*/
public final class StoryBoardRendererRoutes {
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";
private static final String YT_API_KEY = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w";

static final Route GET_PLAYER_RESPONSE_BODY = new Route(Route.Method.POST, "player?key={api_key}");

private StoryBoardRendererRoutes() {
}

public static HttpURLConnection getPlayerResponseConnectionFromRoute() throws IOException {
var connection = Requester.getConnectionFromRoute(YT_API_URL, GET_PLAYER_RESPONSE_BODY, YT_API_KEY);
connection.setRequestProperty("User-Agent", "com.google.android.youtube/18.37.36 (Linux; U; Android 12; GB) gzip");
connection.setRequestProperty("X-Goog-Api-Format-Version", "2");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept-Language", "en-GB, en;q=0.9");
connection.setRequestProperty("Pragma", "no-cache");
connection.setRequestProperty("Cache-Control", "no-cache");
connection.setUseCaches(false);
connection.setDoOutput(true);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
return connection;
}

}

0 comments on commit 978f630

Please sign in to comment.