Skip to content

Commit 6467e96

Browse files
committed
Throw a dedicated exception when errors occur in PlaybackResolver
A new exception, ResolverException, a subclass of PlaybackResolver, is now thrown when errors occur in PlaybackResolver, instead of an IOException
1 parent a2560d5 commit 6467e96

File tree

3 files changed

+112
-86
lines changed

3 files changed

+112
-86
lines changed

app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.schabi.newpipe.player.mediaitem.StreamInfoTag;
1818
import org.schabi.newpipe.util.ListHelper;
1919

20-
import java.io.IOException;
2120
import java.util.List;
2221

2322
public class AudioPlaybackResolver implements PlaybackResolver {
@@ -55,8 +54,8 @@ public MediaSource resolve(@NonNull final StreamInfo info) {
5554
try {
5655
return PlaybackResolver.buildMediaSource(
5756
dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag);
58-
} catch (final IOException e) {
59-
Log.e(TAG, "Unable to create audio source:", e);
57+
} catch (final ResolverException e) {
58+
Log.e(TAG, "Unable to create audio source", e);
6059
return null;
6160
}
6261
}

app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java

Lines changed: 106 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,22 @@ private static StringBuilder commonCacheKeyOf(final StreamInfo info,
9797
}
9898

9999
/**
100-
* Builds the cache key of a video stream. A cache key is unique to the features of the
101-
* provided video stream, and when possible independent of <i>transient</i> parameters (such as
102-
* the url of the stream). This ensures that there are no conflicts, but also that the cache is
103-
* used as much as possible: the same cache should be used for two streams which have the same
104-
* features but e.g. a different url, since the url might have been reloaded in the meantime,
105-
* but the stream actually referenced by the url is still the same.
100+
* Builds the cache key of a {@link VideoStream video stream}.
106101
*
107-
* @param info the stream info, to distinguish between streams with the same features but coming
108-
* from different stream infos
109-
* @param videoStream the video stream for which the cache key should be created
110-
* @return a key to be used to store the cache of the provided video stream
102+
* <p>
103+
* A cache key is unique to the features of the provided video stream, and when possible
104+
* independent of <i>transient</i> parameters (such as the URL of the stream).
105+
* This ensures that there are no conflicts, but also that the cache is used as much as
106+
* possible: the same cache should be used for two streams which have the same features but
107+
* e.g. a different URL, since the URL might have been reloaded in the meantime, but the stream
108+
* actually referenced by the URL is still the same.
109+
* </p>
110+
*
111+
* @param info the {@link StreamInfo stream info}, to distinguish between streams with
112+
* the same features but coming from different stream infos
113+
* @param videoStream the {@link VideoStream video stream} for which the cache key should be
114+
* created
115+
* @return a key to be used to store the cache of the provided {@link VideoStream video stream}
111116
*/
112117
static String cacheKeyOf(final StreamInfo info, final VideoStream videoStream) {
113118
final boolean resolutionUnknown = videoStream.getResolution().equals(RESOLUTION_UNKNOWN);
@@ -127,17 +132,22 @@ static String cacheKeyOf(final StreamInfo info, final VideoStream videoStream) {
127132
}
128133

129134
/**
130-
* Builds the cache key of an audio stream. A cache key is unique to the features of the
131-
* provided audio stream, and when possible independent of <i>transient</i> parameters (such as
132-
* the url of the stream). This ensures that there are no conflicts, but also that the cache is
133-
* used as much as possible: the same cache should be used for two streams which have the same
134-
* features but e.g. a different url, since the url might have been reloaded in the meantime,
135-
* but the stream actually referenced by the url is still the same.
135+
* Builds the cache key of an audio stream.
136+
*
137+
* <p>
138+
* A cache key is unique to the features of the provided {@link AudioStream audio stream}, and
139+
* when possible independent of <i>transient</i> parameters (such as the URL of the stream).
140+
* This ensures that there are no conflicts, but also that the cache is used as much as
141+
* possible: the same cache should be used for two streams which have the same features but
142+
* e.g. a different URL, since the URL might have been reloaded in the meantime, but the stream
143+
* actually referenced by the URL is still the same.
144+
* </p>
136145
*
137-
* @param info the stream info, to distinguish between streams with the same features but coming
138-
* from different stream infos
139-
* @param audioStream the audio stream for which the cache key should be created
140-
* @return a key to be used to store the cache of the provided audio stream
146+
* @param info the {@link StreamInfo stream info}, to distinguish between streams with
147+
* the same features but coming from different stream infos
148+
* @param audioStream the {@link AudioStream audio stream} for which the cache key should be
149+
* created
150+
* @return a key to be used to store the cache of the provided {@link AudioStream audio stream}
141151
*/
142152
static String cacheKeyOf(final StreamInfo info, final AudioStream audioStream) {
143153
final boolean averageBitrateUnknown = audioStream.getAverageBitrate() == UNKNOWN_BITRATE;
@@ -158,16 +168,20 @@ static String cacheKeyOf(final StreamInfo info, final AudioStream audioStream) {
158168
@Nullable
159169
static MediaSource maybeBuildLiveMediaSource(final PlayerDataSource dataSource,
160170
final StreamInfo info) {
161-
final StreamType streamType = info.getStreamType();
162-
if (!StreamTypeUtil.isLiveStream(streamType)) {
171+
if (!StreamTypeUtil.isLiveStream(info.getStreamType())) {
163172
return null;
164173
}
165174

166-
final StreamInfoTag tag = StreamInfoTag.of(info);
167-
if (!info.getHlsUrl().isEmpty()) {
168-
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
169-
} else if (!info.getDashMpdUrl().isEmpty()) {
170-
return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
175+
try {
176+
final StreamInfoTag tag = StreamInfoTag.of(info);
177+
if (!info.getHlsUrl().isEmpty()) {
178+
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
179+
} else if (!info.getDashMpdUrl().isEmpty()) {
180+
return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
181+
}
182+
} catch (final Exception e) {
183+
Log.w(TAG, "Error when generating live media source, falling back to standard sources",
184+
e);
171185
}
172186

173187
return null;
@@ -176,7 +190,7 @@ static MediaSource maybeBuildLiveMediaSource(final PlayerDataSource dataSource,
176190
static MediaSource buildLiveMediaSource(final PlayerDataSource dataSource,
177191
final String sourceUrl,
178192
@C.ContentType final int type,
179-
final MediaItemTag metadata) {
193+
final MediaItemTag metadata) throws ResolverException {
180194
final MediaSource.Factory factory;
181195
switch (type) {
182196
case C.TYPE_SS:
@@ -188,8 +202,10 @@ static MediaSource buildLiveMediaSource(final PlayerDataSource dataSource,
188202
case C.TYPE_HLS:
189203
factory = dataSource.getLiveHlsMediaSourceFactory();
190204
break;
191-
case C.TYPE_OTHER: case C.TYPE_RTSP: default:
192-
throw new IllegalStateException("Unsupported type: " + type);
205+
case C.TYPE_OTHER:
206+
case C.TYPE_RTSP:
207+
default:
208+
throw new ResolverException("Unsupported type: " + type);
193209
}
194210

195211
return factory.createMediaSource(
@@ -210,8 +226,7 @@ static MediaSource buildMediaSource(final PlayerDataSource dataSource,
210226
final Stream stream,
211227
final StreamInfo streamInfo,
212228
final String cacheKey,
213-
final MediaItemTag metadata)
214-
throws IOException {
229+
final MediaItemTag metadata) throws ResolverException {
215230
if (streamInfo.getService() == ServiceList.YouTube) {
216231
return createYoutubeMediaSource(stream, streamInfo, dataSource, cacheKey, metadata);
217232
}
@@ -228,19 +243,19 @@ static MediaSource buildMediaSource(final PlayerDataSource dataSource,
228243
return buildSSMediaSource(dataSource, stream, cacheKey, metadata);
229244
// Torrent streams are not supported by ExoPlayer
230245
default:
231-
throw new IllegalArgumentException("Unsupported delivery type: " + deliveryMethod);
246+
throw new ResolverException("Unsupported delivery type: " + deliveryMethod);
232247
}
233248
}
234249

235250
private static ProgressiveMediaSource buildProgressiveMediaSource(
236251
final PlayerDataSource dataSource,
237252
final Stream stream,
238253
final String cacheKey,
239-
final MediaItemTag metadata) throws IOException {
254+
final MediaItemTag metadata) throws ResolverException {
240255
final String url = stream.getContent();
241256

242257
if (isNullOrEmpty(url)) {
243-
throw new IOException(
258+
throw new ResolverException(
244259
"Try to generate a progressive media source from an empty string or from a "
245260
+ "null object");
246261
} else {
@@ -257,11 +272,11 @@ private static DashMediaSource buildDashMediaSource(final PlayerDataSource dataS
257272
final Stream stream,
258273
final String cacheKey,
259274
final MediaItemTag metadata)
260-
throws IOException {
275+
throws ResolverException {
261276
final boolean isUrlStream = stream.isUrl();
262277
if (isUrlStream && isNullOrEmpty(stream.getContent())) {
263-
throw new IOException("Try to generate a DASH media source from an empty string or "
264-
+ "from a null object");
278+
throw new ResolverException(
279+
"Could not build a DASH media source from an empty or a null URL content");
265280
}
266281

267282
if (isUrlStream) {
@@ -279,41 +294,42 @@ private static DashMediaSource buildDashMediaSource(final PlayerDataSource dataS
279294

280295
final Uri uri = Uri.parse(baseUrl);
281296

282-
return dataSource.getDashMediaSourceFactory().createMediaSource(
283-
createDashManifest(stream.getContent(), stream),
284-
new MediaItem.Builder()
285-
.setTag(metadata)
286-
.setUri(uri)
287-
.setCustomCacheKey(cacheKey)
288-
.build());
297+
try {
298+
return dataSource.getDashMediaSourceFactory().createMediaSource(
299+
createDashManifest(stream.getContent(), stream),
300+
new MediaItem.Builder()
301+
.setTag(metadata)
302+
.setUri(uri)
303+
.setCustomCacheKey(cacheKey)
304+
.build());
305+
} catch (final IOException e) {
306+
throw new ResolverException(
307+
"Could not create a DASH media source/manifest from the manifest text");
308+
}
289309
}
290310
}
291311

292312
private static DashManifest createDashManifest(final String manifestContent,
293313
final Stream stream) throws IOException {
294-
try {
295-
final ByteArrayInputStream dashManifestInput = new ByteArrayInputStream(
296-
manifestContent.getBytes(StandardCharsets.UTF_8));
297-
String baseUrl = stream.getManifestUrl();
298-
if (baseUrl == null) {
299-
baseUrl = "";
300-
}
301-
302-
return new DashManifestParser().parse(Uri.parse(baseUrl), dashManifestInput);
303-
} catch (final IOException e) {
304-
throw new IOException("Error when parsing manual DASH manifest", e);
314+
final ByteArrayInputStream dashManifestInput = new ByteArrayInputStream(
315+
manifestContent.getBytes(StandardCharsets.UTF_8));
316+
String baseUrl = stream.getManifestUrl();
317+
if (baseUrl == null) {
318+
baseUrl = "";
305319
}
320+
321+
return new DashManifestParser().parse(Uri.parse(baseUrl), dashManifestInput);
306322
}
307323

308324
private static HlsMediaSource buildHlsMediaSource(final PlayerDataSource dataSource,
309325
final Stream stream,
310326
final String cacheKey,
311327
final MediaItemTag metadata)
312-
throws IOException {
328+
throws ResolverException {
313329
final boolean isUrlStream = stream.isUrl();
314330
if (isUrlStream && isNullOrEmpty(stream.getContent())) {
315-
throw new IOException("Try to generate an HLS media source from an empty string or "
316-
+ "from a null object");
331+
throw new ResolverException(
332+
"Could not build a HLS media source from an empty or a null URL content");
317333
}
318334

319335
if (isUrlStream) {
@@ -337,7 +353,7 @@ private static HlsMediaSource buildHlsMediaSource(final PlayerDataSource dataSou
337353
stream.getContent().getBytes(StandardCharsets.UTF_8));
338354
hlsPlaylist = new HlsPlaylistParser().parse(uri, hlsManifestInput);
339355
} catch (final IOException e) {
340-
throw new IOException("Error when parsing manual HLS manifest", e);
356+
throw new ResolverException("Error when parsing manual HLS manifest", e);
341357
}
342358

343359
return dataSource.getHlsMediaSourceFactory(
@@ -354,11 +370,11 @@ private static SsMediaSource buildSSMediaSource(final PlayerDataSource dataSourc
354370
final Stream stream,
355371
final String cacheKey,
356372
final MediaItemTag metadata)
357-
throws IOException {
373+
throws ResolverException {
358374
final boolean isUrlStream = stream.isUrl();
359375
if (isUrlStream && isNullOrEmpty(stream.getContent())) {
360-
throw new IOException("Try to generate an SmoothStreaming media source from an empty "
361-
+ "string or from a null object");
376+
throw new ResolverException(
377+
"Could not build a SS media source from an empty or a null URL content");
362378
}
363379

364380
if (isUrlStream) {
@@ -383,7 +399,7 @@ private static SsMediaSource buildSSMediaSource(final PlayerDataSource dataSourc
383399
smoothStreamingManifest = new SsManifestParser().parse(uri,
384400
smoothStreamingManifestInput);
385401
} catch (final IOException e) {
386-
throw new IOException("Error when parsing manual SmoothStreaming manifest", e);
402+
throw new ResolverException("Error when parsing manual SS manifest", e);
387403
}
388404

389405
return dataSource.getSSMediaSourceFactory().createMediaSource(
@@ -404,10 +420,10 @@ private static MediaSource createYoutubeMediaSource(final Stream stream,
404420
final PlayerDataSource dataSource,
405421
final String cacheKey,
406422
final MediaItemTag metadata)
407-
throws IOException {
423+
throws ResolverException {
408424
if (!(stream instanceof AudioStream || stream instanceof VideoStream)) {
409-
throw new IOException("Try to generate a DASH manifest of a YouTube "
410-
+ stream.getClass() + " " + stream.getContent());
425+
throw new ResolverException("Generation of YouTube DASH manifest for "
426+
+ stream.getClass().getSimpleName() + " is not supported");
411427
}
412428

413429
final StreamType streamType = streamInfo.getStreamType();
@@ -430,15 +446,15 @@ private static MediaSource createYoutubeMediaSource(final Stream stream,
430446
return buildYoutubeManualDashMediaSource(dataSource,
431447
createDashManifest(manifestString, stream), stream, cacheKey,
432448
metadata);
433-
} catch (final CreationException | NullPointerException e) {
449+
} catch (final CreationException | IOException | NullPointerException e) {
434450
Log.e(TAG, "Error when generating the DASH manifest of YouTube ended live stream",
435451
e);
436-
throw new IOException("Error when generating the DASH manifest of YouTube ended "
437-
+ "live stream " + stream.getContent(), e);
452+
throw new ResolverException(
453+
"Error when generating the DASH manifest of YouTube ended live stream", e);
438454
}
439455
} else {
440-
throw new IllegalArgumentException("DASH manifest generation of YouTube livestreams is "
441-
+ "not supported");
456+
throw new ResolverException(
457+
"DASH manifest generation of YouTube livestreams is not supported");
442458
}
443459
}
444460

@@ -447,7 +463,7 @@ private static MediaSource createYoutubeMediaSourceOfVideoStreamType(
447463
final Stream stream,
448464
final StreamInfo streamInfo,
449465
final String cacheKey,
450-
final MediaItemTag metadata) throws IOException {
466+
final MediaItemTag metadata) throws ResolverException {
451467
final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
452468
switch (deliveryMethod) {
453469
case PROGRESSIVE_HTTP:
@@ -488,12 +504,11 @@ private static MediaSource createYoutubeMediaSourceOfVideoStreamType(
488504
return buildYoutubeManualDashMediaSource(dataSource,
489505
createDashManifest(manifestString, stream), stream, cacheKey,
490506
metadata);
491-
} catch (final CreationException | NullPointerException e) {
507+
} catch (final CreationException | IOException | NullPointerException e) {
492508
Log.e(TAG,
493509
"Error when generating the DASH manifest of YouTube OTF stream", e);
494-
throw new IOException(
495-
"Error when generating the DASH manifest of YouTube OTF stream "
496-
+ stream.getContent(), e);
510+
throw new ResolverException(
511+
"Error when generating the DASH manifest of YouTube OTF stream", e);
497512
}
498513
case HLS:
499514
return dataSource.getYoutubeHlsMediaSourceFactory().createMediaSource(
@@ -503,7 +518,7 @@ private static MediaSource createYoutubeMediaSourceOfVideoStreamType(
503518
.setCustomCacheKey(cacheKey)
504519
.build());
505520
default:
506-
throw new IOException("Unsupported delivery method for YouTube contents: "
521+
throw new ResolverException("Unsupported delivery method for YouTube contents: "
507522
+ deliveryMethod);
508523
}
509524
}
@@ -535,4 +550,17 @@ private static ProgressiveMediaSource buildYoutubeProgressiveMediaSource(
535550
.build());
536551
}
537552
//endregion
553+
554+
555+
//region resolver exception
556+
final class ResolverException extends Exception {
557+
public ResolverException(final String message) {
558+
super(message);
559+
}
560+
561+
public ResolverException(final String message, final Throwable cause) {
562+
super(message, cause);
563+
}
564+
}
565+
//endregion
538566
}

0 commit comments

Comments
 (0)