diff --git a/app/src/main/java/app/revanced/Test.java b/app/src/main/java/app/revanced/Test.java new file mode 100644 index 00000000..3bfdd25a --- /dev/null +++ b/app/src/main/java/app/revanced/Test.java @@ -0,0 +1,37 @@ +package app.revanced; + +import android.net.Uri; +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester; + +import java.util.Map; + +public class Test { + private static Map formats; + + public static String hook(String s) { + if (!s.contains("googlevideo")) return s; + + if (formats == null) + formats = StoryboardRendererRequester.getFormats("piKJAUwCYTo"); + + var itag = Uri.parse(s).getQueryParameter("itag"); + Logger.printInfo(() -> "Hooked itag: " + itag); + + if (itag == null) return s; + + String m; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + m = formats.values().stream().findFirst().get(); + } else { + m = null; + } + Logger.printInfo(() -> "Hooked format: " + m); + if (m == null) return s; + + var c = new Throwable().getStackTrace()[1].toString(); + Logger.printInfo(() -> "Hooked " + c + ": From " + s + " to " + m); + + return m; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java index 7909387f..8d709abe 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java @@ -1,9 +1,8 @@ package app.revanced.integrations.youtube.patches.spoof.requests; +import app.revanced.integrations.shared.Logger; import app.revanced.integrations.youtube.requests.Requester; import app.revanced.integrations.youtube.requests.Route; -import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; import org.json.JSONException; import org.json.JSONObject; @@ -21,7 +20,13 @@ final class PlayerRoutes { "playabilityStatus.status" ).compile(); - static final String ANDROID_INNER_TUBE_BODY; + static final Route.CompiledRoute GET_FORMATS = new Route( + Route.Method.POST, + "player" + + "?fields=streamingData.formats.itag,streamingData.formats.url,streamingData.adaptiveFormats.itag,streamingData.adaptiveFormats.url" + ).compile(); + + static final String WEB_INNERTUBE_BODY; static final String TV_EMBED_INNER_TUBE_BODY; /** @@ -36,9 +41,8 @@ final class PlayerRoutes { JSONObject context = new JSONObject(); JSONObject client = new JSONObject(); - client.put("clientName", "ANDROID"); - client.put("clientVersion", Utils.getAppVersionName()); - client.put("androidSdkVersion", 34); + client.put("clientName", "WEB"); + client.put("clientVersion", "2.20240111.09.00"); context.put("client", client); @@ -48,7 +52,7 @@ final class PlayerRoutes { Logger.printException(() -> "Failed to create innerTubeBody", e); } - ANDROID_INNER_TUBE_BODY = innerTubeBody.toString(); + WEB_INNERTUBE_BODY = innerTubeBody.toString(); JSONObject tvEmbedInnerTubeBody = new JSONObject(); @@ -83,12 +87,6 @@ final class PlayerRoutes { static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route) throws IOException { var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route); - connection.setRequestProperty( - "User-Agent", "com.google.android.youtube/" + - Utils.getAppVersionName() + - " (Linux; U; Android 12; GB) gzip" - ); - connection.setRequestProperty("X-Goog-Api-Format-Version", "2"); connection.setRequestProperty("Content-Type", "application/json"); connection.setUseCaches(false); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java index 31d8c3ef..eaa76763 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java @@ -2,11 +2,11 @@ package app.revanced.integrations.youtube.patches.spoof.requests; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.shared.Utils; import app.revanced.integrations.shared.settings.BaseSettings; import app.revanced.integrations.youtube.patches.spoof.StoryboardRenderer; import app.revanced.integrations.youtube.requests.Requester; -import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; import org.json.JSONException; import org.json.JSONObject; @@ -14,6 +14,8 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import static app.revanced.integrations.shared.StringRef.str; @@ -56,7 +58,7 @@ public class StoryboardRendererRequester { final byte[] innerTubeBody = requestBody.getBytes(StandardCharsets.UTF_8); - HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STORYBOARD_SPEC_RENDERER); + HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_FORMATS); connection.getOutputStream().write(innerTubeBody, 0, innerTubeBody.length); final int responseCode = connection.getResponseCode(); @@ -145,7 +147,7 @@ public class StoryboardRendererRequester { Objects.requireNonNull(videoId); var renderer = getStoryboardRendererUsingBody( - String.format(ANDROID_INNER_TUBE_BODY, videoId), false); + String.format(WEB_INNERTUBE_BODY, videoId), false); if (renderer == null) { Logger.printDebug(() -> videoId + " not available using Android client"); renderer = getStoryboardRendererUsingBody( @@ -157,4 +159,42 @@ public class StoryboardRendererRequester { return renderer; } + + + + @Nullable + public static Map getFormats(@NonNull String videoId) { + Objects.requireNonNull(videoId); + + var f = new HashMap(); + + final JSONObject playerResponse = fetchPlayerResponse(String.format(WEB_INNERTUBE_BODY, videoId), true); + try { + var formats = playerResponse.getJSONObject("streamingData").getJSONArray("formats"); + var adaptiveFormats = playerResponse.getJSONObject("streamingData").getJSONArray("adaptiveFormats"); + + for (int i = 0; i < formats.length(); i++) { + var format = formats.getJSONObject(i); + f.put(format.getInt("itag"), format.getString("url")); + } + + for (int i = 0; i < adaptiveFormats.length(); i++) { + var format = adaptiveFormats.getJSONObject(i); + f.put(format.getInt("itag"), format.getString("url")); + } + } catch (JSONException e) { + } + + return f; + } +} + +class Format { + int itag; + String url; + + public Format(int itag, String url) { + this.itag = itag; + this.url = url; + } } \ No newline at end of file