@@ -80,6 +80,9 @@ public final class YoutubeParsingHelper {
8080 private YoutubeParsingHelper () {
8181 }
8282
83+ /**
84+ * The base URL of requests of the {@code WEB} client to the InnerTube internal API
85+ */
8386 public static final String YOUTUBEI_V1_URL = "https://www.youtube.com/youtubei/v1/" ;
8487
8588 /**
@@ -91,14 +94,60 @@ private YoutubeParsingHelper() {
9194 * </p>
9295 **/
9396 public static final String DISABLE_PRETTY_PRINT_PARAMETER = "&prettyPrint=false" ;
97+
98+ /**
99+ * A parameter sent by official clients named {@code contentPlaybackNonce}.
100+ *
101+ * <p>
102+ * It is sent by official clients on videoplayback requests, and by all clients (except the
103+ * {@code WEB} one to the player requests.
104+ * </p>
105+ *
106+ * <p>
107+ * It is composed of 16 characters which are generated from
108+ * {@link #CONTENT_PLAYBACK_NONCE_ALPHABET this alphabet}, with the use of strong random
109+ * values.
110+ * </p>
111+ *
112+ * @see #generateContentPlaybackNonce()
113+ */
94114 public static final String CPN = "cpn" ;
95115 public static final String VIDEO_ID = "videoId" ;
96116
117+ /**
118+ * The client version for InnerTube requests with the {@code WEB} client, used as the last
119+ * fallback if the extraction of the real one failed.
120+ *
121+ * You can get it directly either into YouTube pages or the service worker JavaScript file
122+ * ({@code https://www.youtube.com/sw.js}) (also applies for YouTube Music).
123+ */
97124 private static final String HARDCODED_CLIENT_VERSION = "2.20220315.01.00" ;
98125 private static final String HARDCODED_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" ;
99126
127+ /**
128+ * The InnerTube API key used by the {@code ANDROID} client. Found with the help of
129+ * reverse-engineering app network requests.
130+ */
100131 private static final String ANDROID_YOUTUBE_KEY = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w" ;
132+
133+ /**
134+ * The InnerTube API key used by the {@code iOS} client. Found with the help of
135+ * reverse-engineering app network requests.
136+ */
101137 private static final String IOS_YOUTUBE_KEY = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc" ;
138+
139+ /**
140+ * The hardcoded client version of the Android app used for InnerTube requests with this
141+ * client.
142+ *
143+ * <p>
144+ * It can be extracted by getting the latest release version of the app in an APK repository
145+ * such as APKMirror.
146+ * </p>
147+ *
148+ * @implNote This version is also used for the {@code iOS} client, as getting the app version
149+ * without an iPhone device is not so easily.
150+ */
102151 private static final String MOBILE_YOUTUBE_CLIENT_VERSION = "17.10.35" ;
103152
104153 private static String clientVersion ;
@@ -128,6 +177,16 @@ private YoutubeParsingHelper() {
128177 private static final String CONTENT_PLAYBACK_NONCE_ALPHABET =
129178 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ;
130179
180+ /**
181+ * The device machine id for the iPhone 13, used to get 60fps with the {@code iOS} client.
182+ *
183+ * <p>
184+ * See <a href="https://gist.github.com/adamawolf/3048717">this GitHub Gist</a> for more
185+ * information.
186+ * </p>
187+ */
188+ private static final String IOS_DEVICE_MODEL = "iPhone14,5" ;
189+
131190 private static Random numberGenerator = new SecureRandom ();
132191
133192 /**
@@ -1071,7 +1130,7 @@ public static JsonBuilder<JsonObject> prepareIosMobileJsonBuilder(
10711130 .value ("clientName" , "IOS" )
10721131 .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
10731132 // Device model is required to get 60fps streams
1074- .value ("deviceModel" , "iPhone14,5" )
1133+ .value ("deviceModel" , IOS_DEVICE_MODEL )
10751134 .value ("platform" , "MOBILE" )
10761135 .value ("hl" , localization .getLocalizationCode ())
10771136 .value ("gl" , contentCountry .getCountryCode ())
@@ -1169,7 +1228,7 @@ public static JsonBuilder<JsonObject> prepareIosMobileEmbedVideoJsonBuilder(
11691228 .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
11701229 .value ("clientScreen" , "EMBED" )
11711230 // Device model is required to get 60fps streams
1172- .value ("deviceModel" , "iPhone14,5" )
1231+ .value ("deviceModel" , IOS_DEVICE_MODEL )
11731232 .value ("platform" , "MOBILE" )
11741233 .value ("hl" , localization .getLocalizationCode ())
11751234 .value ("gl" , contentCountry .getCountryCode ())
@@ -1208,6 +1267,8 @@ public static byte[] createDesktopPlayerBody(
12081267 : prepareDesktopJsonBuilder (localization , contentCountry ))
12091268 .object ("playbackContext" )
12101269 .object ("contentPlaybackContext" )
1270+ // Some parameters which are sent by the official WEB client (probably some
1271+ // of them are not useful)
12111272 .value ("currentUrl" , "/watch?v=" + videoId )
12121273 .value ("vis" , 0 )
12131274 .value ("splay" , false )
@@ -1260,9 +1321,10 @@ public static String getAndroidUserAgent(@Nullable final Localization localizati
12601321 */
12611322 @ Nonnull
12621323 public static String getIosUserAgent (@ Nullable final Localization localization ) {
1263- // Spoofing an iPhone 13 running iOS 15.4 with the hardcoded mobile client version
1324+ // Spoofing an iPhone running iOS 15.4 with the hardcoded mobile client version
12641325 return "com.google.ios.youtube/" + MOBILE_YOUTUBE_CLIENT_VERSION
1265- + "(iPhone14,5; U; CPU iOS 15_4 like Mac OS X; "
1326+ + "(" + IOS_DEVICE_MODEL
1327+ + "; U; CPU iOS 15_4 like Mac OS X; "
12661328 + (localization != null ? localization .getCountryCode ()
12671329 : Localization .DEFAULT .getCountryCode ())
12681330 + ")" ;
0 commit comments