kopia lustrzana https://github.com/TeamNewPipe/NewPipeExtractor
[YouTube] Fix crash on SABR-only player responses, do not use WEB client
rodzic
67f3301d9f
commit
f35874e76f
|
@ -11,12 +11,6 @@ import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS
|
|||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_DEVICE_MODEL;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_OS_VERSION;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.MOBILE_CLIENT_PLATFORM;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_ID;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_NAME;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_PLATFORM;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_VERSION;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_DEVICE_MAKE;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_DEVICE_MODEL_AND_OS_NAME;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WATCH_CLIENT_SCREEN;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_ID;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_NAME;
|
||||
|
@ -118,16 +112,6 @@ public final class InnertubeClientRequestInfo {
|
|||
null, null, -1));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static InnertubeClientRequestInfo ofTvHtml5Client() {
|
||||
return new InnertubeClientRequestInfo(
|
||||
new InnertubeClientRequestInfo.ClientInfo(TVHTML5_CLIENT_NAME,
|
||||
TVHTML5_CLIENT_VERSION, WATCH_CLIENT_SCREEN, TVHTML5_CLIENT_ID, null),
|
||||
new InnertubeClientRequestInfo.DeviceInfo(TVHTML5_CLIENT_PLATFORM,
|
||||
TVHTML5_DEVICE_MAKE, TVHTML5_DEVICE_MODEL_AND_OS_NAME,
|
||||
TVHTML5_DEVICE_MODEL_AND_OS_NAME, "", -1));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static InnertubeClientRequestInfo ofAndroidClient() {
|
||||
return new InnertubeClientRequestInfo(
|
||||
|
|
|
@ -17,9 +17,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import static org.schabi.newpipe.extractor.NewPipe.getDownloader;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_ID;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_VERSION;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_USER_AGENT;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_EMBEDDED_CLIENT_ID;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_EMBEDDED_CLIENT_VERSION;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.CONTENT_CHECK_OK;
|
||||
|
@ -82,77 +79,6 @@ public final class YoutubeStreamHelper {
|
|||
url, headers, body, localization)));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static JsonObject getTvHtml5PlayerResponse(
|
||||
@Nonnull final Localization localization,
|
||||
@Nonnull final ContentCountry contentCountry,
|
||||
@Nonnull final String videoId,
|
||||
@Nonnull final String cpn,
|
||||
final int signatureTimestamp) throws IOException, ExtractionException {
|
||||
final InnertubeClientRequestInfo innertubeClientRequestInfo =
|
||||
InnertubeClientRequestInfo.ofTvHtml5Client();
|
||||
|
||||
final Map<String, List<String>> headers = new HashMap<>(
|
||||
getClientHeaders(TVHTML5_CLIENT_ID, TVHTML5_CLIENT_VERSION));
|
||||
headers.putAll(getOriginReferrerHeaders("https://www.youtube.com"));
|
||||
headers.put("User-Agent", List.of(TVHTML5_USER_AGENT));
|
||||
|
||||
// We must always pass a valid visitorData to get valid player responses, which needs to be
|
||||
// got from YouTube
|
||||
// For some reason, the TVHTML5 client doesn't support the visitor_id endpoint, use the
|
||||
// guide one instead, which is quite lightweight
|
||||
innertubeClientRequestInfo.clientInfo.visitorData =
|
||||
YoutubeParsingHelper.getVisitorDataFromInnertube(innertubeClientRequestInfo,
|
||||
localization, contentCountry, headers, YOUTUBEI_V1_URL, null, true);
|
||||
|
||||
final JsonBuilder<JsonObject> builder = prepareJsonBuilder(localization, contentCountry,
|
||||
innertubeClientRequestInfo, null);
|
||||
|
||||
addVideoIdCpnAndOkChecks(builder, videoId, cpn);
|
||||
|
||||
addPlaybackContext(builder, BASE_YT_DESKTOP_WATCH_URL + videoId, signatureTimestamp);
|
||||
|
||||
final byte[] body = JsonWriter.string(builder.done())
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
final String url = YOUTUBEI_V1_URL + PLAYER + "?" + DISABLE_PRETTY_PRINT_PARAMETER;
|
||||
|
||||
return JsonUtils.toJsonObject(getValidJsonResponseBody(
|
||||
getDownloader().postWithContentTypeJson(url, headers, body, localization)));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static JsonObject getWebFullPlayerResponse(
|
||||
@Nonnull final Localization localization,
|
||||
@Nonnull final ContentCountry contentCountry,
|
||||
@Nonnull final String videoId,
|
||||
@Nonnull final String cpn,
|
||||
@Nonnull final PoTokenResult webPoTokenResult,
|
||||
final int signatureTimestamp) throws IOException, ExtractionException {
|
||||
final InnertubeClientRequestInfo innertubeClientRequestInfo =
|
||||
InnertubeClientRequestInfo.ofWebClient();
|
||||
innertubeClientRequestInfo.clientInfo.clientVersion = getClientVersion();
|
||||
innertubeClientRequestInfo.clientInfo.visitorData = webPoTokenResult.visitorData;
|
||||
|
||||
final JsonBuilder<JsonObject> builder = prepareJsonBuilder(localization, contentCountry,
|
||||
innertubeClientRequestInfo, null);
|
||||
|
||||
addVideoIdCpnAndOkChecks(builder, videoId, cpn);
|
||||
|
||||
addPlaybackContext(builder, BASE_YT_DESKTOP_WATCH_URL + videoId, signatureTimestamp);
|
||||
|
||||
addPoToken(builder, webPoTokenResult.playerRequestPoToken);
|
||||
|
||||
final byte[] body = JsonWriter.string(builder.done())
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
final String url = YOUTUBEI_V1_URL + PLAYER + "?" + DISABLE_PRETTY_PRINT_PARAMETER;
|
||||
|
||||
return JsonUtils.toJsonObject(getValidJsonResponseBody(
|
||||
getDownloader().postWithContentTypeJson(
|
||||
url, getYouTubeHeaders(), body, localization)));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static JsonObject getWebEmbeddedPlayerResponse(
|
||||
@Nonnull final Localization localization,
|
||||
|
@ -247,11 +173,9 @@ public final class YoutubeStreamHelper {
|
|||
final JsonBuilder<JsonObject> builder = prepareJsonBuilder(localization, contentCountry,
|
||||
innertubeClientRequestInfo, null);
|
||||
|
||||
builder.object("playerRequest");
|
||||
addVideoIdCpnAndOkChecks(builder, videoId, cpn);
|
||||
|
||||
builder.object("playerRequest")
|
||||
.value(VIDEO_ID, videoId)
|
||||
.end()
|
||||
builder.end()
|
||||
.value("disablePlayerResponse", false);
|
||||
|
||||
final byte[] body = JsonWriter.string(builder.done())
|
||||
|
|
|
@ -813,22 +813,21 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
final Localization localization = getExtractorLocalization();
|
||||
final ContentCountry contentCountry = getExtractorContentCountry();
|
||||
|
||||
final PoTokenProvider poTokenproviderInstance = poTokenProvider;
|
||||
final boolean noPoTokenProviderSet = poTokenproviderInstance == null;
|
||||
final PoTokenProvider poTokenProviderInstance = poTokenProvider;
|
||||
final boolean noPoTokenProviderSet = poTokenProviderInstance == null;
|
||||
|
||||
fetchHtml5Client(localization, contentCountry, videoId, poTokenproviderInstance,
|
||||
noPoTokenProviderSet);
|
||||
fetchHtml5Client(localization, contentCountry, videoId, poTokenProviderInstance);
|
||||
|
||||
setStreamType();
|
||||
|
||||
final PoTokenResult androidPoTokenResult = noPoTokenProviderSet ? null
|
||||
: poTokenproviderInstance.getAndroidClientPoToken(videoId);
|
||||
: poTokenProviderInstance.getAndroidClientPoToken(videoId);
|
||||
|
||||
fetchAndroidClient(localization, contentCountry, videoId, androidPoTokenResult);
|
||||
|
||||
if (fetchIosClient) {
|
||||
final PoTokenResult iosPoTokenResult = noPoTokenProviderSet ? null
|
||||
: poTokenproviderInstance.getIosClientPoToken(videoId);
|
||||
: poTokenProviderInstance.getIosClientPoToken(videoId);
|
||||
fetchIosClient(localization, contentCountry, videoId, iosPoTokenResult);
|
||||
}
|
||||
|
||||
|
@ -904,82 +903,32 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
private void fetchHtml5Client(@Nonnull final Localization localization,
|
||||
@Nonnull final ContentCountry contentCountry,
|
||||
@Nonnull final String videoId,
|
||||
@Nullable final PoTokenProvider poTokenProviderInstance,
|
||||
final boolean noPoTokenProviderSet)
|
||||
@Nullable final PoTokenProvider poTokenProviderInstance)
|
||||
throws IOException, ExtractionException {
|
||||
html5Cpn = generateContentPlaybackNonce();
|
||||
|
||||
// Suppress NPE warning as nullability is already checked before and passed with
|
||||
// noPoTokenProviderSet
|
||||
//noinspection DataFlowIssue
|
||||
final PoTokenResult webPoTokenResult = noPoTokenProviderSet ? null
|
||||
: poTokenProviderInstance.getWebClientPoToken(videoId);
|
||||
final JsonObject webPlayerResponse;
|
||||
if (noPoTokenProviderSet || webPoTokenResult == null) {
|
||||
webPlayerResponse = YoutubeStreamHelper.getWebMetadataPlayerResponse(
|
||||
final JsonObject webPlayerResponse = YoutubeStreamHelper.getWebMetadataPlayerResponse(
|
||||
localization, contentCountry, videoId);
|
||||
|
||||
throwExceptionIfPlayerResponseNotValid(webPlayerResponse, videoId);
|
||||
throwExceptionIfPlayerResponseNotValid(webPlayerResponse, videoId);
|
||||
|
||||
// Save the webPlayerResponse into playerResponse in the case the video cannot be
|
||||
// played, so some metadata can be retrieved
|
||||
playerResponse = webPlayerResponse;
|
||||
// Save the webPlayerResponse into playerResponse in the case the video cannot be
|
||||
// played, so some metadata can be retrieved
|
||||
playerResponse = webPlayerResponse;
|
||||
|
||||
// The microformat JSON object of the content is only returned on the WEB client,
|
||||
// so we need to store it instead of getting it directly from the playerResponse
|
||||
playerMicroFormatRenderer = playerResponse.getObject("microformat")
|
||||
.getObject("playerMicroformatRenderer");
|
||||
// The microformat JSON object of the content is only returned on the WEB client,
|
||||
// so we need to store it instead of getting it directly from the playerResponse
|
||||
playerMicroFormatRenderer = playerResponse.getObject("microformat")
|
||||
.getObject("playerMicroformatRenderer");
|
||||
|
||||
final JsonObject playabilityStatus = webPlayerResponse.getObject(PLAYABILITY_STATUS);
|
||||
final JsonObject playabilityStatus = webPlayerResponse.getObject(PLAYABILITY_STATUS);
|
||||
|
||||
if (isVideoAgeRestricted(playabilityStatus)) {
|
||||
fetchHtml5EmbedClient(localization, contentCountry, videoId,
|
||||
noPoTokenProviderSet ? null
|
||||
: poTokenProviderInstance.getWebEmbedClientPoToken(videoId));
|
||||
} else {
|
||||
checkPlayabilityStatus(playabilityStatus);
|
||||
|
||||
final JsonObject tvHtml5PlayerResponse =
|
||||
YoutubeStreamHelper.getTvHtml5PlayerResponse(
|
||||
localization, contentCountry, videoId, html5Cpn,
|
||||
YoutubeJavaScriptPlayerManager.getSignatureTimestamp(videoId));
|
||||
|
||||
if (isPlayerResponseNotValid(tvHtml5PlayerResponse, videoId)) {
|
||||
throw new ExtractionException("TVHTML5 player response is not valid");
|
||||
}
|
||||
|
||||
html5StreamingData = tvHtml5PlayerResponse.getObject(STREAMING_DATA);
|
||||
playerCaptionsTracklistRenderer = tvHtml5PlayerResponse.getObject(CAPTIONS)
|
||||
.getObject(PLAYER_CAPTIONS_TRACKLIST_RENDERER);
|
||||
}
|
||||
if (isVideoAgeRestricted(playabilityStatus)) {
|
||||
fetchHtml5EmbedClient(localization, contentCountry, videoId,
|
||||
poTokenProviderInstance == null ? null
|
||||
: poTokenProviderInstance.getWebEmbedClientPoToken(videoId));
|
||||
} else {
|
||||
webPlayerResponse = YoutubeStreamHelper.getWebFullPlayerResponse(
|
||||
localization, contentCountry, videoId, html5Cpn, webPoTokenResult,
|
||||
YoutubeJavaScriptPlayerManager.getSignatureTimestamp(videoId));
|
||||
|
||||
throwExceptionIfPlayerResponseNotValid(webPlayerResponse, videoId);
|
||||
|
||||
// Save the webPlayerResponse into playerResponse in the case the video cannot be
|
||||
// played, so some metadata can be retrieved
|
||||
playerResponse = webPlayerResponse;
|
||||
|
||||
// The microformat JSON object of the content is only returned on the WEB client,
|
||||
// so we need to store it instead of getting it directly from the playerResponse
|
||||
playerMicroFormatRenderer = playerResponse.getObject("microformat")
|
||||
.getObject("playerMicroformatRenderer");
|
||||
|
||||
final JsonObject playabilityStatus = webPlayerResponse.getObject(PLAYABILITY_STATUS);
|
||||
|
||||
if (isVideoAgeRestricted(playabilityStatus)) {
|
||||
fetchHtml5EmbedClient(localization, contentCountry, videoId,
|
||||
poTokenProviderInstance.getWebEmbedClientPoToken(videoId));
|
||||
} else {
|
||||
checkPlayabilityStatus(playabilityStatus);
|
||||
html5StreamingData = webPlayerResponse.getObject(STREAMING_DATA);
|
||||
playerCaptionsTracklistRenderer = webPlayerResponse.getObject(CAPTIONS)
|
||||
.getObject(PLAYER_CAPTIONS_TRACKLIST_RENDERER);
|
||||
html5StreamingUrlsPoToken = webPoTokenResult.streamingDataPoToken;
|
||||
}
|
||||
checkPlayabilityStatus(playabilityStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1383,6 +1332,11 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
// This url has an obfuscated signature
|
||||
final String cipherString = formatData.getString(CIPHER,
|
||||
formatData.getString(SIGNATURE_CIPHER));
|
||||
|
||||
if (isNullOrEmpty(cipherString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final var cipher = Parser.compatParseMap(cipherString);
|
||||
final String signature = YoutubeJavaScriptPlayerManager.deobfuscateSignature(videoId,
|
||||
cipher.getOrDefault("s", ""));
|
||||
|
|
Ładowanie…
Reference in New Issue