Skip to content

Commit 26f93f5

Browse files
committed
[YouTube] Extract streams of livestreams from the iOS client and disabled the Android client for livestreams
The iOS client is only enabled for livestreams and the Android client is now only enabled for videos, both by default. A way to force, or not, the fetch of both clients have been added with two new static methods in YoutubeStreamExtractor.
1 parent 7d07924 commit 26f93f5

2 files changed

Lines changed: 281 additions & 22 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)