Skip to content

Commit f35874e

Browse files
committed
[YouTube] Fix crash on SABR-only player responses, do not use WEB client
1 parent 67f3301 commit f35874e

3 files changed

Lines changed: 28 additions & 166 deletions

File tree

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

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@
1111
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_DEVICE_MODEL;
1212
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_OS_VERSION;
1313
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.MOBILE_CLIENT_PLATFORM;
14-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_ID;
15-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_NAME;
16-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_PLATFORM;
17-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_VERSION;
18-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_DEVICE_MAKE;
19-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_DEVICE_MODEL_AND_OS_NAME;
2014
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WATCH_CLIENT_SCREEN;
2115
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_ID;
2216
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_NAME;
@@ -118,16 +112,6 @@ public static InnertubeClientRequestInfo ofWebEmbeddedPlayerClient() {
118112
null, null, -1));
119113
}
120114

121-
@Nonnull
122-
public static InnertubeClientRequestInfo ofTvHtml5Client() {
123-
return new InnertubeClientRequestInfo(
124-
new InnertubeClientRequestInfo.ClientInfo(TVHTML5_CLIENT_NAME,
125-
TVHTML5_CLIENT_VERSION, WATCH_CLIENT_SCREEN, TVHTML5_CLIENT_ID, null),
126-
new InnertubeClientRequestInfo.DeviceInfo(TVHTML5_CLIENT_PLATFORM,
127-
TVHTML5_DEVICE_MAKE, TVHTML5_DEVICE_MODEL_AND_OS_NAME,
128-
TVHTML5_DEVICE_MODEL_AND_OS_NAME, "", -1));
129-
}
130-
131115
@Nonnull
132116
public static InnertubeClientRequestInfo ofAndroidClient() {
133117
return new InnertubeClientRequestInfo(

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

Lines changed: 2 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
import java.util.Map;
1818

1919
import static org.schabi.newpipe.extractor.NewPipe.getDownloader;
20-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_ID;
21-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_VERSION;
22-
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_USER_AGENT;
2320
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_EMBEDDED_CLIENT_ID;
2421
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_EMBEDDED_CLIENT_VERSION;
2522
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.CONTENT_CHECK_OK;
@@ -82,77 +79,6 @@ public static JsonObject getWebMetadataPlayerResponse(
8279
url, headers, body, localization)));
8380
}
8481

