From b3c620f0d8b19ddc532fe90dec7b813aea2caa29 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 28 May 2022 00:26:53 +0200 Subject: [PATCH] Apply code review and Streams rework --- .../MediaCCCLiveStreamExtractor.java | 41 ++- .../MediaCCCLiveStreamMapperDTO.java | 29 -- .../extractor/services/youtube/ItagItem.java | 6 +- .../YoutubeDashManifestCreatorsUtils.java | 274 +++++++----------- .../YoutubeOtfDashManifestCreator.java | 30 +- ...ePostLiveStreamDvrDashManifestCreator.java | 29 +- ...YoutubeProgressiveDashManifestCreator.java | 60 ++-- .../extractors/YoutubeStreamExtractor.java | 106 +++---- .../newpipe/extractor/stream/Stream.java | 19 +- .../utils/ManifestCreatorCacheTest.java | 5 + 10 files changed, 248 insertions(+), 351 deletions(-) delete mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamMapperDTO.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamExtractor.java index da03e20e..c761b33a 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamExtractor.java @@ -159,11 +159,11 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor { return getStreams("audio", dto -> { final AudioStream.Builder builder = new AudioStream.Builder() - .setId(dto.getUrlValue().getString("tech", ID_UNKNOWN)) - .setContent(dto.getUrlValue().getString(URL), true) + .setId(dto.urlValue.getString("tech", ID_UNKNOWN)) + .setContent(dto.urlValue.getString(URL), true) .setAverageBitrate(UNKNOWN_BITRATE); - if ("hls".equals(dto.getUrlKey())) { + if ("hls".equals(dto.urlKey)) { // We don't know with the type string what media format will // have HLS streams. // However, the tech string may contain some information @@ -172,7 +172,7 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor { .build(); } - return builder.setMediaFormat(MediaFormat.getFromSuffix(dto.getUrlKey())) + return builder.setMediaFormat(MediaFormat.getFromSuffix(dto.urlKey)) .build(); }); } @@ -181,15 +181,15 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor { public List getVideoStreams() throws IOException, ExtractionException { return getStreams("video", dto -> { - final JsonArray videoSize = dto.getStreamJsonObj().getArray("videoSize"); + final JsonArray videoSize = dto.streamJsonObj.getArray("videoSize"); final VideoStream.Builder builder = new VideoStream.Builder() - .setId(dto.getUrlValue().getString("tech", ID_UNKNOWN)) - .setContent(dto.getUrlValue().getString(URL), true) + .setId(dto.urlValue.getString("tech", ID_UNKNOWN)) + .setContent(dto.urlValue.getString(URL), true) .setIsVideoOnly(false) .setResolution(videoSize.getInt(0) + "x" + videoSize.getInt(1)); - if ("hls".equals(dto.getUrlKey())) { + if ("hls".equals(dto.urlKey)) { // We don't know with the type string what media format will // have HLS streams. // However, the tech string may contain some information @@ -198,11 +198,32 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor { .build(); } - return builder.setMediaFormat(MediaFormat.getFromSuffix(dto.getUrlKey())) + return builder.setMediaFormat(MediaFormat.getFromSuffix(dto.urlKey)) .build(); }); } + + /** + * This is just an internal class used in {@link #getStreams(String, Function)} to tie together + * the stream json object, its URL key and its URL value. An object of this class would be + * temporary and the three values it holds would be converted to a proper {@link Stream} + * object based on the wanted stream type. + */ + private static final class MediaCCCLiveStreamMapperDTO { + final JsonObject streamJsonObj; + final String urlKey; + final JsonObject urlValue; + + MediaCCCLiveStreamMapperDTO(final JsonObject streamJsonObj, + final String urlKey, + final JsonObject urlValue) { + this.streamJsonObj = streamJsonObj; + this.urlKey = urlKey; + this.urlValue = urlValue; + } + } + private List getStreams( @Nonnull final String streamType, @Nonnull final Function converter) { @@ -220,7 +241,7 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor { e.getKey(), (JsonObject) e.getValue()))) // The DASH manifest will be extracted with getDashMpdUrl - .filter(dto -> !"dash".equals(dto.getUrlKey())) + .filter(dto -> !"dash".equals(dto.urlKey)) // Convert .map(converter) .collect(Collectors.toList()); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamMapperDTO.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamMapperDTO.java deleted file mode 100644 index c06ef736..00000000 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamMapperDTO.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.schabi.newpipe.extractor.services.media_ccc.extractors; - -import com.grack.nanojson.JsonObject; - -final class MediaCCCLiveStreamMapperDTO { - private final JsonObject streamJsonObj; - private final String urlKey; - private final JsonObject urlValue; - - MediaCCCLiveStreamMapperDTO(final JsonObject streamJsonObj, - final String urlKey, - final JsonObject urlValue) { - this.streamJsonObj = streamJsonObj; - this.urlKey = urlKey; - this.urlValue = urlValue; - } - - JsonObject getStreamJsonObj() { - return streamJsonObj; - } - - String getUrlKey() { - return urlKey; - } - - JsonObject getUrlValue() { - return urlValue; - } -} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java index fa6e9732..e0ff09a6 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java @@ -409,11 +409,7 @@ public class ItagItem implements Serializable { * @param sampleRate the sample rate of an audio itag */ public void setSampleRate(final int sampleRate) { - if (sampleRate > 0) { - this.sampleRate = sampleRate; - } else { - this.sampleRate = SAMPLE_RATE_UNKNOWN; - } + this.sampleRate = sampleRate > 0 ? sampleRate : SAMPLE_RATE_UNKNOWN; } /** diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeDashManifestCreatorsUtils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeDashManifestCreatorsUtils.java index ec876c67..5c45f65d 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeDashManifestCreatorsUtils.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeDashManifestCreatorsUtils.java @@ -97,6 +97,25 @@ public final class YoutubeDashManifestCreatorsUtils { public static final String SEGMENT_BASE = "SegmentBase"; public static final String INITIALIZATION = "Initialization"; + /** + * Create an attribute with {@link Document#createAttribute(String)}, assign to it the provided + * name and value, then add it to the provided element using {@link + * Element#setAttributeNode(Attr)}. + * + * @param element element to which to add the created node + * @param doc document to use to create the attribute + * @param name name of the attribute + * @param value value of the attribute, will be set using {@link Attr#setValue(String)} + */ + public static void setAttribute(final Element element, + final Document doc, + final String name, + final String value) { + final Attr attr = doc.createAttribute(name); + attr.setValue(value); + element.setAttributeNode(attr); + } + /** * Generate a {@link Document} with common manifest creator elements added to it. * @@ -123,17 +142,17 @@ public final class YoutubeDashManifestCreatorsUtils { public static Document generateDocumentAndDoCommonElementsGeneration( @Nonnull final ItagItem itagItem, final long streamDuration) throws CreationException { - final Document document = generateDocumentAndMpdElement(streamDuration); + final Document doc = generateDocumentAndMpdElement(streamDuration); - generatePeriodElement(document); - generateAdaptationSetElement(document, itagItem); - generateRoleElement(document); - generateRepresentationElement(document, itagItem); + generatePeriodElement(doc); + generateAdaptationSetElement(doc, itagItem); + generateRoleElement(doc); + generateRepresentationElement(doc, itagItem); if (itagItem.itagType == ItagItem.ItagType.AUDIO) { - generateAudioChannelConfigurationElement(document, itagItem); + generateAudioChannelConfigurationElement(doc, itagItem); } - return document; + return doc; } /** @@ -161,46 +180,25 @@ public final class YoutubeDashManifestCreatorsUtils { public static Document generateDocumentAndMpdElement(final long duration) throws CreationException { try { - final Document document = newDocument(); + final Document doc = newDocument(); - final Element mpdElement = document.createElement(MPD); - document.appendChild(mpdElement); + final Element mpdElement = doc.createElement(MPD); + doc.appendChild(mpdElement); - final Attr xmlnsXsiAttribute = document.createAttribute("xmlns:xsi"); - xmlnsXsiAttribute.setValue("http://www.w3.org/2001/XMLSchema-instance"); - mpdElement.setAttributeNode(xmlnsXsiAttribute); + setAttribute(mpdElement, doc, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + setAttribute(mpdElement, doc, "xmlns", "urn:mpeg:DASH:schema:MPD:2011"); + setAttribute(mpdElement, doc, "xsi:schemaLocation", + "urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"); + setAttribute(mpdElement, doc, "minBufferTime", "PT1.500S"); + setAttribute(mpdElement, doc, "profiles", "urn:mpeg:dash:profile:full:2011"); + setAttribute(mpdElement, doc, "type", "static"); + setAttribute(mpdElement, doc, "mediaPresentationDuration", + String.format(Locale.ENGLISH, "PT%.3fS", duration / 1000.0)); - final Attr xmlns = document.createAttribute("xmlns"); - xmlns.setValue("urn:mpeg:DASH:schema:MPD:2011"); - mpdElement.setAttributeNode(xmlns); - - final Attr xsiSchemaLocationAttribute = document.createAttribute("xsi:schemaLocation"); - xsiSchemaLocationAttribute.setValue("urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"); - mpdElement.setAttributeNode(xsiSchemaLocationAttribute); - - final Attr minBufferTimeAttribute = document.createAttribute("minBufferTime"); - minBufferTimeAttribute.setValue("PT1.500S"); - mpdElement.setAttributeNode(minBufferTimeAttribute); - - final Attr profilesAttribute = document.createAttribute("profiles"); - profilesAttribute.setValue("urn:mpeg:dash:profile:full:2011"); - mpdElement.setAttributeNode(profilesAttribute); - - final Attr typeAttribute = document.createAttribute("type"); - typeAttribute.setValue("static"); - mpdElement.setAttributeNode(typeAttribute); - - final Attr mediaPresentationDurationAttribute = document.createAttribute( - "mediaPresentationDuration"); - final String durationSeconds = String.format(Locale.ENGLISH, "%.3f", - duration / 1000.0); - mediaPresentationDurationAttribute.setValue("PT" + durationSeconds + "S"); - mpdElement.setAttributeNode(mediaPresentationDurationAttribute); - - return document; + return doc; } catch (final Exception e) { throw new CreationException( - "Could not generate the DASH manifest or append the MPD document to it", e); + "Could not generate the DASH manifest or append the MPD doc to it", e); } } @@ -212,14 +210,13 @@ public final class YoutubeDashManifestCreatorsUtils { * {@link #generateDocumentAndMpdElement(long)}. *

* - * @param document the {@link Document} on which the the {@code } element will be - * appended + * @param doc the {@link Document} on which the the {@code } element will be appended */ - public static void generatePeriodElement(@Nonnull final Document document) + public static void generatePeriodElement(@Nonnull final Document doc) throws CreationException { try { - final Element mpdElement = (Element) document.getElementsByTagName(MPD).item(0); - final Element periodElement = document.createElement(PERIOD); + final Element mpdElement = (Element) doc.getElementsByTagName(MPD).item(0); + final Element periodElement = doc.createElement(PERIOD); mpdElement.appendChild(periodElement); } catch (final DOMException e) { throw CreationException.couldNotAddElement(PERIOD, e); @@ -235,21 +232,18 @@ public final class YoutubeDashManifestCreatorsUtils { * {@link #generatePeriodElement(Document)}. *

* - * @param document the {@link Document} on which the {@code } element will be - * appended + * @param doc the {@link Document} on which the {@code } element will be appended * @param itagItem the {@link ItagItem} corresponding to the stream, which must not be null */ - public static void generateAdaptationSetElement(@Nonnull final Document document, + public static void generateAdaptationSetElement(@Nonnull final Document doc, @Nonnull final ItagItem itagItem) throws CreationException { try { - final Element periodElement = (Element) document.getElementsByTagName(PERIOD) + final Element periodElement = (Element) doc.getElementsByTagName(PERIOD) .item(0); - final Element adaptationSetElement = document.createElement(ADAPTATION_SET); + final Element adaptationSetElement = doc.createElement(ADAPTATION_SET); - final Attr idAttribute = document.createAttribute("id"); - idAttribute.setValue("0"); - adaptationSetElement.setAttributeNode(idAttribute); + setAttribute(adaptationSetElement, doc, "id", "0"); final MediaFormat mediaFormat = itagItem.getMediaFormat(); if (mediaFormat == null || isNullOrEmpty(mediaFormat.getMimeType())) { @@ -257,14 +251,8 @@ public final class YoutubeDashManifestCreatorsUtils { "the MediaFormat or its mime type is null or empty"); } - final Attr mimeTypeAttribute = document.createAttribute("mimeType"); - mimeTypeAttribute.setValue(mediaFormat.getMimeType()); - adaptationSetElement.setAttributeNode(mimeTypeAttribute); - - final Attr subsegmentAlignmentAttribute = document.createAttribute( - "subsegmentAlignment"); - subsegmentAlignmentAttribute.setValue("true"); - adaptationSetElement.setAttributeNode(subsegmentAlignmentAttribute); + setAttribute(adaptationSetElement, doc, "mimeType", mediaFormat.getMimeType()); + setAttribute(adaptationSetElement, doc, "subsegmentAlignment", "true"); periodElement.appendChild(adaptationSetElement); } catch (final DOMException e) { @@ -289,23 +277,17 @@ public final class YoutubeDashManifestCreatorsUtils { * {@link #generateAdaptationSetElement(Document, ItagItem)}). *

* - * @param document the {@link Document} on which the the {@code } element will be - * appended + * @param doc the {@link Document} on which the the {@code } element will be appended */ - public static void generateRoleElement(@Nonnull final Document document) + public static void generateRoleElement(@Nonnull final Document doc) throws CreationException { try { - final Element adaptationSetElement = (Element) document.getElementsByTagName( + final Element adaptationSetElement = (Element) doc.getElementsByTagName( ADAPTATION_SET).item(0); - final Element roleElement = document.createElement(ROLE); + final Element roleElement = doc.createElement(ROLE); - final Attr schemeIdUriAttribute = document.createAttribute("schemeIdUri"); - schemeIdUriAttribute.setValue("urn:mpeg:DASH:role:2011"); - roleElement.setAttributeNode(schemeIdUriAttribute); - - final Attr valueAttribute = document.createAttribute("value"); - valueAttribute.setValue("main"); - roleElement.setAttributeNode(valueAttribute); + setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011"); + setAttribute(roleElement, doc, "value", "main"); adaptationSetElement.appendChild(roleElement); } catch (final DOMException e) { @@ -322,56 +304,43 @@ public final class YoutubeDashManifestCreatorsUtils { * {@link #generateAdaptationSetElement(Document, ItagItem)}). *

* - * @param document the {@link Document} on which the the {@code } element will - * be appended + * @param doc the {@link Document} on which the the {@code } element will be + * appended * @param itagItem the {@link ItagItem} to use, which must not be null */ - public static void generateRepresentationElement(@Nonnull final Document document, + public static void generateRepresentationElement(@Nonnull final Document doc, @Nonnull final ItagItem itagItem) throws CreationException { try { - final Element adaptationSetElement = (Element) document.getElementsByTagName( + final Element adaptationSetElement = (Element) doc.getElementsByTagName( ADAPTATION_SET).item(0); - final Element representationElement = document.createElement(REPRESENTATION); + final Element representationElement = doc.createElement(REPRESENTATION); final int id = itagItem.id; if (id <= 0) { throw CreationException.couldNotAddElement(REPRESENTATION, "the id of the ItagItem is <= 0"); } - final Attr idAttribute = document.createAttribute("id"); - idAttribute.setValue(String.valueOf(id)); - representationElement.setAttributeNode(idAttribute); + setAttribute(representationElement, doc, "id", String.valueOf(id)); final String codec = itagItem.getCodec(); if (isNullOrEmpty(codec)) { throw CreationException.couldNotAddElement(ADAPTATION_SET, "the codec value of the ItagItem is null or empty"); } - final Attr codecsAttribute = document.createAttribute("codecs"); - codecsAttribute.setValue(codec); - representationElement.setAttributeNode(codecsAttribute); - - final Attr startWithSAPAttribute = document.createAttribute("startWithSAP"); - startWithSAPAttribute.setValue("1"); - representationElement.setAttributeNode(startWithSAPAttribute); - - final Attr maxPlayoutRateAttribute = document.createAttribute("maxPlayoutRate"); - maxPlayoutRateAttribute.setValue("1"); - representationElement.setAttributeNode(maxPlayoutRateAttribute); + setAttribute(representationElement, doc, "codecs", codec); + setAttribute(representationElement, doc, "startWithSAP", "1"); + setAttribute(representationElement, doc, "maxPlayoutRate", "1"); final int bitrate = itagItem.getBitrate(); if (bitrate <= 0) { throw CreationException.couldNotAddElement(REPRESENTATION, "the bitrate of the ItagItem is <= 0"); } - final Attr bandwidthAttribute = document.createAttribute("bandwidth"); - bandwidthAttribute.setValue(String.valueOf(bitrate)); - representationElement.setAttributeNode(bandwidthAttribute); + setAttribute(representationElement, doc, "bandwidth", String.valueOf(bitrate)); - final ItagItem.ItagType itagType = itagItem.itagType; - - if (itagType == ItagItem.ItagType.VIDEO || itagType == ItagItem.ItagType.VIDEO_ONLY) { + if (itagItem.itagType == ItagItem.ItagType.VIDEO + || itagItem.itagType == ItagItem.ItagType.VIDEO_ONLY) { final int height = itagItem.getHeight(); final int width = itagItem.getWidth(); if (height <= 0 && width <= 0) { @@ -380,25 +349,19 @@ public final class YoutubeDashManifestCreatorsUtils { } if (width > 0) { - final Attr widthAttribute = document.createAttribute("width"); - widthAttribute.setValue(String.valueOf(width)); - representationElement.setAttributeNode(widthAttribute); + setAttribute(representationElement, doc, "width", String.valueOf(width)); } - - final Attr heightAttribute = document.createAttribute("height"); - heightAttribute.setValue(String.valueOf(itagItem.getHeight())); - representationElement.setAttributeNode(heightAttribute); + setAttribute(representationElement, doc, "height", + String.valueOf(itagItem.getHeight())); final int fps = itagItem.getFps(); if (fps > 0) { - final Attr frameRateAttribute = document.createAttribute("frameRate"); - frameRateAttribute.setValue(String.valueOf(fps)); - representationElement.setAttributeNode(frameRateAttribute); + setAttribute(representationElement, doc, "frameRate", String.valueOf(fps)); } } - if (itagType == ItagItem.ItagType.AUDIO && itagItem.getSampleRate() > 0) { - final Attr audioSamplingRateAttribute = document.createAttribute( + if (itagItem.itagType == ItagItem.ItagType.AUDIO && itagItem.getSampleRate() > 0) { + final Attr audioSamplingRateAttribute = doc.createAttribute( "audioSamplingRate"); audioSamplingRateAttribute.setValue(String.valueOf(itagItem.getSampleRate())); } @@ -433,32 +396,28 @@ public final class YoutubeDashManifestCreatorsUtils { * {@link #generateRepresentationElement(Document, ItagItem)}). *

* - * @param document the {@link Document} on which the {@code } - * element will be appended + * @param doc the {@link Document} on which the {@code } element will + * be appended * @param itagItem the {@link ItagItem} to use, which must not be null */ public static void generateAudioChannelConfigurationElement( - @Nonnull final Document document, + @Nonnull final Document doc, @Nonnull final ItagItem itagItem) throws CreationException { try { - final Element representationElement = (Element) document.getElementsByTagName( + final Element representationElement = (Element) doc.getElementsByTagName( REPRESENTATION).item(0); - final Element audioChannelConfigurationElement = document.createElement( + final Element audioChannelConfigurationElement = doc.createElement( AUDIO_CHANNEL_CONFIGURATION); - final Attr schemeIdUriAttribute = document.createAttribute("schemeIdUri"); - schemeIdUriAttribute.setValue( + setAttribute(audioChannelConfigurationElement, doc, "schemeIdUri", "urn:mpeg:dash:23003:3:audio_channel_configuration:2011"); - audioChannelConfigurationElement.setAttributeNode(schemeIdUriAttribute); - final Attr valueAttribute = document.createAttribute("value"); - final int audioChannels = itagItem.getAudioChannels(); - if (audioChannels <= 0) { + if (itagItem.getAudioChannels() <= 0) { throw new CreationException("the number of audioChannels in the ItagItem is <= 0: " - + audioChannels); + + itagItem.getAudioChannels()); } - valueAttribute.setValue(String.valueOf(itagItem.getAudioChannels())); - audioChannelConfigurationElement.setAttributeNode(valueAttribute); + setAttribute(audioChannelConfigurationElement, doc, "value", + String.valueOf(itagItem.getAudioChannels())); representationElement.appendChild(audioChannelConfigurationElement); } catch (final DOMException e) { @@ -467,22 +426,22 @@ public final class YoutubeDashManifestCreatorsUtils { } /** - * Convert a DASH manifest {@link Document document} to a string and cache it. + * Convert a DASH manifest {@link Document doc} to a string and cache it. * * @param originalBaseStreamingUrl the original base URL of the stream - * @param document the document to be converted + * @param doc the doc to be converted * @param manifestCreatorCache the {@link ManifestCreatorCache} on which store the string * generated - * @return the DASH manifest {@link Document document} converted to a string + * @return the DASH manifest {@link Document doc} converted to a string */ public static String buildAndCacheResult( @Nonnull final String originalBaseStreamingUrl, - @Nonnull final Document document, + @Nonnull final Document doc, @Nonnull final ManifestCreatorCache manifestCreatorCache) throws CreationException { try { - final String documentXml = documentToXml(document); + final String documentXml = documentToXml(doc); manifestCreatorCache.put(originalBaseStreamingUrl, documentXml); return documentXml; } catch (final Exception e) { @@ -517,13 +476,13 @@ public final class YoutubeDashManifestCreatorsUtils { * {@link #generateRepresentationElement(Document, ItagItem)}). *

* - * @param document the {@link Document} on which the {@code } element will + * @param doc the {@link Document} on which the {@code } element will * be appended * @param baseUrl the base URL of the OTF/post-live-DVR stream * @param deliveryType the stream {@link DeliveryType delivery type}, which must be either * {@link DeliveryType#OTF OTF} or {@link DeliveryType#LIVE LIVE} */ - public static void generateSegmentTemplateElement(@Nonnull final Document document, + public static void generateSegmentTemplateElement(@Nonnull final Document doc, @Nonnull final String baseUrl, final DeliveryType deliveryType) throws CreationException { @@ -533,32 +492,22 @@ public final class YoutubeDashManifestCreatorsUtils { } try { - final Element representationElement = (Element) document.getElementsByTagName( + final Element representationElement = (Element) doc.getElementsByTagName( REPRESENTATION).item(0); - final Element segmentTemplateElement = document.createElement(SEGMENT_TEMPLATE); + final Element segmentTemplateElement = doc.createElement(SEGMENT_TEMPLATE); - final Attr startNumberAttribute = document.createAttribute("startNumber"); - final boolean isDeliveryTypeLive = deliveryType == DeliveryType.LIVE; // The first sequence of post DVR streams is the beginning of the video stream and not // an initialization segment - final String startNumberValue = isDeliveryTypeLive ? "0" : "1"; - startNumberAttribute.setValue(startNumberValue); - segmentTemplateElement.setAttributeNode(startNumberAttribute); - - final Attr timescaleAttribute = document.createAttribute("timescale"); - timescaleAttribute.setValue("1000"); - segmentTemplateElement.setAttributeNode(timescaleAttribute); + setAttribute(segmentTemplateElement, doc, "startNumber", + deliveryType == DeliveryType.LIVE ? "0" : "1"); + setAttribute(segmentTemplateElement, doc, "timescale", "1000"); // Post-live-DVR/ended livestreams streams don't require an initialization sequence - if (!isDeliveryTypeLive) { - final Attr initializationAttribute = document.createAttribute("initialization"); - initializationAttribute.setValue(baseUrl + SQ_0); - segmentTemplateElement.setAttributeNode(initializationAttribute); + if (deliveryType != DeliveryType.LIVE) { + setAttribute(segmentTemplateElement, doc, "initialization", baseUrl + SQ_0); } - final Attr mediaAttribute = document.createAttribute("media"); - mediaAttribute.setValue(baseUrl + "&sq=$Number$"); - segmentTemplateElement.setAttributeNode(mediaAttribute); + setAttribute(segmentTemplateElement, doc, "media", baseUrl + "&sq=$Number$"); representationElement.appendChild(segmentTemplateElement); } catch (final DOMException e) { @@ -575,15 +524,15 @@ public final class YoutubeDashManifestCreatorsUtils { * {@link #generateSegmentTemplateElement(Document, String, DeliveryType)}. *

* - * @param document the {@link Document} on which the the {@code } element will - * be appended + * @param doc the {@link Document} on which the the {@code } element will be + * appended */ - public static void generateSegmentTimelineElement(@Nonnull final Document document) + public static void generateSegmentTimelineElement(@Nonnull final Document doc) throws CreationException { try { - final Element segmentTemplateElement = (Element) document.getElementsByTagName( + final Element segmentTemplateElement = (Element) doc.getElementsByTagName( SEGMENT_TEMPLATE).item(0); - final Element segmentTimelineElement = document.createElement(SEGMENT_TIMELINE); + final Element segmentTimelineElement = doc.createElement(SEGMENT_TIMELINE); segmentTemplateElement.appendChild(segmentTimelineElement); } catch (final DOMException e) { @@ -672,8 +621,7 @@ public final class YoutubeDashManifestCreatorsUtils { // supported by all platforms (like the Android implementation) } - final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); - return documentBuilder.newDocument(); + return documentBuilderFactory.newDocumentBuilder().newDocument(); } /** @@ -681,13 +629,13 @@ public final class YoutubeDashManifestCreatorsUtils { * support setting {@link XMLConstants#ACCESS_EXTERNAL_DTD} and * {@link XMLConstants#ACCESS_EXTERNAL_SCHEMA} in {@link TransformerFactory} instances. * - * @param document the document to convert, which must have been created using - * {@link #newDocument()} to properly prevent XXE attacks - * @return the document converted to an XML string, making sure there can't be XXE attacks + * @param doc the doc to convert, which must have been created using {@link #newDocument()} to + * properly prevent XXE attacks + * @return the doc converted to an XML string, making sure there can't be XXE attacks */ // Sonar warning is suppressed because it is still shown even if we apply its solution @SuppressWarnings("squid:S2755") - private static String documentToXml(@Nonnull final Document document) + private static String documentToXml(@Nonnull final Document doc) throws TransformerException { final TransformerFactory transformerFactory = TransformerFactory.newInstance(); @@ -705,7 +653,7 @@ public final class YoutubeDashManifestCreatorsUtils { transformer.setOutputProperty(OutputKeys.STANDALONE, "no"); final StringWriter result = new StringWriter(); - transformer.transform(new DOMSource(document), new StreamResult(result)); + transformer.transform(new DOMSource(doc), new StreamResult(result)); return result.toString(); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeOtfDashManifestCreator.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeOtfDashManifestCreator.java index f76d2235..8161b526 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeOtfDashManifestCreator.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeOtfDashManifestCreator.java @@ -5,7 +5,6 @@ import org.schabi.newpipe.extractor.services.youtube.DeliveryType; import org.schabi.newpipe.extractor.services.youtube.ItagItem; import org.schabi.newpipe.extractor.utils.ManifestCreatorCache; import org.schabi.newpipe.extractor.utils.Utils; -import org.w3c.dom.Attr; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -23,6 +22,7 @@ import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.generateSegmentTemplateElement; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.generateSegmentTimelineElement; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.getInitializationResponse; +import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.setAttribute; import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING; import static org.schabi.newpipe.extractor.utils.Utils.isBlank; @@ -148,14 +148,14 @@ public final class YoutubeOtfDashManifestCreator { streamDuration = durationSecondsFallback * 1000; } - final Document document = generateDocumentAndDoCommonElementsGeneration(itagItem, + final Document doc = generateDocumentAndDoCommonElementsGeneration(itagItem, streamDuration); - generateSegmentTemplateElement(document, realOtfBaseStreamingUrl, DeliveryType.OTF); - generateSegmentTimelineElement(document); - generateSegmentElementsForOtfStreams(segmentDuration, document); + generateSegmentTemplateElement(doc, realOtfBaseStreamingUrl, DeliveryType.OTF); + generateSegmentTimelineElement(doc); + generateSegmentElementsForOtfStreams(segmentDuration, doc); - return buildAndCacheResult(otfBaseStreamingUrl, document, OTF_STREAMS_CACHE); + return buildAndCacheResult(otfBaseStreamingUrl, doc, OTF_STREAMS_CACHE); } /** @@ -192,17 +192,18 @@ public final class YoutubeOtfDashManifestCreator { * * @param segmentDurations the sequences "length" or "length(r=repeat_count" extracted with the * regular expressions - * @param document the {@link Document} on which the {@code } elements will be appended + * @param doc the {@link Document} on which the {@code } elements will be + * appended */ private static void generateSegmentElementsForOtfStreams( @Nonnull final String[] segmentDurations, - @Nonnull final Document document) throws CreationException { + @Nonnull final Document doc) throws CreationException { try { - final Element segmentTimelineElement = (Element) document.getElementsByTagName( + final Element segmentTimelineElement = (Element) doc.getElementsByTagName( SEGMENT_TIMELINE).item(0); for (final String segmentDuration : segmentDurations) { - final Element sElement = document.createElement("S"); + final Element sElement = doc.createElement("S"); final String[] segmentLengthRepeat = segmentDuration.split("\\(r="); // make sure segmentLengthRepeat[0], which is the length, is convertible to int @@ -212,14 +213,9 @@ public final class YoutubeOtfDashManifestCreator { if (segmentLengthRepeat.length > 1) { final int segmentRepeatCount = Integer.parseInt( Utils.removeNonDigitCharacters(segmentLengthRepeat[1])); - final Attr rAttribute = document.createAttribute("r"); - rAttribute.setValue(String.valueOf(segmentRepeatCount)); - sElement.setAttributeNode(rAttribute); + setAttribute(sElement, doc, "r", String.valueOf(segmentRepeatCount)); } - - final Attr dAttribute = document.createAttribute("d"); - dAttribute.setValue(segmentLengthRepeat[0]); - sElement.setAttributeNode(dAttribute); + setAttribute(sElement, doc, "d", segmentLengthRepeat[0]); segmentTimelineElement.appendChild(sElement); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubePostLiveStreamDvrDashManifestCreator.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubePostLiveStreamDvrDashManifestCreator.java index 07ee3b88..43d7e41e 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubePostLiveStreamDvrDashManifestCreator.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubePostLiveStreamDvrDashManifestCreator.java @@ -4,7 +4,6 @@ import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.services.youtube.DeliveryType; import org.schabi.newpipe.extractor.services.youtube.ItagItem; import org.schabi.newpipe.extractor.utils.ManifestCreatorCache; -import org.w3c.dom.Attr; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -23,6 +22,7 @@ import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.generateSegmentTemplateElement; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.generateSegmentTimelineElement; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.getInitializationResponse; +import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.setAttribute; import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; @@ -159,15 +159,15 @@ public final class YoutubePostLiveStreamDvrDashManifestCreator { streamDuration = durationSecondsFallback; } - final Document document = generateDocumentAndDoCommonElementsGeneration(itagItem, + final Document doc = generateDocumentAndDoCommonElementsGeneration(itagItem, streamDuration); - generateSegmentTemplateElement(document, realPostLiveStreamDvrStreamingUrl, + generateSegmentTemplateElement(doc, realPostLiveStreamDvrStreamingUrl, DeliveryType.LIVE); - generateSegmentTimelineElement(document); - generateSegmentElementForPostLiveDvrStreams(document, targetDurationSec, segmentCount); + generateSegmentTimelineElement(doc); + generateSegmentElementForPostLiveDvrStreams(doc, targetDurationSec, segmentCount); - return buildAndCacheResult(postLiveStreamDvrStreamingUrl, document, + return buildAndCacheResult(postLiveStreamDvrStreamingUrl, doc, POST_LIVE_DVR_STREAMS_CACHE); } @@ -190,7 +190,7 @@ public final class YoutubePostLiveStreamDvrDashManifestCreator { * {@code } *

* - * @param document the {@link Document} on which the {@code } element will + * @param doc the {@link Document} on which the {@code } element will * be appended * @param targetDurationSeconds the {@code targetDurationSec} value from YouTube player * response's stream @@ -198,21 +198,16 @@ public final class YoutubePostLiveStreamDvrDashManifestCreator { * #fromPostLiveStreamDvrStreamingUrl(String, ItagItem, int, long)} */ private static void generateSegmentElementForPostLiveDvrStreams( - @Nonnull final Document document, + @Nonnull final Document doc, final int targetDurationSeconds, @Nonnull final String segmentCount) throws CreationException { try { - final Element segmentTimelineElement = (Element) document.getElementsByTagName( + final Element segmentTimelineElement = (Element) doc.getElementsByTagName( SEGMENT_TIMELINE).item(0); - final Element sElement = document.createElement("S"); + final Element sElement = doc.createElement("S"); - final Attr dAttribute = document.createAttribute("d"); - dAttribute.setValue(String.valueOf(targetDurationSeconds * 1000)); - sElement.setAttributeNode(dAttribute); - - final Attr rAttribute = document.createAttribute("r"); - rAttribute.setValue(segmentCount); - sElement.setAttributeNode(rAttribute); + setAttribute(sElement, doc, "d", String.valueOf(targetDurationSeconds * 1000)); + setAttribute(sElement, doc, "r", segmentCount); segmentTimelineElement.appendChild(sElement); } catch (final DOMException e) { diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeProgressiveDashManifestCreator.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeProgressiveDashManifestCreator.java index 2b4ff86a..0f69895b 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeProgressiveDashManifestCreator.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeProgressiveDashManifestCreator.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators; import org.schabi.newpipe.extractor.services.youtube.DeliveryType; import org.schabi.newpipe.extractor.services.youtube.ItagItem; import org.schabi.newpipe.extractor.utils.ManifestCreatorCache; -import org.w3c.dom.Attr; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -18,6 +17,7 @@ import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.SEGMENT_BASE; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.buildAndCacheResult; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.generateDocumentAndDoCommonElementsGeneration; +import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.setAttribute; /** * Class which generates DASH manifests of {@link DeliveryType#PROGRESSIVE YouTube progressive} @@ -100,14 +100,14 @@ public final class YoutubeProgressiveDashManifestCreator { } } - final Document document = generateDocumentAndDoCommonElementsGeneration(itagItem, + final Document doc = generateDocumentAndDoCommonElementsGeneration(itagItem, streamDuration); - generateBaseUrlElement(document, progressiveStreamingBaseUrl); - generateSegmentBaseElement(document, itagItem); - generateInitializationElement(document, itagItem); + generateBaseUrlElement(doc, progressiveStreamingBaseUrl); + generateSegmentBaseElement(doc, itagItem); + generateInitializationElement(doc, itagItem); - return buildAndCacheResult(progressiveStreamingBaseUrl, document, + return buildAndCacheResult(progressiveStreamingBaseUrl, doc, PROGRESSIVE_STREAMS_CACHE); } @@ -128,18 +128,17 @@ public final class YoutubeProgressiveDashManifestCreator { * {@link YoutubeDashManifestCreatorsUtils#generateRepresentationElement(Document, ItagItem)}). *

* - * @param document the {@link Document} on which the {@code } element will - * be appended + * @param doc the {@link Document} on which the {@code } element will be appended * @param baseUrl the base URL of the stream, which must not be null and will be set as the * content of the {@code } element */ - private static void generateBaseUrlElement(@Nonnull final Document document, + private static void generateBaseUrlElement(@Nonnull final Document doc, @Nonnull final String baseUrl) throws CreationException { try { - final Element representationElement = (Element) document.getElementsByTagName( + final Element representationElement = (Element) doc.getElementsByTagName( REPRESENTATION).item(0); - final Element baseURLElement = document.createElement(BASE_URL); + final Element baseURLElement = doc.createElement(BASE_URL); baseURLElement.setTextContent(baseUrl); representationElement.appendChild(baseURLElement); } catch (final DOMException e) { @@ -167,28 +166,23 @@ public final class YoutubeProgressiveDashManifestCreator { * should be generated too. *

* - * @param document the {@link Document} on which the {@code } element will be - * appended + * @param doc the {@link Document} on which the {@code } element will be appended * @param itagItem the {@link ItagItem} to use, which must not be null */ - private static void generateSegmentBaseElement(@Nonnull final Document document, + private static void generateSegmentBaseElement(@Nonnull final Document doc, @Nonnull final ItagItem itagItem) throws CreationException { try { - final Element representationElement = (Element) document.getElementsByTagName( + final Element representationElement = (Element) doc.getElementsByTagName( REPRESENTATION).item(0); + final Element segmentBaseElement = doc.createElement(SEGMENT_BASE); - final Element segmentBaseElement = document.createElement(SEGMENT_BASE); - final Attr indexRangeAttribute = document.createAttribute("indexRange"); - + final String range = itagItem.getIndexStart() + "-" + itagItem.getIndexEnd(); if (itagItem.getIndexStart() < 0 || itagItem.getIndexEnd() < 0) { throw CreationException.couldNotAddElement(SEGMENT_BASE, - "ItagItem's indexStart or " + "indexEnd are < 0: " - + itagItem.getIndexStart() + "-" + itagItem.getIndexEnd()); + "ItagItem's indexStart or " + "indexEnd are < 0: " + range); } - - indexRangeAttribute.setValue(itagItem.getIndexStart() + "-" + itagItem.getIndexEnd()); - segmentBaseElement.setAttributeNode(indexRangeAttribute); + setAttribute(segmentBaseElement, doc, "indexRange", range); representationElement.appendChild(segmentBaseElement); } catch (final DOMException e) { @@ -214,28 +208,24 @@ public final class YoutubeProgressiveDashManifestCreator { * {@link #generateSegmentBaseElement(Document, ItagItem)}). *

* - * @param document the {@link Document} on which the {@code } element will - * be appended + * @param doc the {@link Document} on which the {@code } element will be + * appended * @param itagItem the {@link ItagItem} to use, which must not be null */ - private static void generateInitializationElement(@Nonnull final Document document, + private static void generateInitializationElement(@Nonnull final Document doc, @Nonnull final ItagItem itagItem) throws CreationException { try { - final Element segmentBaseElement = (Element) document.getElementsByTagName( + final Element segmentBaseElement = (Element) doc.getElementsByTagName( SEGMENT_BASE).item(0); + final Element initializationElement = doc.createElement(INITIALIZATION); - final Element initializationElement = document.createElement(INITIALIZATION); - final Attr rangeAttribute = document.createAttribute("range"); - + final String range = itagItem.getInitStart() + "-" + itagItem.getInitEnd(); if (itagItem.getInitStart() < 0 || itagItem.getInitEnd() < 0) { throw CreationException.couldNotAddElement(INITIALIZATION, - "ItagItem's initStart and/or " + "initEnd are/is < 0: " - + itagItem.getInitStart() + "-" + itagItem.getInitEnd()); + "ItagItem's initStart and/or " + "initEnd are/is < 0: " + range); } - - rangeAttribute.setValue(itagItem.getInitStart() + "-" + itagItem.getInitEnd()); - initializationElement.setAttributeNode(rangeAttribute); + setAttribute(initializationElement, doc, "range", range); segmentBaseElement.appendChild(initializationElement); } catch (final DOMException e) { diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index 5e57b2a1..a41325f3 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -1147,34 +1147,27 @@ public class YoutubeStreamExtractor extends StreamExtractor { final java.util.function.Function streamBuilderHelper, final String streamTypeExceptionMessage) throws ParsingException { try { - final List itagInfos = new ArrayList<>(); - if (html5StreamingData == null && androidStreamingData == null - && iosStreamingData == null) { - return Collections.emptyList(); - } - - final List> streamingDataAndCpnLoopList = new ArrayList<>(); - // Use the androidStreamingData object first because there is no n param and no - // signatureCiphers in streaming URLs of the Android client - streamingDataAndCpnLoopList.add(new Pair<>(androidStreamingData, androidCpn)); - streamingDataAndCpnLoopList.add(new Pair<>(html5StreamingData, html5Cpn)); - // Use the iosStreamingData object in the last position because most of the available - // streams can be extracted with the Android and web clients and also because the iOS - // client is only enabled by default on livestreams - streamingDataAndCpnLoopList.add(new Pair<>(iosStreamingData, iosCpn)); - - for (final Pair pair : streamingDataAndCpnLoopList) { - itagInfos.addAll(getStreamsFromStreamingDataKey(pair.getFirst(), streamingDataKey, - itagTypeWanted, pair.getSecond())); - } - + final String videoId = getId(); final List streamList = new ArrayList<>(); - for (final ItagInfo itagInfo : itagInfos) { - final T stream = streamBuilderHelper.apply(itagInfo); - if (!Stream.containSimilarStream(stream, streamList)) { - streamList.add(stream); - } - } + + java.util.stream.Stream.of( + // Use the androidStreamingData object first because there is no n param and no + // signatureCiphers in streaming URLs of the Android client + new Pair<>(androidStreamingData, androidCpn), + new Pair<>(html5StreamingData, html5Cpn), + // Use the iosStreamingData object in the last position because most of the + // available streams can be extracted with the Android and web clients and also + // because the iOS client is only enabled by default on livestreams + new Pair<>(iosStreamingData, iosCpn) + ) + .flatMap(pair -> getStreamsFromStreamingDataKey(videoId, pair.getFirst(), + streamingDataKey, itagTypeWanted, pair.getSecond())) + .map(streamBuilderHelper) + .forEachOrdered(stream -> { + if (!Stream.containSimilarStream(stream, streamList)) { + streamList.add(stream); + } + }); return streamList; } catch (final Exception e) { @@ -1293,43 +1286,36 @@ public class YoutubeStreamExtractor extends StreamExtractor { } @Nonnull - private List getStreamsFromStreamingDataKey( + private java.util.stream.Stream getStreamsFromStreamingDataKey( + final String videoId, final JsonObject streamingData, final String streamingDataKey, @Nonnull final ItagItem.ItagType itagTypeWanted, - @Nonnull final String contentPlaybackNonce) throws ParsingException { + @Nonnull final String contentPlaybackNonce) { if (streamingData == null || !streamingData.has(streamingDataKey)) { - return Collections.emptyList(); + return java.util.stream.Stream.empty(); } - final String videoId = getId(); - final List itagInfos = new ArrayList<>(); - final JsonArray formats = streamingData.getArray(streamingDataKey); - for (int i = 0; i != formats.size(); ++i) { - final JsonObject formatData = formats.getObject(i); - final int itag = formatData.getInt("itag"); - - if (!ItagItem.isSupported(itag)) { - continue; - } - - try { - final ItagItem itagItem = ItagItem.getItag(itag); - final ItagItem.ItagType itagType = itagItem.itagType; - if (itagType == itagTypeWanted) { - buildAndAddItagInfoToList(videoId, itagInfos, formatData, itagItem, - itagType, contentPlaybackNonce); - } - } catch (final IOException | ExtractionException ignored) { - } - } - - return itagInfos; + return streamingData.getArray(streamingDataKey).stream() + .filter(JsonObject.class::isInstance) + .map(JsonObject.class::cast) + .map(formatData -> { + try { + final ItagItem itagItem = ItagItem.getItag(formatData.getInt("itag")); + if (itagItem.itagType == itagTypeWanted) { + return buildAndAddItagInfoToList(videoId, formatData, itagItem, + itagItem.itagType, contentPlaybackNonce); + } + } catch (final IOException | ExtractionException ignored) { + // if the itag is not supported and getItag fails, we end up here + } + return null; + }) + .filter(Objects::nonNull); } - private void buildAndAddItagInfoToList( + private ItagInfo buildAndAddItagInfoToList( @Nonnull final String videoId, - @Nonnull final List itagInfos, @Nonnull final JsonObject formatData, @Nonnull final ItagItem itagItem, @Nonnull final ItagItem.ItagType itagType, @@ -1372,12 +1358,10 @@ public class YoutubeStreamExtractor extends StreamExtractor { if (streamType == StreamType.LIVE_STREAM || streamType == StreamType.POST_LIVE_STREAM) { itagItem.setTargetDurationSec(formatData.getInt("targetDurationSec")); - } - - if (itagType == ItagItem.ItagType.VIDEO || itagType == ItagItem.ItagType.VIDEO_ONLY) { + } else if (itagType == ItagItem.ItagType.VIDEO + || itagType == ItagItem.ItagType.VIDEO_ONLY) { itagItem.setFps(formatData.getInt("fps")); - } - if (itagType == ItagItem.ItagType.AUDIO) { + } else if (itagType == ItagItem.ItagType.AUDIO) { // YouTube return the audio sample rate as a string itagItem.setSampleRate(Integer.parseInt(formatData.getString("audioSampleRate"))); itagItem.setAudioChannels(formatData.getInt("audioChannels")); @@ -1403,7 +1387,7 @@ public class YoutubeStreamExtractor extends StreamExtractor { itagInfo.setIsUrl(streamType != StreamType.POST_LIVE_STREAM); } - itagInfos.add(itagInfo); + return itagInfo; } @Nonnull diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java index e9232c8c..04d2b3fa 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java @@ -94,24 +94,15 @@ public abstract class Stream implements Serializable { * Note: This method always returns false if the stream passed is null. *

* - * @param cmp the stream object to be compared to this stream object + * @param other the stream object to be compared to this stream object * @return whether the stream have the same stats or not, based on the criteria above */ - public boolean equalStats(@Nullable final Stream cmp) { - if (cmp == null) { + public boolean equalStats(@Nullable final Stream other) { + if (other == null || mediaFormat == null || other.mediaFormat == null) { return false; } - - Boolean haveSameMediaFormatId = null; - if (mediaFormat != null && cmp.mediaFormat != null) { - haveSameMediaFormatId = mediaFormat.id == cmp.mediaFormat.id; - } - final boolean areUsingSameDeliveryMethodAndAreUrlStreams = - deliveryMethod == cmp.deliveryMethod && isUrl == cmp.isUrl; - - return haveSameMediaFormatId != null - ? haveSameMediaFormatId && areUsingSameDeliveryMethodAndAreUrlStreams - : areUsingSameDeliveryMethodAndAreUrlStreams; + return mediaFormat.id == other.mediaFormat.id && deliveryMethod == other.deliveryMethod + && isUrl == other.isUrl; } /** diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/ManifestCreatorCacheTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/ManifestCreatorCacheTest.java index 7d3fa5b6..83c5c1df 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/ManifestCreatorCacheTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/ManifestCreatorCacheTest.java @@ -53,6 +53,11 @@ class ManifestCreatorCacheTest { + "call"); } + /** + * Adds sample strings to the provided manifest creator cache, in order to test clear factor and + * maximum size. + * @param cache the cache to fill with some data + */ private static void setCacheContent(final ManifestCreatorCache cache) { int i = 0; while (i < 26) {