feat: add audio track types

This commit is contained in:
ThetaDev 2023-03-19 02:12:01 +01:00
parent 5a9b6ed2e3
commit 3fb356a706
15 changed files with 1753 additions and 57 deletions

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -201,7 +202,7 @@ public class ItagItem implements Serializable {
this.contentLength = itagItem.contentLength; this.contentLength = itagItem.contentLength;
this.audioTrackId = itagItem.audioTrackId; this.audioTrackId = itagItem.audioTrackId;
this.audioTrackName = itagItem.audioTrackName; this.audioTrackName = itagItem.audioTrackName;
this.isDescriptiveAudio = itagItem.isDescriptiveAudio; this.audioTrackType = itagItem.audioTrackType;
this.audioLocale = itagItem.audioLocale; this.audioLocale = itagItem.audioLocale;
} }
@ -251,7 +252,8 @@ public class ItagItem implements Serializable {
private long contentLength = CONTENT_LENGTH_UNKNOWN; private long contentLength = CONTENT_LENGTH_UNKNOWN;
private String audioTrackId; private String audioTrackId;
private String audioTrackName; private String audioTrackName;
private boolean isDescriptiveAudio; @Nullable
private AudioTrackType audioTrackType;
@Nullable @Nullable
private Locale audioLocale; private Locale audioLocale;
@ -594,21 +596,22 @@ public class ItagItem implements Serializable {
} }
/** /**
* Return whether the stream is a descriptive audio. * Get the {@link AudioTrackType} of the stream.
* *
* @return whether the stream is a descriptive audio * @return the {@link AudioTrackType} of the stream or {@code null}
*/ */
public boolean isDescriptiveAudio() { @Nullable
return isDescriptiveAudio; public AudioTrackType getAudioTrackType() {
return audioTrackType;
} }
/** /**
* Set whether the stream is a descriptive audio. * Set the {@link AudioTrackType} of the stream, if present.
* *
* @param isDescriptiveAudio whether the stream is a descriptive audio * @param audioTrackType the {@link AudioTrackType} of the stream or {@code null}
*/ */
public void setIsDescriptiveAudio(final boolean isDescriptiveAudio) { public void setAudioTrackType(@Nullable final AudioTrackType audioTrackType) {
this.isDescriptiveAudio = isDescriptiveAudio; this.audioTrackType = audioTrackType;
} }
/** /**

View File

@ -16,6 +16,7 @@ import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.youtube.DeliveryType; import org.schabi.newpipe.extractor.services.youtube.DeliveryType;
import org.schabi.newpipe.extractor.services.youtube.ItagItem; import org.schabi.newpipe.extractor.services.youtube.ItagItem;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.utils.ManifestCreatorCache; import org.schabi.newpipe.extractor.utils.ManifestCreatorCache;
import org.w3c.dom.Attr; import org.w3c.dom.Attr;
import org.w3c.dom.DOMException; import org.w3c.dom.DOMException;
@ -298,8 +299,10 @@ public final class YoutubeDashManifestCreatorsUtils {
final Element roleElement = doc.createElement(ROLE); final Element roleElement = doc.createElement(ROLE);
setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011"); setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011");
setAttribute(roleElement, doc, "value", itagItem.isDescriptiveAudio() setAttribute(roleElement, doc, "value",
? "alternate" : "main"); itagItem.getAudioTrackType() == null
|| itagItem.getAudioTrackType() == AudioTrackType.ORIGINAL
? "main" : "alternate");
adaptationSetElement.appendChild(roleElement); adaptationSetElement.appendChild(roleElement);
} catch (final DOMException e) { } catch (final DOMException e) {

View File

@ -72,6 +72,7 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.services.youtube.YoutubeThrottlingDecrypter; import org.schabi.newpipe.extractor.services.youtube.YoutubeThrottlingDecrypter;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelLinkHandlerFactory; import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelLinkHandlerFactory;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.stream.DeliveryMethod; import org.schabi.newpipe.extractor.stream.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.Description; import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.Frameset; import org.schabi.newpipe.extractor.stream.Frameset;
@ -810,6 +811,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
"\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*(:encodeURIComponent\\s*\\()([a-zA-Z0-9$]+)\\(" "\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*(:encodeURIComponent\\s*\\()([a-zA-Z0-9$]+)\\("
}; };
private static final String STS_REGEX = "signatureTimestamp[=:](\\d+)"; private static final String STS_REGEX = "signatureTimestamp[=:](\\d+)";
private static final String AUDIO_STREAM_TYPE_REGEX =
"&xtags=[\\w\\d%]*acont(?:=|%3D)([a-z]+)(?:=|%3D|:|%3A|&|$)";
@Override @Override
public void onFetchPage(@Nonnull final Downloader downloader) public void onFetchPage(@Nonnull final Downloader downloader)
@ -1311,7 +1314,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.setAudioTrackId(itagItem.getAudioTrackId()) .setAudioTrackId(itagItem.getAudioTrackId())
.setAudioTrackName(itagItem.getAudioTrackName()) .setAudioTrackName(itagItem.getAudioTrackName())
.setAudioLocale(itagItem.getAudioLocale()) .setAudioLocale(itagItem.getAudioLocale())
.setIsDescriptive(itagItem.isDescriptiveAudio()) .setAudioTrackType(itagItem.getAudioTrackType())
.setItagItem(itagItem); .setItagItem(itagItem);
if (streamType == StreamType.LIVE_STREAM if (streamType == StreamType.LIVE_STREAM
@ -1484,16 +1487,25 @@ public class YoutubeStreamExtractor extends StreamExtractor {
itagItem.setAudioLocale(LocaleCompat.forLanguageTag( itagItem.setAudioLocale(LocaleCompat.forLanguageTag(
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter))); audioTrackId.substring(0, audioTrackIdLastLocaleCharacter)));
} }
try {
final String atype = Parser.matchGroup1(AUDIO_STREAM_TYPE_REGEX, streamUrl);
switch (atype) {
case "original":
itagItem.setAudioTrackType(AudioTrackType.ORIGINAL);
break;
case "dubbed":
itagItem.setAudioTrackType(AudioTrackType.DUBBED);
break;
case "descriptive":
itagItem.setAudioTrackType(AudioTrackType.DESCRIPTIVE);
break;
}
} catch (final Parser.RegexException ignored) { }
} }
itagItem.setAudioTrackName(formatData.getObject("audioTrack") itagItem.setAudioTrackName(formatData.getObject("audioTrack")
.getString("displayName")); .getString("displayName"));
// Descriptive audio tracks
// This information is also provided as a protobuf object in the formatData
itagItem.setIsDescriptiveAudio(streamUrl.contains("acont%3Ddescriptive")
// Support "decoded" URLs
|| streamUrl.contains("acont=descriptive"));
} }
// YouTube return the content length and the approximate duration as strings // YouTube return the content length and the approximate duration as strings

View File

@ -50,7 +50,8 @@ public final class AudioStream extends Stream {
private final String audioTrackName; private final String audioTrackName;
@Nullable @Nullable
private final Locale audioLocale; private final Locale audioLocale;
private final boolean isDescriptive; @Nullable
private final AudioTrackType audioTrackType;
@Nullable @Nullable
private ItagItem itagItem; private ItagItem itagItem;
@ -75,7 +76,8 @@ public final class AudioStream extends Stream {
private String audioTrackName; private String audioTrackName;
@Nullable @Nullable
private Locale audioLocale; private Locale audioLocale;
private boolean isDescriptive; @Nullable
private AudioTrackType audioTrackType;
@Nullable @Nullable
private ItagItem itagItem; private ItagItem itagItem;
@ -223,25 +225,17 @@ public final class AudioStream extends Stream {
} }
/** /**
* Set whether this {@link AudioStream} is a descriptive audio. * Set the {@link AudioTrackType} of the {@link AudioStream}.
* *
* <p> * <p>
* A descriptive audio is an audio in which descriptions of visual elements of a video are * The default value is {@code null}.
* added in the original audio, with the goal to make a video more accessible to blind and
* visually impaired people.
* </p> * </p>
* *
* <p> * @param audioTrackType the audio track type of the {@link AudioStream}, which can be null
* The default value is {@code false}.
* </p>
*
* @param isDescriptive whether this {@link AudioStream} is a descriptive audio
* @return this {@link Builder} instance * @return this {@link Builder} instance
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
* https://en.wikipedia.org/wiki/Audio_description</a>
*/ */
public Builder setIsDescriptive(final boolean isDescriptive) { public Builder setAudioTrackType(final AudioTrackType audioTrackType) {
this.isDescriptive = isDescriptive; this.audioTrackType = audioTrackType;
return this; return this;
} }
@ -313,7 +307,7 @@ public final class AudioStream extends Stream {
} }
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate, return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
manifestUrl, audioTrackId, audioTrackName, audioLocale, isDescriptive, manifestUrl, audioTrackId, audioTrackName, audioLocale, audioTrackType,
itagItem); itagItem);
} }
} }
@ -350,7 +344,7 @@ public final class AudioStream extends Stream {
@Nullable final String audioTrackId, @Nullable final String audioTrackId,
@Nullable final String audioTrackName, @Nullable final String audioTrackName,
@Nullable final Locale audioLocale, @Nullable final Locale audioLocale,
final boolean isDescriptive, @Nullable final AudioTrackType audioTrackType,
@Nullable final ItagItem itagItem) { @Nullable final ItagItem itagItem) {
super(id, content, isUrl, format, deliveryMethod, manifestUrl); super(id, content, isUrl, format, deliveryMethod, manifestUrl);
if (itagItem != null) { if (itagItem != null) {
@ -368,7 +362,7 @@ public final class AudioStream extends Stream {
this.audioTrackId = audioTrackId; this.audioTrackId = audioTrackId;
this.audioTrackName = audioTrackName; this.audioTrackName = audioTrackName;
this.audioLocale = audioLocale; this.audioLocale = audioLocale;
this.isDescriptive = isDescriptive; this.audioTrackType = audioTrackType;
} }
/** /**
@ -379,7 +373,7 @@ public final class AudioStream extends Stream {
return super.equalStats(cmp) && cmp instanceof AudioStream return super.equalStats(cmp) && cmp instanceof AudioStream
&& averageBitrate == ((AudioStream) cmp).averageBitrate && averageBitrate == ((AudioStream) cmp).averageBitrate
&& Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId) && Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId)
&& isDescriptive == ((AudioStream) cmp).isDescriptive && audioTrackType == ((AudioStream) cmp).audioTrackType
&& Objects.equals(audioLocale, ((AudioStream) cmp).audioLocale); && Objects.equals(audioLocale, ((AudioStream) cmp).audioLocale);
} }
@ -506,21 +500,9 @@ public final class AudioStream extends Stream {
return audioLocale; return audioLocale;
} }
/** @Nullable
* Returns whether this stream is a descriptive audio. public AudioTrackType getAudioTrackType() {
* return audioTrackType;
* <p>
* A descriptive audio is an audio in which descriptions of visual elements of a video are
* added in the original audio, with the goal to make a video more accessible to blind and
* visually impaired people.
* </p>
*
* @return {@code true} this audio stream is a descriptive audio, {@code false} otherwise
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
* https://en.wikipedia.org/wiki/Audio_description</a>
*/
public boolean isDescriptive() {
return isDescriptive;
} }
/** /**

View File

@ -0,0 +1,33 @@
package org.schabi.newpipe.extractor.stream;
/**
* An enum representing the track type of an {@link AudioStream} extracted by a {@link
* StreamExtractor}.
*/
public enum AudioTrackType {
/**
* The original audio track of the video
*/
ORIGINAL,
/**
* Audio track with the original voices replaced, typically in a different language
*
* @see <a href="https://en.wikipedia.org/wiki/Dubbing">
* https://en.wikipedia.org/wiki/Dubbing</a>
*/
DUBBED,
/**
* Descriptive audio
* <p>
* A descriptive audio is an audio in which descriptions of visual elements of a video are
* added in the original audio, with the goal to make a video more accessible to blind and
* visually impaired people.
* </p>
*
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
* https://en.wikipedia.org/wiki/Audio_description</a>
*/
DESCRIPTIVE,
}

View File

@ -8,7 +8,7 @@ public class DownloaderFactory {
public static final String RESOURCE_PATH = "src/test/resources/org/schabi/newpipe/extractor/"; public static final String RESOURCE_PATH = "src/test/resources/org/schabi/newpipe/extractor/";
private static final DownloaderType DEFAULT_DOWNLOADER = DownloaderType.REAL; private static final DownloaderType DEFAULT_DOWNLOADER = DownloaderType.RECORDING;
public static DownloaderType getDownloaderType() { public static DownloaderType getDownloaderType() {
try { try {

View File

@ -8,6 +8,7 @@ import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.Creati
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator; import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator; import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.stream.DeliveryMethod; import org.schabi.newpipe.extractor.stream.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.Stream;
import org.w3c.dom.Document; import org.w3c.dom.Document;
@ -233,7 +234,8 @@ class YoutubeDashManifestCreatorsTest {
private void assertRoleElement(@Nonnull final Document document, private void assertRoleElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) { @Nonnull final ItagItem itagItem) {
final Element element = assertGetElement(document, ROLE, ADAPTATION_SET); final Element element = assertGetElement(document, ROLE, ADAPTATION_SET);
assertAttrEquals(itagItem.isDescriptiveAudio() ? "alternate" : "main", element, "value"); assertAttrEquals(itagItem.getAudioTrackType() == null || itagItem.getAudioTrackType() == AudioTrackType.ORIGINAL
? "main" : "alternate", element, "value");
} }
private void assertRepresentationElement(@Nonnull final Document document, private void assertRepresentationElement(@Nonnull final Document document,

View File

@ -45,6 +45,7 @@ import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
import org.schabi.newpipe.extractor.services.youtube.YoutubeTestsUtils; import org.schabi.newpipe.extractor.services.youtube.YoutubeTestsUtils;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.stream.Description; import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamSegment; import org.schabi.newpipe.extractor.stream.StreamSegment;
@ -581,12 +582,49 @@ public class YoutubeStreamExtractorDefaultTest {
} }
@Test @Test
void testCheckDescriptiveAudio() throws Exception { void testCheckOriginalAudio() throws Exception {
assertFalse(extractor.getAudioStreams().isEmpty()); assertFalse(extractor.getAudioStreams().isEmpty());
assertTrue(extractor.getAudioStreams() assertTrue(extractor.getAudioStreams()
.stream() .stream()
.anyMatch(AudioStream::isDescriptive)); .anyMatch(s -> s.getAudioTrackType() == AudioTrackType.ORIGINAL));
}
@Test
void testCheckDescriptiveAudio() throws Exception {
assertTrue(extractor.getAudioStreams()
.stream()
.anyMatch(s -> s.getAudioTrackType() == AudioTrackType.DESCRIPTIVE));
}
}
public static class DubbedAudio {
private static final String ID = "_8W2LIfl5RE";
private static final String URL = BASE_URL + ID;
private static StreamExtractor extractor;
@BeforeAll
public static void setUp() throws Exception {
YoutubeTestsUtils.ensureStateless();
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "dubbedAudio"));
extractor = YouTube.getStreamExtractor(URL);
extractor.fetchPage();
}
@Test
void testCheckOriginalAudio() throws Exception {
assertFalse(extractor.getAudioStreams().isEmpty());
assertTrue(extractor.getAudioStreams()
.stream()
.anyMatch(s -> s.getAudioTrackType() == AudioTrackType.ORIGINAL));
}
@Test
void testCheckDubbedAudio() throws Exception {
assertTrue(extractor.getAudioStreams()
.stream()
.anyMatch(s -> s.getAudioTrackType() == AudioTrackType.DUBBED));
} }
} }
} }

View File

@ -0,0 +1,73 @@
{
"request": {
"httpMethod": "GET",
"url": "https://www.youtube.com/iframe_api",
"headers": {
"Accept-Language": [
"en-GB, en;q\u003d0.9"
]
},
"localization": {
"languageCode": "en",
"countryCode": "GB"
}
},
"response": {
"responseCode": 200,
"responseMessage": "",
"responseHeaders": {
"alt-svc": [
"h3\u003d\":443\"; ma\u003d2592000,h3-29\u003d\":443\"; ma\u003d2592000"
],
"cache-control": [
"private, max-age\u003d0"
],
"content-type": [
"text/javascript; charset\u003dutf-8"
],
"cross-origin-opener-policy-report-only": [
"same-origin; report-to\u003d\"youtube_main\""
],
"cross-origin-resource-policy": [
"cross-origin"
],
"date": [
"Sun, 19 Mar 2023 01:11:13 GMT"
],
"expires": [
"Sun, 19 Mar 2023 01:11:13 GMT"
],
"p3p": [
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
],
"permissions-policy": [
"ch-ua-arch\u003d*, ch-ua-bitness\u003d*, ch-ua-full-version\u003d*, ch-ua-full-version-list\u003d*, ch-ua-model\u003d*, ch-ua-wow64\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*"
],
"report-to": [
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
],
"server": [
"ESF"
],
"set-cookie": [
"YSC\u003d57sN-x5lva8; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003d1VebVMxRMHc; Domain\u003d.youtube.com; Expires\u003dFri, 15-Sep-2023 01:11:13 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+346; expires\u003dTue, 18-Mar-2025 01:11:13 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
],
"strict-transport-security": [
"max-age\u003d31536000"
],
"x-content-type-options": [
"nosniff"
],
"x-frame-options": [
"SAMEORIGIN"
],
"x-xss-protection": [
"0"
]
},
"responseBody": "var scriptUrl \u003d \u0027https:\\/\\/www.youtube.com\\/s\\/player\\/59acb1f3\\/www-widgetapi.vflset\\/www-widgetapi.js\u0027;try{var ttPolicy\u003dwindow.trustedTypes.createPolicy(\"youtube-widget-api\",{createScriptURL:function(x){return x}});scriptUrl\u003dttPolicy.createScriptURL(scriptUrl)}catch(e){}var YT;if(!window[\"YT\"])YT\u003d{loading:0,loaded:0};var YTConfig;if(!window[\"YTConfig\"])YTConfig\u003d{\"host\":\"https://www.youtube.com\"};\nif(!YT.loading){YT.loading\u003d1;(function(){var l\u003d[];YT.ready\u003dfunction(f){if(YT.loaded)f();else l.push(f)};window.onYTReady\u003dfunction(){YT.loaded\u003d1;for(var i\u003d0;i\u003cl.length;i++)try{l[i]()}catch(e$0){}};YT.setConfig\u003dfunction(c){for(var k in c)if(c.hasOwnProperty(k))YTConfig[k]\u003dc[k]};var a\u003ddocument.createElement(\"script\");a.type\u003d\"text/javascript\";a.id\u003d\"www-widgetapi-script\";a.src\u003dscriptUrl;a.async\u003dtrue;var c\u003ddocument.currentScript;if(c){var n\u003dc.nonce||c.getAttribute(\"nonce\");if(n)a.setAttribute(\"nonce\",n)}var b\u003d\ndocument.getElementsByTagName(\"script\")[0];b.parentNode.insertBefore(a,b)})()};\n",
"latestUrl": "https://www.youtube.com/iframe_api"
}
}

View File

@ -0,0 +1,82 @@
{
"request": {
"httpMethod": "GET",
"url": "https://www.youtube.com/sw.js",
"headers": {
"Referer": [
"https://www.youtube.com"
],
"Origin": [
"https://www.youtube.com"
],
"Accept-Language": [
"en-GB, en;q\u003d0.9"
]
},
"localization": {
"languageCode": "en",
"countryCode": "GB"
}
},
"response": {
"responseCode": 200,
"responseMessage": "",
"responseHeaders": {
"access-control-allow-credentials": [
"true"
],
"access-control-allow-origin": [
"https://www.youtube.com"
],
"alt-svc": [
"h3\u003d\":443\"; ma\u003d2592000,h3-29\u003d\":443\"; ma\u003d2592000"
],
"cache-control": [
"private, max-age\u003d0"
],
"content-type": [
"text/javascript; charset\u003dutf-8"
],
"cross-origin-opener-policy-report-only": [
"same-origin; report-to\u003d\"youtube_main\""
],
"date": [
"Sun, 19 Mar 2023 01:11:14 GMT"
],
"expires": [
"Sun, 19 Mar 2023 01:11:14 GMT"
],
"p3p": [
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
],
"permissions-policy": [
"ch-ua-arch\u003d*, ch-ua-bitness\u003d*, ch-ua-full-version\u003d*, ch-ua-full-version-list\u003d*, ch-ua-model\u003d*, ch-ua-wow64\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*"
],
"report-to": [
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
],
"server": [
"ESF"
],
"set-cookie": [
"YSC\u003dozmmoKNftY8; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003d; Domain\u003d.youtube.com; Expires\u003dMon, 22-Jun-2020 01:11:14 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+665; expires\u003dTue, 18-Mar-2025 01:11:14 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
],
"strict-transport-security": [
"max-age\u003d31536000"
],
"x-content-type-options": [
"nosniff"
],
"x-frame-options": [
"SAMEORIGIN"
],
"x-xss-protection": [
"0"
]
},
"responseBody": "\n self.addEventListener(\u0027install\u0027, event \u003d\u003e {\n event.waitUntil(self.skipWaiting());\n });\n self.addEventListener(\u0027activate\u0027, event \u003d\u003e {\n event.waitUntil(\n self.clients.claim().then(() \u003d\u003e self.registration.unregister()));\n });\n ",
"latestUrl": "https://www.youtube.com/sw.js"
}
}