85-
@Nonnull
86-
public static JsonObject getTvHtml5PlayerResponse(
87-
@Nonnull final Localization localization,
88-
@Nonnull final ContentCountry contentCountry,
89-
@Nonnull final String videoId,
90-
@Nonnull final String cpn,
91-
final int signatureTimestamp) throws IOException, ExtractionException {
92-
final InnertubeClientRequestInfo innertubeClientRequestInfo =
93-
InnertubeClientRequestInfo.ofTvHtml5Client();
94-
95-
final Map<String, List<String>> headers = new HashMap<>(
96-
getClientHeaders(TVHTML5_CLIENT_ID, TVHTML5_CLIENT_VERSION));
97-
headers.putAll(getOriginReferrerHeaders("https://www.youtube.com"));
98-
headers.put("User-Agent", List.of(TVHTML5_USER_AGENT));
99-
100-
// We must always pass a valid visitorData to get valid player responses, which needs to be
101-
// got from YouTube
102-
// For some reason, the TVHTML5 client doesn't support the visitor_id endpoint, use the
103-
// guide one instead, which is quite lightweight
104-
innertubeClientRequestInfo.clientInfo.visitorData =
105-
YoutubeParsingHelper.getVisitorDataFromInnertube(innertubeClientRequestInfo,
106-
localization, contentCountry, headers, YOUTUBEI_V1_URL, null, true);
107-
108-
final JsonBuilder<JsonObject> builder = prepareJsonBuilder(localization, contentCountry,
109-
innertubeClientRequestInfo, null);
110-
111-
addVideoIdCpnAndOkChecks(builder, videoId, cpn);
112-
113-
addPlaybackContext(builder, BASE_YT_DESKTOP_WATCH_URL + videoId, signatureTimestamp);
114-
115-
final byte[] body = JsonWriter.string(builder.done())
116-
.getBytes(StandardCharsets.UTF_8);
117-
118-
final String url = YOUTUBEI_V1_URL + PLAYER + "?" + DISABLE_PRETTY_PRINT_PARAMETER;
119-
120-
return JsonUtils.toJsonObject(getValidJsonResponseBody(
121-
getDownloader().postWithContentTypeJson(url, headers, body, localization)));
122-
}
123-
124-
@Nonnull
125-
public static JsonObject getWebFullPlayerResponse(
126-
@Nonnull final Localization localization,
127-
@Nonnull final ContentCountry contentCountry,
128-
@Nonnull final String videoId,
129-
@Nonnull final String cpn,
130-
@Nonnull final PoTokenResult webPoTokenResult,
131-
final int signatureTimestamp) throws IOException, ExtractionException {
132-
final InnertubeClientRequestInfo innertubeClientRequestInfo =
133-
InnertubeClientRequestInfo.ofWebClient();
134-
innertubeClientRequestInfo.clientInfo.clientVersion = getClientVersion();
135-
innertubeClientRequestInfo.clientInfo.visitorData = webPoTokenResult.visitorData;
136-
137-
final JsonBuilder<JsonObject> builder = prepareJsonBuilder(localization, contentCountry,
138-
innertubeClientRequestInfo, null);
139-
140-
addVideoIdCpnAndOkChecks(builder, videoId, cpn);
141-
142-
addPlaybackContext(builder, BASE_YT_DESKTOP_WATCH_URL + videoId, signatureTimestamp);
143-
144-
addPoToken(builder, webPoTokenResult.playerRequestPoToken);
145-
146-
final byte[] body = JsonWriter.string(builder.done())
147-
.getBytes(StandardCharsets.UTF_8);
148-
149-
final String url = YOUTUBEI_V1_URL + PLAYER + "?" + DISABLE_PRETTY_PRINT_PARAMETER;
150-
151-
return JsonUtils.toJsonObject(getValidJsonResponseBody(
152-
getDownloader().postWithContentTypeJson(
153-
url, getYouTubeHeaders(), body, localization)));
154-
}
155-
15682
@Nonnull
15783
public static JsonObject getWebEmbeddedPlayerResponse(
15884
@Nonnull final Localization localization,
@@ -247,11 +173,9 @@ public static JsonObject getAndroidReelPlayerResponse(
247173
final JsonBuilder<JsonObject> builder = prepareJsonBuilder(localization, contentCountry,
248174
innertubeClientRequestInfo, null);
249175

176+
builder.object("playerRequest");
250177
addVideoIdCpnAndOkChecks(builder, videoId, cpn);
251-
252-
builder.object("playerRequest")
253-
.value(VIDEO_ID, videoId)
254-
.end()
178+
builder.end()
255179
.value("disablePlayerResponse", false);
256180

257181
final byte[] body = JsonWriter.string(builder.done())

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java

Lines changed: 26 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -813,22 +813,21 @@ public void onFetchPage(@Nonnull final Downloader downloader)
813813
final Localization localization = getExtractorLocalization();
814814
final ContentCountry contentCountry = getExtractorContentCountry();
815815

816-
final PoTokenProvider poTokenproviderInstance = poTokenProvider;
817-
final boolean noPoTokenProviderSet = poTokenproviderInstance == null;
816+
final PoTokenProvider poTokenProviderInstance = poTokenProvider;
817+
final boolean noPoTokenProviderSet = poTokenProviderInstance == null;
818818

819-
fetchHtml5Client(localization, contentCountry, videoId, poTokenproviderInstance,
820-
noPoTokenProviderSet);
819+
fetchHtml5Client(localization, contentCountry, videoId, poTokenProviderInstance);
821820

822821
setStreamType();
823822

824823
final PoTokenResult androidPoTokenResult = noPoTokenProviderSet ? null
825-
: poTokenproviderInstance.getAndroidClientPoToken(videoId);
824+
: poTokenProviderInstance.getAndroidClientPoToken(videoId);
826825

827826
fetchAndroidClient(localization, contentCountry, videoId, androidPoTokenResult);
828827

829828
if (fetchIosClient) {
830829
final PoTokenResult iosPoTokenResult = noPoTokenProviderSet ? null
831-
: poTokenproviderInstance.getIosClientPoToken(videoId);
830+
: poTokenProviderInstance.getIosClientPoToken(videoId);
832831
fetchIosClient(localization, contentCountry, videoId, iosPoTokenResult);
833832
}
834833

@@ -904,82 +903,32 @@ private static void checkPlayabilityStatus(@Nonnull final JsonObject playability
904903
private void fetchHtml5Client(@Nonnull final Localization localization,
905904
@Nonnull final ContentCountry contentCountry,
906905
@Nonnull final String videoId,
907-
@Nullable final PoTokenProvider poTokenProviderInstance,
908-
final boolean noPoTokenProviderSet)
906+
@Nullable final PoTokenProvider poTokenProviderInstance)
909907
throws IOException, ExtractionException {
910908
html5Cpn = generateContentPlaybackNonce();
911909

912-
// Suppress NPE warning as nullability is already checked before and passed with
913-
// noPoTokenProviderSet
914-
//noinspection DataFlowIssue
915-
final PoTokenResult webPoTokenResult = noPoTokenProviderSet ? null
916-
: poTokenProviderInstance.getWebClientPoToken(videoId);
917-
final JsonObject webPlayerResponse;
918-
if (noPoTokenProviderSet || webPoTokenResult == null) {
919-
webPlayerResponse = YoutubeStreamHelper.getWebMetadataPlayerResponse(
910+
final JsonObject webPlayerResponse = YoutubeStreamHelper.getWebMetadataPlayerResponse(
920911
localization, contentCountry, videoId);
921912

922-
throwExceptionIfPlayerResponseNotValid(webPlayerResponse, videoId);
913+
throwExceptionIfPlayerResponseNotValid(webPlayerResponse, videoId);
923914

924-
// Save the webPlayerResponse into playerResponse in the case the video cannot be
925-
// played, so some metadata can be retrieved
926-
playerResponse = webPlayerResponse;
927-
928-
// The microformat JSON object of the content is only returned on the WEB client,
929-
// so we need to store it instead of getting it directly from the playerResponse
930-
playerMicroFormatRenderer = playerResponse.getObject("microformat")
931-
.getObject("playerMicroformatRenderer");
915+
// Save the webPlayerResponse into playerResponse in the case the video cannot be
916+
// played, so some metadata can be retrieved
917+
playerResponse = webPlayerResponse;
932918

933-
final JsonObject playabilityStatus = webPlayerResponse.getObject(PLAYABILITY_STATUS);
919+
// The microformat JSON object of the content is only returned on the WEB client,
920+
// so we need to store it instead of getting it directly from the playerResponse
921+
playerMicroFormatRenderer = playerResponse.getObject("microformat")
922+
.getObject("playerMicroformatRenderer");
934923

935-
if (isVideoAgeRestricted(playabilityStatus)) {
936-
fetchHtml5EmbedClient(localization, contentCountry, videoId,
937-
noPoTokenProviderSet ? null
938-
: poTokenProviderInstance.getWebEmbedClientPoToken(videoId));
939-
} else {
940-
checkPlayabilityStatus(playabilityStatus);
924+
final JsonObject playabilityStatus = webPlayerResponse.getObject(PLAYABILITY_STATUS);
941925

942-
final JsonObject tvHtml5PlayerResponse =
943-
YoutubeStreamHelper.getTvHtml5PlayerResponse(
944-
localization, contentCountry, videoId, html5Cpn,
945-
YoutubeJavaScriptPlayerManager.getSignatureTimestamp(videoId));
946-
947-
if (isPlayerResponseNotValid(tvHtml5PlayerResponse, videoId)) {
948-
throw new ExtractionException("TVHTML5 player response is not valid");
949-
}
950-
951-
html5StreamingData = tvHtml5PlayerResponse.getObject(STREAMING_DATA);
952-
playerCaptionsTracklistRenderer = tvHtml5PlayerResponse.getObject(CAPTIONS)
953-
.getObject(PLAYER_CAPTIONS_TRACKLIST_RENDERER);
954-
}
926+
if (isVideoAgeRestricted(playabilityStatus)) {
927+
fetchHtml5EmbedClient(localization, contentCountry, videoId,
928+
poTokenProviderInstance == null ? null
929+
: poTokenProviderInstance.getWebEmbedClientPoToken(videoId));
955930
} else {
956-
webPlayerResponse = YoutubeStreamHelper.getWebFullPlayerResponse(
957-
localization, contentCountry, videoId, html5Cpn, webPoTokenResult,
958-
YoutubeJavaScriptPlayerManager.getSignatureTimestamp(videoId));
959-
960-
throwExceptionIfPlayerResponseNotValid(webPlayerResponse, videoId);
961-
962-
// Save the webPlayerResponse into playerResponse in the case the video cannot be
963-
// played, so some metadata can be retrieved
964-
playerResponse = webPlayerResponse;
965-
966-
// The microformat JSON object of the content is only returned on the WEB client,
967-
// so we need to store it instead of getting it directly from the playerResponse
968-
playerMicroFormatRenderer = playerResponse.getObject("microformat")
969-
.getObject("playerMicroformatRenderer");
970-
971-
final JsonObject playabilityStatus = webPlayerResponse.getObject(PLAYABILITY_STATUS);
972-
973-
if (isVideoAgeRestricted(playabilityStatus)) {
974-
fetchHtml5EmbedClient(localization, contentCountry, videoId,
975-
poTokenProviderInstance.getWebEmbedClientPoToken(videoId));
976-
} else {
977-
checkPlayabilityStatus(playabilityStatus);
978-
html5StreamingData = webPlayerResponse.getObject(STREAMING_DATA);
979-
playerCaptionsTracklistRenderer = webPlayerResponse.getObject(CAPTIONS)
980-
.getObject(PLAYER_CAPTIONS_TRACKLIST_RENDERER);
981-
html5StreamingUrlsPoToken = webPoTokenResult.streamingDataPoToken;
982-
}
931+
checkPlayabilityStatus(playabilityStatus);
983932
}
984933
}
985934

@@ -1383,6 +1332,11 @@ private ItagInfo buildAndAddItagInfoToList(
13831332
// This url has an obfuscated signature
13841333
final String cipherString = formatData.getString(CIPHER,
13851334
formatData.getString(SIGNATURE_CIPHER));
1335+
1336+
if (isNullOrEmpty(cipherString)) {
1337+
return null;
1338+
}
1339+
13861340
final var cipher = Parser.compatParseMap(cipherString);
13871341
final String signature = YoutubeJavaScriptPlayerManager.deobfuscateSignature(videoId,
13881342
cipher.getOrDefault("s", ""));

0 commit comments

Comments
 (0)