From d936ca6b89440124584be0940db4b8ae82148c0c Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sat, 17 Feb 2018 11:55:45 -0800 Subject: [PATCH 1/7] -Added view registration on repeats. -Added drag reorder speed clamping to play queue list. -Fixed service player activity memory leak. -Fixed media source manager sync disposable fallthrough causing NPE. -Fixed thread bouncing during play queue item async stream resolution. -Updated ExoPlayer to 2.6.0. --- app/build.gradle | 2 +- .../org/schabi/newpipe/player/BasePlayer.java | 69 ++++++++++++++----- .../newpipe/player/ServicePlayerActivity.java | 26 ++++++- .../newpipe/player/helper/AudioReactor.java | 4 +- .../newpipe/player/helper/LoadController.java | 16 +++-- .../newpipe/player/helper/PlayerHelper.java | 8 +-- .../player/playback/DeferredMediaSource.java | 26 +------ .../player/playback/MediaSourceManager.java | 55 +++++++-------- .../player/playback/PlaybackListener.java | 2 + .../newpipe/playlist/PlayQueueAdapter.java | 4 ++ .../newpipe/playlist/PlayQueueItem.java | 10 +-- .../playlist/PlayQueueItemBuilder.java | 47 +++++-------- 12 files changed, 144 insertions(+), 125 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0244ae4b9..ea4d5384d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation 'de.hdodenhof:circleimageview:2.2.0' implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1' implementation 'com.nononsenseapps:filepicker:3.0.1' - implementation 'com.google.android.exoplayer:exoplayer:r2.5.4' + implementation 'com.google.android.exoplayer:exoplayer:2.6.0' debugImplementation 'com.facebook.stetho:stetho:1.5.0' debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0' 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 7558f1375..07d567d57 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -81,6 +81,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; /** @@ -279,6 +283,11 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen if (playbackManager != null) playbackManager.dispose(); if (audioReactor != null) audioReactor.abandonAudioFocus(); if (databaseUpdateReactor != null) databaseUpdateReactor.dispose(); + + if (playQueueAdapter != null) { + playQueueAdapter.unsetSelectedListener(); + playQueueAdapter.dispose(); + } } public void destroy() { @@ -460,11 +469,11 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen final PlayQueueItem currentSourceItem = playQueue.getItem(); // Check if already playing correct window - final boolean isCurrentWindowCorrect = - simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex; + final boolean isCurrentPeriodCorrect = + simpleExoPlayer.getCurrentPeriodIndex() == currentSourceIndex; // Check if recovering - if (isCurrentWindowCorrect && currentSourceItem != null) { + if (isCurrentPeriodCorrect && currentSourceItem != null) { /* Recovering with sub-second position may cause a long buffer delay in ExoPlayer, * rounding this position to the nearest second will help alleviate this.*/ final long position = currentSourceItem.getRecoveryPosition(); @@ -605,17 +614,25 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen } @Override - public void onPositionDiscontinuity() { + public void onPositionDiscontinuity(int reason) { + if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with reason = [" + reason + "]"); // Refresh the playback if there is a transition to the next video - final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex(); - if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with window index = [" + newWindowIndex + "]"); + final int newWindowIndex = simpleExoPlayer.getCurrentPeriodIndex(); - // If the user selects a new track, then the discontinuity occurs after the index is changed. - // Therefore, the only source that causes a discrepancy would be gapless transition, - // which can only offset the current track by +1. - if (newWindowIndex == playQueue.getIndex() + 1 || - (newWindowIndex == 0 && playQueue.getIndex() == playQueue.size() - 1)) { - playQueue.offsetIndex(+1); + /* Discontinuity reasons!! Thank you ExoPlayer lords */ + switch (reason) { + case DISCONTINUITY_REASON_PERIOD_TRANSITION: + if (newWindowIndex == playQueue.getIndex()) { + registerView(); + } else { + playQueue.offsetIndex(+1); + } + break; + case DISCONTINUITY_REASON_SEEK: + case DISCONTINUITY_REASON_SEEK_ADJUSTMENT: + case DISCONTINUITY_REASON_INTERNAL: + default: + break; } playbackManager.load(); } @@ -625,6 +642,16 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen if (DEBUG) Log.d(TAG, "onRepeatModeChanged() called with: mode = [" + i + "]"); } + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + if (DEBUG) Log.d(TAG, "onShuffleModeEnabledChanged() called with: " + + "mode = [" + shuffleModeEnabled + "]"); + } + + @Override + public void onSeekProcessed() { + if (DEBUG) Log.d(TAG, "onSeekProcessed() called"); + } /*////////////////////////////////////////////////////////////////////////// // Playback Listener //////////////////////////////////////////////////////////////////////////*/ @@ -668,19 +695,14 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen if (currentSourceIndex != playQueue.getIndex()) { Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex + "], queue index=[" + playQueue.getIndex() + "]"); - } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) { + } else if (simpleExoPlayer.getCurrentPeriodIndex() != currentSourceIndex || !isPlaying()) { final long startPos = info != null ? info.start_position : 0; if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos)); simpleExoPlayer.seekTo(currentSourceIndex, startPos); } - // TODO: update exoplayer to 2.6.x in order to register view count on repeated streams - databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() - .subscribe( - ignored -> {/* successful */}, - error -> Log.e(TAG, "Player onViewed() failure: ", error) - )); + registerView(); initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); } @@ -814,6 +836,15 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen // Utils //////////////////////////////////////////////////////////////////////////*/ + private void registerView() { + if (databaseUpdateReactor == null || recordManager == null || currentInfo == null) return; + databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() + .subscribe( + ignored -> {/* successful */}, + error -> Log.e(TAG, "Player onViewed() failure: ", error) + )); + } + protected void reload() { if (playbackManager != null) { playbackManager.reset(); diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 5518357a8..1378d9a80 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -61,6 +61,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; + private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 10; + private static final int MAXIMUM_INITIAL_DRAG_VELOCITY = 25; + private View rootView; private RecyclerView itemsList; @@ -211,6 +214,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity unbindService(serviceConnection); serviceBound = false; stopPlayerListener(); + + if (player != null && player.getPlayQueueAdapter() != null) { + player.getPlayQueueAdapter().unsetSelectedListener(); + } + if (itemsList != null) itemsList.setAdapter(null); + if (itemTouchHelper != null) itemTouchHelper.attachToRecyclerView(null); + + itemsList = null; + itemTouchHelper = null; player = null; } } @@ -385,7 +397,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { + public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, + int viewSizeOutOfBounds, int totalSize, + long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, + viewSizeOutOfBounds, totalSize, msSinceStartScroll); + final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, + Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY)); + return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { if (source.getItemViewType() != target.getItemViewType()) { return false; } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index 4e031a0dd..2c85cfc34 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -181,7 +181,9 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au public void onAudioInputFormatChanged(Format format) {} @Override - public void onAudioTrackUnderrun(int i, long l, long l1) {} + public void onAudioSinkUnderrun(int bufferSize, + long bufferSizeMs, + long elapsedSinceLastFeedMs) {} @Override public void onAudioDisabled(DecoderCounters decoderCounters) {} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java index acc20f5b0..be7b8efde 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.player.helper; import android.content.Context; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.Renderer; @@ -10,6 +11,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DefaultAllocator; +import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS; + public class LoadController implements LoadControl { public static final String TAG = "LoadController"; @@ -23,16 +26,17 @@ public class LoadController implements LoadControl { public LoadController(final Context context) { this(PlayerHelper.getMinBufferMs(context), PlayerHelper.getMaxBufferMs(context), - PlayerHelper.getBufferForPlaybackMs(context), - PlayerHelper.getBufferForPlaybackAfterRebufferMs(context)); + PlayerHelper.getBufferForPlaybackMs(context)); } public LoadController(final int minBufferMs, final int maxBufferMs, - final long bufferForPlaybackMs, - final long bufferForPlaybackAfterRebufferMs) { - final DefaultAllocator allocator = new DefaultAllocator(true, 65536); - internalLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs); + final int bufferForPlaybackMs) { + final DefaultAllocator allocator = new DefaultAllocator(true, + C.DEFAULT_BUFFER_SEGMENT_SIZE); + + internalLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, + bufferForPlaybackMs, DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 476838f13..4929be9a3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -113,12 +113,8 @@ public class PlayerHelper { return 30000; } - public static long getBufferForPlaybackMs(@NonNull final Context context) { - return 2500L; - } - - public static long getBufferForPlaybackAfterRebufferMs(@NonNull final Context context) { - return 5000L; + public static int getBufferForPlaybackMs(@NonNull final Context context) { + return 2500; } public static boolean isUsingDSP(@NonNull final Context context) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/DeferredMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/playback/DeferredMediaSource.java index b0990f56a..3ae744d18 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/DeferredMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/DeferredMediaSource.java @@ -114,32 +114,10 @@ public final class DeferredMediaSource implements MediaSource { Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl()); - final Function onReceive = new Function() { - @Override - public MediaSource apply(StreamInfo streamInfo) throws Exception { - return onStreamInfoReceived(stream, streamInfo); - } - }; - - final Consumer onSuccess = new Consumer() { - @Override - public void accept(MediaSource mediaSource) throws Exception { - onMediaSourceReceived(mediaSource); - } - }; - - final Consumer onError = new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - onStreamInfoError(throwable); - } - }; - loader = stream.getStream() - .observeOn(Schedulers.io()) - .map(onReceive) + .map(streamInfo -> onStreamInfoReceived(stream, streamInfo)) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(onSuccess, onError); + .subscribe(this::onMediaSourceReceived, this::onStreamInfoError); } private MediaSource onStreamInfoReceived(@NonNull final PlayQueueItem item, 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 04f1606fa..baf2b9c29 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 @@ -4,7 +4,6 @@ import android.support.annotation.Nullable; import android.util.Log; import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource; -import com.google.android.exoplayer2.source.MediaSource; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -21,8 +20,8 @@ import java.util.concurrent.TimeUnit; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; -import io.reactivex.disposables.SerialDisposable; import io.reactivex.functions.Consumer; import io.reactivex.subjects.PublishSubject; @@ -46,7 +45,9 @@ public class MediaSourceManager { private DynamicConcatenatingMediaSource sources; private Subscription playQueueReactor; - private SerialDisposable syncReactor; + private CompositeDisposable syncReactor; + + private PlayQueueItem syncedItem; private boolean isBlocked; @@ -68,7 +69,7 @@ public class MediaSourceManager { this.windowSize = windowSize; this.loadDebounceMillis = loadDebounceMillis; - this.syncReactor = new SerialDisposable(); + this.syncReactor = new CompositeDisposable(); this.debouncedLoadSignal = PublishSubject.create(); this.debouncedLoader = getDebouncedLoader(); @@ -86,12 +87,7 @@ public class MediaSourceManager { //////////////////////////////////////////////////////////////////////////*/ private DeferredMediaSource.Callback getSourceBuilder() { - return new DeferredMediaSource.Callback() { - @Override - public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) { - return playbackListener.sourceOf(item, info); - } - }; + return playbackListener::sourceOf; } /*////////////////////////////////////////////////////////////////////////// @@ -241,22 +237,28 @@ public class MediaSourceManager { final PlayQueueItem currentItem = playQueue.getItem(); if (currentItem == null) return; - final Consumer syncPlayback = new Consumer() { - @Override - public void accept(StreamInfo streamInfo) throws Exception { - playbackListener.sync(currentItem, streamInfo); - } + final Consumer onSuccess = info -> syncInternal(currentItem, info); + final Consumer onError = throwable -> { + Log.e(TAG, "Sync error:", throwable); + syncInternal(currentItem, null); }; - final Consumer onError = new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - Log.e(TAG, "Sync error:", throwable); - playbackListener.sync(currentItem,null); - } - }; + final Disposable sync = currentItem.getStream() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onSuccess, onError); + syncReactor.add(sync); + } - syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError)); + private void syncInternal(@android.support.annotation.NonNull final PlayQueueItem item, + @Nullable final StreamInfo info) { + if (playQueue == null || playbackListener == null) return; + + // Sync each new item once only and ensure the current item is up to date + // with the play queue + if (playQueue.getItem() != syncedItem && playQueue.getItem() == item) { + syncedItem = item; + playbackListener.sync(syncedItem,info); + } } private void loadDebounced() { @@ -313,12 +315,7 @@ public class MediaSourceManager { return debouncedLoadSignal .debounce(loadDebounceMillis, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Consumer() { - @Override - public void accept(Long timestamp) throws Exception { - loadImmediate(); - } - }); + .subscribe(timestamp -> loadImmediate()); } /*////////////////////////////////////////////////////////////////////////// // Media Source List Manipulation diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index dfed04c01..c6fdde656 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -33,6 +33,8 @@ public interface PlaybackListener { * Signals to the listener to synchronize the player's window to the manager's * window. * + * Occurs once only per play queue item change. + * * May be called only after unblock is called. * */ void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info); 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 e16693ec6..cd833c1ab 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java @@ -73,6 +73,10 @@ public class PlayQueueAdapter extends RecyclerView.Adapter observer = new Observer() { @Override diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java index f8e7b8655..752dc223d 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java @@ -104,17 +104,9 @@ public class PlayQueueItem implements Serializable { @NonNull private Single getInfo() { - final Consumer onError = new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - error = throwable; - } - }; - return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false) .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(onError); + .doOnError(throwable -> error = throwable); } //////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java index 82277a4e7..73cdf1113 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java @@ -53,24 +53,18 @@ public class PlayQueueItemBuilder { ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, imageOptions); - holder.itemRoot.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (onItemClickListener != null) { - onItemClickListener.selected(item, view); - } + holder.itemRoot.setOnClickListener(view -> { + if (onItemClickListener != null) { + onItemClickListener.selected(item, view); } }); - holder.itemRoot.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - if (onItemClickListener != null) { - onItemClickListener.held(item, view); - return true; - } - return false; + holder.itemRoot.setOnLongClickListener(view -> { + if (onItemClickListener != null) { + onItemClickListener.held(item, view); + return true; } + return false; }); holder.itemThumbnailView.setOnTouchListener(getOnTouchListener(holder)); @@ -78,26 +72,21 @@ public class PlayQueueItemBuilder { } private View.OnTouchListener getOnTouchListener(final PlayQueueItemHolder holder) { - return new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - view.performClick(); - if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { - onItemClickListener.onStartDrag(holder); - } - return false; + return (view, motionEvent) -> { + view.performClick(); + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN + && onItemClickListener != null) { + onItemClickListener.onStartDrag(holder); } + return false; }; } private DisplayImageOptions buildImageOptions(final int widthPx, final int heightPx) { - final BitmapProcessor bitmapProcessor = new BitmapProcessor() { - @Override - public Bitmap process(Bitmap bitmap) { - final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, widthPx, heightPx, false); - bitmap.recycle(); - return resizedBitmap; - } + final BitmapProcessor bitmapProcessor = bitmap -> { + final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, widthPx, heightPx, false); + bitmap.recycle(); + return resizedBitmap; }; return new DisplayImageOptions.Builder() From e21d2bd511f58200d450d04ba921008151f40833 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sat, 17 Feb 2018 21:01:02 -0800 Subject: [PATCH 2/7] -Fixed video player source loading for audio only streams. -Changed "monitor leak" string to "LeakCanary" as untranslatable. --- .../schabi/newpipe/player/VideoPlayer.java | 67 +++++++++++++------ app/src/main/res/values/strings.xml | 2 +- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index a0bc7223f..67e4e6919 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -263,7 +263,9 @@ public abstract class VideoPlayer extends BasePlayer VideoStream videoStream = availableStreams.get(i); qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); } - qualityTextView.setText(getSelectedVideoStream().resolution); + if (getSelectedVideoStream() != null) { + qualityTextView.setText(getSelectedVideoStream().resolution); + } qualityPopupMenu.setOnMenuItemClickListener(this); qualityPopupMenu.setOnDismissListener(this); } @@ -326,7 +328,7 @@ public abstract class VideoPlayer extends BasePlayer qualityTextView.setVisibility(View.GONE); playbackSpeedTextView.setVisibility(View.GONE); - if (info != null) { + if (info != null && info.video_streams.size() + info.video_only_streams.size() > 0) { final List videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); availableStreams = new ArrayList<>(videos); @@ -337,41 +339,55 @@ public abstract class VideoPlayer extends BasePlayer } buildQualityMenu(); - buildPlaybackSpeedMenu(); qualityTextView.setVisibility(View.VISIBLE); - playbackSpeedTextView.setVisibility(View.VISIBLE); + surfaceView.setVisibility(View.VISIBLE); + } else { + surfaceView.setVisibility(View.GONE); } + + buildPlaybackSpeedMenu(); + playbackSpeedTextView.setVisibility(View.VISIBLE); } @Override @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { - final List videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); + List mediaSources = new ArrayList<>(); + // Create video stream source + final List videos = ListHelper.getSortedStreamVideosList(context, + info.video_streams, info.video_only_streams, false); final int index; - if (playbackQuality == null) { + if (videos.isEmpty()) { + index = -1; + } else if (playbackQuality == null) { index = getDefaultResolutionIndex(videos); } else { index = getOverrideResolutionIndex(videos, getPlaybackQuality()); } - if (index < 0 || index >= videos.size()) return null; - final VideoStream video = videos.get(index); - - List mediaSources = new ArrayList<>(); - // Create video stream source - final MediaSource streamSource = buildMediaSource(video.getUrl(), - MediaFormat.getSuffixById(video.getFormatId())); - mediaSources.add(streamSource); + final VideoStream video = index >= 0 && index < videos.size() ? videos.get(index) : null; + if (video != null) { + final MediaSource streamSource = buildMediaSource(video.getUrl(), + MediaFormat.getSuffixById(video.getFormatId())); + mediaSources.add(streamSource); + } // Create optional audio stream source - final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams); - if (video.isVideoOnly && audio != null) { - // Merge with audio stream in case if video does not contain audio + final List audioStreams = info.getAudioStreams(); + final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get( + ListHelper.getDefaultAudioFormat(context, audioStreams)); + // Use the audio stream if there is no video stream, or + // Merge with audio stream in case if video does not contain audio + if (audio != null && ((video != null && video.isVideoOnly) || video == null)) { final MediaSource audioSource = buildMediaSource(audio.getUrl(), MediaFormat.getSuffixById(audio.getFormatId())); mediaSources.add(audioSource); } + // If there is no audio or video sources, then this media source cannot be played back + if (mediaSources.isEmpty()) return null; + // Below are auxiliary media sources + // Create subtitle sources for (final Subtitles subtitle : info.getSubtitles()) { final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType()); @@ -658,7 +674,9 @@ public abstract class VideoPlayer extends BasePlayer public void onDismiss(PopupMenu menu) { if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); isSomePopupMenuVisible = false; - qualityTextView.setText(getSelectedVideoStream().resolution); + if (getSelectedVideoStream() != null) { + qualityTextView.setText(getSelectedVideoStream().resolution); + } } public void onQualitySelectorClicked() { @@ -668,8 +686,12 @@ public abstract class VideoPlayer extends BasePlayer showControls(300); final VideoStream videoStream = getSelectedVideoStream(); - final String qualityText = MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution; - qualityTextView.setText(qualityText); + if (videoStream != null) { + final String qualityText = MediaFormat.getNameById(videoStream.getFormatId()) + " " + + videoStream.resolution; + qualityTextView.setText(qualityText); + } + wasPlaying = simpleExoPlayer.getPlayWhenReady(); } @@ -864,8 +886,11 @@ public abstract class VideoPlayer extends BasePlayer return wasPlaying; } + @Nullable public VideoStream getSelectedVideoStream() { - return availableStreams.get(selectedStreamIndex); + return (selectedStreamIndex >= 0 && availableStreams != null && + availableStreams.size() > selectedStreamIndex) ? + availableStreams.get(selectedStreamIndex) : null; } public Handler getControlsVisibilityHandler() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08872694a..ab94ddce3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -412,7 +412,7 @@ Larger Font - Monitor Leaks + LeakCanary Memory leak monitoring enabled, app may become unresponsive when heap dumping Memory leak monitoring disabled From 762f374f9330c054a5cbf37726a7af267d9e446a Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sun, 18 Feb 2018 00:26:53 -0800 Subject: [PATCH 3/7] -Fixed media source manager sync identical item multiple times, causing OOM. -Removed deprecated translated leak canary string from other languages. --- .../player/playback/MediaSourceManager.java | 21 +++++++++++-------- app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - 7 files changed, 12 insertions(+), 15 deletions(-) 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 baf2b9c29..a4438af70 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 @@ -105,6 +105,7 @@ public class MediaSourceManager { playQueueReactor = null; syncReactor = null; + syncedItem = null; sources = null; } @@ -124,6 +125,8 @@ public class MediaSourceManager { * */ public void reset() { tryBlock(); + + syncedItem = null; populateSources(); } /*////////////////////////////////////////////////////////////////////////// @@ -243,20 +246,20 @@ public class MediaSourceManager { syncInternal(currentItem, null); }; - final Disposable sync = currentItem.getStream() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(onSuccess, onError); - syncReactor.add(sync); + if (syncedItem != currentItem) { + syncedItem = currentItem; + final Disposable sync = currentItem.getStream() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onSuccess, onError); + syncReactor.add(sync); + } } private void syncInternal(@android.support.annotation.NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { if (playQueue == null || playbackListener == null) return; - - // Sync each new item once only and ensure the current item is up to date - // with the play queue - if (playQueue.getItem() != syncedItem && playQueue.getItem() == item) { - syncedItem = item; + // Ensure the current item is up to date with the play queue + if (playQueue.getItem() == item && playQueue.getItem() == syncedItem) { playbackListener.sync(syncedItem,info); } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8e9821b10..171cc5ee0 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -373,5 +373,4 @@ Keine Untertitel Schriftgröße der Untertitel - "Speicherlecks nachverfolgen " diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4d2f8a10e..afdb6efd0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -395,7 +395,6 @@ Carattere normale Carattere più grande - Controllo delle perdite Controllo delle perdite di memoria abilitato, l\'applicazione può non rispondere mentre effettua il dumping dell\'heap Controllo delle perdite di memoria disabilitato A breve qualcosa si troverà qui ;D diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 9c1e21a36..9f8762827 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -383,7 +383,6 @@ Normal skrift Større skrift - Hold oppsyn med lekkasjer Oppsyn med minnelekasjer påslått, programmet kan slutte å svare under haug-dumping Oppsyn med minnelekasjer slått av diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6ab9a5b41..bb9e07f2c 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -391,7 +391,6 @@ te openen in pop-upmodus Normaal lettertype Groter lettertype - Controleren op lekken Controleren op geheugenlekken ingeschakeld, tijdens heapdumping kan de app tijdelijk niet reageren Controleren op geheugenlekken uitgeschakeld Hier zal binnenkort iets verschijnen ;D diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 83f6ee085..81a7456a9 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -369,7 +369,6 @@ abrir em modo popup Fonte normal Maior fonte - Monitorar vazamentos de memória Monitoramento de vazamentos de memória habilitado, o aplicativo pode ficar sem responder quando estiver descarregando pilha de memória Monitoramento de vazamentos de memória desabilitado diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 19d47f880..37542a332 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -385,7 +385,6 @@ Olağan Yazı Tipi Büyük Yazı Tipi - Sızıntıları Gözlemle Bellek sızıntısı gözlemleme etkinleştirildi, uygulama yığın atımı sırasında yanıtsız kalabilir Bellek sızıntısı gözlemleme devre dışı From c1a302834ccbb98c11dc5e957c3c1807c9a535bd Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 20 Feb 2018 04:58:51 -0800 Subject: [PATCH 4/7] -Fixed auto-generated string not translatable. --- app/src/main/java/org/schabi/newpipe/player/BasePlayer.java | 4 ++-- .../main/java/org/schabi/newpipe/player/VideoPlayer.java | 4 ++-- .../java/org/schabi/newpipe/player/helper/PlayerHelper.java | 6 ++++-- app/src/main/res/values/strings.xml | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) 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 07d567d57..222f0fad8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -617,12 +617,12 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen public void onPositionDiscontinuity(int reason) { if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with reason = [" + reason + "]"); // Refresh the playback if there is a transition to the next video - final int newWindowIndex = simpleExoPlayer.getCurrentPeriodIndex(); + final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex(); /* Discontinuity reasons!! Thank you ExoPlayer lords */ switch (reason) { case DISCONTINUITY_REASON_PERIOD_TRANSITION: - if (newWindowIndex == playQueue.getIndex()) { + if (newPeriodIndex == playQueue.getIndex()) { registerView(); } else { playQueue.offsetIndex(+1); diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 67e4e6919..40b7df2dc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -391,10 +391,10 @@ public abstract class VideoPlayer extends BasePlayer // Create subtitle sources for (final Subtitles subtitle : info.getSubtitles()) { final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType()); - if (mimeType == null) continue; + if (mimeType == null || context == null) continue; final Format textFormat = Format.createTextSampleFormat(null, mimeType, - SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(subtitle)); + SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle)); final MediaSource textSource = new SingleSampleMediaSource( Uri.parse(subtitle.getURL()), cacheDataSourceFactory, textFormat, TIME_UNSET); mediaSources.add(textSource); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 4929be9a3..ea3f73a17 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -66,9 +66,11 @@ public class PlayerHelper { } @NonNull - public static String captionLanguageOf(@NonNull final Subtitles subtitles) { + public static String captionLanguageOf(@NonNull final Context context, + @NonNull final Subtitles subtitles) { final String displayName = subtitles.getLocale().getDisplayName(subtitles.getLocale()); - return displayName + (subtitles.isAutoGenerated() ? " (auto-generated)" : ""); + return displayName + (subtitles.isAutoGenerated() ? + " (" + context.getString(R.string.caption_auto_generated)+ ")" : ""); } public static String resizeTypeOf(@NonNull final Context context, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab94ddce3..184219bad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -406,6 +406,7 @@ FILL ZOOM + Auto-generated Caption Font Size Smaller Font Normal Font From e8402008bcea58fa27bd59e8f6427533e5212ae5 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 20 Feb 2018 05:45:12 -0800 Subject: [PATCH 5/7] -Added debug preference settings for debug and beta builds. -Removed leak canary toggle on app menu. -Added leak canary settings to debug preference. -Removed/renamed leak canary related strings. --- .../java/org/schabi/newpipe/MainActivity.java | 36 ------------------- .../settings/DebugSettingsFragment.java | 12 +++++++ .../settings/MainSettingsFragment.java | 8 +++++ app/src/main/res/menu/debug_menu.xml | 12 ------- app/src/main/res/values-it/strings.xml | 7 ++-- app/src/main/res/values-nb-rNO/strings.xml | 3 -- app/src/main/res/values-nl/strings.xml | 8 ++--- app/src/main/res/values-pt-rBR/strings.xml | 3 -- app/src/main/res/values-sk/strings.xml | 4 --- app/src/main/res/values-tr/strings.xml | 3 -- app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 8 ++--- app/src/main/res/xml/debug_settings.xml | 13 +++++++ app/src/main/res/xml/main_settings.xml | 6 ++++ 14 files changed, 48 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java delete mode 100644 app/src/main/res/menu/debug_menu.xml create mode 100644 app/src/main/res/xml/debug_settings.xml diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index e696f867f..573479ea7 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -20,7 +20,6 @@ package org.schabi.newpipe; -import android.annotation.SuppressLint; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; @@ -28,7 +27,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; import android.support.v4.app.Fragment; import android.support.v4.view.GravityCompat; @@ -264,22 +262,6 @@ public class MainActivity extends AppCompatActivity { } } - @SuppressLint("ShowToast") - private void onHeapDumpToggled(@NonNull MenuItem item) { - final boolean isHeapDumpEnabled = !item.isChecked(); - - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putBoolean(getString(R.string.allow_heap_dumping_key), isHeapDumpEnabled).apply(); - item.setChecked(isHeapDumpEnabled); - - final String heapDumpNotice; - if (isHeapDumpEnabled) { - heapDumpNotice = getString(R.string.enable_leak_canary_notice); - } else { - heapDumpNotice = getString(R.string.disable_leak_canary_notice); - } - Toast.makeText(getApplicationContext(), heapDumpNotice, Toast.LENGTH_SHORT).show(); - } /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -301,10 +283,6 @@ public class MainActivity extends AppCompatActivity { inflater.inflate(R.menu.main_menu, menu); } - if (DEBUG) { - getMenuInflater().inflate(R.menu.debug_menu, menu); - } - ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(false); @@ -315,17 +293,6 @@ public class MainActivity extends AppCompatActivity { return true; } - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem heapDumpToggle = menu.findItem(R.id.action_toggle_heap_dump); - if (heapDumpToggle != null) { - final boolean isToggled = PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean(getString(R.string.allow_heap_dumping_key), false); - heapDumpToggle.setChecked(isToggled); - } - return super.onPrepareOptionsMenu(menu); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]"); @@ -346,9 +313,6 @@ public class MainActivity extends AppCompatActivity { case R.id.action_history: NavigationHelper.openHistory(this); return true; - case R.id.action_toggle_heap_dump: - onHeapDumpToggled(item); - return true; default: return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java new file mode 100644 index 000000000..0956f47d6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -0,0 +1,12 @@ +package org.schabi.newpipe.settings; + +import android.os.Bundle; + +import org.schabi.newpipe.R; + +public class DebugSettingsFragment extends BasePreferenceFragment { + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.debug_settings); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java index 728da0ae5..5e07e2b12 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java @@ -3,11 +3,19 @@ package org.schabi.newpipe.settings; import android.os.Bundle; import android.support.v7.preference.Preference; +import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; public class MainSettingsFragment extends BasePreferenceFragment { + public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.main_settings); + + if (!DEBUG) { + final Preference debug = findPreference(getString(R.string.debug_pref_screen_key)); + getPreferenceScreen().removePreference(debug); + } } } diff --git a/app/src/main/res/menu/debug_menu.xml b/app/src/main/res/menu/debug_menu.xml deleted file mode 100644 index 448f9cf23..000000000 --- a/app/src/main/res/menu/debug_menu.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index afdb6efd0..25cba8191 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -395,9 +395,6 @@ Carattere normale Carattere più grande - Controllo delle perdite di memoria abilitato, l\'applicazione può non rispondere mentre effettua il dumping dell\'heap - Controllo delle perdite di memoria disabilitato -A breve qualcosa si troverà qui ;D + A breve qualcosa si troverà qui ;D - - + diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 9f8762827..c214f5bd1 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -382,7 +382,4 @@ Mindre skrift Normal skrift Større skrift - - Oppsyn med minnelekasjer påslått, programmet kan slutte å svare under haug-dumping - Oppsyn med minnelekasjer slått av diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index bb9e07f2c..8ed57a2bf 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -391,9 +391,5 @@ te openen in pop-upmodus Normaal lettertype Groter lettertype - Controleren op geheugenlekken ingeschakeld, tijdens heapdumping kan de app tijdelijk niet reageren - Controleren op geheugenlekken uitgeschakeld -Hier zal binnenkort iets verschijnen ;D - - - + Hier zal binnenkort iets verschijnen ;D + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 81a7456a9..5203e6b6b 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -368,7 +368,4 @@ abrir em modo popup Fonte menor Fonte normal Maior fonte - - Monitoramento de vazamentos de memória habilitado, o aplicativo pode ficar sem responder quando estiver descarregando pilha de memória - Monitoramento de vazamentos de memória desabilitado diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 347724f85..e620d55ba 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -391,8 +391,4 @@ otvorenie okna na popredí Menšie Písmo Normálne Písmo Väčšie Písmo - - Monitorovanie pretečenia - Monitorovanie pretečenia pamäte je povolené, pri hromadnom zbere môže aplikácia prestať reagovať - Monitorovanie pretečenia pamäte je vypnuté diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 37542a332..a704f8c10 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -384,7 +384,4 @@ Küçük Yazı Tipi Olağan Yazı Tipi Büyük Yazı Tipi - - Bellek sızıntısı gözlemleme etkinleştirildi, uygulama yığın atımı sırasında yanıtsız kalabilir - Bellek sızıntısı gözlemleme devre dışı diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index ee784b5f7..f5b6802e8 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -84,6 +84,7 @@ last_orientation_landscape_key + debug_pref_screen_key allow_heap_dumping_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 184219bad..10f9272af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,6 +98,7 @@ Popup Appearance Other + Debug Playing in background Playing in popup mode Queued on background player @@ -412,8 +413,7 @@ Normal Font Larger Font - - LeakCanary - Memory leak monitoring enabled, app may become unresponsive when heap dumping - Memory leak monitoring disabled + + Enable LeakCanary + Memory leak monitoring may cause app to become unresponsive when heap dumping diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml new file mode 100644 index 000000000..9b0fd00d6 --- /dev/null +++ b/app/src/main/res/xml/debug_settings.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings.xml index 300265557..a0767844f 100644 --- a/app/src/main/res/xml/main_settings.xml +++ b/app/src/main/res/xml/main_settings.xml @@ -28,4 +28,10 @@ android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment" android:icon="?attr/language" android:title="@string/content"/> + + From cc7f27fb539368f0fd5851ec5210dcdc6d6bb273 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 20 Feb 2018 21:09:57 -0800 Subject: [PATCH 6/7] -Added debug default values on settings init. --- .../main/java/org/schabi/newpipe/settings/NewPipeSettings.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index 109466c02..92f98a9a2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -64,6 +64,7 @@ public class NewPipeSettings { PreferenceManager.setDefaultValues(context, R.xml.history_settings, true); PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); PreferenceManager.setDefaultValues(context, R.xml.video_audio_settings, true); + PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true); getVideoDownloadFolder(context); getAudioDownloadFolder(context); From 1a92dfb019c55543d8e4e640d5386e87e40a735b Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 20 Feb 2018 22:35:25 -0800 Subject: [PATCH 7/7] -Changed global Rx exception handling to no longer trigger error activity if the exception is undeliverable. -Added debug settings to force reporting of undeliverable Rx exceptions. -Changed back MediaSourceManager to use serial disposable for syncing. --- .../java/org/schabi/newpipe/DebugApp.java | 6 ++ app/src/main/java/org/schabi/newpipe/App.java | 55 +++++++++++++++---- .../player/playback/MediaSourceManager.java | 7 ++- app/src/main/res/values/settings_keys.xml | 2 + app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/xml/debug_settings.xml | 5 ++ 6 files changed, 66 insertions(+), 13 deletions(-) diff --git a/app/src/debug/java/org/schabi/newpipe/DebugApp.java b/app/src/debug/java/org/schabi/newpipe/DebugApp.java index 1ba837cdd..df4949edd 100644 --- a/app/src/debug/java/org/schabi/newpipe/DebugApp.java +++ b/app/src/debug/java/org/schabi/newpipe/DebugApp.java @@ -58,6 +58,12 @@ public class DebugApp extends App { Stetho.initialize(initializer); } + @Override + protected boolean isDisposedRxExceptionsReported() { + return PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(getString(R.string.allow_disposed_exceptions_key), true); + } + @Override protected RefWatcher installLeakCanary() { return LeakCanary.refWatcher(this) diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index b15a38aae..9de6f183d 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -30,9 +30,13 @@ import org.schabi.newpipe.util.StateSaver; import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketException; +import java.util.Collections; +import java.util.List; import io.reactivex.annotations.NonNull; import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.exceptions.OnErrorNotImplementedException; import io.reactivex.exceptions.UndeliverableException; import io.reactivex.functions.Consumer; import io.reactivex.plugins.RxJavaPlugins; @@ -99,31 +103,58 @@ public class App extends Application { RxJavaPlugins.setErrorHandler(new Consumer() { @Override public void accept(@NonNull Throwable throwable) throws Exception { - Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : throwable = [" + throwable.getClass().getName() + "]"); + Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " + + "throwable = [" + throwable.getClass().getName() + "]"); if (throwable instanceof UndeliverableException) { // As UndeliverableException is a wrapper, get the cause of it to get the "real" exception throwable = throwable.getCause(); } + final List errors; if (throwable instanceof CompositeException) { - for (Throwable element : ((CompositeException) throwable).getExceptions()) { - if (checkThrowable(element)) return; + errors = ((CompositeException) throwable).getExceptions(); + } else { + errors = Collections.singletonList(throwable); + } + + for (final Throwable error : errors) { + if (isThrowableIgnored(error)) return; + if (isThrowableCritical(error)) { + reportException(error); + return; } } - if (checkThrowable(throwable)) return; + // Out-of-lifecycle exceptions should only be reported if a debug user wishes so, + // When exception is not reported, log it + if (isDisposedRxExceptionsReported()) { + reportException(throwable); + } else { + Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable); + } + } + private boolean isThrowableIgnored(@NonNull final Throwable throwable) { + // Don't crash the application over a simple network problem + return ExtractorHelper.hasAssignableCauseThrowable(throwable, + IOException.class, SocketException.class, // network api cancellation + InterruptedException.class, InterruptedIOException.class); // blocking code disposed + } + + private boolean isThrowableCritical(@NonNull final Throwable throwable) { + // Though these exceptions cannot be ignored + return ExtractorHelper.hasAssignableCauseThrowable(throwable, + NullPointerException.class, IllegalArgumentException.class, // bug in app + OnErrorNotImplementedException.class, MissingBackpressureException.class, + IllegalStateException.class); // bug in operator + } + + private void reportException(@NonNull final Throwable throwable) { // Throw uncaught exception that will trigger the report system Thread.currentThread().getUncaughtExceptionHandler() .uncaughtException(Thread.currentThread(), throwable); } - - private boolean checkThrowable(@NonNull Throwable throwable) { - // Don't crash the application over a simple network problem - return ExtractorHelper.hasAssignableCauseThrowable(throwable, - IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class); - } }); } @@ -177,4 +208,8 @@ public class App extends Application { protected RefWatcher installLeakCanary() { return RefWatcher.DISABLED; } + + protected boolean isDisposedRxExceptionsReported() { + return true; + } } 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 a4438af70..54eb4078a 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 @@ -22,6 +22,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.annotations.NonNull; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.SerialDisposable; import io.reactivex.functions.Consumer; import io.reactivex.subjects.PublishSubject; @@ -45,7 +46,7 @@ public class MediaSourceManager { private DynamicConcatenatingMediaSource sources; private Subscription playQueueReactor; - private CompositeDisposable syncReactor; + private SerialDisposable syncReactor; private PlayQueueItem syncedItem; @@ -69,7 +70,7 @@ public class MediaSourceManager { this.windowSize = windowSize; this.loadDebounceMillis = loadDebounceMillis; - this.syncReactor = new CompositeDisposable(); + this.syncReactor = new SerialDisposable(); this.debouncedLoadSignal = PublishSubject.create(); this.debouncedLoader = getDebouncedLoader(); @@ -251,7 +252,7 @@ public class MediaSourceManager { final Disposable sync = currentItem.getStream() .observeOn(AndroidSchedulers.mainThread()) .subscribe(onSuccess, onError); - syncReactor.add(sync); + syncReactor.set(sync); } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index f5b6802e8..fc31ee02c 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -87,6 +87,8 @@ debug_pref_screen_key allow_heap_dumping_key + allow_disposed_exceptions_key + theme light_theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 10f9272af..495842092 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -416,4 +416,8 @@ Enable LeakCanary Memory leak monitoring may cause app to become unresponsive when heap dumping + + Report Out-of-Lifecycle Errors + Force reporting of undeliverable Rx exceptions occurring outside of fragment or activity lifecycle after dispose + diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index 9b0fd00d6..67705e018 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -10,4 +10,9 @@ android:title="@string/enable_leak_canary_title" android:summary="@string/enable_leak_canary_summary"/> +