mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	-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.
This commit is contained in:
		| @@ -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' | ||||
|   | ||||
| @@ -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,18 +614,26 @@ 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)) { | ||||
|         /* 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(); | ||||
|   | ||||
| @@ -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; | ||||
|                 } | ||||
|   | ||||
| @@ -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) {} | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -114,32 +114,10 @@ public final class DeferredMediaSource implements MediaSource { | ||||
|  | ||||
|         Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl()); | ||||
|  | ||||
|         final Function<StreamInfo, MediaSource> onReceive = new Function<StreamInfo, MediaSource>() { | ||||
|             @Override | ||||
|             public MediaSource apply(StreamInfo streamInfo) throws Exception { | ||||
|                 return onStreamInfoReceived(stream, streamInfo); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         final Consumer<MediaSource> onSuccess = new Consumer<MediaSource>() { | ||||
|             @Override | ||||
|             public void accept(MediaSource mediaSource) throws Exception { | ||||
|                 onMediaSourceReceived(mediaSource); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         final Consumer<Throwable> onError = new Consumer<Throwable>() { | ||||
|             @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, | ||||
|   | ||||
| @@ -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<StreamInfo> syncPlayback = new Consumer<StreamInfo>() { | ||||
|             @Override | ||||
|             public void accept(StreamInfo streamInfo) throws Exception { | ||||
|                 playbackListener.sync(currentItem, streamInfo); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         final Consumer<Throwable> onError = new Consumer<Throwable>() { | ||||
|             @Override | ||||
|             public void accept(Throwable throwable) throws Exception { | ||||
|         final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info); | ||||
|         final Consumer<Throwable> onError = throwable -> { | ||||
|             Log.e(TAG, "Sync error:", throwable); | ||||
|                 playbackListener.sync(currentItem,null); | ||||
|             } | ||||
|             syncInternal(currentItem, null); | ||||
|         }; | ||||
|  | ||||
|         syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError)); | ||||
|         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; | ||||
|             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<Long>() { | ||||
|             @Override | ||||
|             public void accept(Long timestamp) throws Exception { | ||||
|                 loadImmediate(); | ||||
|             } | ||||
|         }); | ||||
|                 .subscribe(timestamp -> loadImmediate()); | ||||
|     } | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Media Source List Manipulation | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -73,6 +73,10 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | ||||
|         playQueueItemBuilder.setOnSelectedListener(listener); | ||||
|     } | ||||
|  | ||||
|     public void unsetSelectedListener() { | ||||
|         playQueueItemBuilder.setOnSelectedListener(null); | ||||
|     } | ||||
|  | ||||
|     private void startReactor() { | ||||
|         final Observer<PlayQueueEvent> observer = new Observer<PlayQueueEvent>() { | ||||
|             @Override | ||||
|   | ||||
| @@ -104,17 +104,9 @@ public class PlayQueueItem implements Serializable { | ||||
|  | ||||
|     @NonNull | ||||
|     private Single<StreamInfo> getInfo() { | ||||
|         final Consumer<Throwable> onError = new Consumer<Throwable>() { | ||||
|             @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); | ||||
|     } | ||||
|  | ||||
|     //////////////////////////////////////////////////////////////////////////// | ||||
|   | ||||
| @@ -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) { | ||||
|         holder.itemRoot.setOnClickListener(view -> { | ||||
|             if (onItemClickListener != null) { | ||||
|                 onItemClickListener.selected(item, view); | ||||
|             } | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         holder.itemRoot.setOnLongClickListener(new View.OnLongClickListener() { | ||||
|             @Override | ||||
|             public boolean onLongClick(View view) { | ||||
|         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) { | ||||
|         return (view, motionEvent) -> { | ||||
|             view.performClick(); | ||||
|                 if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { | ||||
|             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 BitmapProcessor bitmapProcessor = bitmap -> { | ||||
|             final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, widthPx, heightPx, false); | ||||
|             bitmap.recycle(); | ||||
|             return resizedBitmap; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return new DisplayImageOptions.Builder() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 John Zhen Mo
					John Zhen Mo