From e2a0fae0fbdecebd704574f3f4cfca2eee8c549c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ho=C3=A0ng=20Gia=20B=E1=BA=A3o?= <70064328+YT-Advanced@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:15:30 +0700 Subject: [PATCH] fix(Disable Music playback speed): Use `microformat` to fetch --- .../shared/patches/client/AppClient.java | 56 +++++++++++++-- .../patches/spoof/requests/PlayerRoutes.java | 23 +++---- .../patches/video/PlaybackSpeedPatch.java | 25 ++++--- ...ylistRequest.java => CategoryRequest.java} | 68 +++++-------------- .../extension/youtube/settings/Settings.java | 1 - .../youtube/settings/host/values/strings.xml | 7 +- .../youtube/settings/xml/revanced_prefs.xml | 1 - 7 files changed, 93 insertions(+), 88 deletions(-) rename extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/{PlaylistRequest.java => CategoryRequest.java} (69%) diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java index 80033c845a..5e2f863a46 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java @@ -7,6 +7,19 @@ import androidx.annotation.Nullable; public class AppClient { + + // WEB + private static final String CLIENT_VERSION_WEB = "2.20240726.00.00"; + private static final String DEVICE_MODEL_WEB = "Surface Book 3"; + private static final String OS_NAME_WEB = "Windows"; + private static final String OS_VERSION_WEB = "10"; + private static final String USER_AGENT_WEB = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0)" + + " Gecko/20100101" + + " Firefox/129.0"; + + // ANDROID + private static final String OS_NAME_ANDROID = "Android"; + // IOS /** * The hardcoded client version of the iOS app used for InnerTube requests with this client. @@ -18,6 +31,7 @@ public class AppClient { *

*/ private static final String CLIENT_VERSION_IOS = "19.49.5"; + private static final String DEVICE_MAKE_IOS = "Apple"; /** * The device machine id for the iPhone 16 Pro Max (iPhone17,2), used to get HDR with AV1 hardware decoding. * @@ -27,6 +41,7 @@ public class AppClient { *

*/ private static final String DEVICE_MODEL_IOS = "iPhone17,2"; + private static final String OS_NAME_IOS = "iOS"; /** * The minimum supported OS version for the iOS YouTube client is iOS 14.0. * Using an invalid OS version will use the AVC codec. @@ -71,6 +86,7 @@ public class AppClient { *

*/ private static final String CLIENT_VERSION_ANDROID_VR = "1.61.47"; + private static final String DEVICE_MAKE_ANDROID_VR = "Oculus"; /** * The device machine id for the Meta Quest 3, used to get opus codec with the Android VR client. * @@ -80,7 +96,7 @@ public class AppClient { *

*/ private static final String DEVICE_MODEL_ANDROID_VR = "Quest 3"; - private static final String OS_VERSION_ANDROID_VR = "12"; + private static final String OS_VERSION_ANDROID_VR = "12L"; /** * The SDK version for Android 12 is 31, * but for some reason the build.props for the {@code Quest 3} state that the SDK version is 32. @@ -95,7 +111,7 @@ public class AppClient { CLIENT_VERSION_ANDROID_VR + " (Linux; U; Android " + OS_VERSION_ANDROID_VR + - "; GB) gzip"; + "; eureka-user Build/SQ3A.220605.009.A1) gzip"; // ANDROID UNPLUGGED private static final String CLIENT_VERSION_ANDROID_UNPLUGGED = "8.49.0"; @@ -120,8 +136,20 @@ private AppClient() { } public enum ClientType { + WEB(1, + null, + DEVICE_MODEL_WEB, + OS_NAME_WEB, + OS_VERSION_WEB, + USER_AGENT_WEB, + null, + CLIENT_VERSION_WEB, + true + ), IOS(5, + DEVICE_MAKE_IOS, DEVICE_MODEL_IOS, + OS_NAME_IOS, OS_VERSION_IOS, USER_AGENT_IOS, null, @@ -129,7 +157,9 @@ public enum ClientType { false ), ANDROID_VR(28, + DEVICE_MAKE_ANDROID_VR, DEVICE_MODEL_ANDROID_VR, + OS_NAME_ANDROID, OS_VERSION_ANDROID_VR, USER_AGENT_ANDROID_VR, ANDROID_SDK_VERSION_ANDROID_VR, @@ -137,16 +167,19 @@ public enum ClientType { true ), ANDROID_UNPLUGGED(29, + null, DEVICE_MODEL_ANDROID_UNPLUGGED, + OS_NAME_ANDROID, OS_VERSION_ANDROID_UNPLUGGED, USER_AGENT_ANDROID_UNPLUGGED, ANDROID_SDK_VERSION_ANDROID_UNPLUGGED, CLIENT_VERSION_ANDROID_UNPLUGGED, true ), - IOS_MUSIC( - 26, + IOS_MUSIC(26, + DEVICE_MAKE_IOS, DEVICE_MODEL_IOS, + OS_NAME_IOS, OS_VERSION_IOS, USER_AGENT_IOS_MUSIC, null, @@ -162,11 +195,22 @@ public enum ClientType { public final String clientName; + /** + * Device manufacturer. + */ + @Nullable + public final String deviceMake; + /** * Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model) */ public final String deviceModel; + /** + * Device OS name. + */ + public final String osName; + /** * Device OS version. */ @@ -195,7 +239,9 @@ public enum ClientType { public final boolean canLogin; ClientType(int id, + String deviceMake, String deviceModel, + String osName, String osVersion, String userAgent, @Nullable String androidSdkVersion, @@ -204,8 +250,10 @@ public enum ClientType { ) { this.id = id; this.clientName = name(); + this.deviceMake = deviceMake; this.deviceModel = deviceModel; this.clientVersion = clientVersion; + this.osName = osName; this.osVersion = osVersion; this.androidSdkVersion = androidSdkVersion; this.userAgent = userAgent; diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java index f42e5443c9..1983bbeb43 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java @@ -14,10 +14,10 @@ @SuppressWarnings({"ExtractMethodRecommender", "deprecation"}) public final class PlayerRoutes { - public static final Route.CompiledRoute GET_PLAYLIST_PAGE = new Route( + public static final Route.CompiledRoute GET_CATEGORY = new Route( Route.Method.POST, - "next" + - "?fields=contents.singleColumnWatchNextResults.playlist.playlist" + "player" + + "?fields=microformat.playerMicroformatRenderer" ).compile(); static final Route.CompiledRoute GET_STREAMING_DATA = new Route( Route.Method.POST, @@ -38,21 +38,21 @@ private PlayerRoutes() { } public static String createInnertubeBody(ClientType clientType) { - return createInnertubeBody(clientType, false); - } - - public static String createInnertubeBody(ClientType clientType, boolean playlistId) { JSONObject innerTubeBody = new JSONObject(); try { JSONObject client = new JSONObject(); client.put("clientName", clientType.clientName); client.put("clientVersion", clientType.clientVersion); - client.put("deviceModel", clientType.deviceModel); - client.put("osVersion", clientType.osVersion); if (clientType.androidSdkVersion != null) { client.put("androidSdkVersion", clientType.androidSdkVersion); } + if (clientType.deviceMake != null) { + client.put("deviceMake", clientType.deviceMake); + } + client.put("deviceModel", clientType.deviceModel); + client.put("osName", clientType.osName); + client.put("osVersion", clientType.osVersion); client.put("hl", LOCALE_LANGUAGE); JSONObject context = new JSONObject(); @@ -62,9 +62,6 @@ public static String createInnertubeBody(ClientType clientType, boolean playlist innerTubeBody.put("contentCheckOk", true); innerTubeBody.put("racyCheckOk", true); innerTubeBody.put("videoId", "%s"); - if (playlistId) { - innerTubeBody.put("playlistId", "%s"); - } } catch (JSONException e) { Logger.printException(() -> "Failed to create innerTubeBody", e); } @@ -88,4 +85,4 @@ public static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.Compi connection.setReadTimeout(CONNECTION_TIMEOUT_MILLISECONDS); return connection; } -} \ No newline at end of file +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java index 99b658d0ac..8db52e1958 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java @@ -10,7 +10,7 @@ import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.patches.utils.PatchStatus; -import app.revanced.extension.youtube.patches.video.requests.PlaylistRequest; +import app.revanced.extension.youtube.patches.video.requests.CategoryRequest; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.VideoInformation; import app.revanced.extension.youtube.whitelist.Whitelist; @@ -50,7 +50,7 @@ public static void fetchPlaylistData(@NonNull String videoId, boolean isShortAnd return; } - PlaylistRequest.fetchRequestIfNeeded(videoId); + CategoryRequest.fetchRequestIfNeeded(videoId); } catch (Exception ex) { Logger.printException(() -> "fetchPlaylistData failure", ex); } @@ -114,23 +114,22 @@ public static void userSelectedPlaybackSpeed(float playbackSpeed) { } private static float getDefaultPlaybackSpeed(@NonNull String channelId, @Nullable String videoId) { - return (Settings.DISABLE_DEFAULT_PLAYBACK_SPEED_LIVE.get() && isLiveStream) || - Whitelist.isChannelWhitelistedPlaybackSpeed(channelId) || - getPlaylistData(videoId) - ? 1.0f - : Settings.DEFAULT_PLAYBACK_SPEED.get(); + return (isLiveStream || + Whitelist.isChannelWhitelistedPlaybackSpeed(channelId) || + getCategory(videoId) + ) ? 1.0f : Settings.DEFAULT_PLAYBACK_SPEED.get(); } - private static boolean getPlaylistData(@Nullable String videoId) { + private static boolean getCategory(@Nullable String videoId) { if (Settings.DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC.get() && videoId != null) { try { - PlaylistRequest request = PlaylistRequest.getRequestForVideoId(videoId); - final boolean isPlaylist = request != null && BooleanUtils.toBoolean(request.getStream()); - Logger.printDebug(() -> "isPlaylist: " + isPlaylist); + CategoryRequest request = CategoryRequest.getRequestForVideoId(videoId); + final boolean isMusic = request != null && BooleanUtils.toBoolean(request.getStream()); + Logger.printDebug(() -> "isMusic: " + isMusic); - return isPlaylist; + return isMusic; } catch (Exception ex) { - Logger.printException(() -> "getPlaylistData failure", ex); + Logger.printException(() -> "getCategoryData failure", ex); } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/PlaylistRequest.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/CategoryRequest.java similarity index 69% rename from extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/PlaylistRequest.java rename to extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/CategoryRequest.java index b7c69cd0a1..9b2591126e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/PlaylistRequest.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/CategoryRequest.java @@ -1,6 +1,6 @@ package app.revanced.extension.youtube.patches.video.requests; -import static app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_PLAYLIST_PAGE; +import static app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_CATEGORY; import android.annotation.SuppressLint; @@ -16,7 +16,6 @@ import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; @@ -29,9 +28,8 @@ import app.revanced.extension.shared.requests.Requester; import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.Utils; -import app.revanced.extension.youtube.shared.VideoInformation; -public class PlaylistRequest { +public class CategoryRequest { /** * How long to keep fetches until they are expired. @@ -41,7 +39,7 @@ public class PlaylistRequest { private static final long MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; // 20 seconds @GuardedBy("itself") - private static final Map cache = new HashMap<>(); + private static final Map cache = new HashMap<>(); @SuppressLint("ObsoleteSdkInt") public static void fetchRequestIfNeeded(@Nullable String videoId) { @@ -56,13 +54,13 @@ public static void fetchRequestIfNeeded(@Nullable String videoId) { }); if (!cache.containsKey(videoId)) { - cache.put(videoId, new PlaylistRequest(videoId)); + cache.put(videoId, new CategoryRequest(videoId)); } } } @Nullable - public static PlaylistRequest getRequestForVideoId(@Nullable String videoId) { + public static CategoryRequest getRequestForVideoId(@Nullable String videoId) { synchronized (cache) { return cache.get(videoId); } @@ -79,17 +77,13 @@ private static JSONObject send(ClientType clientType, String videoId) { final long startTime = System.currentTimeMillis(); String clientTypeName = clientType.name(); - Logger.printDebug(() -> "Fetching playlist request for: " + videoId + " using client: " + clientTypeName); + Logger.printDebug(() -> "Fetching category request for: " + videoId + " using client: " + clientTypeName); try { - HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_PLAYLIST_PAGE, clientType); - - String innerTubeBody = String.format( - Locale.ENGLISH, - PlayerRoutes.createInnertubeBody(clientType, true), - videoId, - "RD" + videoId - ); + HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_CATEGORY, clientType); + + String innerTubeBody = String.format(PlayerRoutes.createInnertubeBody(clientType), videoId); + byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8); connection.setFixedLengthStreamingMode(requestBody.length); connection.getOutputStream().write(requestBody); @@ -114,41 +108,15 @@ private static JSONObject send(ClientType clientType, String videoId) { } private static Boolean fetch(@NonNull String videoId) { - final ClientType clientType = ClientType.ANDROID_VR; - final JSONObject playlistJson = send(clientType, videoId); - if (playlistJson != null) { + final JSONObject microFormatJson = send(ClientType.WEB, videoId); + if (microformatJson != null) { try { - final JSONObject singleColumnWatchNextResultsJsonObject = playlistJson - .getJSONObject("contents") - .getJSONObject("singleColumnWatchNextResults"); - - if (!singleColumnWatchNextResultsJsonObject.has("playlist")) { - return false; - } - - final JSONObject playlistJsonObject = singleColumnWatchNextResultsJsonObject - .getJSONObject("playlist") - .getJSONObject("playlist"); - - final Object currentStreamObject = playlistJsonObject - .getJSONArray("contents") - .get(0); - - if (!(currentStreamObject instanceof JSONObject currentStreamJsonObject)) { - return false; - } - - final JSONObject watchEndpointJsonObject = currentStreamJsonObject - .getJSONObject("playlistPanelVideoRenderer") - .getJSONObject("navigationEndpoint") - .getJSONObject("watchEndpoint"); - - Logger.printDebug(() -> "watchEndpoint: " + watchEndpointJsonObject); - - return watchEndpointJsonObject.has("playerParams") && - VideoInformation.isMixPlaylistsOpenedByUser(watchEndpointJsonObject.getString("playerParams")); + return microFormatJson.getJSONObject("microformat") + .getJSONObject("playerMicroformatRenderer") + .getString("category") + .equals("Music"); } catch (JSONException e) { - Logger.printDebug(() -> "Fetch failed while processing response data for response: " + playlistJson); + Logger.printDebug(() -> "Fetch failed while processing response data for response: " + microFormatJson); } } @@ -162,7 +130,7 @@ private static Boolean fetch(@NonNull String videoId) { private final String videoId; private final Future future; - private PlaylistRequest(String videoId) { + private CategoryRequest(String videoId) { this.timeFetched = System.currentTimeMillis(); this.videoId = videoId; this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId)); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 466fd03ebf..d1a7601e86 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -523,7 +523,6 @@ public class Settings extends BaseSettings { public static final IntegerSetting DEFAULT_VIDEO_QUALITY_MOBILE = new IntegerSetting("revanced_default_video_quality_mobile", -2); public static final IntegerSetting DEFAULT_VIDEO_QUALITY_WIFI = new IntegerSetting("revanced_default_video_quality_wifi", -2); public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE, true); - public static final BooleanSetting DISABLE_DEFAULT_PLAYBACK_SPEED_LIVE = new BooleanSetting("revanced_disable_default_playback_speed_live", TRUE); public static final BooleanSetting ENABLE_CUSTOM_PLAYBACK_SPEED = new BooleanSetting("revanced_enable_custom_playback_speed", FALSE, true); public static final BooleanSetting CUSTOM_PLAYBACK_SPEED_MENU_TYPE = new BooleanSetting("revanced_custom_playback_speed_menu_type", FALSE, parent(ENABLE_CUSTOM_PLAYBACK_SPEED)); public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", "0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.25\n2.5", true, parent(ENABLE_CUSTOM_PLAYBACK_SPEED)); diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index b6c9bc6a77..4c2a2d746d 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -1487,9 +1487,6 @@ Limitations: Disable HDR video HDR video is disabled. HDR video is enabled. - Disable playback speed for live streams - Default playback speed is disabled for live streams. - Default playback speed is enabled for live streams. Enable custom playback speed Custom playback speed is enabled. Custom playback speed is disabled. @@ -1514,9 +1511,7 @@ Limitations: Old video quality menu is shown. Old video quality menu is not shown. Disable playback speed for music - "Default playback speed is disabled for music. - -Limitation: This setting may not apply to videos that do not include the 'Listen on YouTube Music' banner." + Default playback speed is disabled for music. Default playback speed is enabled for music. Enable Shorts default playback speed Default playback speed applies to Shorts. diff --git a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml index b37c2b4fa0..b7b5f5b30f 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -676,7 +676,6 @@ -