@@ -87,6 +87,7 @@ private YoutubeParsingHelper() {
8787 private static final String HARDCODED_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" ;
8888
8989 private static final String ANDROID_YOUTUBE_KEY = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w" ;
90+ private static final String IOS_YOUTUBE_KEY = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc" ;
9091 private static final String MOBILE_YOUTUBE_CLIENT_VERSION = "16.49.37" ;
9192
9293 private static String clientVersion ;
@@ -717,9 +718,9 @@ public static String[] getYoutubeMusicKey()
717718 return youtubeMusicKey ;
718719 }
719720
720- String musicClientVersion = null ;
721- String musicKey = null ;
722- String musicClientName = null ;
721+ String musicClientVersion ;
722+ String musicKey ;
723+ String musicClientName ;
723724
724725 try {
725726 final String url = "https://music.youtube.com/sw.js" ;
@@ -950,16 +951,15 @@ public static JsonObject getJsonPostResponse(final String endpoint,
950951 public static JsonObject getJsonAndroidPostResponse (
951952 final String endpoint ,
952953 final byte [] body ,
953- @ Nonnull final ContentCountry contentCountry ,
954- final Localization localization ,
954+ @ Nonnull final Localization localization ,
955955 @ Nullable final String endPartOfUrlRequest ) throws IOException , ExtractionException {
956956 final Map <String , List <String >> headers = new HashMap <>();
957957 headers .put ("Content-Type" , Collections .singletonList ("application/json" ));
958958 // Spoofing an Android 11 device with the hardcoded version of the Android app
959959 headers .put ("User-Agent" , Collections .singletonList ("com.google.android.youtube/"
960960 + MOBILE_YOUTUBE_CLIENT_VERSION + " (Linux; U; Android 11; "
961- + contentCountry .getCountryCode () + ") gzip" ));
962- headers .put ("x-goog-api-format-version " , Collections .singletonList ("2" ));
961+ + localization .getCountryCode () + ") gzip" ));
962+ headers .put ("X-Goog-Api-Format-Version " , Collections .singletonList ("2" ));
963963
964964 final String baseEndpointUrl = "https://youtubei.googleapis.com/youtubei/v1/" + endpoint
965965 + "?key=" + ANDROID_YOUTUBE_KEY ;
@@ -971,6 +971,29 @@ public static JsonObject getJsonAndroidPostResponse(
971971 return JsonUtils .toJsonObject (getValidJsonResponseBody (response ));
972972 }
973973
974+ public static JsonObject getJsonIosPostResponse (
975+ final String endpoint ,
976+ final byte [] body ,
977+ @ Nonnull final Localization localization ,
978+ @ Nullable final String endPartOfUrlRequest ) throws IOException , ExtractionException {
979+ final Map <String , List <String >> headers = new HashMap <>();
980+ headers .put ("Content-Type" , Collections .singletonList ("application/json" ));
981+ // Spoofing an iPhone 13 running iOS 15.2 with the hardcoded mobile client version
982+ headers .put ("User-Agent" , Collections .singletonList ("com.google.ios.youtube/"
983+ + MOBILE_YOUTUBE_CLIENT_VERSION + "(iPhone14,5; U; CPU iOS 15_2 like Mac OS X; "
984+ + localization .getCountryCode () + ")" ));
985+ headers .put ("X-Goog-Api-Format-Version" , Collections .singletonList ("2" ));
986+
987+ final String baseEndpointUrl = "https://youtubei.googleapis.com/youtubei/v1/" + endpoint
988+ + "?key=" + IOS_YOUTUBE_KEY ;
989+
990+ final Response response = getDownloader ().post (isNullOrEmpty (endPartOfUrlRequest )
991+ ? baseEndpointUrl : baseEndpointUrl + endPartOfUrlRequest ,
992+ headers , body , localization );
993+
994+ return JsonUtils .toJsonObject (getValidJsonResponseBody (response ));
995+ }
996+
974997 @ Nonnull
975998 public static JsonBuilder <JsonObject > prepareDesktopJsonBuilder (
976999 @ Nonnull final Localization localization ,
@@ -1011,6 +1034,32 @@ public static JsonBuilder<JsonObject> prepareAndroidMobileJsonBuilder(
10111034 .object ("client" )
10121035 .value ("clientName" , "ANDROID" )
10131036 .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
1037+ .value ("platform" , "MOBILE" )
1038+ .value ("hl" , localization .getLocalizationCode ())
1039+ .value ("gl" , contentCountry .getCountryCode ())
1040+ .end ()
1041+ .object ("user" )
1042+ // TO DO: provide a way to enable restricted mode with:
1043+ // .value("enableSafetyMode", boolean)
1044+ .value ("lockedSafetyMode" , false )
1045+ .end ()
1046+ .end ();
1047+ // @formatter:on
1048+ }
1049+
1050+ @ Nonnull
1051+ public static JsonBuilder <JsonObject > prepareIosMobileJsonBuilder (
1052+ @ Nonnull final Localization localization ,
1053+ @ Nonnull final ContentCountry contentCountry ) {
1054+ // @formatter:off
1055+ return JsonObject .builder ()
1056+ .object ("context" )
1057+ .object ("client" )
1058+ .value ("clientName" , "IOS" )
1059+ .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
1060+ // Device model is required to get 60fps streams
1061+ .value ("deviceModel" , "iPhone14,5" )
1062+ .value ("platform" , "MOBILE" )
10141063 .value ("hl" , localization .getLocalizationCode ())
10151064 .value ("gl" , contentCountry .getCountryCode ())
10161065 .end ()
@@ -1070,6 +1119,45 @@ public static JsonBuilder<JsonObject> prepareAndroidMobileEmbedVideoJsonBuilder(
10701119 .value ("clientName" , "ANDROID" )
10711120 .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
10721121 .value ("clientScreen" , "EMBED" )
1122+ .value ("platform" , "MOBILE" )
1123+ .value ("hl" , localization .getLocalizationCode ())
1124+ .value ("gl" , contentCountry .getCountryCode ())
1125+ .end ()
1126+ .object ("thirdParty" )
1127+ .value ("embedUrl" , "https://www.youtube.com/watch?v=" + videoId )
1128+ .end ()
1129+ .object ("request" )
1130+ .array ("internalExperimentFlags" )
1131+ .end ()
1132+ .value ("useSsl" , true )
1133+ .end ()
1134+ .object ("user" )
1135+ // TO DO: provide a way to enable restricted mode with:
1136+ // .value("enableSafetyMode", boolean)
1137+ .value ("lockedSafetyMode" , false )
1138+ .end ()
1139+ .end ()
1140+ .value (CPN , contentPlaybackNonce )
1141+ .value (VIDEO_ID , videoId );
1142+ // @formatter:on
1143+ }
1144+
1145+ @ Nonnull
1146+ public static JsonBuilder <JsonObject > prepareIosMobileEmbedVideoJsonBuilder (
1147+ @ Nonnull final Localization localization ,
1148+ @ Nonnull final ContentCountry contentCountry ,
1149+ @ Nonnull final String videoId ,
1150+ @ Nonnull final String contentPlaybackNonce ) {
1151+ // @formatter:off
1152+ return JsonObject .builder ()
1153+ .object ("context" )
1154+ .object ("client" )
1155+ .value ("clientName" , "IOS" )
1156+ .value ("clientVersion" , MOBILE_YOUTUBE_CLIENT_VERSION )
1157+ .value ("clientScreen" , "EMBED" )
1158+ // Device model is required to get 60fps streams
1159+ .value ("deviceModel" , "iPhone14,5" )
1160+ .value ("platform" , "MOBILE" )
10731161 .value ("hl" , localization .getLocalizationCode ())
10741162 .value ("gl" , contentCountry .getCountryCode ())
10751163 .end ()
0 commit comments