Add support of other delivery methods than progressive HTTP in Stream classes

Stream constructors are now private and streams can be constructed with new Builder classes per stream class. This change has been made to prevent creating and using several constructors in stream classes.

Some default cases have been also added in these Builder classes, so not everything has to be set, depending of the service and the content.
This commit is contained in:
TiA4f8R 2022-03-03 09:42:20 +01:00
parent 1dc80957d8
commit 2f061b8dbd
No known key found for this signature in database
GPG Key ID: E6D3E7F5949450DD
5 changed files with 1290 additions and 161 deletions

View File

@ -4,30 +4,36 @@ package org.schabi.newpipe.extractor.stream;
* Created by Christian Schabesberger on 04.03.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* AudioStream.java is part of NewPipe.
* AudioStream.java is part of NewPipe Extractor.
*
* NewPipe is free software: you can redistribute it and/or modify
* NewPipe Extractor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* NewPipe Extractor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
*/
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.services.youtube.ItagItem;
public class AudioStream extends Stream {
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
public final class AudioStream extends Stream {
public static final int UNKNOWN_BITRATE = -1;
private final int averageBitrate;
// Fields for Dash
private int itag;
// Fields for DASH
private int itag = ITAG_NOT_AVAILABLE_OR_NOT_APPLICABLE;
private int bitrate;
private int initStart;
private int initEnd;
@ -35,37 +41,249 @@ public class AudioStream extends Stream {
private int indexEnd;
private String quality;
private String codec;
@Nullable
private ItagItem itagItem;
/**
* Create a new audio stream
* @param url the url
* @param format the format
* @param averageBitrate the average bitrate
* Class to build {@link AudioStream} objects.
*/
public AudioStream(final String url,
final MediaFormat format,
final int averageBitrate) {
super(url, format);
@SuppressWarnings("checkstyle:hiddenField")
public static final class Builder {
private String id;
private String content;
private boolean isUrl;
private DeliveryMethod deliveryMethod = DeliveryMethod.PROGRESSIVE_HTTP;
@Nullable
private MediaFormat mediaFormat;
@Nullable
private String baseUrl;
private int averageBitrate = UNKNOWN_BITRATE;
@Nullable
private ItagItem itagItem;
/**
* Create a new {@link Builder} instance with its default values.
*/
public Builder() {
}
/**
* Set the identifier of the {@link SubtitlesStream}.
*
* <p>
* It <b>must be not null</b> and should be non empty.
* </p>
*
* <p>
* If you are not able to get an identifier, use the static constant {@link
* Stream#ID_UNKNOWN ID_UNKNOWN} of the {@link Stream} class.
* </p>
*
* @param id the identifier of the {@link SubtitlesStream}, which must be not null
* @return this {@link Builder} instance
*/
public Builder setId(@Nonnull final String id) {
this.id = id;
return this;
}
/**
* Set the content of the {@link AudioStream}.
*
* <p>
* It must be non null and should be non empty.
* </p>
*
* @param content the content of the {@link AudioStream}
* @param isUrl whether the content is a URL
* @return this {@link Builder} instance
*/
public Builder setContent(@Nonnull final String content,
final boolean isUrl) {
this.content = content;
this.isUrl = isUrl;
return this;
}
/**
* Set the {@link MediaFormat} used by the {@link AudioStream}.
*
* <p>
* It should be one of the audio {@link MediaFormat}s ({@link MediaFormat#M4A M4A},
* {@link MediaFormat#WEBMA WEBMA}, {@link MediaFormat#MP3 MP3}, {@link MediaFormat#OPUS
* OPUS}, {@link MediaFormat#OGG OGG}, {@link MediaFormat#WEBMA_OPUS WEBMA_OPUS}) but can
* be {@code null} if the media format could not be determined.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param mediaFormat the {@link MediaFormat} of the {@link AudioStream}, which can be null
* @return this {@link Builder} instance
*/
public Builder setMediaFormat(@Nullable final MediaFormat mediaFormat) {
this.mediaFormat = mediaFormat;
return this;
}
/**
* Set the {@link DeliveryMethod} of the {@link AudioStream}.
*
* <p>
* It must be not null.
* </p>
*
* <p>
* The default delivery method is {@link DeliveryMethod#PROGRESSIVE_HTTP}.
* </p>
*
* @param deliveryMethod the {@link DeliveryMethod} of the {@link AudioStream}, which must
* be not null
* @return this {@link Builder} instance
*/
public Builder setDeliveryMethod(@Nonnull final DeliveryMethod deliveryMethod) {
this.deliveryMethod = deliveryMethod;
return this;
}
/**
* Set the base URL of the {@link AudioStream}.
*
* <p>
* Base URLs are for instance, for non-URLs content, the DASH or HLS manifest from which
* they have been parsed.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param baseUrl the base URL of the {@link AudioStream}, which can be null
* @return this {@link Builder} instance
*/
public Builder setBaseUrl(@Nullable final String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
/**
* Set the average bitrate of the {@link AudioStream}.
*
* <p>
* The default value is {@link #UNKNOWN_BITRATE}.
* </p>
*
* @param averageBitrate the average bitrate of the {@link AudioStream}, which should
* positive
* @return this {@link Builder} instance
*/
public Builder setAverageBitrate(final int averageBitrate) {
this.averageBitrate = averageBitrate;
return this;
}
/**
* Set the {@link ItagItem} corresponding to the {@link AudioStream}.
*
* <p>
* {@link ItagItem}s are YouTube specific objects, so they are only known for this service
* and can be null.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param itagItem the {@link ItagItem} of the {@link AudioStream}, which can be null
* @return this {@link Builder} instance
*/
public Builder setItagItem(@Nullable final ItagItem itagItem) {
this.itagItem = itagItem;
return this;
}
/**
* Build an {@link AudioStream} using the builder's current values.
*
* <p>
* The identifier and the content (and so the {@code isUrl} boolean) properties must have
* been set.
* </p>
*
* @return a new {@link AudioStream} using the builder's current values
* @throws IllegalStateException if {@code id}, {@code content} (and so {@code isUrl}) or
* {@code deliveryMethod} have been not set or set as {@code null}
*/
@Nonnull
public AudioStream build() {
if (id == null) {
throw new IllegalStateException(
"The identifier of the audio stream has been not set or is null. If you "
+ "are not able to get an identifier, use the static constant "
+ "ID_UNKNOWN of the Stream class.");
}
if (content == null) {
throw new IllegalStateException("The content of the audio stream has been not set "
+ "or is null. Please specify a non-null one with setContent.");
}
if (deliveryMethod == null) {
throw new IllegalStateException(
"The delivery method of the audio stream has been set as null, which is "
+ "not allowed. Pass a valid one instead with setDeliveryMethod.");
}
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
baseUrl, itagItem);
}
}
/**
* Create a new audio stream.
*
* @param id the ID which uniquely identifies the stream, e.g. for YouTube this
* would be the itag
* @param content the content or the URL of the stream, depending on whether isUrl is
* true
* @param isUrl whether content is the URL or the actual content of e.g. a DASH
* manifest
* @param format the {@link MediaFormat} used by the stream, which can be null
* @param deliveryMethod the {@link DeliveryMethod} of the stream
* @param averageBitrate the average bitrate of the stream (which can be unknown, see
* {@link #UNKNOWN_BITRATE})
* @param itagItem the {@link ItagItem} corresponding to the stream, which cannot be null
* @param baseUrl the base URL of the stream (see {@link Stream#getBaseUrl()} for more
* information)
*/
private AudioStream(@Nonnull final String id,
@Nonnull final String content,
final boolean isUrl,
@Nullable final MediaFormat format,
@Nonnull final DeliveryMethod deliveryMethod,
final int averageBitrate,
@Nullable final String baseUrl,
@Nullable final ItagItem itagItem) {
super(id, content, isUrl, format, deliveryMethod, baseUrl);
if (itagItem != null) {
this.itagItem = itagItem;
this.itag = itagItem.id;
this.quality = itagItem.getQuality();
this.bitrate = itagItem.getBitrate();
this.initStart = itagItem.getInitStart();
this.initEnd = itagItem.getInitEnd();
this.indexStart = itagItem.getIndexStart();
this.indexEnd = itagItem.getIndexEnd();
this.codec = itagItem.getCodec();
}
this.averageBitrate = averageBitrate;
}
/**
* Create a new audio stream
* @param url the url
* @param itag the ItagItem of the Stream
* {@inheritDoc}
*/
public AudioStream(final String url, final ItagItem itag) {
this(url, itag.getMediaFormat(), itag.avgBitrate);
this.itag = itag.id;
this.quality = itag.getQuality();
this.bitrate = itag.getBitrate();
this.initStart = itag.getInitStart();
this.initEnd = itag.getInitEnd();
this.indexStart = itag.getIndexStart();
this.indexEnd = itag.getIndexEnd();
this.codec = itag.getCodec();
}
@Override
public boolean equalStats(final Stream cmp) {
return super.equalStats(cmp) && cmp instanceof AudioStream
@ -73,42 +291,125 @@ public class AudioStream extends Stream {
}
/**
* Get the average bitrate
* @return the average bitrate or -1
* Get the average bitrate of the stream.
*
* @return the average bitrate or {@link #UNKNOWN_BITRATE} if it is unknown
*/
public int getAverageBitrate() {
return averageBitrate;
}
/**
* Get the itag identifier of the stream.
*
* <p>
* Always equals to {@link #ITAG_NOT_AVAILABLE_OR_NOT_APPLICABLE} for other streams than the
* ones of the YouTube service.
* </p>
*
* @return the number of the {@link ItagItem} passed in the constructor of the audio stream.
*/
public int getItag() {
return itag;
}
/**
* Get the bitrate of the stream.
*
* @return the bitrate set from the {@link ItagItem} passed in the constructor of the stream.
*/
public int getBitrate() {
return bitrate;
}
/**
* Get the initialization start of the stream.
*
* @return the initialization start value set from the {@link ItagItem} passed in the
* constructor of the stream.
*/
public int getInitStart() {
return initStart;
}
/**
* Get the initialization end of the stream.
*
* @return the initialization end value set from the {@link ItagItem} passed in the constructor
* of the stream.
*/
public int getInitEnd() {
return initEnd;
}
/**
* Get the index start of the stream.
*
* @return the index start value set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public int getIndexStart() {
return indexStart;
}
/**
* Get the index end of the stream.
*
* @return the index end value set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public int getIndexEnd() {
return indexEnd;
}
/**
* Get the quality of the stream.
*
* @return the quality label set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public String getQuality() {
return quality;
}
/**
* Get the codec of the stream.
*
* @return the codec set from the {@link ItagItem} passed in the constructor of the stream.
*/
public String getCodec() {
return codec;
}
/**
* {@inheritDoc}
*/
@Override
@Nullable
public ItagItem getItagItem() {
return itagItem;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
if (!super.equals(obj)) {
return false;
}
final AudioStream audioStream = (AudioStream) obj;
return averageBitrate == audioStream.averageBitrate;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), averageBitrate);
}
}

View File

@ -0,0 +1,37 @@
package org.schabi.newpipe.extractor.stream;
/**
* An enum to represent the different delivery methods of {@link Stream streams} which are returned
* by the extractor.
*/
public enum DeliveryMethod {
/**
* Enum constant which represents the use of the progressive HTTP streaming method to fetch a
* {@link Stream stream}.
*/
PROGRESSIVE_HTTP,
/**
* Enum constant which represents the use of the DASH adaptive streaming method to fetch a
* {@link Stream stream}.
*/
DASH,
/**
* Enum constant which represents the use of the HLS adaptive streaming method to fetch a
* {@link Stream stream}.
*/
HLS,
/**
* Enum constant which represents the use of the SmoothStreaming adaptive streaming method to
* fetch a {@link Stream stream}.
*/
SS,
/**
* Enum constant which represents the use of a torrent to fetch a {@link Stream stream}.
*/
TORRENT
}

View File

@ -1,68 +1,73 @@
package org.schabi.newpipe.extractor.stream;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.services.youtube.ItagItem;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import org.schabi.newpipe.extractor.MediaFormat;
import java.io.Serializable;
import java.util.List;
/**
* Creates a stream object from url, format and optional torrent url
* Abstract class which represents streams in the extractor.
*/
public abstract class Stream implements Serializable {
private final MediaFormat mediaFormat;
private final String url;
private final String torrentUrl;
public static final int FORMAT_ID_UNKNOWN = -1;
public static final String ID_UNKNOWN = " ";
/**
* @deprecated Use {@link #getFormat()} or {@link #getFormatId()}
*/
@Deprecated
public final int format;
/**
* Instantiates a new stream object.
* An integer to represent that the itag id returned is not available (only for YouTube, this
* should never happen) or not applicable (for other services than YouTube).
*
* @param url the url
* @param format the format
* <p>
* An itag should not have a negative value so {@code -1} is used for this constant.
* </p>
*/
public Stream(final String url, final MediaFormat format) {
this(url, null, format);
}
public static final int ITAG_NOT_AVAILABLE_OR_NOT_APPLICABLE = -1;
private final String id;
@Nullable private final MediaFormat mediaFormat;
private final String content;
private final boolean isUrl;
private final DeliveryMethod deliveryMethod;
@Nullable private final String baseUrl;
/**
* Instantiates a new stream object.
* Instantiates a new {@code Stream} object.
*
* @param url the url
* @param torrentUrl the url to torrent file, example
* https://webtorrent.io/torrents/big-buck-bunny.torrent
* @param format the format
* @param id the ID which uniquely identifies the file, e.g. for YouTube this would
* be the itag
* @param content the content or URL, depending on whether isUrl is true
* @param isUrl whether content is the URL or the actual content of e.g. a DASH
* manifest
* @param format the {@link MediaFormat}, which can be null
* @param deliveryMethod the delivery method of the stream
* @param baseUrl the base URL of the content if the stream is a DASH or an HLS
* manifest, which can be null
*/
public Stream(final String url, final String torrentUrl, final MediaFormat format) {
this.url = url;
this.torrentUrl = torrentUrl;
//noinspection deprecation
this.format = format.id;
public Stream(final String id,
final String content,
final boolean isUrl,
@Nullable final MediaFormat format,
final DeliveryMethod deliveryMethod,
@Nullable final String baseUrl) {
this.id = id;
this.content = content;
this.isUrl = isUrl;
this.mediaFormat = format;
this.deliveryMethod = deliveryMethod;
this.baseUrl = baseUrl;
}
/**
* Reveals whether two streams have the same stats (format and bitrate, for example)
*/
public boolean equalStats(final Stream cmp) {
return cmp != null && getFormatId() == cmp.getFormatId();
}
/**
* Reveals whether two Streams are equal
*/
public boolean equals(final Stream cmp) {
return equalStats(cmp) && url.equals(cmp.url);
}
/**
* Check if the list already contains one stream with equals stats
* Checks if the list already contains one stream with equals stats.
*
* @param stream the stream which will be compared to the streams in the stream list
* @param streamList the list of {@link Stream Streams} which will be compared
* @return whether the list already contains one stream with equals stats
*/
public static boolean containSimilarStream(final Stream stream,
final List<? extends Stream> streamList) {
@ -78,38 +83,170 @@ public abstract class Stream implements Serializable {
}
/**
* Gets the url.
* Reveals whether two streams have the same stats ({@link MediaFormat media format} and
* {@link DeliveryMethod delivery method}).
*
* @return the url
* <p>
* If the {@link MediaFormat media format} of the stream is unknown, the streams are compared
* by only using the {@link DeliveryMethod delivery method} and their id.
* </p>
*
* <p>
* Note: This method always returns always false if the stream passed is null.
* </p>
*
* @param cmp 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) {
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;
}
/**
* Reveals whether two streams are equal.
*
* @param cmp the stream object to be compared to this stream object
* @return whether streams are equal
*/
public boolean equals(final Stream cmp) {
return equalStats(cmp) && content.equals(cmp.content);
}
/**
* Gets the identifier of this stream, e.g. the itag for YouTube.
*
* <p>
* It should be normally unique but {@link #ID_UNKNOWN} may be returned as the identifier if
* one used by the stream extractor cannot be extracted, if the extractor uses a value from a
* streaming service.
* </p>
*
* @return the id (which may be {@link #ID_UNKNOWN})
*/
public String getId() {
return id;
}
/**
* Gets the URL of this stream if the content is a URL, or {@code null} if that's the not case.
*
* @return the URL if the content is a URL, {@code null} otherwise
* @deprecated Use {@link #getContent()} instead.
*/
@Deprecated
@Nullable
public String getUrl() {
return url;
return isUrl ? content : null;
}
/**
* Gets the torrent url.
* Gets the content or URL.
*
* @return the torrent url, example https://webtorrent.io/torrents/big-buck-bunny.torrent
* @return the content or URL
*/
public String getTorrentUrl() {
return torrentUrl;
public String getContent() {
return content;
}
/**
* Gets the format.
* Returns if the content is a URL or not.
*
* @return {@code true} if the content of this stream content is a URL, {@code false}
* if it is the actual content
*/
public boolean isUrl() {
return isUrl;
}
/**
* Gets the {@link MediaFormat}, which can be null.
*
* @return the format
*/
@Nullable
public MediaFormat getFormat() {
return mediaFormat;
}
/**
* Gets the format id.
* Gets the format id, which can be unknown.
*
* @return the format id
* @return the format id or {@link #FORMAT_ID_UNKNOWN}
*/
public int getFormatId() {
return mediaFormat.id;
if (mediaFormat != null) {
return mediaFormat.id;
}
return FORMAT_ID_UNKNOWN;
}
}
/**
* Gets the delivery method.
*
* @return the delivery method
*/
@Nonnull
public DeliveryMethod getDeliveryMethod() {
return deliveryMethod;
}
/**
* Gets the base URL of a stream.
*
* <p>
* If the stream is not a DASH stream or an HLS stream, this value will always be null.
* It may be also null for these streams too.
* </p>
*
* @return the base URL of the stream or {@code null}
*/
@Nullable
public String getBaseUrl() {
return baseUrl;
}
/**
* Gets the {@link ItagItem} of a stream.
*
* <p>
* If the stream is not a YouTube stream, this value will always be null.
* </p>
*
* @return the {@link ItagItem} of the stream or {@code null}
*/
@Nullable
public abstract ItagItem getItagItem();
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final Stream stream = (Stream) obj;
return id.equals(stream.id) && mediaFormat == stream.mediaFormat
&& deliveryMethod == stream.deliveryMethod;
}
@Override
public int hashCode() {
return Objects.hash(id, mediaFormat, deliveryMethod);
}
}

View File

@ -1,53 +1,295 @@
package org.schabi.newpipe.extractor.stream;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.services.youtube.ItagItem;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.Locale;
import java.util.Objects;
public class SubtitlesStream extends Stream implements Serializable {
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
public final class SubtitlesStream extends Stream {
private final MediaFormat format;
private final Locale locale;
private final boolean autoGenerated;
private final String code;
public SubtitlesStream(final MediaFormat format,
final String languageCode,
final String url,
final boolean autoGenerated) {
super(url, format);
/**
* Class to build {@link SubtitlesStream} objects.
*/
@SuppressWarnings("checkstyle:HiddenField")
public static final class Builder {
private String id;
private String content;
private boolean isUrl;
private DeliveryMethod deliveryMethod = DeliveryMethod.PROGRESSIVE_HTTP;
@Nullable
private MediaFormat mediaFormat;
@Nullable
private String baseUrl;
private String languageCode;
// Use of the Boolean class instead of the primitive type needed for setter call check
private Boolean autoGenerated;
/**
* Create a new {@link Builder} instance with its default values.
*/
public Builder() {
}
/**
* Set the identifier of the {@link SubtitlesStream}.
*
* @param id the identifier of the {@link SubtitlesStream}, which should be not null
* (otherwise the fallback to create the identifier will be used when building
* the builder)
* @return this {@link Builder} instance
*/
public Builder setId(@Nonnull final String id) {
this.id = id;
return this;
}
/**
* Set the content of the {@link SubtitlesStream}.
*
* <p>
* It must be non null and should be non empty.
* </p>
*
* @param content the content of the {@link SubtitlesStream}
* @param isUrl whether the content is a URL
* @return this {@link Builder} instance
*/
public Builder setContent(@Nonnull final String content,
final boolean isUrl) {
this.content = content;
this.isUrl = isUrl;
return this;
}
/**
* Set the {@link MediaFormat} used by the {@link SubtitlesStream}.
*
* <p>
* It should be one of the subtitles {@link MediaFormat}s ({@link MediaFormat#SRT SRT},
* {@link MediaFormat#TRANSCRIPT1 TRANSCRIPT1}, {@link MediaFormat#TRANSCRIPT2
* TRANSCRIPT2}, {@link MediaFormat#TRANSCRIPT3 TRANSCRIPT3}, {@link MediaFormat#TTML
* TTML}, {@link MediaFormat#VTT VTT}) but can be {@code null} if the media format could
* not be determined.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param mediaFormat the {@link MediaFormat} of the {@link SubtitlesStream}, which can be
* null
* @return this {@link Builder} instance
*/
public Builder setMediaFormat(@Nullable final MediaFormat mediaFormat) {
this.mediaFormat = mediaFormat;
return this;
}
/**
* Set the {@link DeliveryMethod} of the {@link SubtitlesStream}.
*
* <p>
* It must be not null.
* </p>
*
* <p>
* The default delivery method is {@link DeliveryMethod#PROGRESSIVE_HTTP}.
* </p>
*
* @param deliveryMethod the {@link DeliveryMethod} of the {@link SubtitlesStream}, which
* must be not null
* @return this {@link Builder} instance
*/
public Builder setDeliveryMethod(@Nonnull final DeliveryMethod deliveryMethod) {
this.deliveryMethod = deliveryMethod;
return this;
}
/**
* Set the base URL of the {@link SubtitlesStream}.
*
* <p>
* Base URLs are for instance, for non-URLs content, the DASH or HLS manifest from which
* they have been parsed.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param baseUrl the base URL of the {@link SubtitlesStream}, which can be null
* @return this {@link Builder} instance
*/
public Builder setBaseUrl(@Nullable final String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
/**
* Set the language code of the {@link SubtitlesStream}.
*
* <p>
* It <b>must be not null</b> and should be not an empty string.
* </p>
*
* @param languageCode the language code of the {@link SubtitlesStream}
* @return this {@link Builder} instance
*/
public Builder setLanguageCode(@Nonnull final String languageCode) {
this.languageCode = languageCode;
return this;
}
/**
* Set whether the subtitles have been generated by the streaming service.
*
* @param autoGenerated whether the subtitles have been generated by the streaming
* service
* @return this {@link Builder} instance
*/
public Builder setAutoGenerated(final boolean autoGenerated) {
this.autoGenerated = autoGenerated;
return this;
}
/**
* Build a {@link SubtitlesStream} using the builder's current values.
*
* <p>
* The content (and so the {@code isUrl} boolean), the language code and the {@code
* isAutoGenerated} properties must have been set.
* </p>
*
* <p>
* If no identifier has been set, an identifier will be generated using the language code
* and the media format suffix if the media format is known
* </p>
*
* @return a new {@link SubtitlesStream} using the builder's current values
* @throws IllegalStateException if {@code id}, {@code content} (and so {@code isUrl}),
* {@code deliveryMethod}, {@code languageCode} or the {@code isAutogenerated} have been
* not set or set as {@code null}
*/
@Nonnull
public SubtitlesStream build() {
if (content == null) {
throw new IllegalStateException("No valid content was specified. Please specify a "
+ "valid one with setContent.");
}
if (deliveryMethod == null) {
throw new IllegalStateException(
"The delivery method of the subtitles stream has been set as null, which "
+ "is not allowed. Pass a valid one instead with"
+ "setDeliveryMethod.");
}
if (languageCode == null) {
throw new IllegalStateException("The language code of the subtitles stream has "
+ "been not set or is null. Make sure you specified an non null language "
+ "code with setLanguageCode.");
}
if (autoGenerated == null) {
throw new IllegalStateException("The subtitles stream has been not set as an "
+ "autogenerated subtitles stream or not. Please specify this information "
+ "with setIsAutoGenerated.");
}
if (id == null) {
id = languageCode + (mediaFormat != null ? "." + mediaFormat.suffix
: EMPTY_STRING);
}
return new SubtitlesStream(id, content, isUrl, mediaFormat, deliveryMethod,
languageCode, autoGenerated, baseUrl);
}
}
/**
* Create a new subtitles stream.
*
* @param id the ID which uniquely identifies the stream, e.g. for YouTube this
* would be the itag
* @param content the content or the URL of the stream, depending on whether isUrl is
* true
* @param isUrl whether content is the URL or the actual content of e.g. a DASH
* manifest
* @param format the {@link MediaFormat} used by the stream
* @param deliveryMethod the {@link DeliveryMethod} of the stream
* @param languageCode the language code of the stream
* @param autoGenerated whether the subtitles are auto-generated by the streaming service
* @param baseUrl the base URL of the stream (see {@link Stream#getBaseUrl()} for more
* information)
*/
private SubtitlesStream(@Nonnull final String id,
@Nonnull final String content,
final boolean isUrl,
@Nullable final MediaFormat format,
@Nonnull final DeliveryMethod deliveryMethod,
@Nonnull final String languageCode,
final boolean autoGenerated,
@Nullable final String baseUrl) {
super(id, content, isUrl, format, deliveryMethod, baseUrl);
/*
* Locale.forLanguageTag only for API >= 21
* Locale.Builder only for API >= 21
* Country codes doesn't work well without
*/
* Locale.forLanguageTag only for Android API >= 21
* Locale.Builder only for Android API >= 21
* Country codes doesn't work well without
*/
final String[] splits = languageCode.split("-");
switch (splits.length) {
default:
this.locale = new Locale(splits[0]);
break;
case 3:
// complex variants doesn't work!
this.locale = new Locale(splits[0], splits[1], splits[2]);
break;
case 2:
this.locale = new Locale(splits[0], splits[1]);
break;
case 3:
// Complex variants don't work!
this.locale = new Locale(splits[0], splits[1], splits[2]);
break;
default:
this.locale = new Locale(splits[0]);
break;
}
this.code = languageCode;
this.format = format;
this.autoGenerated = autoGenerated;
}
/**
* Get the extension of the subtitles.
*
* @return the extension of the subtitles
*/
public String getExtension() {
return format.suffix;
}
/**
* Return whether if the subtitles are auto-generated.
* <p>
* Some streaming services can generate subtitles for their contents, like YouTube.
* </p>
*
* @return {@code true} if the subtitles are auto-generated, {@code false} otherwise
*/
public boolean isAutoGenerated() {
return autoGenerated;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equalStats(final Stream cmp) {
return super.equalStats(cmp)
@ -56,16 +298,67 @@ public class SubtitlesStream extends Stream implements Serializable {
&& autoGenerated == ((SubtitlesStream) cmp).autoGenerated;
}
/**
* Get the display language name of the subtitles.
*
* @return the display language name of the subtitles
*/
public String getDisplayLanguageName() {
return locale.getDisplayName(locale);
}
/**
* Get the language tag of the subtitles.
*
* @return the language tag of the subtitles
*/
public String getLanguageTag() {
return code;
}
/**
* Get the {@link Locale locale} of the subtitles.
*
* @return the {@link Locale locale} of the subtitles
*/
public Locale getLocale() {
return locale;
}
/**
* No subtitles which are currently extracted use an {@link ItagItem}, so {@code null} is
* returned by this method.
*
* @return {@code null}
*/
@Nullable
@Override
public ItagItem getItagItem() {
return null;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
if (!super.equals(obj)) {
return false;
}
final SubtitlesStream subtitlesStream = (SubtitlesStream) obj;
return autoGenerated == subtitlesStream.autoGenerated
&& locale.equals(subtitlesStream.locale)
&& code.equals(subtitlesStream.code);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), locale, autoGenerated, code);
}
}

View File

@ -4,31 +4,42 @@ package org.schabi.newpipe.extractor.stream;
* Created by Christian Schabesberger on 04.03.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* VideoStream.java is part of NewPipe.
* VideoStream.java is part of NewPipe Extractor.
*
* NewPipe is free software: you can redistribute it and/or modify
* NewPipe Extractor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* NewPipe Extractor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
*/
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.services.youtube.ItagItem;
public class VideoStream extends Stream {
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
public final class VideoStream extends Stream {
public static final String RESOLUTION_UNKNOWN = "";
/** @deprecated Use {@link #getResolution()} instead. */
@Deprecated
public final String resolution;
/** @deprecated Use {@link #isVideoOnly()} instead. */
@Deprecated
public final boolean isVideoOnly;
// Fields for Dash
private int itag;
// Fields for DASH
private int itag = ITAG_NOT_AVAILABLE_OR_NOT_APPLICABLE;
private int bitrate;
private int initStart;
private int initEnd;
@ -39,118 +50,468 @@ public class VideoStream extends Stream {
private int fps;
private String quality;
private String codec;
@Nullable private ItagItem itagItem;
public VideoStream(final String url, final MediaFormat format, final String resolution) {
this(url, format, resolution, false);
/**
* Class to build {@link VideoStream} objects.
*/
@SuppressWarnings("checkstyle:hiddenField")
public static final class Builder {
private String id;
private String content;
private boolean isUrl;
private DeliveryMethod deliveryMethod = DeliveryMethod.PROGRESSIVE_HTTP;
@Nullable
private MediaFormat mediaFormat;
@Nullable
private String baseUrl;
// Use of the Boolean class instead of the primitive type needed for setter call check
private Boolean isVideoOnly;
private String resolution;
@Nullable
private ItagItem itagItem;
/**
* Create a new {@link Builder} instance with its default values.
*/
public Builder() {
}
/**
* Set the identifier of the {@link VideoStream}.
*
* <p>
* It <b>must be not null</b> and should be non empty.
* </p>
*
* <p>
* If you are not able to get an identifier, use the static constant {@link
* Stream#ID_UNKNOWN ID_UNKNOWN} of the {@link Stream} class.
* </p>
*
* @param id the identifier of the {@link VideoStream}, which must be not null
* @return this {@link Builder} instance
*/
public Builder setId(@Nonnull final String id) {
this.id = id;
return this;
}
/**
* Set the content of the {@link VideoStream}.
*
* <p>
* It must be non null and should be non empty.
* </p>
*
* @param content the content of the {@link VideoStream}
* @param isUrl whether the content is a URL
* @return this {@link Builder} instance
*/
public Builder setContent(@Nonnull final String content,
final boolean isUrl) {
this.content = content;
this.isUrl = isUrl;
return this;
}
/**
* Set the {@link MediaFormat} used by the {@link VideoStream}.
*
* <p>
* It should be one of the video {@link MediaFormat}s ({@link MediaFormat#MPEG_4 MPEG_4},
* {@link MediaFormat#v3GPP v3GPP}, {@link MediaFormat#WEBM WEBM}) but can be {@code null}
* if the media format could not be determined.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param mediaFormat the {@link MediaFormat} of the {@link VideoStream}, which can be null
* @return this {@link Builder} instance
*/
public Builder setMediaFormat(@Nullable final MediaFormat mediaFormat) {
this.mediaFormat = mediaFormat;
return this;
}
/**
* Set the {@link DeliveryMethod} of the {@link VideoStream}.
*
* <p>
* It must be not null.
* </p>
*
* <p>
* The default delivery method is {@link DeliveryMethod#PROGRESSIVE_HTTP}.
* </p>
*
* @param deliveryMethod the {@link DeliveryMethod} of the {@link VideoStream}, which must
* be not null
* @return this {@link Builder} instance
*/
public Builder setDeliveryMethod(@Nonnull final DeliveryMethod deliveryMethod) {
this.deliveryMethod = deliveryMethod;
return this;
}
/**
* Set the base URL of the {@link VideoStream}.
*
* <p>
* Base URLs are for instance, for non-URLs content, the DASH or HLS manifest from which
* they have been parsed.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param baseUrl the base URL of the {@link VideoStream}, which can be null
* @return this {@link Builder} instance
*/
public Builder setBaseUrl(@Nullable final String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
/**
* Set whether the {@link VideoStream} is video-only.
*
* <p>
* This property must be set before building the {@link VideoStream}.
* </p>
*
* @param isVideoOnly whether the {@link VideoStream} is video-only
* @return this {@link Builder} instance
*/
public Builder setIsVideoOnly(final boolean isVideoOnly) {
this.isVideoOnly = isVideoOnly;
return this;
}
/**
* Set the resolution of the {@link VideoStream}.
*
* <p>
* This resolution can be used by clients to know the quality of the video stream.
* </p>
*
* <p>
* If you are not able to know the resolution, you should use {@link #RESOLUTION_UNKNOWN}
* as the resolution of the video stream.
* </p>
*
* <p>
* It must be set before building the builder and not null.
* </p>
*
* @param resolution the resolution of the {@link VideoStream}
* @return this {@link Builder} instance
*/
public Builder setResolution(@Nonnull final String resolution) {
this.resolution = resolution;
return this;
}
/**
* Set the {@link ItagItem} corresponding to the {@link VideoStream}.
*
* <p>
* {@link ItagItem}s are YouTube specific objects, so they are only known for this service
* and can be null.
* </p>
*
* <p>
* The default value is {@code null}.
* </p>
*
* @param itagItem the {@link ItagItem} of the {@link VideoStream}, which can be null
* @return this {@link Builder} instance
*/
public Builder setItagItem(@Nullable final ItagItem itagItem) {
this.itagItem = itagItem;
return this;
}
/**
* Build a {@link VideoStream} using the builder's current values.
*
* <p>
* The identifier, the content (and so the {@code isUrl} boolean), the {@code isVideoOnly}
* and the {@code resolution} properties must have been set.
* </p>
*
* @return a new {@link VideoStream} using the builder's current values
* @throws IllegalStateException if {@code id}, {@code content} (and so {@code isUrl}),
* {@code deliveryMethod}, {@code isVideoOnly} or {@code resolution} have been not set or
* set as {@code null}
*/
@Nonnull
public VideoStream build() {
if (id == null) {
throw new IllegalStateException(
"The identifier of the video stream has been not set or is null. If you "
+ "are not able to get an identifier, use the static constant "
+ "ID_UNKNOWN of the Stream class.");
}
if (content == null) {
throw new IllegalStateException("The content of the video stream has been not set "
+ "or is null. Please specify a non-null one with setContent.");
}
if (deliveryMethod == null) {
throw new IllegalStateException(
"The delivery method of the video stream has been set as null, which is "
+ "not allowed. Pass a valid one instead with setDeliveryMethod.");
}
if (isVideoOnly == null) {
throw new IllegalStateException("The video stream has been not set as a "
+ "video-only stream or as a video stream with embedded audio. Please "
+ "specify this information with setIsVideoOnly.");
}
if (resolution == null) {
throw new IllegalStateException(
"The resolution of the video stream has been not set. Please specify it "
+ "with setResolution (use an empty string if you are not able to "
+ "get it).");
}
return new VideoStream(id, content, isUrl, mediaFormat, deliveryMethod, resolution,
isVideoOnly, baseUrl, itagItem);
}
}
public VideoStream(final String url,
final MediaFormat format,
final String resolution,
final boolean isVideoOnly) {
this(url, null, format, resolution, isVideoOnly);
}
public VideoStream(final String url, final boolean isVideoOnly, final ItagItem itag) {
this(url, itag.getMediaFormat(), itag.resolutionString, isVideoOnly);
this.itag = itag.id;
this.bitrate = itag.getBitrate();
this.initStart = itag.getInitStart();
this.initEnd = itag.getInitEnd();
this.indexStart = itag.getIndexStart();
this.indexEnd = itag.getIndexEnd();
this.codec = itag.getCodec();
this.height = itag.getHeight();
this.width = itag.getWidth();
this.quality = itag.getQuality();
this.fps = itag.fps;
}
public VideoStream(final String url,
final String torrentUrl,
final MediaFormat format,
final String resolution) {
this(url, torrentUrl, format, resolution, false);
}
public VideoStream(final String url,
final String torrentUrl,
final MediaFormat format,
final String resolution,
final boolean isVideoOnly) {
super(url, torrentUrl, format);
/**
* Create a new video stream.
*
* @param id the ID which uniquely identifies the stream, e.g. for YouTube this
* would be the itag
* @param content the content or the URL of the stream, depending on whether isUrl is
* true
* @param isUrl whether content is the URL or the actual content of e.g. a DASH
* manifest
* @param format the {@link MediaFormat} used by the stream, which can be null
* @param deliveryMethod the {@link DeliveryMethod} of the stream
* @param resolution the resolution of the stream
* @param isVideoOnly whether the stream is video-only
* @param itagItem the {@link ItagItem} corresponding to the stream, which cannot be null
* @param baseUrl the base URL of the stream (see {@link Stream#getBaseUrl()} for more
* information)
*/
private VideoStream(@Nonnull final String id,
@Nonnull final String content,
final boolean isUrl,
@Nullable final MediaFormat format,
@Nonnull final DeliveryMethod deliveryMethod,
@Nonnull final String resolution,
final boolean isVideoOnly,
@Nullable final String baseUrl,
@Nullable final ItagItem itagItem) {
super(id, content, isUrl, format, deliveryMethod, baseUrl);
if (itagItem != null) {
this.itagItem = itagItem;
this.itag = itagItem.id;
this.bitrate = itagItem.getBitrate();
this.initStart = itagItem.getInitStart();
this.initEnd = itagItem.getInitEnd();
this.indexStart = itagItem.getIndexStart();
this.indexEnd = itagItem.getIndexEnd();
this.codec = itagItem.getCodec();
this.height = itagItem.getHeight();
this.width = itagItem.getWidth();
this.quality = itagItem.getQuality();
this.fps = itagItem.getFps();
}
this.resolution = resolution;
this.isVideoOnly = isVideoOnly;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equalStats(final Stream cmp) {
return super.equalStats(cmp) && cmp instanceof VideoStream
return super.equalStats(cmp)
&& cmp instanceof VideoStream
&& resolution.equals(((VideoStream) cmp).resolution)
&& isVideoOnly == ((VideoStream) cmp).isVideoOnly;
}
/**
* Get the video resolution
* Get the video resolution.
*
* @return the video resolution
* <p>
* It can be unknown for some streams, like for HLS master playlists. In this case,
* {@link #RESOLUTION_UNKNOWN} is returned by this method.
* </p>
*
* @return the video resolution or {@link #RESOLUTION_UNKNOWN}
*/
@Nonnull
public String getResolution() {
return resolution;
}
/**
* Check if the video is video only.
* <p>
* Video only streams have no audio
* Return whether the stream is video-only.
*
* @return {@code true} if this stream is vid
* <p>
* Video-only streams have no audio.
* </p>
*
* @return {@code true} if this stream is video-only, {@code false} otherwise
*/
public boolean isVideoOnly() {
return isVideoOnly;
}
/**
* Get the itag identifier of the stream.
*
* <p>
* Always equals to {@link #ITAG_NOT_AVAILABLE_OR_NOT_APPLICABLE} for other streams than the
* ones of the YouTube service.
* </p>
*
* @return the number of the {@link ItagItem} passed in the constructor of the video stream.
*/
public int getItag() {
return itag;
}
/**
* Get the bitrate of the stream.
*
* @return the bitrate set from the {@link ItagItem} passed in the constructor of the stream.
*/
public int getBitrate() {
return bitrate;
}
/**
* Get the initialization start of the stream.
*
* @return the initialization start value set from the {@link ItagItem} passed in the
* constructor of the
* stream.
*/
public int getInitStart() {
return initStart;
}
/**
* Get the initialization end of the stream.
*
* @return the initialization end value set from the {@link ItagItem} passed in the constructor
* of the stream.
*/
public int getInitEnd() {
return initEnd;
}
/**
* Get the index start of the stream.
*
* @return the index start value set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public int getIndexStart() {
return indexStart;
}
/**
* Get the index end of the stream.
*
* @return the index end value set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public int getIndexEnd() {
return indexEnd;
}
/**
* Get the width of the video stream.
*
* @return the width set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public int getWidth() {
return width;
}
/**
* Get the height of the video stream.
*
* @return the height set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public int getHeight() {
return height;
}
/**
* Get the frames per second of the video stream.
*
* @return the frames per second set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public int getFps() {
return fps;
}
/**
* Get the quality of the stream.
*
* @return the quality label set from the {@link ItagItem} passed in the constructor of the
* stream.
*/
public String getQuality() {
return quality;
}
/**
* Get the codec of the stream.
*
* @return the codec set from the {@link ItagItem} passed in the constructor of the stream.
*/
public String getCodec() {
return codec;
}
/**
* {@inheritDoc}
*/
@Override
@Nullable
public ItagItem getItagItem() {
return itagItem;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
if (!super.equals(obj)) {
return false;
}
final VideoStream videoStream = (VideoStream) obj;
return isVideoOnly == videoStream.isVideoOnly && resolution.equals(videoStream.resolution);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), resolution, isVideoOnly);
}
}