mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	-Added debouncing to index change reactor.
-Fixed repeat mode on background notification.
This commit is contained in:
		 John Zhen M
					John Zhen M
				
			
				
					committed by
					
						 John Zhen Mo
						John Zhen Mo
					
				
			
			
				
	
			
			
			 John Zhen Mo
						John Zhen Mo
					
				
			
						parent
						
							7d7a6f7ccc
						
					
				
				
					commit
					eb15c04254
				
			| @@ -311,7 +311,7 @@ public class BackgroundPlayer extends Service { | |||||||
|             super.onRepeatClicked(); |             super.onRepeatClicked(); | ||||||
|  |  | ||||||
|             int opacity = 255; |             int opacity = 255; | ||||||
|             switch (currentRepeatMode) { |             switch (simpleExoPlayer.getRepeatMode()) { | ||||||
|                 case Player.REPEAT_MODE_OFF: |                 case Player.REPEAT_MODE_OFF: | ||||||
|                     opacity = 77; |                     opacity = 77; | ||||||
|                     break; |                     break; | ||||||
| @@ -338,17 +338,17 @@ public class BackgroundPlayer extends Service { | |||||||
|  |  | ||||||
|         @Override |         @Override | ||||||
|         public void onFastRewind() { |         public void onFastRewind() { | ||||||
|             if (isPlayerBuffering()) return; |             if (!isPlayerReady()) return; | ||||||
|  |  | ||||||
|             playQueue.setIndex(playQueue.getIndex() - 1); |             playQueue.offsetIndex(-1); | ||||||
|             triggerProgressUpdate(); |             triggerProgressUpdate(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         @Override |         @Override | ||||||
|         public void onFastForward() { |         public void onFastForward() { | ||||||
|             if (isPlayerBuffering()) return; |             if (!isPlayerReady()) return; | ||||||
|  |  | ||||||
|             playQueue.setIndex(playQueue.getIndex() + 1); |             playQueue.offsetIndex(+1); | ||||||
|             triggerProgressUpdate(); |             triggerProgressUpdate(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -74,6 +74,8 @@ import org.schabi.newpipe.R; | |||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
|  | import org.schabi.newpipe.player.playback.PlaybackManager; | ||||||
|  | import org.schabi.newpipe.player.playback.PlaybackListener; | ||||||
| import org.schabi.newpipe.playlist.ExternalPlayQueue; | import org.schabi.newpipe.playlist.ExternalPlayQueue; | ||||||
| import org.schabi.newpipe.playlist.PlayQueue; | import org.schabi.newpipe.playlist.PlayQueue; | ||||||
| import org.schabi.newpipe.playlist.PlayQueueItem; | import org.schabi.newpipe.playlist.PlayQueueItem; | ||||||
| @@ -96,10 +98,10 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||||||
|  */ |  */ | ||||||
| @SuppressWarnings({"WeakerAccess", "unused"}) | @SuppressWarnings({"WeakerAccess", "unused"}) | ||||||
| public abstract class BasePlayer implements Player.EventListener, | public abstract class BasePlayer implements Player.EventListener, | ||||||
|         AudioManager.OnAudioFocusChangeListener, MediaSourceManager.PlaybackListener { |         AudioManager.OnAudioFocusChangeListener, PlaybackListener { | ||||||
|     // TODO: Check api version for deprecated audio manager methods |     // TODO: Check api version for deprecated audio manager methods | ||||||
|  |  | ||||||
|     public static final boolean DEBUG = true; |     public static final boolean DEBUG = false; | ||||||
|     public static final String TAG = "BasePlayer"; |     public static final String TAG = "BasePlayer"; | ||||||
|  |  | ||||||
|     protected Context context; |     protected Context context; | ||||||
| @@ -139,7 +141,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|     // Playlist |     // Playlist | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     protected MediaSourceManager playbackManager; |     protected PlaybackManager playbackManager; | ||||||
|     protected PlayQueue playQueue; |     protected PlayQueue playQueue; | ||||||
|  |  | ||||||
|     protected int restoreQueueIndex; |     protected int restoreQueueIndex; | ||||||
| @@ -270,7 +272,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|         playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index); |         playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index); | ||||||
|         playQueue.init(); |         playQueue.init(); | ||||||
|  |  | ||||||
|         playbackManager = new MediaSourceManager(this, playQueue); |         playbackManager = new PlaybackManager(this, playQueue); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unchecked") |     @SuppressWarnings("unchecked") | ||||||
| @@ -281,7 +283,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|         playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY); |         playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY); | ||||||
|         playQueue.init(); |         playQueue.init(); | ||||||
|  |  | ||||||
|         playbackManager = new MediaSourceManager(this, playQueue); |         playbackManager = new PlaybackManager(this, playQueue); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void initThumbnail() { |     public void initThumbnail() { | ||||||
| @@ -494,9 +496,6 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|     // Repeat |     // Repeat | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     protected int currentRepeatMode = Player.REPEAT_MODE_OFF; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public void onRepeatClicked() { |     public void onRepeatClicked() { | ||||||
|         if (DEBUG) Log.d(TAG, "onRepeatClicked() called"); |         if (DEBUG) Log.d(TAG, "onRepeatClicked() called"); | ||||||
|  |  | ||||||
| @@ -569,6 +568,10 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|                 changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); |                 changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); | ||||||
|                 break; |                 break; | ||||||
|             case Player.STATE_ENDED: // 4 |             case Player.STATE_ENDED: // 4 | ||||||
|  |                 if (playQueue.getIndex() < playQueue.size() - 1) { | ||||||
|  |                     playQueue.offsetIndex(+1); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|                 changeState(STATE_COMPLETED); |                 changeState(STATE_COMPLETED); | ||||||
|                 isPrepared = false; |                 isPrepared = false; | ||||||
|                 break; |                 break; | ||||||
| @@ -589,10 +592,8 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|         int newIndex = simpleExoPlayer.getCurrentWindowIndex(); |         int newIndex = simpleExoPlayer.getCurrentWindowIndex(); | ||||||
|         if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with: index = [" + newIndex + "]"); |         if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with: index = [" + newIndex + "]"); | ||||||
|  |  | ||||||
|         if (newIndex == playbackManager.getCurrentSourceIndex() + 1) { |  | ||||||
|         playbackManager.refresh(newIndex); |         playbackManager.refresh(newIndex); | ||||||
|     } |     } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Playback Listener |     // Playback Listener | ||||||
| @@ -601,7 +602,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|     @Override |     @Override | ||||||
|     public void block() { |     public void block() { | ||||||
|         if (simpleExoPlayer == null) return; |         if (simpleExoPlayer == null) return; | ||||||
|         Log.d(TAG, "Blocking..."); |         if (DEBUG) Log.d(TAG, "Blocking..."); | ||||||
|  |  | ||||||
|         simpleExoPlayer.stop(); |         simpleExoPlayer.stop(); | ||||||
|  |  | ||||||
| @@ -611,7 +612,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|     @Override |     @Override | ||||||
|     public void unblock() { |     public void unblock() { | ||||||
|         if (simpleExoPlayer == null) return; |         if (simpleExoPlayer == null) return; | ||||||
|         Log.d(TAG, "Unblocking..."); |         if (DEBUG) Log.d(TAG, "Unblocking..."); | ||||||
|  |  | ||||||
|         if (restoreQueueIndex != playQueue.getIndex()) { |         if (restoreQueueIndex != playQueue.getIndex()) { | ||||||
|             restoreQueueIndex = playQueue.getIndex(); |             restoreQueueIndex = playQueue.getIndex(); | ||||||
| @@ -626,7 +627,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|     @Override |     @Override | ||||||
|     public void sync(final StreamInfo info, final int sortedStreamsIndex) { |     public void sync(final StreamInfo info, final int sortedStreamsIndex) { | ||||||
|         if (simpleExoPlayer == null) return; |         if (simpleExoPlayer == null) return; | ||||||
|         Log.d(TAG, "Syncing..."); |         if (DEBUG) Log.d(TAG, "Syncing..."); | ||||||
|  |  | ||||||
|         videoUrl = info.url; |         videoUrl = info.url; | ||||||
|         videoThumbnailUrl = info.thumbnail_url; |         videoThumbnailUrl = info.thumbnail_url; | ||||||
| @@ -635,11 +636,11 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|         initThumbnail(); |         initThumbnail(); | ||||||
|  |  | ||||||
|         if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) { |         if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) { | ||||||
|             Log.w(TAG, "Rewinding to correct window"); |             if (DEBUG) Log.w(TAG, "Rewinding to correct window"); | ||||||
|             if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) { |             if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) { | ||||||
|                 simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex()); |                 simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex()); | ||||||
|             } else { |             } else { | ||||||
|                 Toast.makeText(context, "Player out of sync", Toast.LENGTH_SHORT).show(); |                 Toast.makeText(context, "Play Queue out of sync", Toast.LENGTH_SHORT).show(); | ||||||
|                 simpleExoPlayer.seekToDefaultPosition(); |                 simpleExoPlayer.seekToDefaultPosition(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -649,7 +650,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void shutdown() { |     public void shutdown() { | ||||||
|         Log.d(TAG, "Shutting down..."); |         if (DEBUG) Log.d(TAG, "Shutting down..."); | ||||||
|  |  | ||||||
|         playbackManager.dispose(); |         playbackManager.dispose(); | ||||||
|         playQueue.dispose(); |         playQueue.dispose(); | ||||||
| @@ -898,7 +899,7 @@ public abstract class BasePlayer implements Player.EventListener, | |||||||
|         return playQueue; |         return playQueue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public boolean isPlayerBuffering() { |     public boolean isPlayerReady() { | ||||||
|         return currentState == STATE_BUFFERING; |         return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -471,13 +471,13 @@ public class MainVideoPlayer extends Activity { | |||||||
|         public boolean onDoubleTap(MotionEvent e) { |         public boolean onDoubleTap(MotionEvent e) { | ||||||
|             if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); |             if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); | ||||||
|             //if (!playerImpl.isPlaying()) return false; |             //if (!playerImpl.isPlaying()) return false; | ||||||
|             if (playerImpl.isPlayerBuffering()) return false; |             if (!playerImpl.isPlayerReady()) return false; | ||||||
|  |  | ||||||
|             if (e.getX() > playerImpl.getRootView().getWidth() / 2) |             if (e.getX() > playerImpl.getRootView().getWidth() / 2) | ||||||
|                 playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1); |                 playerImpl.playQueue.offsetIndex(+1); | ||||||
|                 //playerImpl.onFastForward(); |                 //playerImpl.onFastForward(); | ||||||
|             else |             else | ||||||
|                 playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1); |                 playerImpl.playQueue.offsetIndex(-1); | ||||||
|                 //playerImpl.onFastRewind(); |                 //playerImpl.onFastRewind(); | ||||||
|  |  | ||||||
|             return true; |             return true; | ||||||
|   | |||||||
| @@ -580,14 +580,14 @@ public class PopupVideoPlayer extends Service { | |||||||
|         public boolean onDoubleTap(MotionEvent e) { |         public boolean onDoubleTap(MotionEvent e) { | ||||||
|             if (DEBUG) |             if (DEBUG) | ||||||
|                 Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); |                 Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); | ||||||
|             if (!playerImpl.isPlaying() || playerImpl.isPlayerBuffering()) return false; |             if (!playerImpl.isPlaying() || !playerImpl.isPlayerReady()) return false; | ||||||
|  |  | ||||||
|             if (e.getX() > popupWidth / 2) { |             if (e.getX() > popupWidth / 2) { | ||||||
|                 //playerImpl.onFastForward(); |                 //playerImpl.onFastForward(); | ||||||
|                 playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1); |                 playerImpl.playQueue.offsetIndex(+1); | ||||||
|             } else { |             } else { | ||||||
|                 //playerImpl.onFastRewind(); |                 //playerImpl.onFastRewind(); | ||||||
|                 playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1); |                 playerImpl.playQueue.offsetIndex(-1); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return true; |             return true; | ||||||
|   | |||||||
| @@ -57,6 +57,7 @@ import org.schabi.newpipe.extractor.MediaFormat; | |||||||
| import org.schabi.newpipe.extractor.stream.AudioStream; | import org.schabi.newpipe.extractor.stream.AudioStream; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream.VideoStream; | import org.schabi.newpipe.extractor.stream.VideoStream; | ||||||
|  | import org.schabi.newpipe.player.playback.PlaybackManager; | ||||||
| import org.schabi.newpipe.playlist.PlayQueue; | import org.schabi.newpipe.playlist.PlayQueue; | ||||||
| import org.schabi.newpipe.playlist.PlayQueueItem; | import org.schabi.newpipe.playlist.PlayQueueItem; | ||||||
| import org.schabi.newpipe.playlist.SinglePlayQueue; | import org.schabi.newpipe.playlist.SinglePlayQueue; | ||||||
| @@ -66,7 +67,6 @@ import org.schabi.newpipe.util.ListHelper; | |||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Vector; |  | ||||||
|  |  | ||||||
| import static org.schabi.newpipe.util.AnimationUtils.animateView; | import static org.schabi.newpipe.util.AnimationUtils.animateView; | ||||||
|  |  | ||||||
| @@ -226,7 +226,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. | |||||||
|         playQueue = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex); |         playQueue = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex); | ||||||
|         playQueue.init(); |         playQueue.init(); | ||||||
|  |  | ||||||
|         playbackManager = new MediaSourceManager(this, playQueue); |         playbackManager = new PlaybackManager(this, playQueue); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressWarnings("unchecked") |     @SuppressWarnings("unchecked") | ||||||
| @@ -239,7 +239,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. | |||||||
|         playQueue = (PlayQueue) serializable; |         playQueue = (PlayQueue) serializable; | ||||||
|         playQueue.init(); |         playQueue.init(); | ||||||
|  |  | ||||||
|         playbackManager = new MediaSourceManager(this, playQueue); |         playbackManager = new PlaybackManager(this, playQueue); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | package org.schabi.newpipe.player.playback; | ||||||
|  |  | ||||||
|  | import com.google.android.exoplayer2.source.MediaSource; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
|  |  | ||||||
|  | public interface PlaybackListener { | ||||||
|  |     /* | ||||||
|  |     * Called when the stream at the current queue index is not ready yet. | ||||||
|  |     * Signals to the listener to block the player from playing anything. | ||||||
|  |     * */ | ||||||
|  |     void block(); | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |     * Called when the stream at the current queue index is ready. | ||||||
|  |     * Signals to the listener to resume the player. | ||||||
|  |     * May be called at any time, even when the player is unblocked. | ||||||
|  |     * */ | ||||||
|  |     void unblock(); | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |     * Called when the queue index is refreshed. | ||||||
|  |     * Signals to the listener to synchronize the player's window to the manager's | ||||||
|  |     * window. | ||||||
|  |     * | ||||||
|  |     * CAN ONLY BE CALLED ONCE UNBLOCKED! | ||||||
|  |     * */ | ||||||
|  |     void sync(final StreamInfo info, final int sortedStreamsIndex); | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |     * Requests the listener to resolve a stream info into a media source respective | ||||||
|  |     * of the listener's implementation (background, popup or main video player), | ||||||
|  |     * */ | ||||||
|  |     MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex); | ||||||
|  |  | ||||||
|  |     void shutdown(); | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package org.schabi.newpipe.player; | package org.schabi.newpipe.player.playback; | ||||||
| 
 | 
 | ||||||
| import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| @@ -25,11 +25,11 @@ import io.reactivex.disposables.CompositeDisposable; | |||||||
| import io.reactivex.disposables.Disposable; | import io.reactivex.disposables.Disposable; | ||||||
| import io.reactivex.functions.Consumer; | import io.reactivex.functions.Consumer; | ||||||
| 
 | 
 | ||||||
| class MediaSourceManager { | public class PlaybackManager { | ||||||
|     private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode()); |     private final String TAG = "PlaybackManager@" + Integer.toHexString(hashCode()); | ||||||
|     // One-side rolling window size for default loading |     // One-side rolling window size for default loading | ||||||
|     // Effectively loads WINDOW_SIZE * 2 streams |     // Effectively loads WINDOW_SIZE * 2 streams | ||||||
|     private static final int WINDOW_SIZE = 3; |     private static final int WINDOW_SIZE = 2; | ||||||
| 
 | 
 | ||||||
|     private final PlaybackListener playbackListener; |     private final PlaybackListener playbackListener; | ||||||
|     private final PlayQueue playQueue; |     private final PlayQueue playQueue; | ||||||
| @@ -41,42 +41,12 @@ class MediaSourceManager { | |||||||
|     private List<Integer> sourceToQueueIndex; |     private List<Integer> sourceToQueueIndex; | ||||||
| 
 | 
 | ||||||
|     private Subscription playQueueReactor; |     private Subscription playQueueReactor; | ||||||
|     private Subscription loadingReactor; |     private Disposable syncReactor; | ||||||
|     private CompositeDisposable disposables; |     private CompositeDisposable disposables; | ||||||
| 
 | 
 | ||||||
|     private boolean isBlocked; |     private boolean isBlocked; | ||||||
| 
 | 
 | ||||||
|     interface PlaybackListener { |     public PlaybackManager(@NonNull final PlaybackListener listener, | ||||||
|         /* |  | ||||||
|         * Called when the stream at the current queue index is not ready yet. |  | ||||||
|         * Signals to the listener to block the player from playing anything. |  | ||||||
|         * */ |  | ||||||
|         void block(); |  | ||||||
| 
 |  | ||||||
|         /* |  | ||||||
|         * Called when the stream at the current queue index is ready. |  | ||||||
|         * Signals to the listener to resume the player. |  | ||||||
|         * May be called at any time, even when the player is unblocked. |  | ||||||
|         * */ |  | ||||||
|         void unblock(); |  | ||||||
| 
 |  | ||||||
|         /* |  | ||||||
|         * Called when the queue index is refreshed. |  | ||||||
|         * Signals to the listener to synchronize the player's window to the manager's |  | ||||||
|         * window. |  | ||||||
|         * */ |  | ||||||
|         void sync(final StreamInfo info, final int sortedStreamsIndex); |  | ||||||
| 
 |  | ||||||
|         /* |  | ||||||
|         * Requests the listener to resolve a stream info into a media source respective |  | ||||||
|         * of the listener's implementation (background, popup or main video player), |  | ||||||
|         * */ |  | ||||||
|         MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex); |  | ||||||
| 
 |  | ||||||
|         void shutdown(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MediaSourceManager(@NonNull final MediaSourceManager.PlaybackListener listener, |  | ||||||
|                            @NonNull final PlayQueue playQueue) { |                            @NonNull final PlayQueue playQueue) { | ||||||
|         this.playbackListener = listener; |         this.playbackListener = listener; | ||||||
|         this.playQueue = playQueue; |         this.playQueue = playQueue; | ||||||
| @@ -98,25 +68,28 @@ class MediaSourceManager { | |||||||
|     /* |     /* | ||||||
|     * Returns the media source index of the currently playing stream. |     * Returns the media source index of the currently playing stream. | ||||||
|     * */ |     * */ | ||||||
|     int getCurrentSourceIndex() { |     public int getCurrentSourceIndex() { | ||||||
|         return sourceToQueueIndex.indexOf(playQueue.getIndex()); |         return sourceToQueueIndex.indexOf(playQueue.getIndex()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     DynamicConcatenatingMediaSource getMediaSource() { |     public DynamicConcatenatingMediaSource getMediaSource() { | ||||||
|         return sources; |         return sources; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* |     /* | ||||||
|     * Called when the player has transitioned to another stream. |     * Called when the player has transitioned to another stream. | ||||||
|     * */ |     * */ | ||||||
|     void refresh(final int newSourceIndex) { |     public void refresh(final int newSourceIndex) { | ||||||
|         if (sourceToQueueIndex.indexOf(newSourceIndex) != -1) { |         if (sourceToQueueIndex.indexOf(newSourceIndex) != -1 && newSourceIndex == getCurrentSourceIndex() + 1) { | ||||||
|             playQueue.setIndex(sourceToQueueIndex.indexOf(newSourceIndex)); |             playQueue.offsetIndex(+1); | ||||||
|  | 
 | ||||||
|  |             // free up some memory | ||||||
|  |             if (sourceToQueueIndex.size() > 1) remove(sourceToQueueIndex.get(0)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void report(final Exception error) { |     public void report(final Exception error) { | ||||||
|         // ignore error checking for now, just remove the current index |         // ignore error checking for now, just remove the current index | ||||||
|         if (error == null || !tryBlock()) return; |         if (error == null || !tryBlock()) return; | ||||||
| 
 | 
 | ||||||
| @@ -127,27 +100,28 @@ class MediaSourceManager { | |||||||
|         load(); |         load(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int queueIndexOf(final int sourceIndex) { |     public void updateCurrent(final int newSortedStreamsIndex) { | ||||||
|         return sourceIndex < sourceToQueueIndex.size() ? sourceToQueueIndex.get(sourceIndex) : -1; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void updateCurrent(final int newSortedStreamsIndex) { |  | ||||||
|         if (!tryBlock()) return; |         if (!tryBlock()) return; | ||||||
| 
 | 
 | ||||||
|         PlayQueueItem item = playQueue.getCurrent(); |         PlayQueueItem item = playQueue.getCurrent(); | ||||||
|         item.setSortedQualityIndex(newSortedStreamsIndex); |         item.setSortedQualityIndex(newSortedStreamsIndex); | ||||||
|  | 
 | ||||||
|         resetSources(); |         resetSources(); | ||||||
|         load(); |         load(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void dispose() { |     public void dispose() { | ||||||
|         if (loadingReactor != null) loadingReactor.cancel(); |  | ||||||
|         if (playQueueReactor != null) playQueueReactor.cancel(); |         if (playQueueReactor != null) playQueueReactor.cancel(); | ||||||
|         if (disposables != null) disposables.dispose(); |         if (disposables != null) disposables.dispose(); | ||||||
|  |         if (syncReactor != null) syncReactor.dispose(); | ||||||
|  |         if (sources != null) sources.releaseSource(); | ||||||
|  |         if (sourceToQueueIndex != null) sourceToQueueIndex.clear(); | ||||||
| 
 | 
 | ||||||
|         loadingReactor = null; |  | ||||||
|         playQueueReactor = null; |         playQueueReactor = null; | ||||||
|         disposables = null; |         disposables = null; | ||||||
|  |         syncReactor = null; | ||||||
|  |         sources = null; | ||||||
|  |         sourceToQueueIndex = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -182,8 +156,8 @@ class MediaSourceManager { | |||||||
|                     case SWAP: |                     case SWAP: | ||||||
|                         final SwapEvent swapEvent = (SwapEvent) event; |                         final SwapEvent swapEvent = (SwapEvent) event; | ||||||
|                         swap(swapEvent.getFrom(), swapEvent.getTo()); |                         swap(swapEvent.getFrom(), swapEvent.getTo()); | ||||||
|  |                         load(); | ||||||
|                         break; |                         break; | ||||||
|                     case NEXT: |  | ||||||
|                     default: |                     default: | ||||||
|                         break; |                         break; | ||||||
|                 } |                 } | ||||||
| @@ -240,14 +214,18 @@ class MediaSourceManager { | |||||||
| 
 | 
 | ||||||
|     /* |     /* | ||||||
|     * Responds to a SELECT event. |     * Responds to a SELECT event. | ||||||
|     * When a change occur, the manager prepares by loading more. |     * If the selected item is already loaded, then we simply synchronize and | ||||||
|     * If the current item has not been fully loaded, |     * start loading some more items. | ||||||
|  |     * | ||||||
|  |     * If the current item has not been fully loaded, then the player will be | ||||||
|  |     * blocked. The sources will be reset and reloaded, to conserve memory. | ||||||
|     * */ |     * */ | ||||||
|     private void onSelect() { |     private void onSelect() { | ||||||
|         if (isCurrentIndexLoaded()) { |         if (isCurrentIndexLoaded() && !isBlocked) { | ||||||
|             sync(); |             sync(); | ||||||
|         } else { |         } else { | ||||||
|             tryBlock(); |             tryBlock(); | ||||||
|  |             resetSources(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         load(); |         load(); | ||||||
| @@ -256,14 +234,14 @@ class MediaSourceManager { | |||||||
|     private void sync() { |     private void sync() { | ||||||
|         final PlayQueueItem currentItem = playQueue.getCurrent(); |         final PlayQueueItem currentItem = playQueue.getCurrent(); | ||||||
| 
 | 
 | ||||||
|         final Consumer<StreamInfo> onSuccess = new Consumer<StreamInfo>() { |         final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() { | ||||||
|             @Override |             @Override | ||||||
|             public void accept(StreamInfo streamInfo) throws Exception { |             public void accept(StreamInfo streamInfo) throws Exception { | ||||||
|                 playbackListener.sync(streamInfo, currentItem.getSortedQualityIndex()); |                 playbackListener.sync(streamInfo, currentItem.getSortedQualityIndex()); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         currentItem.getStream().subscribe(onSuccess); |         currentItem.getStream().subscribe(syncPlayback); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void load() { |     private void load() { | ||||||
| @@ -287,11 +265,13 @@ class MediaSourceManager { | |||||||
|         item.getStream().subscribe(new SingleObserver<StreamInfo>() { |         item.getStream().subscribe(new SingleObserver<StreamInfo>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSubscribe(@NonNull Disposable d) { |             public void onSubscribe(@NonNull Disposable d) { | ||||||
|                 if (disposables != null) { |                 if (disposables == null) { | ||||||
|                     disposables.add(d); |  | ||||||
|                 } else { |  | ||||||
|                     d.dispose(); |                     d.dispose(); | ||||||
|  |                     return; | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 if (disposables.size() > 8) disposables.clear(); | ||||||
|  |                 disposables.add(d); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
| @@ -321,16 +301,6 @@ class MediaSourceManager { | |||||||
|     // Media Source List Manipulation |     // Media Source List Manipulation | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| 
 | 
 | ||||||
|     private void reset(final int queueIndex) { |  | ||||||
|         if (queueIndex < 0) return; |  | ||||||
| 
 |  | ||||||
|         final int sourceIndex = sourceToQueueIndex.indexOf(queueIndex); |  | ||||||
|         if (sourceIndex != -1) { |  | ||||||
|             sourceToQueueIndex.remove(sourceIndex); |  | ||||||
|             sources.removeMediaSource(sourceIndex); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Insert source into playlist with position in respect to the play queue |     // Insert source into playlist with position in respect to the play queue | ||||||
|     // If the play queue index already exists, then the insert is ignored |     // If the play queue index already exists, then the insert is ignored | ||||||
|     private void insert(final int queueIndex, final MediaSource source) { |     private void insert(final int queueIndex, final MediaSource source) { | ||||||
| @@ -5,12 +5,8 @@ import android.util.Log; | |||||||
|  |  | ||||||
| import org.reactivestreams.Subscriber; | import org.reactivestreams.Subscriber; | ||||||
| import org.reactivestreams.Subscription; | import org.reactivestreams.Subscription; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; |  | ||||||
| import org.schabi.newpipe.extractor.StreamingService; |  | ||||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; |  | ||||||
| import org.schabi.newpipe.playlist.events.AppendEvent; | import org.schabi.newpipe.playlist.events.AppendEvent; | ||||||
| import org.schabi.newpipe.playlist.events.InitEvent; | import org.schabi.newpipe.playlist.events.InitEvent; | ||||||
| import org.schabi.newpipe.playlist.events.NextEvent; |  | ||||||
| import org.schabi.newpipe.playlist.events.PlayQueueMessage; | import org.schabi.newpipe.playlist.events.PlayQueueMessage; | ||||||
| import org.schabi.newpipe.playlist.events.RemoveEvent; | import org.schabi.newpipe.playlist.events.RemoveEvent; | ||||||
| import org.schabi.newpipe.playlist.events.SelectEvent; | import org.schabi.newpipe.playlist.events.SelectEvent; | ||||||
| @@ -21,6 +17,7 @@ import java.util.ArrayList; | |||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
| import java.util.concurrent.atomic.AtomicInteger; | import java.util.concurrent.atomic.AtomicInteger; | ||||||
|  |  | ||||||
| import io.reactivex.BackpressureStrategy; | import io.reactivex.BackpressureStrategy; | ||||||
| @@ -29,12 +26,15 @@ import io.reactivex.subjects.BehaviorSubject; | |||||||
|  |  | ||||||
| public abstract class PlayQueue implements Serializable { | public abstract class PlayQueue implements Serializable { | ||||||
|     private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); |     private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); | ||||||
|     public static final boolean DEBUG = true; |     private final int INDEX_CHANGE_DEBOUNCE = 350; | ||||||
|  |  | ||||||
|  |     public static final boolean DEBUG = false; | ||||||
|  |  | ||||||
|     private final ArrayList<PlayQueueItem> streams; |     private final ArrayList<PlayQueueItem> streams; | ||||||
|     private final AtomicInteger queueIndex; |     private final AtomicInteger queueIndex; | ||||||
|  |  | ||||||
|     private transient BehaviorSubject<PlayQueueMessage> eventBroadcast; |     private transient BehaviorSubject<PlayQueueMessage> streamsEventBroadcast; | ||||||
|  |     private transient BehaviorSubject<PlayQueueMessage> indexEventBroadcast; | ||||||
|     private transient Flowable<PlayQueueMessage> broadcastReceiver; |     private transient Flowable<PlayQueueMessage> broadcastReceiver; | ||||||
|     private transient Subscription reportingReactor; |     private transient Subscription reportingReactor; | ||||||
|  |  | ||||||
| @@ -54,16 +54,19 @@ public abstract class PlayQueue implements Serializable { | |||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     public void init() { |     public void init() { | ||||||
|         eventBroadcast = BehaviorSubject.create(); |         streamsEventBroadcast = BehaviorSubject.create(); | ||||||
|         broadcastReceiver = eventBroadcast |         indexEventBroadcast = BehaviorSubject.create(); | ||||||
|                 .startWith(new InitEvent()) |  | ||||||
|                 .toFlowable(BackpressureStrategy.BUFFER); |         broadcastReceiver = Flowable.merge( | ||||||
|  |                 streamsEventBroadcast.toFlowable(BackpressureStrategy.BUFFER), | ||||||
|  |                 indexEventBroadcast.toFlowable(BackpressureStrategy.BUFFER).debounce(INDEX_CHANGE_DEBOUNCE, TimeUnit.MILLISECONDS) | ||||||
|  |         ).startWith(new InitEvent()); | ||||||
|  |  | ||||||
|         if (DEBUG) broadcastReceiver.subscribe(getSelfReporter()); |         if (DEBUG) broadcastReceiver.subscribe(getSelfReporter()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void dispose() { |     public void dispose() { | ||||||
|         eventBroadcast.onComplete(); |         streamsEventBroadcast.onComplete(); | ||||||
|  |  | ||||||
|         if (reportingReactor != null) reportingReactor.cancel(); |         if (reportingReactor != null) reportingReactor.cancel(); | ||||||
|         reportingReactor = null; |         reportingReactor = null; | ||||||
| @@ -121,9 +124,15 @@ public abstract class PlayQueue implements Serializable { | |||||||
|     // Write ops |     // Write ops | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     public synchronized void setIndex(final int index) { |     private synchronized void setIndex(final int index) { | ||||||
|  |         if (index < 0 || index >= streams.size()) return; | ||||||
|  |  | ||||||
|         queueIndex.set(Math.min(Math.max(0, index), streams.size() - 1)); |         queueIndex.set(Math.min(Math.max(0, index), streams.size() - 1)); | ||||||
|         broadcast(new SelectEvent(index)); |         indexEventBroadcast.onNext(new SelectEvent(index)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public synchronized void offsetIndex(final int offset) { | ||||||
|  |         setIndex(getIndex() + offset); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected synchronized void append(final PlayQueueItem item) { |     protected synchronized void append(final PlayQueueItem item) { | ||||||
| @@ -137,7 +146,9 @@ public abstract class PlayQueue implements Serializable { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public synchronized void remove(final int index) { |     public synchronized void remove(final int index) { | ||||||
|         if (index >= streams.size()) return; |         if (index >= streams.size() || index < 0) return; | ||||||
|  |  | ||||||
|  |         final boolean isCurrent = index == getIndex(); | ||||||
|  |  | ||||||
|         streams.remove(index); |         streams.remove(index); | ||||||
|         // Nudge the index if it becomes larger than the queue size |         // Nudge the index if it becomes larger than the queue size | ||||||
| @@ -145,10 +156,12 @@ public abstract class PlayQueue implements Serializable { | |||||||
|             queueIndex.set(size() - 1); |             queueIndex.set(size() - 1); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         broadcast(new RemoveEvent(index)); |         broadcast(new RemoveEvent(index, isCurrent)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected synchronized void swap(final int source, final int target) { |     protected synchronized void swap(final int source, final int target) { | ||||||
|  |         if (source < 0 || target < 0) return; | ||||||
|  |  | ||||||
|         final List<PlayQueueItem> items = streams; |         final List<PlayQueueItem> items = streams; | ||||||
|         if (source < items.size() && target < items.size()) { |         if (source < items.size() && target < items.size()) { | ||||||
|             // Swap two items |             // Swap two items | ||||||
| @@ -174,7 +187,7 @@ public abstract class PlayQueue implements Serializable { | |||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     private void broadcast(final PlayQueueMessage event) { |     private void broadcast(final PlayQueueMessage event) { | ||||||
|         eventBroadcast.onNext(event); |         streamsEventBroadcast.onNext(event); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private Subscriber<PlayQueueMessage> getSelfReporter() { |     private Subscriber<PlayQueueMessage> getSelfReporter() { | ||||||
|   | |||||||
| @@ -1,19 +0,0 @@ | |||||||
| package org.schabi.newpipe.playlist.events; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| public class NextEvent implements PlayQueueMessage { |  | ||||||
|     final private int newIndex; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public PlayQueueEvent type() { |  | ||||||
|         return PlayQueueEvent.NEXT; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public NextEvent(final int newIndex) { |  | ||||||
|         this.newIndex = newIndex; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int index() { |  | ||||||
|         return newIndex; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -3,17 +3,23 @@ package org.schabi.newpipe.playlist.events; | |||||||
|  |  | ||||||
| public class RemoveEvent implements PlayQueueMessage { | public class RemoveEvent implements PlayQueueMessage { | ||||||
|     final private int index; |     final private int index; | ||||||
|  |     final private boolean isCurrent; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public PlayQueueEvent type() { |     public PlayQueueEvent type() { | ||||||
|         return PlayQueueEvent.REMOVE; |         return PlayQueueEvent.REMOVE; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public RemoveEvent(final int index) { |     public RemoveEvent(final int index, final boolean isCurrent) { | ||||||
|         this.index = index; |         this.index = index; | ||||||
|  |         this.isCurrent = isCurrent; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public int index() { |     public int index() { | ||||||
|         return index; |         return index; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public boolean isCurrent() { | ||||||
|  |         return isCurrent; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user