From 1444fe5468e3e6a691b916cb0fa80aecd7c628cd Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sun, 25 Feb 2018 20:12:20 -0800 Subject: [PATCH] -Fixed potential NPE when obtaining broadcast receiver. -Extracted expiration time in media source manager. -Re-enabled long click on live stream info items. -Fixed dash source building to use mpd instead of extractor. --- .../holder/StreamMiniInfoItemHolder.java | 26 +++++------- .../org/schabi/newpipe/player/BasePlayer.java | 40 +++++++++---------- .../player/mediasource/FailedMediaSource.java | 4 ++ .../player/playback/MediaSourceManager.java | 24 +++++++---- .../schabi/newpipe/playlist/PlayQueue.java | 3 +- .../newpipe/playlist/PlayQueueAdapter.java | 4 +- 6 files changed, 54 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 48dc470d0..594a85582 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -59,23 +59,20 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { itemBuilder.getImageLoader() .displayImage(item.thumbnail_url, itemThumbnailView, StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (itemBuilder.getOnStreamSelectedListener() != null) { - itemBuilder.getOnStreamSelectedListener().selected(item); - } + itemView.setOnClickListener(view -> { + if (itemBuilder.getOnStreamSelectedListener() != null) { + itemBuilder.getOnStreamSelectedListener().selected(item); } }); switch (item.stream_type) { case AUDIO_STREAM: case VIDEO_STREAM: - case FILE: - enableLongClick(item); - break; case LIVE_STREAM: case AUDIO_LIVE_STREAM: + enableLongClick(item); + break; + case FILE: case NONE: default: disableLongClick(); @@ -85,14 +82,11 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { private void enableLongClick(final StreamInfoItem item) { itemView.setLongClickable(true); - itemView.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - if (itemBuilder.getOnStreamSelectedListener() != null) { - itemBuilder.getOnStreamSelectedListener().held(item); - } - return true; + itemView.setOnLongClickListener(view -> { + if (itemBuilder.getOnStreamSelectedListener() != null) { + itemBuilder.getOnStreamSelectedListener().held(item); } + return true; }); } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index de86ea587..3e136dbde 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -319,35 +319,27 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen recordManager = null; } - public MediaSource buildMediaSource(String url) { - return buildMediaSource(url, ""); - } - public MediaSource buildMediaSource(String url, String overrideExtension) { if (DEBUG) { - Log.d(TAG, "buildMediaSource() called with: url = [" + url + "], overrideExtension = [" + overrideExtension + "]"); + Log.d(TAG, "buildMediaSource() called with: url = [" + url + + "], overrideExtension = [" + overrideExtension + "]"); } Uri uri = Uri.parse(url); - int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension); - MediaSource mediaSource; + int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) : + Util.inferContentType("." + overrideExtension); switch (type) { case C.TYPE_SS: - mediaSource = ssMediaSourceFactory.createMediaSource(uri); - break; + return ssMediaSourceFactory.createMediaSource(uri); case C.TYPE_DASH: - mediaSource = dashMediaSourceFactory.createMediaSource(uri); - break; + return dashMediaSourceFactory.createMediaSource(uri); case C.TYPE_HLS: - mediaSource = hlsMediaSourceFactory.createMediaSource(uri); - break; + return hlsMediaSourceFactory.createMediaSource(uri); case C.TYPE_OTHER: - mediaSource = extractorMediaSourceFactory.createMediaSource(uri); - break; + return extractorMediaSourceFactory.createMediaSource(uri); default: { throw new IllegalStateException("Unsupported type: " + type); } } - return mediaSource; } /*////////////////////////////////////////////////////////////////////////// @@ -514,8 +506,13 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount()); - if (playbackManager != null) { - playbackManager.load(); + switch (reason) { + case Player.TIMELINE_CHANGE_REASON_PREPARED: + case Player.TIMELINE_CHANGE_REASON_RESET: + case Player.TIMELINE_CHANGE_REASON_DYNAMIC: + default: + if (playbackManager != null) playbackManager.load(); + break; } } @@ -526,7 +523,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - if (DEBUG) Log.d(TAG, "playbackParameters(), speed: " + playbackParameters.speed + ", pitch: " + playbackParameters.pitch); + if (DEBUG) Log.d(TAG, "playbackParameters(), speed: " + playbackParameters.speed + + ", pitch: " + playbackParameters.pitch); } @Override @@ -732,9 +730,9 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen @Override public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) { if (!info.getHlsUrl().isEmpty()) { - return buildMediaSource(info.getHlsUrl()); + return buildMediaSource(info.getHlsUrl(), "m3u8"); } else if (!info.getDashMpdUrl().isEmpty()) { - return buildMediaSource(info.getDashMpdUrl()); + return buildMediaSource(info.getDashMpdUrl(), "mpd"); } return null; diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java index 2454c022c..a73c9975e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java @@ -25,6 +25,10 @@ public class FailedMediaSource implements ManagedMediaSource { this.retryTimestamp = retryTimestamp; } + /** + * Permanently fail the play queue item associated with this source, with no hope of retrying. + * The error will always be propagated to ExoPlayer. + * */ public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem, @NonNull final Throwable error) { this.playQueueItem = playQueueItem; diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 50b485cb5..3b068f730 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -40,6 +40,8 @@ public class MediaSourceManager { private final int windowSize; private final PlaybackListener playbackListener; private final PlayQueue playQueue; + private final long expirationTimeMillis; + private final TimeUnit expirationTimeUnit; // Process only the last load order when receiving a stream of load orders (lessens I/O) // The higher it is, the less loading occurs during rapid noncritical timeline changes @@ -61,13 +63,15 @@ public class MediaSourceManager { public MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue) { - this(listener, playQueue, 1, 400L); + this(listener, playQueue, 1, 400L, 2, TimeUnit.HOURS); } private MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue, final int windowSize, - final long loadDebounceMillis) { + final long loadDebounceMillis, + final long expirationTimeMillis, + @NonNull final TimeUnit expirationTimeUnit) { if (windowSize <= 0) { throw new UnsupportedOperationException( "MediaSourceManager window size must be greater than 0"); @@ -77,6 +81,8 @@ public class MediaSourceManager { this.playQueue = playQueue; this.windowSize = windowSize; this.loadDebounceMillis = loadDebounceMillis; + this.expirationTimeMillis = expirationTimeMillis; + this.expirationTimeUnit = expirationTimeUnit; this.loaderReactor = new CompositeDisposable(); this.debouncedLoadSignal = PublishSubject.create(); @@ -87,9 +93,11 @@ public class MediaSourceManager { this.syncReactor = new SerialDisposable(); this.loadingItems = Collections.synchronizedSet(new HashSet<>()); - playQueue.getBroadcastReceiver() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getReactor()); + if (playQueue.getBroadcastReceiver() != null) { + playQueue.getBroadcastReceiver() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getReactor()); + } } /*////////////////////////////////////////////////////////////////////////// @@ -225,7 +233,7 @@ public class MediaSourceManager { return playQueue.isComplete() || isWindowLoaded; } - // Checks if the current playback media source is a placeholder, if so, then it is not ready + // Checks if the current playback media source is a placeholder, if so, then it is not ready. private boolean isPlaybackReady() { return sources != null && playQueue != null && sources.getSize() > playQueue.getIndex() && !(sources.getMediaSource(playQueue.getIndex()) instanceof PlaceholderMediaSource); @@ -351,11 +359,11 @@ public class MediaSourceManager { final MediaSource source = playbackListener.sourceOf(stream, streamInfo); if (source == null) { return new FailedMediaSource(stream, new IllegalStateException( - "MediaSource resolution is null")); + "MediaSource cannot be resolved")); } final long expiration = System.currentTimeMillis() + - TimeUnit.MILLISECONDS.convert(2, TimeUnit.HOURS); + TimeUnit.MILLISECONDS.convert(expirationTimeMillis, expirationTimeUnit); return new LoadedMediaSource(source, expiration); }).onErrorReturn(throwable -> new FailedMediaSource(stream, throwable)); } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java index 3daa58bb7..cd507c2bf 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.playlist; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import org.reactivestreams.Subscriber; @@ -170,7 +171,7 @@ public abstract class PlayQueue implements Serializable { * Returns the play queue's update broadcast. * May be null if the play queue message bus is not initialized. * */ - @NonNull + @Nullable public Flowable getBroadcastReceiver() { return broadcastReceiver; } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java index cd833c1ab..7c701a637 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java @@ -99,7 +99,9 @@ public class PlayQueueAdapter extends RecyclerView.Adapter