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 1785b755..40890011 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 @@ -60,6 +60,7 @@ import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.utils.JsonUtils; +import org.schabi.newpipe.extractor.utils.Pair; import org.schabi.newpipe.extractor.utils.Parser; import org.schabi.newpipe.extractor.utils.Utils; @@ -71,7 +72,6 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -1170,19 +1170,19 @@ public class YoutubeStreamExtractor extends StreamExtractor { return urlAndItags; } - final Map streamingDataAndCpnLoopMap = new HashMap<>(); + 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 - streamingDataAndCpnLoopMap.put(androidCpn, androidStreamingData); - streamingDataAndCpnLoopMap.put(html5Cpn, html5StreamingData); + 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 - streamingDataAndCpnLoopMap.put(iosCpn, iosStreamingData); + streamingDataAndCpnLoopList.add(new Pair<>(iosStreamingData, iosCpn)); - for (final Map.Entry entry : streamingDataAndCpnLoopMap.entrySet()) { - urlAndItags.putAll(getStreamsFromStreamingDataKey(entry.getValue(), streamingDataKey, - itagTypeWanted, entry.getKey())); + for (final Pair pair : streamingDataAndCpnLoopList) { + urlAndItags.putAll(getStreamsFromStreamingDataKey(pair.getFirst(), streamingDataKey, + itagTypeWanted, pair.getSecond())); } return urlAndItags; diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Pair.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Pair.java new file mode 100644 index 00000000..6efb66b2 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Pair.java @@ -0,0 +1,128 @@ +package org.schabi.newpipe.extractor.utils; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Serializable class to create a pair of objects. + * + *

+ * The two objects of the pair must be {@link Serializable serializable} and can be of the same + * type. + *

+ * + *

+ * Note that this class is not intended to be used as a general-purpose pair and should only be + * used when interfacing with the extractor. + *

+ * + * @param the type of the first object, which must be {@link Serializable} + * @param the type of the second object, which must be {@link Serializable} + */ +public class Pair implements Serializable { + + /** + * The first object of the pair. + */ + private F firstObject; + + /** + * The second object of the pair. + */ + private S secondObject; + + /** + * Creates a new {@link Pair} object. + * + * @param first the first object of the pair + * @param second the second object of the pair + */ + public Pair(final F first, final S second) { + firstObject = first; + secondObject = second; + } + + /** + * Sets the first object, which must be of the {@link F} type. + * + * @param first the new first object of the pair + */ + public void setFirst(final F first) { + firstObject = first; + } + + /** + * Sets the first object, which must be of the {@link S} type. + * + * @param second the new first object of the pair + */ + public void setSecond(final S second) { + secondObject = second; + } + + /** + * Gets the first object of the pair. + * + * @return the first object of the pair + */ + public F getFirst() { + return firstObject; + } + + /** + * Gets the second object of the pair. + * + * @return the second object of the pair + */ + public S getSecond() { + return this.secondObject; + } + + /** + * Returns a string representation of the current {@code Pair}. + * + *

+ * The string representation will look like this: + * + * {firstObject.toString(), secondObject.toString()} + * + *

+ * + * @return a string representation of the current {@code Pair} + */ + @Override + public String toString() { + return "{" + firstObject + ", " + secondObject + "}"; + } + + /** + * Reveals whether an object is equal to this {@code Pair} instance. + * + * @param obj the object to compare with this {@code Pair} instance + * @return whether an object is equal to this {@code Pair} instance + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + final Pair pair = (Pair) obj; + return Objects.equals(firstObject, pair.firstObject) && Objects.equals(secondObject, + pair.secondObject); + } + + /** + * Returns a hash code of the current {@code Pair} by using the first and second object. + * + * @return a hash code of the current {@code Pair} + */ + @Override + public int hashCode() { + return Objects.hash(firstObject, secondObject); + } +}