@@ -81,10 +81,16 @@ private YoutubeParsingHelper() {
8181 }
8282
8383 /**
84- * The base URL of requests of the {@code WEB} client to the InnerTube internal API
84+ * The base URL of requests of the {@code WEB} clients to the InnerTube internal API.
8585 */
8686 public static final String YOUTUBEI_V1_URL = "https://www.youtube.com/youtubei/v1/" ;
8787
88+ /**
89+ * The base URL of requests of non-web clients to the InnerTube internal API.
90+ */
91+ public static final String YOUTUBEI_V1_GAPIS_URL =
92+ "https://youtubei.googleapis.com/youtubei/v1/" ;
93+
8894 /**
8995 * A parameter to disable pretty-printed response of InnerTube requests, to reduce response
9096 * sizes.
@@ -114,6 +120,26 @@ private YoutubeParsingHelper() {
114120 public static final String CPN = "cpn" ;
115121 public static final String VIDEO_ID = "videoId" ;
116122
123+ /**
124+ * A parameter sent by official clients named {@code contentCheckOk}.
125+ *
126+ * <p>
127+ * Setting it to {@code true} allows us to get streaming data on videos with a warning about
128+ * what the sensible content they contain.
129+ * </p>
130+ */
131+ public static final String CONTENT_CHECK_OK = "contentCheckOk" ;
132+
133+ /**
134+ * A parameter which may be send by official clients named {@code racyCheckOk}.
135+ *
136+ * <p>
137+ * What this parameter does is not really known, but it seems to be linked to sensitive
138+ * contents such as age-restricted content.
139+ * </p>
140+ */
141+ public static final String RACY_CHECK_OK = "racyCheckOk" ;
142+
117143 /**
118144 * The client version for InnerTube requests with the {@code WEB} client, used as the last
119145 * fallback if the extraction of the real one failed.
@@ -150,6 +176,12 @@ private YoutubeParsingHelper() {
150176 */
151177 private static final String MOBILE_YOUTUBE_CLIENT_VERSION = "17.10.35" ;
152178
179+ /**
180+ * The hardcoded client version of the Android app used for InnerTube requests with this
181+ * client.
182+ */
183+ private static final String TVHTML5_SIMPLY_EMBED_CLIENT_VERSION = "2.0" ;
184+
153185 private static String clientVersion ;
154186 private static String key ;
155187
@@ -664,6 +696,9 @@ public static String getClientVersion() throws IOException, ExtractionException
664696 return clientVersion ;
665697 }
666698
699+ // Always extract latest client version, by trying first to extract it from the JavaScript
700+ // service worker, then from HTML search results page as a fallback, to prevent
701+ // fingerprinting based on the client version used
667702 try {
668703 extractClientVersionAndKeyFromSwJs ();
669704 } catch (final Exception e ) {
@@ -674,6 +709,7 @@ public static String getClientVersion() throws IOException, ExtractionException
674709 return clientVersion ;
675710 }
676711
712+ // Fallback to the hardcoded one if it's valid
677713 if (areHardcodedClientVersionAndKeyValid ()) {
678714 clientVersion = HARDCODED_CLIENT_VERSION ;
679715 return clientVersion ;
@@ -690,6 +726,9 @@ public static String getKey() throws IOException, ExtractionException {
690726 return key ;
691727 }
692728
729+ // Always extract the key used by the webiste, by trying first to extract it from the
730+ // JavaScript service worker, then from HTML search results page as a fallback, to prevent
731+ // fingerprinting based on the key and/or invalid key issues
693732 try {
694733 extractClientVersionAndKeyFromSwJs ();
695734 } catch (final Exception e ) {
@@ -700,6 +739,7 @@ public static String getKey() throws IOException, ExtractionException {
700739 return key ;
701740 }
702741
742+ // Fallback to the hardcoded one if it's valid
703743 if (areHardcodedClientVersionAndKeyValid ()) {
704744 key = HARDCODED_KEY ;
705745 return key ;
@@ -1058,8 +1098,8 @@ private static JsonObject getMobilePostResponse(
10581098 headers .put ("User-Agent" , Collections .singletonList (userAgent ));
10591099 headers .put ("X-Goog-Api-Format-Version" , Collections .singletonList ("2" ));
10601100
1061- final String baseEndpointUrl = "https://youtubei.googleapis.com/youtubei/v1/ " + endpoint
1062- + "?key=" + innerTubeApiKey + DISABLE_PRETTY_PRINT_PARAMETER ;
1101+ final String baseEndpointUrl = YOUTUBEI_V1_GAPIS_URL + endpoint + "?key= " + innerTubeApiKey
1102+ + DISABLE_PRETTY_PRINT_PARAMETER ;
10631103
10641104 final Response response = getDownloader ().post (isNullOrEmpty (endPartOfUrlRequest )
10651105 ? baseEndpointUrl : baseEndpointUrl + endPartOfUrlRequest ,
@@ -1146,110 +1186,30 @@ public static JsonBuilder<JsonObject> prepareIosMobileJsonBuilder(
11461186 }
11471187
11481188 @ Nonnull
1149- public static JsonBuilder <JsonObject > prepareDesktopEmbedVideoJsonBuilder (
1150- @ Nonnull final Localization localization ,
1151- @ Nonnull final ContentCountry contentCountry ,
1152- @ Nonnull final String videoId ) throws IOException , ExtractionException {
1153- // @formatter:off
1154- return JsonObject .builder ()
1155- .object ("context" )
1156- .object ("client" )
1157- .value ("hl" , localization .getLocalizationCode ())
1158- .value ("gl" , contentCountry .getCountryCode ())
1159- .value ("clientName" , "WEB" )
1160- .value ("clientVersion" , getClientVersion ())
1161- .value ("clientScreen" , "EMBED" )
1162- .value ("originalUrl" , "https://www.youtube.com" )
1163- .value ("platform" , "DESKTOP" )
1164- .end ()
1165- .object ("thirdParty" )
1166- .value ("embedUrl" , "https://www.youtube.com/watch?v=" + videoId )
1167- .end ()
1168- .object ("request" )
1169- .array ("internalExperimentFlags" )
1170- .end ()
1171- .value ("useSsl" , true )
1172- .end ()
1173- .object ("user" )
1174- // TO DO: provide a way to enable restricted mode with:
1175- // .value("enableSafetyMode", boolean)
1176- .value ("lockedSafetyMode" , false )
1177- .end ()
1178- .end ();
1179- // @formatter:on
1180- }
1181-
1182- @ Nonnull
1183- public static JsonBuilder <JsonObject > prepareAndroidMobileEmbedVideoJsonBuilder (
1184- @ Nonnull final Localization localization ,
1185- @ Nonnull final ContentCountry contentCountry ,
1186- @ Nonnull final String videoId ,
1187- @ Nonnull final String contentPlaybackNonce ) {
1188- // @formatter:off
1189- return JsonObject .builder ()
1190- .object ("context" )
1191- .object ("client" )
1192- .value ("clientName" , "ANDROID" )
1193- .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
1194- .value ("clientScreen" , "EMBED" )
1195- .value ("platform" , "MOBILE" )
1196- .value ("hl" , localization .getLocalizationCode ())
1197- .value ("gl" , contentCountry .getCountryCode ())
1198- .end ()
1199- .object ("thirdParty" )
1200- .value ("embedUrl" , "https://www.youtube.com/watch?v=" + videoId )
1201- .end ()
1202- .object ("request" )
1203- .array ("internalExperimentFlags" )
1204- .end ()
1205- .value ("useSsl" , true )
1206- .end ()
1207- .object ("user" )
1208- // TO DO: provide a way to enable restricted mode with:
1209- // .value("enableSafetyMode", boolean)
1210- .value ("lockedSafetyMode" , false )
1211- .end ()
1212- .end ()
1213- .value (CPN , contentPlaybackNonce )
1214- .value (VIDEO_ID , videoId );
1215- // @formatter:on
1216- }
1217-
1218- @ Nonnull
1219- public static JsonBuilder <JsonObject > prepareIosMobileEmbedVideoJsonBuilder (
1189+ public static JsonBuilder <JsonObject > prepareTvHtml5EmbedJsonBuilder (
12201190 @ Nonnull final Localization localization ,
12211191 @ Nonnull final ContentCountry contentCountry ,
1222- @ Nonnull final String videoId ,
1223- @ Nonnull final String contentPlaybackNonce ) {
1224- // @formatter:off
1192+ @ Nonnull final String videoId ) {
1193+ // @formatter:off
12251194 return JsonObject .builder ()
12261195 .object ("context" )
12271196 .object ("client" )
1228- .value ("clientName" , "IOS " )
1229- .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
1197+ .value ("clientName" , "TVHTML5_SIMPLY_EMBEDDED_PLAYER " )
1198+ .value ("clientVersion" , TVHTML5_SIMPLY_EMBED_CLIENT_VERSION )
12301199 .value ("clientScreen" , "EMBED" )
1231- // Device model is required to get 60fps streams
1232- .value ("deviceModel" , IOS_DEVICE_MODEL )
1233- .value ("platform" , "MOBILE" )
1200+ .value ("platform" , "TV" )
12341201 .value ("hl" , localization .getLocalizationCode ())
12351202 .value ("gl" , contentCountry .getCountryCode ())
12361203 .end ()
12371204 .object ("thirdParty" )
12381205 .value ("embedUrl" , "https://www.youtube.com/watch?v=" + videoId )
12391206 .end ()
1240- .object ("request" )
1241- .array ("internalExperimentFlags" )
1242- .end ()
1243- .value ("useSsl" , true )
1244- .end ()
12451207 .object ("user" )
12461208 // TO DO: provide a way to enable restricted mode with:
12471209 // .value("enableSafetyMode", boolean)
12481210 .value ("lockedSafetyMode" , false )
12491211 .end ()
1250- .end ()
1251- .value (CPN , contentPlaybackNonce )
1252- .value (VIDEO_ID , videoId );
1212+ .end ();
12531213 // @formatter:on
12541214 }
12551215
@@ -1259,30 +1219,24 @@ public static byte[] createDesktopPlayerBody(
12591219 @ Nonnull final ContentCountry contentCountry ,
12601220 @ Nonnull final String videoId ,
12611221 @ Nonnull final String sts ,
1262- final boolean isEmbedClientScreen ,
1222+ final boolean isTvHtml5DesktopJsonBuilder ,
12631223 @ Nonnull final String contentPlaybackNonce ) throws IOException , ExtractionException {
12641224 // @formatter:off
1265- return JsonWriter .string ((isEmbedClientScreen
1266- ? prepareDesktopEmbedVideoJsonBuilder (localization , contentCountry ,
1267- videoId )
1225+ return JsonWriter .string ((isTvHtml5DesktopJsonBuilder
1226+ ? prepareTvHtml5EmbedJsonBuilder (localization , contentCountry , videoId )
12681227 : prepareDesktopJsonBuilder (localization , contentCountry ))
12691228 .object ("playbackContext" )
12701229 .object ("contentPlaybackContext" )
1271- // Some parameters which are sent by the official WEB client (probably some
1272- // of them are not useful)
1273- .value ("currentUrl" , "/watch?v=" + videoId )
1274- .value ("vis" , 0 )
1275- .value ("splay" , false )
1276- .value ("autoCaptionsDefaultOn" , false )
1277- .value ("autonavState" , "STATE_NONE" )
1278- .value ("html5Preference" , "HTML5_PREF_WANTS" )
1230+ // Some parameters which are sent by the official WEB client in player
1231+ // requests, which seems to avoid throttling on streams from it
12791232 .value ("signatureTimestamp" , sts )
12801233 .value ("referer" , "https://www.youtube.com/watch?v=" + videoId )
1281- .value ("lactMilliseconds" , "-1" )
12821234 .end ()
12831235 .end ()
12841236 .value (CPN , contentPlaybackNonce )
12851237 .value (VIDEO_ID , videoId )
1238+ .value (CONTENT_CHECK_OK , true )
1239+ .value (RACY_CHECK_OK , true )
12861240 .done ())
12871241 .getBytes (StandardCharsets .UTF_8 );
12881242 // @formatter:on
0 commit comments