mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 06:43:00 +00:00 
			
		
		
		
	-Added on change event bus to Play Queue.
-Added playback manager for player interaction.
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
						
							7c9c3de644
						
					
				
				
					commit
					dcdcf17f5e
				
			| @@ -47,9 +47,7 @@ import com.google.android.exoplayer2.RenderersFactory; | |||||||
| import com.google.android.exoplayer2.SimpleExoPlayer; | import com.google.android.exoplayer2.SimpleExoPlayer; | ||||||
| import com.google.android.exoplayer2.Timeline; | import com.google.android.exoplayer2.Timeline; | ||||||
| import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; | import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; | ||||||
| import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource; |  | ||||||
| import com.google.android.exoplayer2.source.ExtractorMediaSource; | import com.google.android.exoplayer2.source.ExtractorMediaSource; | ||||||
| import com.google.android.exoplayer2.source.LoopingMediaSource; |  | ||||||
| import com.google.android.exoplayer2.source.MediaSource; | import com.google.android.exoplayer2.source.MediaSource; | ||||||
| import com.google.android.exoplayer2.source.TrackGroupArray; | import com.google.android.exoplayer2.source.TrackGroupArray; | ||||||
| import com.google.android.exoplayer2.source.dash.DashMediaSource; | import com.google.android.exoplayer2.source.dash.DashMediaSource; | ||||||
| @@ -72,6 +70,7 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene | |||||||
|  |  | ||||||
| import org.schabi.newpipe.Downloader; | import org.schabi.newpipe.Downloader; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
|  | import org.schabi.newpipe.playlist.PlayQueue; | ||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.text.DecimalFormat; | import java.text.DecimalFormat; | ||||||
| @@ -86,9 +85,9 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||||||
|  * @author mauriciocolli |  * @author mauriciocolli | ||||||
|  */ |  */ | ||||||
| @SuppressWarnings({"WeakerAccess", "unused"}) | @SuppressWarnings({"WeakerAccess", "unused"}) | ||||||
| public abstract class BasePlayer implements Player.EventListener, AudioManager.OnAudioFocusChangeListener { | public abstract class BasePlayer implements Player.EventListener, | ||||||
|  |         AudioManager.OnAudioFocusChangeListener, PlaybackManager.PlaybackListener { | ||||||
|     // TODO: Check api version for deprecated audio manager methods |     // TODO: Check api version for deprecated audio manager methods | ||||||
|  |  | ||||||
|     public static final boolean DEBUG = false; |     public static final boolean DEBUG = false; | ||||||
|     public static final String TAG = "BasePlayer"; |     public static final String TAG = "BasePlayer"; | ||||||
|  |  | ||||||
| @@ -117,6 +116,13 @@ public abstract class BasePlayer implements Player.EventListener, AudioManager.O | |||||||
|     protected long videoStartPos = -1; |     protected long videoStartPos = -1; | ||||||
|     protected String uploaderName = ""; |     protected String uploaderName = ""; | ||||||
|  |  | ||||||
|  |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|  |     // Playlist | ||||||
|  |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     protected PlaybackManager playbackManager; | ||||||
|  |     protected PlayQueue playQueue; | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Player |     // Player | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -540,6 +546,22 @@ public abstract class BasePlayer implements Player.EventListener, AudioManager.O | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onPositionDiscontinuity() { |     public void onPositionDiscontinuity() { | ||||||
|  |         int newIndex = simpleExoPlayer.getCurrentWindowIndex(); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|  |     // Playback Listener | ||||||
|  |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void block() { | ||||||
|  |         if (currentState != STATE_LOADING) changeState(STATE_LOADING); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void unblock() { | ||||||
|  |         if (currentState != STATE_PLAYING) changeState(STATE_PLAYING); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|   | |||||||
| @@ -3,31 +3,200 @@ package org.schabi.newpipe.player; | |||||||
| import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource; | import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource; | ||||||
| import com.google.android.exoplayer2.source.MediaSource; | import com.google.android.exoplayer2.source.MediaSource; | ||||||
|  |  | ||||||
|  | import org.reactivestreams.Subscriber; | ||||||
|  | import org.reactivestreams.Subscription; | ||||||
|  | import org.schabi.newpipe.extractor.stream_info.StreamInfo; | ||||||
| import org.schabi.newpipe.playlist.PlayQueue; | import org.schabi.newpipe.playlist.PlayQueue; | ||||||
|  | import org.schabi.newpipe.playlist.PlayQueueEvent; | ||||||
|  | import org.schabi.newpipe.playlist.PlayQueueItem; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import io.reactivex.Maybe; | ||||||
|  | import io.reactivex.annotations.NonNull; | ||||||
|  |  | ||||||
| public class PlaybackManager { | public class PlaybackManager { | ||||||
|  |  | ||||||
|     private DynamicConcatenatingMediaSource source; |     private DynamicConcatenatingMediaSource mediaSource; | ||||||
|  |     private List<PlayQueueItem> queueSource; | ||||||
|  |     private int sourceIndex; | ||||||
|  |  | ||||||
|  |     private PlaybackListener listener; | ||||||
|     private PlayQueue playQueue; |     private PlayQueue playQueue; | ||||||
|     private int index; |  | ||||||
|  |  | ||||||
|     private List<MediaSource> sources; |  | ||||||
|  |  | ||||||
|     public PlaybackManager(PlayQueue playQueue, int index) { |  | ||||||
|         this.source = new DynamicConcatenatingMediaSource(); |  | ||||||
|  |  | ||||||
|         this.playQueue = playQueue; |  | ||||||
|         this.index = index; |  | ||||||
|  |  | ||||||
|  |     private Subscription playQueueReactor; | ||||||
|  |  | ||||||
|  |     interface PlaybackListener { | ||||||
|  |         void block(); | ||||||
|  |         void unblock(); | ||||||
|  |         void sync(); | ||||||
|  |  | ||||||
|  |         MediaSource sourceOf(StreamInfo info); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     interface OnChangeListener { |     public PlaybackManager(@NonNull final PlaybackListener listener, | ||||||
|         void isLoading(); |                            @NonNull final PlayQueue playQueue) { | ||||||
|         void isLoaded(); |         this.mediaSource = new DynamicConcatenatingMediaSource(); | ||||||
|  |         this.queueSource = Collections.synchronizedList(new ArrayList<PlayQueueItem>(10)); | ||||||
|  |         this.sourceIndex = 0; | ||||||
|  |  | ||||||
|  |         this.listener = listener; | ||||||
|  |         this.playQueue = playQueue; | ||||||
|  |  | ||||||
|  |         playQueue.getPlayQueueFlowable().subscribe(getReactor()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @NonNull | ||||||
|  |     public DynamicConcatenatingMediaSource getMediaSource() { | ||||||
|  |         return mediaSource; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void reload() { | ||||||
|  |         listener.block(); | ||||||
|  |         load(0); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void refreshMedia(final int newMediaIndex) { | ||||||
|  |         if (newMediaIndex == sourceIndex) return; | ||||||
|  |  | ||||||
|  |         if (newMediaIndex == sourceIndex + 1) { | ||||||
|  |             playQueue.incrementIndex(); | ||||||
|  |             mediaSource.removeMediaSource(0); | ||||||
|  |             queueSource.remove(0); | ||||||
|  |         } else { | ||||||
|  |             //something went wrong | ||||||
|  |             onInit(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void removeCurrent() { | ||||||
|  |         listener.block(); | ||||||
|  |         mediaSource.removeMediaSource(0); | ||||||
|  |         queueSource.remove(0); | ||||||
|  |         listener.unblock(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Subscription loaderReactor; | ||||||
|  |  | ||||||
|  |     private void load() { | ||||||
|  |         if (mediaSource.getSize() < 5 && queueSource.size() < 5) load(mediaSource.getSize()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void load(final int from) { | ||||||
|  |         clear(from); | ||||||
|  |  | ||||||
|  |         if (loaderReactor != null) loaderReactor.cancel(); | ||||||
|  |  | ||||||
|  |         List<Maybe<StreamInfo>> maybes = new ArrayList<>(); | ||||||
|  |         for (int i = from; i < 5; i++) { | ||||||
|  |             final int index = playQueue.getIndex() + i; | ||||||
|  |             final PlayQueueItem item = playQueue.get(index); | ||||||
|  |             queueSource.set(i, item); | ||||||
|  |             maybes.add(item.getStream()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Maybe.concat(maybes).subscribe(new Subscriber<StreamInfo>() { | ||||||
|  |             @Override | ||||||
|  |             public void onSubscribe(Subscription s) { | ||||||
|  |                 loaderReactor = s; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public void onNext(StreamInfo streamInfo) { | ||||||
|  |                 mediaSource.addMediaSource(listener.sourceOf(streamInfo)); | ||||||
|  |                 onLoaded(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public void onError(Throwable t) { | ||||||
|  |                 playQueue.remove(queueSource.size()); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public void onComplete() { | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void onLoaded() { | ||||||
|  |         if (mediaSource.getSize() > 0 && queueSource.size() > 0) listener.unblock(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void onInit() { | ||||||
|  |         listener.block(); | ||||||
|  |         load(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void clear(int from) { | ||||||
|  |         listener.block(); | ||||||
|  |         while (mediaSource.getSize() > from) { | ||||||
|  |             queueSource.remove(from); | ||||||
|  |             mediaSource.removeMediaSource(from); | ||||||
|  |         } | ||||||
|  |         listener.unblock(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Subscriber<PlayQueueEvent> getReactor() { | ||||||
|  |         return new Subscriber<PlayQueueEvent>() { | ||||||
|  |             @Override | ||||||
|  |             public void onSubscribe(@NonNull Subscription d) { | ||||||
|  |                 if (playQueueReactor != null) playQueueReactor.cancel(); | ||||||
|  |                 playQueueReactor = d; | ||||||
|  |                 playQueueReactor.request(1); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public void onNext(@NonNull PlayQueueEvent event) { | ||||||
|  |                 if (playQueue.getStreams().size() - playQueue.getIndex() < 10 && !playQueue.isComplete()) { | ||||||
|  |                     listener.block(); | ||||||
|  |                     playQueue.fetch(); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 switch (event) { | ||||||
|  |                     case INIT: | ||||||
|  |                         onInit(); | ||||||
|  |                         break; | ||||||
|  |                     case APPEND: | ||||||
|  |                         load(); | ||||||
|  |                         break; | ||||||
|  |                     case REMOVE_CURRENT: | ||||||
|  |                         removeCurrent(); | ||||||
|  |                         load(); | ||||||
|  |                         break; | ||||||
|  |                     case SELECT: | ||||||
|  |                         reload(); | ||||||
|  |                         break; | ||||||
|  |                     case REMOVE: | ||||||
|  |                     case SWAP: | ||||||
|  |                         load(1); | ||||||
|  |                         break; | ||||||
|  |                     case CLEAR: | ||||||
|  |                         clear(0); | ||||||
|  |                         break; | ||||||
|  |                     case NEXT: | ||||||
|  |                     default: | ||||||
|  |                         break; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 onLoaded(); | ||||||
|  |                 if (playQueueReactor != null) playQueueReactor.request(1); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public void onError(@NonNull Throwable e) { | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public void onComplete() { | ||||||
|  |                 // Never completes, only canceled | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void dispose() { | ||||||
|  |         if (playQueueReactor != null) playQueueReactor.cancel(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,44 +1,45 @@ | |||||||
| package org.schabi.newpipe.playlist; | package org.schabi.newpipe.playlist; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; |  | ||||||
| import org.schabi.newpipe.extractor.StreamingService; | import org.schabi.newpipe.extractor.StreamingService; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; |  | ||||||
| import org.schabi.newpipe.extractor.playlist.PlayListExtractor; | import org.schabi.newpipe.extractor.playlist.PlayListExtractor; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlayListInfo; | import org.schabi.newpipe.extractor.playlist.PlayListInfo; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlayListInfoItem; |  | ||||||
| import org.schabi.newpipe.extractor.stream_info.StreamInfo; | import org.schabi.newpipe.extractor.stream_info.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; | import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.concurrent.Callable; | import java.util.concurrent.Callable; | ||||||
| import java.util.concurrent.atomic.AtomicInteger; | import java.util.concurrent.atomic.AtomicInteger; | ||||||
|  |  | ||||||
| import io.reactivex.Maybe; | import io.reactivex.Maybe; | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
|  | import io.reactivex.disposables.Disposable; | ||||||
| import io.reactivex.functions.Consumer; | import io.reactivex.functions.Consumer; | ||||||
| import io.reactivex.schedulers.Schedulers; | import io.reactivex.schedulers.Schedulers; | ||||||
|  |  | ||||||
| public class ExternalPlayQueue extends PlayQueue { | public class ExternalPlayQueue extends PlayQueue { | ||||||
|  |     private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode()); | ||||||
|     private final static int LOAD_PROXIMITY = 10; |  | ||||||
|  |  | ||||||
|     private boolean isComplete; |     private boolean isComplete; | ||||||
|  |  | ||||||
|     private AtomicInteger pageNumber; |  | ||||||
|  |  | ||||||
|     private StreamingService service; |     private StreamingService service; | ||||||
|  |     private String playlistUrl; | ||||||
|  |  | ||||||
|     private PlayListInfoItem playlist; |     private AtomicInteger pageNumber; | ||||||
|  |     private Disposable fetchReactor; | ||||||
|  |  | ||||||
|     public ExternalPlayQueue(final PlayListInfoItem playlist) { |     public ExternalPlayQueue(final String playlistUrl, | ||||||
|         super(); |                              final PlayListInfo info, | ||||||
|         this.service = getService(playlist.serviceId); |                              final int nextPage, | ||||||
|         this.pageNumber = new AtomicInteger(0); |                              final int index) { | ||||||
|         this.playlist = playlist; |         super(index); | ||||||
|  |         this.service = getService(info.service_id); | ||||||
|  |         this.pageNumber = new AtomicInteger(nextPage); | ||||||
|  |         this.playlistUrl = playlistUrl; | ||||||
|  |  | ||||||
|         fetch(); |         getStreams().addAll(extractPlaylistItems(info)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -47,36 +48,25 @@ public class ExternalPlayQueue extends PlayQueue { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void load(int index, boolean loadNeighbors) { |     public void load(int index) { | ||||||
|         if (index > streams.size() || streams.get(index) == null) return; |         if (index > getStreams().size() || getStreams().get(index) == null) return; | ||||||
|  |         getStreams().get(index).load(); | ||||||
|         streams.get(index).load(); |  | ||||||
|  |  | ||||||
|         if (loadNeighbors) { |  | ||||||
|             int leftBound = index - LOAD_BOUND >= 0 ? index - LOAD_BOUND : 0; |  | ||||||
|             int rightBound = index + LOAD_BOUND < streams.size() ? index + LOAD_BOUND : streams.size() - 1; |  | ||||||
|  |  | ||||||
|             for (int i = leftBound; i < rightBound; i++) { |  | ||||||
|                 final PlayQueueItem item = streams.get(i); |  | ||||||
|                 if (item != null) item.load(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Maybe<StreamInfo> get(int index) { |     public PlayQueueItem get(int index) { | ||||||
|         if (index > streams.size() || streams.get(index) == null) return Maybe.empty(); |         if (index > getStreams().size() || getStreams().get(index) == null) return null; | ||||||
|         return streams.get(index).getStream(); |         return getStreams().get(index); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|     public synchronized void fetch() { |     public void fetch() { | ||||||
|         final int page = pageNumber.getAndIncrement(); |         if (fetchReactor != null && !fetchReactor.isDisposed()) return; | ||||||
|  |  | ||||||
|         final Callable<PlayListInfo> task = new Callable<PlayListInfo>() { |         final Callable<PlayListInfo> task = new Callable<PlayListInfo>() { | ||||||
|             @Override |             @Override | ||||||
|             public PlayListInfo call() throws Exception { |             public PlayListInfo call() throws Exception { | ||||||
|                 PlayListExtractor extractor = service.getPlayListExtractorInstance(playlist.getLink(), page); |                 PlayListExtractor extractor = service.getPlayListExtractorInstance(playlistUrl, pageNumber.get()); | ||||||
|                 return PlayListInfo.getInfo(extractor); |                 return PlayListInfo.getInfo(extractor); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| @@ -86,18 +76,23 @@ public class ExternalPlayQueue extends PlayQueue { | |||||||
|             public void accept(PlayListInfo playListInfo) throws Exception { |             public void accept(PlayListInfo playListInfo) throws Exception { | ||||||
|                 if (!playListInfo.hasNextPage) isComplete = true; |                 if (!playListInfo.hasNextPage) isComplete = true; | ||||||
|  |  | ||||||
|                 streams.addAll(extractPlaylistItems(playListInfo)); |                 append(extractPlaylistItems(playListInfo)); | ||||||
|                 notifyChange(); |                 pageNumber.incrementAndGet(); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         Maybe.fromCallable(task) |         fetchReactor = Maybe.fromCallable(task) | ||||||
|                 .subscribeOn(Schedulers.io()) |                 .subscribeOn(Schedulers.io()) | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                 .onErrorComplete() |                 .onErrorComplete() | ||||||
|                 .subscribe(onSuccess); |                 .subscribe(onSuccess); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void dispose() { | ||||||
|  |         if (fetchReactor != null) fetchReactor.dispose(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private List<PlayQueueItem> extractPlaylistItems(final PlayListInfo info) { |     private List<PlayQueueItem> extractPlaylistItems(final PlayListInfo info) { | ||||||
|         List<PlayQueueItem> result = new ArrayList<>(); |         List<PlayQueueItem> result = new ArrayList<>(); | ||||||
|         for (final InfoItem stream : info.related_streams) { |         for (final InfoItem stream : info.related_streams) { | ||||||
| @@ -107,12 +102,4 @@ public class ExternalPlayQueue extends PlayQueue { | |||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private StreamingService getService(final int serviceId) { |  | ||||||
|         try { |  | ||||||
|             return NewPipe.getService(serviceId); |  | ||||||
|         } catch (ExtractionException e) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,41 +2,138 @@ package org.schabi.newpipe.playlist; | |||||||
|  |  | ||||||
| import android.support.annotation.NonNull; | import android.support.annotation.NonNull; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.extractor.NewPipe; | ||||||
|  | import org.schabi.newpipe.extractor.StreamingService; | ||||||
|  | import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||||
| import org.schabi.newpipe.extractor.stream_info.StreamInfo; | import org.schabi.newpipe.extractor.stream_info.StreamInfo; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.concurrent.atomic.AtomicInteger; | ||||||
|  |  | ||||||
|  | import io.reactivex.BackpressureStrategy; | ||||||
|  | import io.reactivex.Flowable; | ||||||
| import io.reactivex.Maybe; | import io.reactivex.Maybe; | ||||||
| import io.reactivex.subjects.BehaviorSubject; | import io.reactivex.subjects.BehaviorSubject; | ||||||
|  |  | ||||||
| public abstract class PlayQueue { | public abstract class PlayQueue { | ||||||
|     private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); |     private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); | ||||||
|  |  | ||||||
|     final int LOAD_BOUND = 2; |     private List<PlayQueueItem> streams; | ||||||
|  |     private AtomicInteger queueIndex; | ||||||
|  |  | ||||||
|     protected List<PlayQueueItem> streams; |     private BehaviorSubject<PlayQueueEvent> changeBroadcast; | ||||||
|     private BehaviorSubject<List<PlayQueueItem>> changeBroadcast; |     private Flowable<PlayQueueEvent> playQueueFlowable; | ||||||
|  |  | ||||||
|     PlayQueue() { |     PlayQueue(final int index) { | ||||||
|         streams = Collections.synchronizedList(new ArrayList<PlayQueueItem>()); |         streams = Collections.synchronizedList(new ArrayList<PlayQueueItem>()); | ||||||
|  |         queueIndex = new AtomicInteger(index); | ||||||
|  |  | ||||||
|         changeBroadcast = BehaviorSubject.create(); |         changeBroadcast = BehaviorSubject.create(); | ||||||
|  |         playQueueFlowable = changeBroadcast.startWith(PlayQueueEvent.INIT).toFlowable(BackpressureStrategy.BUFFER); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // a queue is complete if it has loaded all items in an external playlist | ||||||
|  |     // single stream or local queues are always complete | ||||||
|  |     public abstract boolean isComplete(); | ||||||
|  |  | ||||||
|  |     // load in the background the item at index, may do nothing if the queue is incomplete | ||||||
|  |     public abstract void load(int index); | ||||||
|  |  | ||||||
|  |     // load partial queue in the background, does nothing if the queue is complete | ||||||
|  |     public abstract void fetch(); | ||||||
|  |  | ||||||
|  |     // returns a Rx Future to the stream info of the play queue item at index | ||||||
|  |     // may return an empty of the queue is incomplete | ||||||
|  |     public abstract PlayQueueItem get(int index); | ||||||
|  |  | ||||||
|  |     public abstract void dispose(); | ||||||
|  |  | ||||||
|  |     public int size() { | ||||||
|  |         return streams.size(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @NonNull |     @NonNull | ||||||
|     public List<PlayQueueItem> getStreams() { |     public List<PlayQueueItem> getStreams() { | ||||||
|         return streams; |         return Collections.unmodifiableList(streams); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void notifyChange() { |     @NonNull | ||||||
|         changeBroadcast.onNext(streams); |     public Flowable<PlayQueueEvent> getPlayQueueFlowable() { | ||||||
|  |         return playQueueFlowable; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public abstract boolean isComplete(); |     private void broadcast(final PlayQueueEvent event) { | ||||||
|  |         changeBroadcast.onNext(event); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public abstract void load(int index, boolean loadNeighbors); |     public int getIndex() { | ||||||
|  |         return queueIndex.get(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public abstract Maybe<StreamInfo> get(int index); |     public void setIndex(final int index) { | ||||||
|  |         queueIndex.set(index); | ||||||
|  |         broadcast(PlayQueueEvent.SELECT); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void incrementIndex() { | ||||||
|  |         queueIndex.incrementAndGet(); | ||||||
|  |         broadcast(PlayQueueEvent.NEXT); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected void append(final PlayQueueItem item) { | ||||||
|  |         streams.add(item); | ||||||
|  |         broadcast(PlayQueueEvent.APPEND); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected void append(final Collection<PlayQueueItem> items) { | ||||||
|  |         streams.addAll(items); | ||||||
|  |         broadcast(PlayQueueEvent.APPEND); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void remove(final int index) { | ||||||
|  |         if (index < streams.size()) { | ||||||
|  |             streams.remove(index); | ||||||
|  |             broadcast(PlayQueueEvent.REMOVE); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected void clear() { | ||||||
|  |         if (!streams.isEmpty()) { | ||||||
|  |             streams.clear(); | ||||||
|  |             broadcast(PlayQueueEvent.CLEAR); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected void swap(final int source, final int target) { | ||||||
|  |         final List<PlayQueueItem> items = streams; | ||||||
|  |         if (source < items.size() && target < items.size()) { | ||||||
|  |             // Swap two items | ||||||
|  |             final PlayQueueItem sourceItem = items.get(source); | ||||||
|  |             final PlayQueueItem targetItem = items.get(target); | ||||||
|  |  | ||||||
|  |             items.set(target, sourceItem); | ||||||
|  |             items.set(source, targetItem); | ||||||
|  |  | ||||||
|  |             // If the current playing index is one of the swapped indices, change that as well | ||||||
|  |             final int index = queueIndex.get(); | ||||||
|  |             if (index == source || index == target) { | ||||||
|  |                 final int newIndex = index == source ? target : source; | ||||||
|  |                 queueIndex.set(newIndex); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             broadcast(PlayQueueEvent.SWAP); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected StreamingService getService(final int serviceId) { | ||||||
|  |         try { | ||||||
|  |             return NewPipe.getService(serviceId); | ||||||
|  |         } catch (ExtractionException e) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,9 @@ import org.schabi.newpipe.info_list.StreamInfoItemHolder; | |||||||
|  |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import io.reactivex.disposables.Disposable; | ||||||
|  | import io.reactivex.functions.Consumer; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Created by Christian Schabesberger on 01.08.16. |  * Created by Christian Schabesberger on 01.08.16. | ||||||
|  * |  * | ||||||
| @@ -34,12 +37,14 @@ import java.util.List; | |||||||
| public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | ||||||
|     private static final String TAG = PlayQueueAdapter.class.toString(); |     private static final String TAG = PlayQueueAdapter.class.toString(); | ||||||
|  |  | ||||||
|     private final PlaylistItemBuilder playlistItemBuilder; |     private final PlayQueueItemBuilder playQueueItemBuilder; | ||||||
|     private final PlayQueue playQueue; |     private final PlayQueue playQueue; | ||||||
|     private boolean showFooter = false; |     private boolean showFooter = false; | ||||||
|     private View header = null; |     private View header = null; | ||||||
|     private View footer = null; |     private View footer = null; | ||||||
|  |  | ||||||
|  |     private Disposable playQueueReactor; | ||||||
|  |  | ||||||
|     public class HFHolder extends RecyclerView.ViewHolder { |     public class HFHolder extends RecyclerView.ViewHolder { | ||||||
|         public HFHolder(View v) { |         public HFHolder(View v) { | ||||||
|             super(v); |             super(v); | ||||||
| @@ -48,66 +53,57 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||||||
|         public View view; |         public View view; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void showFooter(boolean show) { |     public void showFooter(final boolean show) { | ||||||
|         showFooter = show; |         showFooter = show; | ||||||
|         notifyDataSetChanged(); |         notifyDataSetChanged(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public PlayQueueAdapter(PlayQueue playQueue) { |     public PlayQueueAdapter(final PlayQueue playQueue) { | ||||||
|         this.playlistItemBuilder = new PlaylistItemBuilder(); |         this.playQueueItemBuilder = new PlayQueueItemBuilder(); | ||||||
|         this.playQueue = playQueue; |         this.playQueue = playQueue; | ||||||
|  |  | ||||||
|  |         playQueueReactor = getReactor(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setSelectedListener(PlaylistItemBuilder.OnSelectedListener listener) { |     public void setSelectedListener(final PlayQueueItemBuilder.OnSelectedListener listener) { | ||||||
|         playlistItemBuilder.setOnSelectedListener(listener); |         playQueueItemBuilder.setOnSelectedListener(listener); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addItems(List<PlayQueueItem> data) { |     public void add(final List<PlayQueueItem> data) { | ||||||
|         if(data != null) { |         playQueue.append(data); | ||||||
|             playQueue.getStreams().addAll(data); |  | ||||||
|             notifyPlaylistChange(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addItem(PlayQueueItem data) { |     public void add(final PlayQueueItem data) { | ||||||
|         if (data != null) { |         playQueue.append(data); | ||||||
|             playQueue.getStreams().add(data); |  | ||||||
|             notifyPlaylistChange(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void removeItem(int index) { |     public void remove(final int index) { | ||||||
|         if (index < playQueue.getStreams().size()) { |         playQueue.remove(index); | ||||||
|             playQueue.getStreams().remove(index); |  | ||||||
|             notifyPlaylistChange(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void swapItems(int source, int target) { |     public void swap(final int source, final int target) { | ||||||
|         final List<PlayQueueItem> items = playQueue.getStreams(); |         playQueue.swap(source, target); | ||||||
|         if (source < items.size() && target < items.size()) { |  | ||||||
|             final PlayQueueItem sourceItem = items.get(source); |  | ||||||
|             final PlayQueueItem targetItem = items.get(target); |  | ||||||
|  |  | ||||||
|             items.set(target, sourceItem); |  | ||||||
|             items.set(source, targetItem); |  | ||||||
|  |  | ||||||
|             notifyPlaylistChange(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void clear() { |     public void clear() { | ||||||
|         if(playQueue.getStreams().isEmpty()) { |         playQueue.clear(); | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         playQueue.getStreams().clear(); |  | ||||||
|  |  | ||||||
|         notifyPlaylistChange(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void notifyPlaylistChange() { |     private Disposable getReactor() { | ||||||
|         playQueue.notifyChange(); |         final Consumer<PlayQueueEvent> onNext = new Consumer<PlayQueueEvent>() { | ||||||
|         notifyDataSetChanged(); |             @Override | ||||||
|  |             public void accept(PlayQueueEvent playQueueEvent) throws Exception { | ||||||
|  |                 notifyDataSetChanged(); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return playQueue.getPlayQueueFlowable() | ||||||
|  |                 .toObservable() | ||||||
|  |                 .subscribe(onNext); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void dispose() { | ||||||
|  |         if (playQueueReactor != null) playQueueReactor.dispose(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setHeader(View header) { |     public void setHeader(View header) { | ||||||
| @@ -155,7 +151,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||||||
|                 return new HFHolder(footer); |                 return new HFHolder(footer); | ||||||
|             case 2: |             case 2: | ||||||
|                 return new StreamInfoItemHolder(LayoutInflater.from(parent.getContext()) |                 return new StreamInfoItemHolder(LayoutInflater.from(parent.getContext()) | ||||||
|                         .inflate(R.layout.playlist_stream_item, parent, false)); |                         .inflate(R.layout.play_queue_item, parent, false)); | ||||||
|             default: |             default: | ||||||
|                 Log.e(TAG, "Trollolo"); |                 Log.e(TAG, "Trollolo"); | ||||||
|                 return null; |                 return null; | ||||||
| @@ -168,7 +164,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||||||
|             if(header != null) { |             if(header != null) { | ||||||
|                 i--; |                 i--; | ||||||
|             } |             } | ||||||
|             playlistItemBuilder.buildStreamInfoItem((PlayQueueItemHolder) holder, playQueue.getStreams().get(i)); |             playQueueItemBuilder.buildStreamInfoItem((PlayQueueItemHolder) holder, playQueue.getStreams().get(i)); | ||||||
|         } else if(holder instanceof HFHolder && i == 0 && header != null) { |         } else if(holder instanceof HFHolder && i == 0 && header != null) { | ||||||
|             ((HFHolder) holder).view = header; |             ((HFHolder) holder).view = header; | ||||||
|         } else if(holder instanceof HFHolder && i == playQueue.getStreams().size() && footer != null && showFooter) { |         } else if(holder instanceof HFHolder && i == playQueue.getStreams().size() && footer != null && showFooter) { | ||||||
|   | |||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | package org.schabi.newpipe.playlist; | ||||||
|  |  | ||||||
|  | public enum PlayQueueEvent { | ||||||
|  |     INIT, | ||||||
|  |  | ||||||
|  |     // sent when the user is seamlessly transitioned by exoplayer to the next stream | ||||||
|  |     NEXT, | ||||||
|  |  | ||||||
|  |     // sent when the user transitions to an unbuffered period | ||||||
|  |     SELECT, | ||||||
|  |  | ||||||
|  |     // sent when more streams are added to the play queue | ||||||
|  |     APPEND, | ||||||
|  |  | ||||||
|  |     // sent when a pending stream is removed from the play queue | ||||||
|  |     REMOVE, | ||||||
|  |  | ||||||
|  |     // sent when the current stream is removed | ||||||
|  |     REMOVE_CURRENT, | ||||||
|  |  | ||||||
|  |     // sent when two streams swap place in the play queue | ||||||
|  |     SWAP, | ||||||
|  |  | ||||||
|  |     // sent when streams is cleared | ||||||
|  |     CLEAR | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -8,7 +8,6 @@ import org.schabi.newpipe.extractor.stream_info.StreamExtractor; | |||||||
| import org.schabi.newpipe.extractor.stream_info.StreamInfo; | import org.schabi.newpipe.extractor.stream_info.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; | import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; | ||||||
|  |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.util.concurrent.Callable; | import java.util.concurrent.Callable; | ||||||
|  |  | ||||||
| import io.reactivex.Maybe; | import io.reactivex.Maybe; | ||||||
| @@ -17,7 +16,7 @@ import io.reactivex.functions.Action; | |||||||
| import io.reactivex.functions.Consumer; | import io.reactivex.functions.Consumer; | ||||||
| import io.reactivex.schedulers.Schedulers; | import io.reactivex.schedulers.Schedulers; | ||||||
|  |  | ||||||
| public class PlayQueueItem implements Serializable { | public class PlayQueueItem { | ||||||
|  |  | ||||||
|     private String title; |     private String title; | ||||||
|     private String url; |     private String url; | ||||||
|   | |||||||
| @@ -10,9 +10,9 @@ import org.schabi.newpipe.R; | |||||||
| import java.util.Locale; | import java.util.Locale; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| public class PlaylistItemBuilder { | public class PlayQueueItemBuilder { | ||||||
| 
 | 
 | ||||||
|     private static final String TAG = PlaylistItemBuilder.class.toString(); |     private static final String TAG = PlayQueueItemBuilder.class.toString(); | ||||||
| 
 | 
 | ||||||
|     public interface OnSelectedListener { |     public interface OnSelectedListener { | ||||||
|         void selected(int serviceId, String url, String title); |         void selected(int serviceId, String url, String title); | ||||||
| @@ -20,7 +20,7 @@ public class PlaylistItemBuilder { | |||||||
| 
 | 
 | ||||||
|     private OnSelectedListener onStreamInfoItemSelectedListener; |     private OnSelectedListener onStreamInfoItemSelectedListener; | ||||||
| 
 | 
 | ||||||
|     public PlaylistItemBuilder() {} |     public PlayQueueItemBuilder() {} | ||||||
| 
 | 
 | ||||||
|     public void setOnSelectedListener(OnSelectedListener listener) { |     public void setOnSelectedListener(OnSelectedListener listener) { | ||||||
|         this.onStreamInfoItemSelectedListener = listener; |         this.onStreamInfoItemSelectedListener = listener; | ||||||
| @@ -28,7 +28,7 @@ public class PlaylistItemBuilder { | |||||||
| 
 | 
 | ||||||
|     public View buildView(ViewGroup parent, final PlayQueueItem item) { |     public View buildView(ViewGroup parent, final PlayQueueItem item) { | ||||||
|         final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); |         final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); | ||||||
|         final View itemView = inflater.inflate(R.layout.stream_item, parent, false); |         final View itemView = inflater.inflate(R.layout.play_queue_item, parent, false); | ||||||
|         final PlayQueueItemHolder holder = new PlayQueueItemHolder(itemView); |         final PlayQueueItemHolder holder = new PlayQueueItemHolder(itemView); | ||||||
| 
 | 
 | ||||||
|         buildStreamInfoItem(holder, item); |         buildStreamInfoItem(holder, item); | ||||||
		Reference in New Issue
	
	Block a user