[YouTube] Reduce exception generation code and move several attributes of MPD documents into constants

Rename YoutubeDashManifestCreationException to CreationException.
Also use these constants in YoutubeDashManifestCreatorTest.
This commit is contained in:
Stypox 2022-05-01 20:06:04 +02:00 committed by TiA4f8R
parent 00bbe5eb4d
commit cfc13f4a6f
No known key found for this signature in database
GPG key ID: E6D3E7F5949450DD
2 changed files with 110 additions and 105 deletions

View file

@ -96,6 +96,10 @@ public final class YoutubeDashManifestCreator {
public static final String SEGMENT_TIMELINE = "SegmentTimeline"; public static final String SEGMENT_TIMELINE = "SegmentTimeline";
public static final String ADAPTATION_SET = "AdaptationSet"; public static final String ADAPTATION_SET = "AdaptationSet";
public static final String REPRESENTATION = "Representation"; public static final String REPRESENTATION = "Representation";
public static final String SEGMENT_TEMPLATE = "SegmentTemplate";
public static final String INITIALIZATION = "Initialization";
public static final String PERIOD = "Period";
public static final String SEGMENT_BASE = "SegmentBase";
/** /**
* Enum of streaming format types used by YouTube in their streams. * Enum of streaming format types used by YouTube in their streams.
@ -154,15 +158,23 @@ public final class YoutubeDashManifestCreator {
* Exception that is thrown when the {@link YoutubeDashManifestCreator} encounters a problem * Exception that is thrown when the {@link YoutubeDashManifestCreator} encounters a problem
* while creating a manifest. * while creating a manifest.
*/ */
public static final class YoutubeDashManifestCreationException extends Exception { public static final class CreationException extends Exception {
YoutubeDashManifestCreationException(final String message) { CreationException(final String message) {
super(message); super(message);
} }
YoutubeDashManifestCreationException(final String message, final Exception e) { CreationException(final String message, final Exception e) {
super(message, e); super(message, e);
} }
public static CreationException couldNotAdd(final String element, final Exception e) {
return new CreationException("Could not add " + element + " element", e);
}
public static CreationException couldNotAdd(final String element, final String reason) {
return new CreationException("Could not add " + element + " element: " + reason);
}
} }
/** /**
@ -226,7 +238,7 @@ public final class YoutubeDashManifestCreator {
public static String fromOtfStreamingUrl( public static String fromOtfStreamingUrl(
@Nonnull final String otfBaseStreamingUrl, @Nonnull final String otfBaseStreamingUrl,
@Nonnull final ItagItem itagItem, @Nonnull final ItagItem itagItem,
final long durationSecondsFallback) throws YoutubeDashManifestCreationException { final long durationSecondsFallback) throws CreationException {
if (OTF_CACHE.containsKey(otfBaseStreamingUrl)) { if (OTF_CACHE.containsKey(otfBaseStreamingUrl)) {
return Objects.requireNonNull(OTF_CACHE.get(otfBaseStreamingUrl)).getSecond(); return Objects.requireNonNull(OTF_CACHE.get(otfBaseStreamingUrl)).getSecond();
} }
@ -241,9 +253,8 @@ public final class YoutubeDashManifestCreator {
final int responseCode = response.responseCode(); final int responseCode = response.responseCode();
if (responseCode != 200) { if (responseCode != 200) {
throw new YoutubeDashManifestCreationException( throw new CreationException("Could not get the initialization URL of "
"Unable to create the DASH manifest: could not get the initialization URL of " + "the OTF stream: response code " + responseCode);
+ "the OTF stream: response code " + responseCode);
} }
final String[] segmentDuration; final String[] segmentDuration;
@ -263,9 +274,7 @@ public final class YoutubeDashManifestCreator {
segmentDuration = segmentsAndDurationsResponseSplit; segmentDuration = segmentsAndDurationsResponseSplit;
} }
} catch (final Exception e) { } catch (final Exception e) {
throw new YoutubeDashManifestCreationException( throw new CreationException("Could not get segment durations", e);
"Unable to generate the DASH manifest: could not get the duration of segments",
e);
} }
final Document document = generateDocumentAndMpdElement(segmentDuration, DeliveryType.OTF, final Document document = generateDocumentAndMpdElement(segmentDuration, DeliveryType.OTF,
@ -354,7 +363,7 @@ public final class YoutubeDashManifestCreator {
@Nonnull final String postLiveStreamDvrStreamingUrl, @Nonnull final String postLiveStreamDvrStreamingUrl,
@Nonnull final ItagItem itagItem, @Nonnull final ItagItem itagItem,
final int targetDurationSec, final int targetDurationSec,
final long durationSecondsFallback) throws YoutubeDashManifestCreationException { final long durationSecondsFallback) throws CreationException {
if (POST_LIVE_DVR_CACHE.containsKey(postLiveStreamDvrStreamingUrl)) { if (POST_LIVE_DVR_CACHE.containsKey(postLiveStreamDvrStreamingUrl)) {
return Objects.requireNonNull(POST_LIVE_DVR_CACHE.get(postLiveStreamDvrStreamingUrl)) return Objects.requireNonNull(POST_LIVE_DVR_CACHE.get(postLiveStreamDvrStreamingUrl))
.getSecond(); .getSecond();
@ -364,8 +373,7 @@ public final class YoutubeDashManifestCreator {
final String segmentCount; final String segmentCount;
if (targetDurationSec <= 0) { if (targetDurationSec <= 0) {
throw new YoutubeDashManifestCreationException( throw new CreationException("targetDurationSec value is <= 0: " + targetDurationSec);
"targetDurationSec value is <= 0: " + targetDurationSec);
} }
try { try {
@ -378,7 +386,7 @@ public final class YoutubeDashManifestCreator {
final int responseCode = response.responseCode(); final int responseCode = response.responseCode();
if (responseCode != 200) { if (responseCode != 200) {
throw new YoutubeDashManifestCreationException("Could not get the initialization " throw new CreationException("Could not get the initialization "
+ "segment of the post-live-DVR stream: response code " + responseCode); + "segment of the post-live-DVR stream: response code " + responseCode);
} }
@ -386,14 +394,13 @@ public final class YoutubeDashManifestCreator {
streamDuration = responseHeaders.get("X-Head-Time-Millis").get(0); streamDuration = responseHeaders.get("X-Head-Time-Millis").get(0);
segmentCount = responseHeaders.get("X-Head-Seqnum").get(0); segmentCount = responseHeaders.get("X-Head-Seqnum").get(0);
} catch (final IndexOutOfBoundsException e) { } catch (final IndexOutOfBoundsException e) {
throw new YoutubeDashManifestCreationException("Could not get the value of the " throw new CreationException("Could not get the value of the X-Head-Time-Millis or the "
+ "X-Head-Time-Millis or the X-Head-Seqnum header of the post-live-DVR" + "X-Head-Seqnum header of the post-live-DVR streaming URL", e);
+ "streaming URL", e);
} }
if (isNullOrEmpty(segmentCount)) { if (isNullOrEmpty(segmentCount)) {
throw new YoutubeDashManifestCreationException("Could not get the number of segments " throw new CreationException(
+ "of the post-live-DVR stream"); "Could not get the number of segments of the post-live-DVR stream");
} }
final Document document = generateDocumentAndMpdElement(new String[] {streamDuration}, final Document document = generateDocumentAndMpdElement(new String[] {streamDuration},
@ -475,7 +482,7 @@ public final class YoutubeDashManifestCreator {
public static String fromProgressiveStreamingUrl( public static String fromProgressiveStreamingUrl(
@Nonnull final String progressiveStreamingBaseUrl, @Nonnull final String progressiveStreamingBaseUrl,
@Nonnull final ItagItem itagItem, @Nonnull final ItagItem itagItem,
final long durationSecondsFallback) throws YoutubeDashManifestCreationException { final long durationSecondsFallback) throws CreationException {
if (PROGRESSIVE_CACHE.containsKey(progressiveStreamingBaseUrl)) { if (PROGRESSIVE_CACHE.containsKey(progressiveStreamingBaseUrl)) {
return Objects.requireNonNull(PROGRESSIVE_CACHE.get(progressiveStreamingBaseUrl)) return Objects.requireNonNull(PROGRESSIVE_CACHE.get(progressiveStreamingBaseUrl))
.getSecond(); .getSecond();
@ -523,7 +530,7 @@ public final class YoutubeDashManifestCreator {
private static Response getInitializationResponse(@Nonnull String baseStreamingUrl, private static Response getInitializationResponse(@Nonnull String baseStreamingUrl,
@Nonnull final ItagItem itagItem, @Nonnull final ItagItem itagItem,
final DeliveryType deliveryType) final DeliveryType deliveryType)
throws YoutubeDashManifestCreationException { throws CreationException {
final boolean isAHtml5StreamingUrl = isWebStreamingUrl(baseStreamingUrl) final boolean isAHtml5StreamingUrl = isWebStreamingUrl(baseStreamingUrl)
|| isTvHtml5SimplyEmbeddedPlayerStreamingUrl(baseStreamingUrl); || isTvHtml5SimplyEmbeddedPlayerStreamingUrl(baseStreamingUrl);
final boolean isAnAndroidStreamingUrl = isAndroidStreamingUrl(baseStreamingUrl); final boolean isAnAndroidStreamingUrl = isAndroidStreamingUrl(baseStreamingUrl);
@ -549,7 +556,7 @@ public final class YoutubeDashManifestCreator {
final byte[] emptyBody = "".getBytes(StandardCharsets.UTF_8); final byte[] emptyBody = "".getBytes(StandardCharsets.UTF_8);
return downloader.post(baseStreamingUrl, headers, emptyBody); return downloader.post(baseStreamingUrl, headers, emptyBody);
} catch (final IOException | ExtractionException e) { } catch (final IOException | ExtractionException e) {
throw new YoutubeDashManifestCreationException("Could not get the " throw new CreationException("Could not get the "
+ (isAnIosStreamingUrl ? "ANDROID" : "IOS") + " streaming URL response", e); + (isAnIosStreamingUrl ? "ANDROID" : "IOS") + " streaming URL response", e);
} }
} }
@ -557,8 +564,7 @@ public final class YoutubeDashManifestCreator {
try { try {
return downloader.get(baseStreamingUrl); return downloader.get(baseStreamingUrl);
} catch (final IOException | ExtractionException e) { } catch (final IOException | ExtractionException e) {
throw new YoutubeDashManifestCreationException("Could not get the streaming URL " throw new CreationException("Could not get the streaming URL response", e);
+ "response", e);
} }
} }
@ -610,7 +616,7 @@ public final class YoutubeDashManifestCreator {
@Nonnull final Downloader downloader, @Nonnull final Downloader downloader,
@Nonnull String streamingUrl, @Nonnull String streamingUrl,
@Nonnull final String responseMimeTypeExpected, @Nonnull final String responseMimeTypeExpected,
@Nonnull final DeliveryType deliveryType) throws YoutubeDashManifestCreationException { @Nonnull final DeliveryType deliveryType) throws CreationException {
try { try {
final Map<String, List<String>> headers = new HashMap<>(); final Map<String, List<String>> headers = new HashMap<>();
addClientInfoHeaders(headers); addClientInfoHeaders(headers);
@ -631,9 +637,8 @@ public final class YoutubeDashManifestCreator {
final int responseCode = response.responseCode(); final int responseCode = response.responseCode();
if (responseCode != 200) { if (responseCode != 200) {
throw new YoutubeDashManifestCreationException("Could not get the " throw new CreationException("Could not get the initialization URL of the "
+ "initialization URL of the " + deliveryType + deliveryType + " stream: response code " + responseCode);
+ " stream: response code " + responseCode);
} }
// A valid response must include a Content-Type header, so we can require that // A valid response must include a Content-Type header, so we can require that
@ -651,17 +656,16 @@ public final class YoutubeDashManifestCreator {
} }
if (redirectsCount >= MAXIMUM_REDIRECT_COUNT) { if (redirectsCount >= MAXIMUM_REDIRECT_COUNT) {
throw new YoutubeDashManifestCreationException( throw new CreationException(
"Too many redirects when trying to get the WEB streaming URL response"); "Too many redirects when trying to get the WEB streaming URL response");
} }
// This should never be reached, but is required because we don't want to return null // This should never be reached, but is required because we don't want to return null
// here // here
throw new YoutubeDashManifestCreationException( throw new CreationException(
"Could not get the WEB streaming URL response: unreachable code reached!"); "Could not get the WEB streaming URL response: unreachable code reached!");
} catch (final IOException | ExtractionException e) { } catch (final IOException | ExtractionException e) {
throw new YoutubeDashManifestCreationException( throw new CreationException("Could not get the WEB streaming URL response", e);
"Could not get the WEB streaming URL response", e);
} }
} }
@ -678,7 +682,7 @@ public final class YoutubeDashManifestCreator {
* @return the duration of the OTF stream * @return the duration of the OTF stream
*/ */
private static int getStreamDuration(@Nonnull final String[] segmentDuration) private static int getStreamDuration(@Nonnull final String[] segmentDuration)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
int streamLengthMs = 0; int streamLengthMs = 0;
for (final String segDuration : segmentDuration) { for (final String segDuration : segmentDuration) {
@ -694,8 +698,7 @@ public final class YoutubeDashManifestCreator {
} }
return streamLengthMs; return streamLengthMs;
} catch (final NumberFormatException e) { } catch (final NumberFormatException e) {
throw new YoutubeDashManifestCreationException("Unable to get the length of the stream", throw new CreationException("Could not get stream length", e);
e);
} }
} }
@ -736,7 +739,7 @@ public final class YoutubeDashManifestCreator {
final DeliveryType deliveryType, final DeliveryType deliveryType,
@Nonnull final ItagItem itagItem, @Nonnull final ItagItem itagItem,
final long durationSecondsFallback) final long durationSecondsFallback)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Document document = newDocument(); final Document document = newDocument();
@ -782,8 +785,8 @@ public final class YoutubeDashManifestCreator {
if (durationSecondsFallback > 0) { if (durationSecondsFallback > 0) {
streamDuration = durationSecondsFallback * 1000; streamDuration = durationSecondsFallback * 1000;
} else { } else {
throw new YoutubeDashManifestCreationException("Could not add MPD element: " throw CreationException.couldNotAdd("MPD",
+ "the duration of the stream could not be determined and the " "the duration of the stream could not be determined and the "
+ "durationSecondsFallback is <= 0"); + "durationSecondsFallback is <= 0");
} }
} }
@ -795,7 +798,7 @@ public final class YoutubeDashManifestCreator {
return document; return document;
} catch (final Exception e) { } catch (final Exception e) {
throw new YoutubeDashManifestCreationException("Could not add MPD element", e); throw CreationException.couldNotAdd("MPD", e);
} }
} }
@ -811,13 +814,13 @@ public final class YoutubeDashManifestCreator {
* appended * appended
*/ */
private static void generatePeriodElement(@Nonnull final Document document) private static void generatePeriodElement(@Nonnull final Document document)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element mpdElement = (Element) document.getElementsByTagName("MPD").item(0); final Element mpdElement = (Element) document.getElementsByTagName("MPD").item(0);
final Element periodElement = document.createElement("Period"); final Element periodElement = document.createElement(PERIOD);
mpdElement.appendChild(periodElement); mpdElement.appendChild(periodElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add Period element", e); throw CreationException.couldNotAdd(PERIOD, e);
} }
} }
@ -835,9 +838,9 @@ public final class YoutubeDashManifestCreator {
*/ */
private static void generateAdaptationSetElement(@Nonnull final Document document, private static void generateAdaptationSetElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) @Nonnull final ItagItem itagItem)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element periodElement = (Element) document.getElementsByTagName("Period") final Element periodElement = (Element) document.getElementsByTagName(PERIOD)
.item(0); .item(0);
final Element adaptationSetElement = document.createElement(ADAPTATION_SET); final Element adaptationSetElement = document.createElement(ADAPTATION_SET);
@ -847,8 +850,8 @@ public final class YoutubeDashManifestCreator {
final MediaFormat mediaFormat = itagItem.getMediaFormat(); final MediaFormat mediaFormat = itagItem.getMediaFormat();
if (mediaFormat == null || isNullOrEmpty(mediaFormat.mimeType)) { if (mediaFormat == null || isNullOrEmpty(mediaFormat.mimeType)) {
throw new YoutubeDashManifestCreationException("Could not add AdaptationSet " throw CreationException.couldNotAdd(ADAPTATION_SET,
+ "element: the MediaFormat or its mime type are null or empty"); "the MediaFormat or its mime type are null or empty");
} }
final Attr mimeTypeAttribute = document.createAttribute("mimeType"); final Attr mimeTypeAttribute = document.createAttribute("mimeType");
@ -862,8 +865,7 @@ public final class YoutubeDashManifestCreator {
periodElement.appendChild(adaptationSetElement); periodElement.appendChild(adaptationSetElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add AdaptationSet element", throw CreationException.couldNotAdd(ADAPTATION_SET, e);
e);
} }
} }
@ -888,7 +890,7 @@ public final class YoutubeDashManifestCreator {
* appended * appended
*/ */
private static void generateRoleElement(@Nonnull final Document document) private static void generateRoleElement(@Nonnull final Document document)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element adaptationSetElement = (Element) document.getElementsByTagName( final Element adaptationSetElement = (Element) document.getElementsByTagName(
ADAPTATION_SET).item(0); ADAPTATION_SET).item(0);
@ -904,7 +906,7 @@ public final class YoutubeDashManifestCreator {
adaptationSetElement.appendChild(roleElement); adaptationSetElement.appendChild(roleElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add Role element", e); throw CreationException.couldNotAdd("Role", e);
} }
} }
@ -923,7 +925,7 @@ public final class YoutubeDashManifestCreator {
*/ */
private static void generateRepresentationElement(@Nonnull final Document document, private static void generateRepresentationElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) @Nonnull final ItagItem itagItem)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element adaptationSetElement = (Element) document.getElementsByTagName( final Element adaptationSetElement = (Element) document.getElementsByTagName(
ADAPTATION_SET).item(0); ADAPTATION_SET).item(0);
@ -931,8 +933,8 @@ public final class YoutubeDashManifestCreator {
final int id = itagItem.id; final int id = itagItem.id;
if (id <= 0) { if (id <= 0) {
throw new YoutubeDashManifestCreationException("Could not add Representation " throw CreationException.couldNotAdd(REPRESENTATION,
+ "element: the id of the ItagItem is <= 0"); "the id of the ItagItem is <= 0");
} }
final Attr idAttribute = document.createAttribute("id"); final Attr idAttribute = document.createAttribute("id");
idAttribute.setValue(String.valueOf(id)); idAttribute.setValue(String.valueOf(id));
@ -940,8 +942,8 @@ public final class YoutubeDashManifestCreator {
final String codec = itagItem.getCodec(); final String codec = itagItem.getCodec();
if (isNullOrEmpty(codec)) { if (isNullOrEmpty(codec)) {
throw new YoutubeDashManifestCreationException("Could not add AdaptationSet " throw CreationException.couldNotAdd(ADAPTATION_SET,
+ "element: the codec value is null or empty"); "the codec value is null or empty");
} }
final Attr codecsAttribute = document.createAttribute("codecs"); final Attr codecsAttribute = document.createAttribute("codecs");
codecsAttribute.setValue(codec); codecsAttribute.setValue(codec);
@ -957,8 +959,8 @@ public final class YoutubeDashManifestCreator {
final int bitrate = itagItem.getBitrate(); final int bitrate = itagItem.getBitrate();
if (bitrate <= 0) { if (bitrate <= 0) {
throw new YoutubeDashManifestCreationException("Could not add Representation " throw CreationException.couldNotAdd(REPRESENTATION,
+ "element: the bitrate of the ItagItem is <= 0"); "the bitrate of the ItagItem is <= 0");
} }
final Attr bandwidthAttribute = document.createAttribute("bandwidth"); final Attr bandwidthAttribute = document.createAttribute("bandwidth");
bandwidthAttribute.setValue(String.valueOf(bitrate)); bandwidthAttribute.setValue(String.valueOf(bitrate));
@ -970,8 +972,8 @@ public final class YoutubeDashManifestCreator {
final int height = itagItem.getHeight(); final int height = itagItem.getHeight();
final int width = itagItem.getWidth(); final int width = itagItem.getWidth();
if (height <= 0 && width <= 0) { if (height <= 0 && width <= 0) {
throw new YoutubeDashManifestCreationException("Could not add Representation " throw CreationException.couldNotAdd(REPRESENTATION,
+ "element: both width and height of the ItagItem are <= 0"); "both width and height of the ItagItem are <= 0");
} }
if (width > 0) { if (width > 0) {
@ -1000,8 +1002,7 @@ public final class YoutubeDashManifestCreator {
adaptationSetElement.appendChild(representationElement); adaptationSetElement.appendChild(representationElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add Representation element", throw CreationException.couldNotAdd(REPRESENTATION, e);
e);
} }
} }
@ -1035,7 +1036,7 @@ public final class YoutubeDashManifestCreator {
*/ */
private static void generateAudioChannelConfigurationElement( private static void generateAudioChannelConfigurationElement(
@Nonnull final Document document, @Nonnull final Document document,
@Nonnull final ItagItem itagItem) throws YoutubeDashManifestCreationException { @Nonnull final ItagItem itagItem) throws CreationException {
try { try {
final Element representationElement = (Element) document.getElementsByTagName( final Element representationElement = (Element) document.getElementsByTagName(
REPRESENTATION).item(0); REPRESENTATION).item(0);
@ -1050,16 +1051,14 @@ public final class YoutubeDashManifestCreator {
final Attr valueAttribute = document.createAttribute("value"); final Attr valueAttribute = document.createAttribute("value");
final int audioChannels = itagItem.getAudioChannels(); final int audioChannels = itagItem.getAudioChannels();
if (audioChannels <= 0) { if (audioChannels <= 0) {
throw new YoutubeDashManifestCreationException( throw new CreationException("audioChannels is <= 0: " + audioChannels);
"audioChannels is <= 0: " + audioChannels);
} }
valueAttribute.setValue(String.valueOf(itagItem.getAudioChannels())); valueAttribute.setValue(String.valueOf(itagItem.getAudioChannels()));
audioChannelConfigurationElement.setAttributeNode(valueAttribute); audioChannelConfigurationElement.setAttributeNode(valueAttribute);
representationElement.appendChild(audioChannelConfigurationElement); representationElement.appendChild(audioChannelConfigurationElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException( throw CreationException.couldNotAdd("AudioChannelConfiguration", e);
"Could not add AudioChannelConfiguration element", e);
} }
} }
@ -1083,7 +1082,7 @@ public final class YoutubeDashManifestCreator {
*/ */
private static void generateBaseUrlElement(@Nonnull final Document document, private static void generateBaseUrlElement(@Nonnull final Document document,
@Nonnull final String baseUrl) @Nonnull final String baseUrl)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element representationElement = (Element) document.getElementsByTagName( final Element representationElement = (Element) document.getElementsByTagName(
REPRESENTATION).item(0); REPRESENTATION).item(0);
@ -1091,7 +1090,7 @@ public final class YoutubeDashManifestCreator {
baseURLElement.setTextContent(baseUrl); baseURLElement.setTextContent(baseUrl);
representationElement.appendChild(baseURLElement); representationElement.appendChild(baseURLElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add BaseURL element", e); throw CreationException.couldNotAdd("BaseURL", e);
} }
} }
@ -1123,17 +1122,18 @@ public final class YoutubeDashManifestCreator {
*/ */
private static void generateSegmentBaseElement(@Nonnull final Document document, private static void generateSegmentBaseElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) @Nonnull final ItagItem itagItem)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element representationElement = (Element) document.getElementsByTagName( final Element representationElement = (Element) document.getElementsByTagName(
REPRESENTATION).item(0); REPRESENTATION).item(0);
final Element segmentBaseElement = document.createElement("SegmentBase"); final Element segmentBaseElement = document.createElement(SEGMENT_BASE);
final Attr indexRangeAttribute = document.createAttribute("indexRange"); final Attr indexRangeAttribute = document.createAttribute("indexRange");
if (itagItem.getIndexStart() < 0 || itagItem.getIndexEnd() < 0) { if (itagItem.getIndexStart() < 0 || itagItem.getIndexEnd() < 0) {
throw new YoutubeDashManifestCreationException("ItagItem's indexStart or indexEnd " throw CreationException.couldNotAdd(SEGMENT_BASE, "ItagItem's indexStart or "
+ "are < 0: " + itagItem.getIndexStart() + "-" + itagItem.getIndexEnd()); + "indexEnd are < 0: " + itagItem.getIndexStart() + "-"
+ itagItem.getIndexEnd());
} }
indexRangeAttribute.setValue(itagItem.getIndexStart() + "-" + itagItem.getIndexEnd()); indexRangeAttribute.setValue(itagItem.getIndexStart() + "-" + itagItem.getIndexEnd());
@ -1141,7 +1141,7 @@ public final class YoutubeDashManifestCreator {
representationElement.appendChild(segmentBaseElement); representationElement.appendChild(segmentBaseElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add SegmentBase element", e); throw CreationException.couldNotAdd(SEGMENT_BASE, e);
} }
} }
@ -1173,17 +1173,18 @@ public final class YoutubeDashManifestCreator {
*/ */
private static void generateInitializationElement(@Nonnull final Document document, private static void generateInitializationElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) @Nonnull final ItagItem itagItem)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element segmentBaseElement = (Element) document.getElementsByTagName( final Element segmentBaseElement = (Element) document.getElementsByTagName(
"SegmentBase").item(0); SEGMENT_BASE).item(0);
final Element initializationElement = document.createElement("Initialization"); final Element initializationElement = document.createElement(INITIALIZATION);
final Attr rangeAttribute = document.createAttribute("range"); final Attr rangeAttribute = document.createAttribute("range");
if (itagItem.getInitStart() < 0 || itagItem.getInitEnd() < 0) { if (itagItem.getInitStart() < 0 || itagItem.getInitEnd() < 0) {
throw new YoutubeDashManifestCreationException("ItagItem's initStart or initEnd " throw CreationException.couldNotAdd(INITIALIZATION, "ItagItem's initStart or "
+ "are < 0: " + itagItem.getInitStart() + "-" + itagItem.getInitEnd()); + "initEnd are < 0: " + itagItem.getInitStart() + "-"
+ itagItem.getInitEnd());
} }
rangeAttribute.setValue(itagItem.getInitStart() + "-" + itagItem.getInitEnd()); rangeAttribute.setValue(itagItem.getInitStart() + "-" + itagItem.getInitEnd());
@ -1191,8 +1192,7 @@ public final class YoutubeDashManifestCreator {
segmentBaseElement.appendChild(initializationElement); segmentBaseElement.appendChild(initializationElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add Initialization element", throw CreationException.couldNotAdd(INITIALIZATION, e);
e);
} }
} }
@ -1230,11 +1230,11 @@ public final class YoutubeDashManifestCreator {
private static void generateSegmentTemplateElement(@Nonnull final Document document, private static void generateSegmentTemplateElement(@Nonnull final Document document,
@Nonnull final String baseUrl, @Nonnull final String baseUrl,
final DeliveryType deliveryType) final DeliveryType deliveryType)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element representationElement = (Element) document.getElementsByTagName( final Element representationElement = (Element) document.getElementsByTagName(
REPRESENTATION).item(0); REPRESENTATION).item(0);
final Element segmentTemplateElement = document.createElement("SegmentTemplate"); final Element segmentTemplateElement = document.createElement(SEGMENT_TEMPLATE);
final Attr startNumberAttribute = document.createAttribute("startNumber"); final Attr startNumberAttribute = document.createAttribute("startNumber");
final boolean isDeliveryTypeLive = deliveryType == DeliveryType.LIVE; final boolean isDeliveryTypeLive = deliveryType == DeliveryType.LIVE;
@ -1261,8 +1261,7 @@ public final class YoutubeDashManifestCreator {
representationElement.appendChild(segmentTemplateElement); representationElement.appendChild(segmentTemplateElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add SegmentTemplate element", throw CreationException.couldNotAdd(SEGMENT_TEMPLATE, e);
e);
} }
} }
@ -1279,16 +1278,15 @@ public final class YoutubeDashManifestCreator {
* be appended * be appended
*/ */
private static void generateSegmentTimelineElement(@Nonnull final Document document) private static void generateSegmentTimelineElement(@Nonnull final Document document)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element segmentTemplateElement = (Element) document.getElementsByTagName( final Element segmentTemplateElement = (Element) document.getElementsByTagName(
"SegmentTemplate").item(0); SEGMENT_TEMPLATE).item(0);
final Element segmentTimelineElement = document.createElement(SEGMENT_TIMELINE); final Element segmentTimelineElement = document.createElement(SEGMENT_TIMELINE);
segmentTemplateElement.appendChild(segmentTimelineElement); segmentTemplateElement.appendChild(segmentTimelineElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add SegmentTimeline element", throw CreationException.couldNotAdd(SEGMENT_TIMELINE, e);
e);
} }
} }
@ -1324,7 +1322,7 @@ public final class YoutubeDashManifestCreator {
*/ */
private static void generateSegmentElementsForOtfStreams(final String[] segmentDurations, private static void generateSegmentElementsForOtfStreams(final String[] segmentDurations,
final Document document) final Document document)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final Element segmentTimelineElement = (Element) document.getElementsByTagName( final Element segmentTimelineElement = (Element) document.getElementsByTagName(
@ -1355,7 +1353,7 @@ public final class YoutubeDashManifestCreator {
} catch (final DOMException | IllegalStateException | IndexOutOfBoundsException } catch (final DOMException | IllegalStateException | IndexOutOfBoundsException
| NumberFormatException e) { | NumberFormatException e) {
throw new YoutubeDashManifestCreationException("Could not add segment (S) elements", e); throw CreationException.couldNotAdd("segment (S)", e);
} }
} }
@ -1380,7 +1378,7 @@ public final class YoutubeDashManifestCreator {
private static void generateSegmentElementForPostLiveDvrStreams( private static void generateSegmentElementForPostLiveDvrStreams(
@Nonnull final Document document, @Nonnull final Document document,
final int targetDurationSeconds, final int targetDurationSeconds,
@Nonnull final String segmentCount) throws YoutubeDashManifestCreationException { @Nonnull final String segmentCount) throws CreationException {
try { try {
final Element segmentTimelineElement = (Element) document.getElementsByTagName( final Element segmentTimelineElement = (Element) document.getElementsByTagName(
SEGMENT_TIMELINE).item(0); SEGMENT_TIMELINE).item(0);
@ -1396,7 +1394,7 @@ public final class YoutubeDashManifestCreator {
segmentTimelineElement.appendChild(sElement); segmentTimelineElement.appendChild(sElement);
} catch (final DOMException e) { } catch (final DOMException e) {
throw new YoutubeDashManifestCreationException("Could not add segment (S) elements", e); throw CreationException.couldNotAdd("segment (S)", e);
} }
} }
@ -1414,14 +1412,14 @@ public final class YoutubeDashManifestCreator {
@Nonnull final String originalBaseStreamingUrl, @Nonnull final String originalBaseStreamingUrl,
@Nonnull final Document document, @Nonnull final Document document,
@Nonnull final ManifestCreatorCache<String, String> manifestCreatorCache) @Nonnull final ManifestCreatorCache<String, String> manifestCreatorCache)
throws YoutubeDashManifestCreationException { throws CreationException {
try { try {
final String documentXml = documentToXml(document); final String documentXml = documentToXml(document);
manifestCreatorCache.put(originalBaseStreamingUrl, documentXml); manifestCreatorCache.put(originalBaseStreamingUrl, documentXml);
return documentXml; return documentXml;
} catch (final Exception e) { } catch (final Exception e) {
throw new YoutubeDashManifestCreationException( throw new CreationException(
"Could not convert the DASH manifest generated to a string", e); "Could not convert the DASH manifest generated to a string", e);
} }
} }

View file

@ -11,6 +11,13 @@ import static org.schabi.newpipe.extractor.ExtractorAsserts.assertGreaterOrEqual
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsValidUrl; import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsValidUrl;
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertNotBlank; import static org.schabi.newpipe.extractor.ExtractorAsserts.assertNotBlank;
import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeDashManifestCreator.ADAPTATION_SET;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeDashManifestCreator.INITIALIZATION;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeDashManifestCreator.PERIOD;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeDashManifestCreator.REPRESENTATION;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeDashManifestCreator.SEGMENT_BASE;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeDashManifestCreator.SEGMENT_TEMPLATE;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeDashManifestCreator.SEGMENT_TIMELINE;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@ -95,7 +102,7 @@ class YoutubeDashManifestCreatorTest {
assertProgressiveStreams(extractor.getAudioStreams()); assertProgressiveStreams(extractor.getAudioStreams());
// we are not able to generate DASH manifests of video formats with audio // we are not able to generate DASH manifests of video formats with audio
assertThrows(YoutubeDashManifestCreator.YoutubeDashManifestCreationException.class, assertThrows(YoutubeDashManifestCreator.CreationException.class,
() -> assertProgressiveStreams(extractor.getVideoStreams())); () -> assertProgressiveStreams(extractor.getVideoStreams()));
} }
@ -193,22 +200,22 @@ class YoutubeDashManifestCreatorTest {
} }
private void assertPeriodElement(@Nonnull final Document document) { private void assertPeriodElement(@Nonnull final Document document) {
assertGetElement(document, "Period", "MPD"); assertGetElement(document, PERIOD, "MPD");
} }
private void assertAdaptationSetElement(@Nonnull final Document document, private void assertAdaptationSetElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) { @Nonnull final ItagItem itagItem) {
final Element element = assertGetElement(document, "AdaptationSet", "Period"); final Element element = assertGetElement(document, ADAPTATION_SET, PERIOD);
assertAttrEquals(itagItem.getMediaFormat().getMimeType(), element, "mimeType"); assertAttrEquals(itagItem.getMediaFormat().getMimeType(), element, "mimeType");
} }
private void assertRoleElement(@Nonnull final Document document) { private void assertRoleElement(@Nonnull final Document document) {
assertGetElement(document, "Role", "AdaptationSet"); assertGetElement(document, "Role", ADAPTATION_SET);
} }
private void assertRepresentationElement(@Nonnull final Document document, private void assertRepresentationElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) { @Nonnull final ItagItem itagItem) {
final Element element = assertGetElement(document, "Representation", "AdaptationSet"); final Element element = assertGetElement(document, REPRESENTATION, ADAPTATION_SET);
assertAttrEquals(itagItem.getBitrate(), element, "bandwidth"); assertAttrEquals(itagItem.getBitrate(), element, "bandwidth");
assertAttrEquals(itagItem.getCodec(), element, "codecs"); assertAttrEquals(itagItem.getCodec(), element, "codecs");
@ -226,12 +233,12 @@ class YoutubeDashManifestCreatorTest {
private void assertAudioChannelConfigurationElement(@Nonnull final Document document, private void assertAudioChannelConfigurationElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) { @Nonnull final ItagItem itagItem) {
final Element element = assertGetElement(document, final Element element = assertGetElement(document,
"AudioChannelConfiguration", "Representation"); "AudioChannelConfiguration", REPRESENTATION);
assertAttrEquals(itagItem.getAudioChannels(), element, "value"); assertAttrEquals(itagItem.getAudioChannels(), element, "value");
} }
private void assertSegmentTemplateElement(@Nonnull final Document document) { private void assertSegmentTemplateElement(@Nonnull final Document document) {
final Element element = assertGetElement(document, "SegmentTemplate", "Representation"); final Element element = assertGetElement(document, SEGMENT_TEMPLATE, REPRESENTATION);
final String initializationValue = element.getAttribute("initialization"); final String initializationValue = element.getAttribute("initialization");
assertIsValidUrl(initializationValue); assertIsValidUrl(initializationValue);
@ -245,7 +252,7 @@ class YoutubeDashManifestCreatorTest {
} }
private void assertSegmentTimelineAndSElements(@Nonnull final Document document) { private void assertSegmentTimelineAndSElements(@Nonnull final Document document) {
final Element element = assertGetElement(document, "SegmentTimeline", "SegmentTemplate"); final Element element = assertGetElement(document, SEGMENT_TIMELINE, SEGMENT_TEMPLATE);
final NodeList childNodes = element.getChildNodes(); final NodeList childNodes = element.getChildNodes();
assertGreater(0, childNodes.getLength()); assertGreater(0, childNodes.getLength());
@ -269,19 +276,19 @@ class YoutubeDashManifestCreatorTest {
} }
private void assertBaseUrlElement(@Nonnull final Document document) { private void assertBaseUrlElement(@Nonnull final Document document) {
final Element element = assertGetElement(document, "BaseURL", "Representation"); final Element element = assertGetElement(document, "BaseURL", REPRESENTATION);
assertIsValidUrl(element.getTextContent()); assertIsValidUrl(element.getTextContent());
} }
private void assertSegmentBaseElement(@Nonnull final Document document, private void assertSegmentBaseElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) { @Nonnull final ItagItem itagItem) {
final Element element = assertGetElement(document, "SegmentBase", "Representation"); final Element element = assertGetElement(document, SEGMENT_BASE, REPRESENTATION);
assertRangeEquals(itagItem.getIndexStart(), itagItem.getIndexEnd(), element, "indexRange"); assertRangeEquals(itagItem.getIndexStart(), itagItem.getIndexEnd(), element, "indexRange");
} }
private void assertInitializationElement(@Nonnull final Document document, private void assertInitializationElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) { @Nonnull final ItagItem itagItem) {
final Element element = assertGetElement(document, "Initialization", "SegmentBase"); final Element element = assertGetElement(document, INITIALIZATION, SEGMENT_BASE);
assertRangeEquals(itagItem.getInitStart(), itagItem.getInitEnd(), element, "range"); assertRangeEquals(itagItem.getInitStart(), itagItem.getInitEnd(), element, "range");
} }