-Added documentations for play queue components.
This commit is contained in:
parent
80f3e3c3b6
commit
c75c2d0f21
8 changed files with 179 additions and 34 deletions
|
@ -27,6 +27,7 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.audiofx.AudioEffect;
|
import android.media.audiofx.AudioEffect;
|
||||||
|
@ -34,6 +35,7 @@ import android.net.Uri;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
@ -70,6 +72,7 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto
|
||||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
import com.nostra13.universalimageloader.core.assist.ImageSize;
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.Downloader;
|
import org.schabi.newpipe.Downloader;
|
||||||
|
|
|
@ -64,7 +64,6 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
|
||||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
|
|
@ -65,6 +65,7 @@ import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
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.playlist.PlayQueue;
|
import org.schabi.newpipe.playlist.PlayQueue;
|
||||||
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
|
@ -395,20 +396,25 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
final Format format = group.getFormat(trackIndex);
|
final Format format = group.getFormat(trackIndex);
|
||||||
final boolean isSetCurrent = selectedTrackGroup.indexOf(format) != -1;
|
final boolean isSetCurrent = selectedTrackGroup.indexOf(format) != -1;
|
||||||
|
|
||||||
// If the source is extracted (e.g. mp4), then we use the resolution contained in the stream
|
|
||||||
if (group.length == 1 && videoTrackGroups.length == availableStreams.size()) {
|
if (group.length == 1 && videoTrackGroups.length == availableStreams.size()) {
|
||||||
popupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, MediaFormat.getNameById(stream.format) + " " + stream.resolution + " (" + format.width + "x" + format.height + ")");
|
// If the source is non-adaptive (extractor source), then we use the resolution contained in the stream
|
||||||
if (isSetCurrent) qualityTextView.setText(stream.resolution);
|
if (isSetCurrent) qualityTextView.setText(stream.resolution);
|
||||||
|
|
||||||
|
final String menuItem = MediaFormat.getNameById(stream.format) + " " +
|
||||||
|
stream.resolution + " (" + format.width + "x" + format.height + ")";
|
||||||
|
popupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we have a DASH source, which contains multiple formats and
|
// Otherwise, we have an adaptive source, which contains multiple formats and
|
||||||
// thus have no inherent quality format
|
// thus have no inherent quality format
|
||||||
|
if (isSetCurrent) qualityTextView.setText(resolutionStringOf(format));
|
||||||
|
|
||||||
final MediaFormat mediaFormat = MediaFormat.getFromMimeType(format.sampleMimeType);
|
final MediaFormat mediaFormat = MediaFormat.getFromMimeType(format.sampleMimeType);
|
||||||
final String mediaName = mediaFormat == null ? format.sampleMimeType : mediaFormat.name;
|
final String mediaName = mediaFormat == null ? format.sampleMimeType : mediaFormat.name;
|
||||||
|
|
||||||
final String resolution = resolutionStringOf(format);
|
final String menuItem = mediaName + " " + format.width + "x" + format.height;
|
||||||
popupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, mediaName + " " + resolution);
|
popupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
||||||
if (isSetCurrent) qualityTextView.setText(resolution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trackGroupInfos.add(new TrackGroupInfo(trackIndex, groupIndex, MediaFormat.getNameById(stream.format), stream.resolution, format));
|
trackGroupInfos.add(new TrackGroupInfo(trackIndex, groupIndex, MediaFormat.getNameById(stream.format), stream.resolution, format));
|
||||||
acc++;
|
acc++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,15 @@ 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeferredMediaSource is specifically designed to allow external control over when
|
||||||
|
* the source metadata are loaded while being compatible with ExoPlayer's playlists.
|
||||||
|
*
|
||||||
|
* This media source follows the structure of how NewPipeExtractor's
|
||||||
|
* {@link org.schabi.newpipe.extractor.stream.StreamInfoItem} is converted into
|
||||||
|
* {@link org.schabi.newpipe.extractor.stream.StreamInfo}. Once conversion is complete,
|
||||||
|
* this media source behaves identically as any other native media sources.
|
||||||
|
* */
|
||||||
public final class DeferredMediaSource implements MediaSource {
|
public final class DeferredMediaSource implements MediaSource {
|
||||||
private final String TAG = "DeferredMediaSource@" + Integer.toHexString(hashCode());
|
private final String TAG = "DeferredMediaSource@" + Integer.toHexString(hashCode());
|
||||||
|
|
||||||
|
@ -30,6 +39,9 @@ public final class DeferredMediaSource implements MediaSource {
|
||||||
public final static int STATE_DISPOSED = 3;
|
public final static int STATE_DISPOSED = 3;
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
/**
|
||||||
|
* Player-specific MediaSource resolution from given StreamInfo.
|
||||||
|
* */
|
||||||
MediaSource sourceOf(final StreamInfo info);
|
MediaSource sourceOf(final StreamInfo info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +63,9 @@ public final class DeferredMediaSource implements MediaSource {
|
||||||
this.state = STATE_INIT;
|
this.state = STATE_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters are kept in the class for delayed preparation.
|
||||||
|
* */
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource(ExoPlayer exoPlayer, boolean isTopLevelSource, Listener listener) {
|
public void prepareSource(ExoPlayer exoPlayer, boolean isTopLevelSource, Listener listener) {
|
||||||
this.exoPlayer = exoPlayer;
|
this.exoPlayer = exoPlayer;
|
||||||
|
@ -62,6 +77,17 @@ public final class DeferredMediaSource implements MediaSource {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Externally controlled loading. This method fully prepares the source to be used
|
||||||
|
* like any other native MediaSource.
|
||||||
|
*
|
||||||
|
* Ideally, this should be called after this source has entered PREPARED state and
|
||||||
|
* called once only.
|
||||||
|
*
|
||||||
|
* If loading fails here, an error will be propagated out and result in a
|
||||||
|
* {@link com.google.android.exoplayer2.ExoPlaybackException}, which is delegated
|
||||||
|
* out to the player.
|
||||||
|
* */
|
||||||
public synchronized void load() {
|
public synchronized void load() {
|
||||||
if (state != STATE_PREPARED || stream == null || loader != null) return;
|
if (state != STATE_PREPARED || stream == null || loader != null) return;
|
||||||
Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
||||||
|
|
|
@ -60,22 +60,37 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
.subscribe(getReactor());
|
.subscribe(getReactor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// DeferredMediaSource listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaSource sourceOf(StreamInfo info) {
|
||||||
|
return playbackListener.sourceOf(info);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Exposed Methods
|
// Exposed Methods
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Returns the media source index of the currently playing stream.
|
* Returns the media source index of the currently playing stream.
|
||||||
* */
|
* */
|
||||||
public int getCurrentSourceIndex() {
|
public int getCurrentSourceIndex() {
|
||||||
return sourceToQueueIndex.indexOf(playQueue.getIndex());
|
return sourceToQueueIndex.indexOf(playQueue.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the play queue index of a given media source playlist index.
|
||||||
|
* */
|
||||||
public int getQueueIndexOf(final int sourceIndex) {
|
public int getQueueIndexOf(final int sourceIndex) {
|
||||||
if (sourceIndex < 0 || sourceIndex >= sourceToQueueIndex.size()) return -1;
|
if (sourceIndex < 0 || sourceIndex >= sourceToQueueIndex.size()) return -1;
|
||||||
return sourceToQueueIndex.get(sourceIndex);
|
return sourceToQueueIndex.get(sourceIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose the manager and releases all message buses and loaders.
|
||||||
|
* */
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if (playQueueReactor != null) playQueueReactor.cancel();
|
if (playQueueReactor != null) playQueueReactor.cancel();
|
||||||
if (syncReactor != null) syncReactor.dispose();
|
if (syncReactor != null) syncReactor.dispose();
|
||||||
|
@ -90,6 +105,9 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
playQueue = null;
|
playQueue = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the current playing stream and the streams within its WINDOW_SIZE bound.
|
||||||
|
* */
|
||||||
public void load() {
|
public void load() {
|
||||||
// The current item has higher priority
|
// The current item has higher priority
|
||||||
final int currentIndex = playQueue.getIndex();
|
final int currentIndex = playQueue.getIndex();
|
||||||
|
@ -140,6 +158,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
remove(removeEvent.index());
|
remove(removeEvent.index());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Reset the sources if the index to remove is the current playing index
|
||||||
case INIT:
|
case INIT:
|
||||||
case REORDER:
|
case REORDER:
|
||||||
tryBlock();
|
tryBlock();
|
||||||
|
@ -249,8 +268,12 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
// Media Source List Manipulation
|
// Media Source List Manipulation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
// Insert source into playlist with position in respect to the play queue
|
/**
|
||||||
// If the play queue index already exists, then the insert is ignored
|
* Inserts a source into {@link DynamicConcatenatingMediaSource} with position
|
||||||
|
* in respect to the play queue.
|
||||||
|
*
|
||||||
|
* If the play queue index already exists, then the insert is ignored.
|
||||||
|
* */
|
||||||
private void insert(final int queueIndex, final DeferredMediaSource source) {
|
private void insert(final int queueIndex, final DeferredMediaSource source) {
|
||||||
if (queueIndex < 0) return;
|
if (queueIndex < 0) return;
|
||||||
|
|
||||||
|
@ -262,6 +285,11 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a source from {@link DynamicConcatenatingMediaSource} with the given play queue index.
|
||||||
|
*
|
||||||
|
* If the play queue index does not exist, the removal is ignored.
|
||||||
|
* */
|
||||||
private void remove(final int queueIndex) {
|
private void remove(final int queueIndex) {
|
||||||
if (queueIndex < 0) return;
|
if (queueIndex < 0) return;
|
||||||
|
|
||||||
|
@ -276,9 +304,4 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
sourceToQueueIndex.set(i, sourceToQueueIndex.get(i) - 1);
|
sourceToQueueIndex.set(i, sourceToQueueIndex.get(i) - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaSource sourceOf(StreamInfo info) {
|
|
||||||
return playbackListener.sourceOf(info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,6 @@ import io.reactivex.schedulers.Schedulers;
|
||||||
public final class ExternalPlayQueue extends PlayQueue {
|
public final class ExternalPlayQueue extends PlayQueue {
|
||||||
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode());
|
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode());
|
||||||
|
|
||||||
public static final String SERVICE_ID = "service_id";
|
|
||||||
public static final String INDEX = "index";
|
|
||||||
public static final String STREAMS = "streams";
|
|
||||||
public static final String URL = "url";
|
|
||||||
public static final String NEXT_PAGE_URL = "next_page_url";
|
|
||||||
|
|
||||||
private static final int RETRY_COUNT = 2;
|
private static final int RETRY_COUNT = 2;
|
||||||
|
|
||||||
private boolean isComplete;
|
private boolean isComplete;
|
||||||
|
@ -87,7 +81,7 @@ public final class ExternalPlayQueue extends PlayQueue {
|
||||||
public void onError(@NonNull Throwable e) {
|
public void onError(@NonNull Throwable e) {
|
||||||
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
|
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
|
||||||
isComplete = true;
|
isComplete = true;
|
||||||
append(Collections.<PlayQueueItem>emptyList());
|
append(); // Notify change
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,16 @@ import io.reactivex.Flowable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.subjects.BehaviorSubject;
|
import io.reactivex.subjects.BehaviorSubject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlayQueue is responsible for keeping track of a list of streams and the index of
|
||||||
|
* the stream that should be currently playing.
|
||||||
|
*
|
||||||
|
* This class contains basic manipulation of a playlist while also functions as a
|
||||||
|
* message bus, providing all listeners with new updates to the play queue.
|
||||||
|
*
|
||||||
|
* This class can be serialized for passing intents, but in order to start the
|
||||||
|
* message bus, it must be initialized.
|
||||||
|
* */
|
||||||
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());
|
||||||
|
|
||||||
|
@ -54,6 +64,11 @@ public abstract class PlayQueue implements Serializable {
|
||||||
// Playlist actions
|
// Playlist actions
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the play queue message buses.
|
||||||
|
*
|
||||||
|
* Also starts a self reporter for logging if debug mode is enabled.
|
||||||
|
* */
|
||||||
public void init() {
|
public void init() {
|
||||||
streamsEventBroadcast = BehaviorSubject.create();
|
streamsEventBroadcast = BehaviorSubject.create();
|
||||||
indexEventBroadcast = BehaviorSubject.create();
|
indexEventBroadcast = BehaviorSubject.create();
|
||||||
|
@ -66,6 +81,9 @@ public abstract class PlayQueue implements Serializable {
|
||||||
if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
|
if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose this play queue by stopping all message buses and clearing the playlist.
|
||||||
|
* */
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if (backup != null) backup.clear();
|
if (backup != null) backup.clear();
|
||||||
if (streams != null) streams.clear();
|
if (streams != null) streams.clear();
|
||||||
|
@ -78,49 +96,82 @@ public abstract class PlayQueue implements Serializable {
|
||||||
reportingReactor = null;
|
reportingReactor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a queue is complete if it has loaded all items in an external playlist
|
/**
|
||||||
// single stream or local queues are always complete
|
* Checks if the queue is complete.
|
||||||
|
*
|
||||||
|
* 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();
|
public abstract boolean isComplete();
|
||||||
|
|
||||||
// load partial queue in the background, does nothing if the queue is complete
|
/**
|
||||||
|
* Load partial queue in the background, does nothing if the queue is complete.
|
||||||
|
* */
|
||||||
public abstract void fetch();
|
public abstract void fetch();
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Readonly ops
|
// Readonly ops
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current index that should be played.
|
||||||
|
* */
|
||||||
public int getIndex() {
|
public int getIndex() {
|
||||||
return queueIndex.get();
|
return queueIndex.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current item that should be played.
|
||||||
|
* */
|
||||||
public PlayQueueItem getCurrent() {
|
public PlayQueueItem getCurrent() {
|
||||||
return get(getIndex());
|
return get(getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item at the given index.
|
||||||
|
* May throw {@link IndexOutOfBoundsException}.
|
||||||
|
* */
|
||||||
public PlayQueueItem get(int index) {
|
public PlayQueueItem get(int index) {
|
||||||
if (index >= streams.size() || streams.get(index) == null) return null;
|
if (index >= streams.size() || streams.get(index) == null) return null;
|
||||||
return streams.get(index);
|
return streams.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the given item using referential equality.
|
||||||
|
* May be null despite play queue contains identical item.
|
||||||
|
* */
|
||||||
public int indexOf(final PlayQueueItem item) {
|
public int indexOf(final PlayQueueItem item) {
|
||||||
// referential equality, can't think of a better way to do this
|
// referential equality, can't think of a better way to do this
|
||||||
// todo: better than this
|
// todo: better than this
|
||||||
return streams.indexOf(item);
|
return streams.indexOf(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current size of play queue.
|
||||||
|
* */
|
||||||
public int size() {
|
public int size() {
|
||||||
return streams.size();
|
return streams.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the play queue is empty.
|
||||||
|
* */
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return streams.isEmpty();
|
return streams.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an immutable view of the play queue.
|
||||||
|
* */
|
||||||
@NonNull
|
@NonNull
|
||||||
public List<PlayQueueItem> getStreams() {
|
public List<PlayQueueItem> getStreams() {
|
||||||
return Collections.unmodifiableList(streams);
|
return Collections.unmodifiableList(streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the play queue's update broadcast.
|
||||||
|
* May be null if the play queue message bus is not initialized.
|
||||||
|
* */
|
||||||
@NonNull
|
@NonNull
|
||||||
public Flowable<PlayQueueMessage> getBroadcastReceiver() {
|
public Flowable<PlayQueueMessage> getBroadcastReceiver() {
|
||||||
return broadcastReceiver;
|
return broadcastReceiver;
|
||||||
|
@ -130,6 +181,13 @@ public abstract class PlayQueue implements Serializable {
|
||||||
// Write ops
|
// Write ops
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the current playing index to a new index.
|
||||||
|
*
|
||||||
|
* This method is guarded using in a circular manner for index exceeding the play queue size.
|
||||||
|
*
|
||||||
|
* Will emit a {@link SelectEvent} if the index is not the current playing index.
|
||||||
|
* */
|
||||||
public synchronized void setIndex(final int index) {
|
public synchronized void setIndex(final int index) {
|
||||||
if (index == getIndex()) return;
|
if (index == getIndex()) return;
|
||||||
|
|
||||||
|
@ -141,34 +199,65 @@ public abstract class PlayQueue implements Serializable {
|
||||||
indexEventBroadcast.onNext(new SelectEvent(newIndex));
|
indexEventBroadcast.onNext(new SelectEvent(newIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the current playing index by an offset amount.
|
||||||
|
*
|
||||||
|
* Will emit a {@link SelectEvent} if offset is non-zero.
|
||||||
|
* */
|
||||||
public synchronized void offsetIndex(final int offset) {
|
public synchronized void offsetIndex(final int offset) {
|
||||||
setIndex(getIndex() + offset);
|
setIndex(getIndex() + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
||||||
|
*
|
||||||
|
* Will emit a {@link AppendEvent} on any given context.
|
||||||
|
* */
|
||||||
protected synchronized void append(final PlayQueueItem... items) {
|
protected synchronized void append(final PlayQueueItem... items) {
|
||||||
streams.addAll(Arrays.asList(items));
|
streams.addAll(Arrays.asList(items));
|
||||||
broadcast(new AppendEvent(items.length));
|
broadcast(new AppendEvent(items.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
||||||
|
*
|
||||||
|
* Will emit a {@link AppendEvent} on any given context.
|
||||||
|
* */
|
||||||
protected synchronized void append(final Collection<PlayQueueItem> items) {
|
protected synchronized void append(final Collection<PlayQueueItem> items) {
|
||||||
streams.addAll(items);
|
streams.addAll(items);
|
||||||
broadcast(new AppendEvent(items.size()));
|
broadcast(new AppendEvent(items.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the item at the given index from the play queue.
|
||||||
|
*
|
||||||
|
* The current playing index will decrement if greater than or equal to the index being removed.
|
||||||
|
*
|
||||||
|
* Will emit a {@link RemoveEvent} if the index is within the play queue index range.
|
||||||
|
*
|
||||||
|
* */
|
||||||
public synchronized void remove(final int index) {
|
public synchronized void remove(final int index) {
|
||||||
if (index >= streams.size() || index < 0) return;
|
if (index >= streams.size() || index < 0) return;
|
||||||
|
|
||||||
final boolean isCurrent = index == getIndex();
|
final boolean isCurrent = index == getIndex();
|
||||||
|
|
||||||
streams.remove(index);
|
if (queueIndex.get() >= index) {
|
||||||
// Nudge the index if it becomes larger than the queue size
|
queueIndex.decrementAndGet();
|
||||||
if (queueIndex.get() > size()) {
|
|
||||||
queueIndex.set(size() - 1);
|
|
||||||
}
|
}
|
||||||
|
streams.remove(index);
|
||||||
|
|
||||||
broadcast(new RemoveEvent(index, isCurrent));
|
broadcast(new RemoveEvent(index, isCurrent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuffles the current play queue.
|
||||||
|
*
|
||||||
|
* This method first backs up the existing play queue and item being played.
|
||||||
|
* Then a newly shuffled play queue will be generated along with the index of
|
||||||
|
* the previously playing item.
|
||||||
|
*
|
||||||
|
* Will emit a {@link ReorderEvent} in any context.
|
||||||
|
* */
|
||||||
public synchronized void shuffle() {
|
public synchronized void shuffle() {
|
||||||
backup = new ArrayList<>(streams);
|
backup = new ArrayList<>(streams);
|
||||||
final PlayQueueItem current = getCurrent();
|
final PlayQueueItem current = getCurrent();
|
||||||
|
@ -178,6 +267,13 @@ public abstract class PlayQueue implements Serializable {
|
||||||
broadcast(new ReorderEvent(true));
|
broadcast(new ReorderEvent(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unshuffles the current play queue if a backup play queue exists.
|
||||||
|
*
|
||||||
|
* This method undoes shuffling and index will be set to the previously playing item.
|
||||||
|
*
|
||||||
|
* Will emit a {@link ReorderEvent} if a backup exists.
|
||||||
|
* */
|
||||||
public synchronized void unshuffle() {
|
public synchronized void unshuffle() {
|
||||||
if (backup == null) return;
|
if (backup == null) return;
|
||||||
final PlayQueueItem current = getCurrent();
|
final PlayQueueItem current = getCurrent();
|
||||||
|
@ -218,7 +314,7 @@ public abstract class PlayQueue implements Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete() {
|
public void onComplete() {
|
||||||
Log.d(TAG, "Broadcast is shut down.");
|
Log.d(TAG, "Broadcast is shutting down.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
public final class SinglePlayQueue extends PlayQueue {
|
public final class SinglePlayQueue extends PlayQueue {
|
||||||
public static final String STREAM = "stream";
|
|
||||||
|
|
||||||
public SinglePlayQueue(final StreamInfo info) {
|
public SinglePlayQueue(final StreamInfo info) {
|
||||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue