2018-04-21 23:10:01 +02:00
|
|
|
package org.schabi.newpipe.player.playqueue;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2019-10-04 14:59:08 +02:00
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.Nullable;
|
2017-09-02 20:06:36 +02:00
|
|
|
import android.util.Log;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-09-02 20:06:36 +02:00
|
|
|
import org.reactivestreams.Subscriber;
|
|
|
|
import org.reactivestreams.Subscription;
|
2018-08-22 16:04:32 +02:00
|
|
|
import org.schabi.newpipe.BuildConfig;
|
2018-04-21 23:10:01 +02:00
|
|
|
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.InitEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.MoveEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.RecoveryEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
|
|
|
|
import org.schabi.newpipe.player.playqueue.events.SelectEvent;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
import java.io.Serializable;
|
2017-08-29 17:00:11 +02:00
|
|
|
import java.util.ArrayList;
|
2017-09-24 02:02:05 +02:00
|
|
|
import java.util.Arrays;
|
2017-08-29 17:00:11 +02:00
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.List;
|
2017-08-31 19:07:18 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-08-31 19:07:18 +02:00
|
|
|
import io.reactivex.BackpressureStrategy;
|
|
|
|
import io.reactivex.Flowable;
|
2017-09-14 20:02:18 +02:00
|
|
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
2017-08-29 17:00:11 +02:00
|
|
|
import io.reactivex.subjects.BehaviorSubject;
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
* */
|
2017-09-05 21:27:12 +02:00
|
|
|
public abstract class PlayQueue implements Serializable {
|
2017-08-29 17:00:11 +02:00
|
|
|
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
2017-09-06 08:49:00 +02:00
|
|
|
|
2018-08-22 16:04:32 +02:00
|
|
|
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-09-16 06:28:56 +02:00
|
|
|
private ArrayList<PlayQueueItem> backup;
|
|
|
|
private ArrayList<PlayQueueItem> streams;
|
2020-07-12 02:59:47 +02:00
|
|
|
private final ArrayList<PlayQueueItem> history;
|
2018-03-01 08:25:45 +01:00
|
|
|
@NonNull private final AtomicInteger queueIndex;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-10-23 03:58:01 +02:00
|
|
|
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
|
|
|
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
2017-09-05 21:27:12 +02:00
|
|
|
private transient Subscription reportingReactor;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2020-01-06 11:39:01 +01:00
|
|
|
private transient boolean disposed;
|
|
|
|
|
2017-09-01 21:10:36 +02:00
|
|
|
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
2017-09-05 21:27:12 +02:00
|
|
|
streams = new ArrayList<>();
|
2017-09-01 21:10:36 +02:00
|
|
|
streams.addAll(startWith);
|
2020-01-06 11:39:01 +01:00
|
|
|
history = new ArrayList<>();
|
2020-02-05 06:59:30 +01:00
|
|
|
if (streams.size() > index) history.add(streams.get(index));
|
2017-09-01 21:10:36 +02:00
|
|
|
|
2017-09-04 20:05:13 +02:00
|
|
|
queueIndex = new AtomicInteger(index);
|
2020-01-06 11:39:01 +01:00
|
|
|
disposed = false;
|
2017-09-05 21:27:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Playlist actions
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
2017-08-31 19:07:18 +02:00
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Initializes the play queue message buses.
|
|
|
|
*
|
|
|
|
* Also starts a self reporter for logging if debug mode is enabled.
|
|
|
|
* */
|
2017-09-05 21:27:12 +02:00
|
|
|
public void init() {
|
2017-10-08 06:39:34 +02:00
|
|
|
eventBroadcast = BehaviorSubject.create();
|
2017-09-06 08:49:00 +02:00
|
|
|
|
2017-10-08 06:39:34 +02:00
|
|
|
broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
.startWith(new InitEvent());
|
2017-09-02 20:06:36 +02:00
|
|
|
|
2017-09-03 04:30:34 +02:00
|
|
|
if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
2017-10-22 21:43:49 +02:00
|
|
|
* Dispose the play queue by stopping all message buses.
|
2017-09-29 04:37:04 +02:00
|
|
|
* */
|
2017-09-05 21:27:12 +02:00
|
|
|
public void dispose() {
|
2017-10-08 06:39:34 +02:00
|
|
|
if (eventBroadcast != null) eventBroadcast.onComplete();
|
2017-09-05 21:27:12 +02:00
|
|
|
if (reportingReactor != null) reportingReactor.cancel();
|
2017-09-24 22:44:31 +02:00
|
|
|
|
2018-03-01 02:47:12 +01:00
|
|
|
eventBroadcast = null;
|
2017-09-24 22:44:31 +02:00
|
|
|
broadcastReceiver = null;
|
2017-09-05 21:27:12 +02:00
|
|
|
reportingReactor = null;
|
2020-01-06 11:39:01 +01:00
|
|
|
disposed = true;
|
2017-09-05 21:27:12 +02:00
|
|
|
}
|
2017-09-04 20:05:13 +02:00
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
* */
|
2017-08-31 19:07:18 +02:00
|
|
|
public abstract boolean isComplete();
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Load partial queue in the background, does nothing if the queue is complete.
|
|
|
|
* */
|
2017-08-31 19:07:18 +02:00
|
|
|
public abstract void fetch();
|
|
|
|
|
2017-09-04 20:05:13 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Readonly ops
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Returns the current index that should be played.
|
|
|
|
* */
|
2017-09-05 21:27:12 +02:00
|
|
|
public int getIndex() {
|
|
|
|
return queueIndex.get();
|
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Returns the current item that should be played.
|
|
|
|
* */
|
2017-10-08 06:39:34 +02:00
|
|
|
public PlayQueueItem getItem() {
|
|
|
|
return getItem(getIndex());
|
2017-09-05 21:27:12 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Returns the item at the given index.
|
|
|
|
* May throw {@link IndexOutOfBoundsException}.
|
|
|
|
* */
|
2020-07-12 02:59:47 +02:00
|
|
|
public PlayQueueItem getItem(final int index) {
|
2017-11-04 19:30:01 +01:00
|
|
|
if (index < 0 || index >= streams.size() || streams.get(index) == null) return null;
|
2017-09-05 21:27:12 +02:00
|
|
|
return streams.get(index);
|
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Returns the index of the given item using referential equality.
|
|
|
|
* May be null despite play queue contains identical item.
|
|
|
|
* */
|
2018-03-01 08:25:45 +01:00
|
|
|
public int indexOf(@NonNull final PlayQueueItem item) {
|
2017-09-11 02:43:21 +02:00
|
|
|
// referential equality, can't think of a better way to do this
|
2017-09-05 21:27:12 +02:00
|
|
|
// todo: better than this
|
|
|
|
return streams.indexOf(item);
|
2017-09-03 04:30:34 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Returns the current size of play queue.
|
|
|
|
* */
|
2017-08-31 19:07:18 +02:00
|
|
|
public int size() {
|
|
|
|
return streams.size();
|
2017-08-29 17:00:11 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Checks if the play queue is empty.
|
|
|
|
* */
|
2017-09-05 00:38:58 +02:00
|
|
|
public boolean isEmpty() {
|
|
|
|
return streams.isEmpty();
|
|
|
|
}
|
|
|
|
|
2017-10-08 06:39:34 +02:00
|
|
|
/**
|
|
|
|
* Determines if the current play queue is shuffled.
|
|
|
|
* */
|
|
|
|
public boolean isShuffled() {
|
|
|
|
return backup != null;
|
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Returns an immutable view of the play queue.
|
|
|
|
* */
|
2017-08-29 17:00:11 +02:00
|
|
|
@NonNull
|
|
|
|
public List<PlayQueueItem> getStreams() {
|
2017-08-31 19:07:18 +02:00
|
|
|
return Collections.unmodifiableList(streams);
|
2017-08-29 17:00:11 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Returns the play queue's update broadcast.
|
|
|
|
* May be null if the play queue message bus is not initialized.
|
|
|
|
* */
|
2018-02-26 05:12:20 +01:00
|
|
|
@Nullable
|
2017-10-23 03:58:01 +02:00
|
|
|
public Flowable<PlayQueueEvent> getBroadcastReceiver() {
|
2017-09-03 04:30:34 +02:00
|
|
|
return broadcastReceiver;
|
2017-08-29 17:00:11 +02:00
|
|
|
}
|
|
|
|
|
2017-09-04 20:05:13 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Write ops
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
* */
|
2017-09-08 16:52:38 +02:00
|
|
|
public synchronized void setIndex(final int index) {
|
2017-10-08 06:39:34 +02:00
|
|
|
final int oldIndex = getIndex();
|
|
|
|
|
2017-09-18 05:14:02 +02:00
|
|
|
int newIndex = index;
|
|
|
|
if (index < 0) newIndex = 0;
|
|
|
|
if (index >= streams.size()) newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
2020-01-06 11:39:01 +01:00
|
|
|
if (oldIndex != newIndex) history.add(streams.get(newIndex));
|
2017-09-06 08:49:00 +02:00
|
|
|
|
2017-09-18 05:14:02 +02:00
|
|
|
queueIndex.set(newIndex);
|
2017-10-08 06:39:34 +02:00
|
|
|
broadcast(new SelectEvent(oldIndex, newIndex));
|
2017-09-06 08:49:00 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Changes the current playing index by an offset amount.
|
|
|
|
*
|
|
|
|
* Will emit a {@link SelectEvent} if offset is non-zero.
|
|
|
|
* */
|
2017-09-06 08:49:00 +02:00
|
|
|
public synchronized void offsetIndex(final int offset) {
|
|
|
|
setIndex(getIndex() + offset);
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
|
|
|
*
|
2017-10-26 22:42:50 +02:00
|
|
|
* @see #append(List items)
|
2017-09-29 04:37:04 +02:00
|
|
|
* */
|
2018-03-01 08:25:45 +01:00
|
|
|
public synchronized void append(@NonNull final PlayQueueItem... items) {
|
2017-10-26 22:42:50 +02:00
|
|
|
append(Arrays.asList(items));
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
|
|
|
*
|
2017-10-26 22:42:50 +02:00
|
|
|
* If the play queue is shuffled, then append the items to the backup queue as is and
|
|
|
|
* append the shuffle items to the play queue.
|
|
|
|
*
|
2017-09-29 04:37:04 +02:00
|
|
|
* Will emit a {@link AppendEvent} on any given context.
|
|
|
|
* */
|
2018-03-01 08:25:45 +01:00
|
|
|
public synchronized void append(@NonNull final List<PlayQueueItem> items) {
|
2020-07-12 02:59:47 +02:00
|
|
|
final List<PlayQueueItem> itemList = new ArrayList<>(items);
|
2017-10-10 03:20:11 +02:00
|
|
|
|
2017-10-26 22:42:50 +02:00
|
|
|
if (isShuffled()) {
|
|
|
|
backup.addAll(itemList);
|
|
|
|
Collections.shuffle(itemList);
|
|
|
|
}
|
2018-11-11 11:54:49 +01:00
|
|
|
if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued() && !itemList.get(0).isAutoQueued()) {
|
2018-11-24 12:50:57 +01:00
|
|
|
streams.remove(streams.size() - 1);
|
2018-11-11 11:54:49 +01:00
|
|
|
}
|
2018-11-24 12:50:57 +01:00
|
|
|
streams.addAll(itemList);
|
2017-10-26 22:42:50 +02:00
|
|
|
|
|
|
|
broadcast(new AppendEvent(itemList.size()));
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Removes the item at the given index from the play queue.
|
|
|
|
*
|
2017-09-30 05:38:26 +02:00
|
|
|
* The current playing index will decrement if it is greater than the index being removed.
|
|
|
|
* On cases where the current playing index exceeds the playlist range, it is set to 0.
|
2017-09-29 04:37:04 +02:00
|
|
|
*
|
|
|
|
* Will emit a {@link RemoveEvent} if the index is within the play queue index range.
|
|
|
|
* */
|
2017-09-05 21:27:12 +02:00
|
|
|
public synchronized void remove(final int index) {
|
2017-09-06 08:49:00 +02:00
|
|
|
if (index >= streams.size() || index < 0) return;
|
2017-10-08 06:39:34 +02:00
|
|
|
removeInternal(index);
|
2017-10-26 22:42:50 +02:00
|
|
|
broadcast(new RemoveEvent(index, getIndex()));
|
2017-10-08 06:39:34 +02:00
|
|
|
}
|
2017-09-06 08:49:00 +02:00
|
|
|
|
2017-10-08 06:39:34 +02:00
|
|
|
/**
|
2017-10-25 06:47:14 +02:00
|
|
|
* Report an exception for the item at the current index in order and the course of action:
|
|
|
|
* if the error can be skipped or the current item should be removed.
|
2017-10-08 06:39:34 +02:00
|
|
|
*
|
|
|
|
* This is done as a separate event as the underlying manager may have
|
|
|
|
* different implementation regarding exceptions.
|
|
|
|
* */
|
2017-10-25 06:47:14 +02:00
|
|
|
public synchronized void error(final boolean skippable) {
|
2017-10-08 06:39:34 +02:00
|
|
|
final int index = getIndex();
|
2017-10-25 06:47:14 +02:00
|
|
|
|
|
|
|
if (skippable) {
|
|
|
|
queueIndex.incrementAndGet();
|
2020-01-06 11:39:01 +01:00
|
|
|
if (streams.size() > queueIndex.get()) {
|
|
|
|
history.add(streams.get(queueIndex.get()));
|
|
|
|
}
|
2017-10-25 06:47:14 +02:00
|
|
|
} else {
|
|
|
|
removeInternal(index);
|
|
|
|
}
|
|
|
|
|
2017-10-26 22:42:50 +02:00
|
|
|
broadcast(new ErrorEvent(index, getIndex(), skippable));
|
2017-10-08 06:39:34 +02:00
|
|
|
}
|
|
|
|
|
2017-10-26 22:42:50 +02:00
|
|
|
private synchronized void removeInternal(final int removeIndex) {
|
2017-09-30 05:38:26 +02:00
|
|
|
final int currentIndex = queueIndex.get();
|
2017-10-22 21:43:49 +02:00
|
|
|
final int size = size();
|
2017-09-01 02:47:56 +02:00
|
|
|
|
2017-10-26 22:42:50 +02:00
|
|
|
if (currentIndex > removeIndex) {
|
2017-09-29 04:37:04 +02:00
|
|
|
queueIndex.decrementAndGet();
|
2017-10-26 22:42:50 +02:00
|
|
|
|
2017-10-22 21:43:49 +02:00
|
|
|
} else if (currentIndex >= size) {
|
|
|
|
queueIndex.set(currentIndex % (size - 1));
|
2017-10-26 22:42:50 +02:00
|
|
|
|
|
|
|
} else if (currentIndex == removeIndex && currentIndex == size - 1){
|
2017-11-11 23:47:34 +01:00
|
|
|
queueIndex.set(0);
|
2017-09-04 20:05:13 +02:00
|
|
|
}
|
2017-09-04 19:23:56 +02:00
|
|
|
|
2017-10-10 03:20:11 +02:00
|
|
|
if (backup != null) {
|
2020-07-12 02:59:47 +02:00
|
|
|
backup.remove(getItem(removeIndex));
|
2017-10-10 03:20:11 +02:00
|
|
|
}
|
2020-01-06 11:39:01 +01:00
|
|
|
|
|
|
|
history.remove(streams.remove(removeIndex));
|
2020-02-05 06:59:30 +01:00
|
|
|
if (streams.size() > queueIndex.get()) {
|
|
|
|
history.add(streams.get(queueIndex.get()));
|
|
|
|
}
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
2017-10-23 03:58:01 +02:00
|
|
|
/**
|
|
|
|
* Moves a queue item at the source index to the target index.
|
|
|
|
*
|
|
|
|
* If the item being moved is the currently playing, then the current playing index is set
|
|
|
|
* to that of the target.
|
|
|
|
* If the moved item is not the currently playing and moves to an index <b>AFTER</b> the
|
|
|
|
* current playing index, then the current playing index is decremented.
|
|
|
|
* Vice versa if the an item after the currently playing is moved <b>BEFORE</b>.
|
|
|
|
* */
|
2017-10-09 07:43:07 +02:00
|
|
|
public synchronized void move(final int source, final int target) {
|
|
|
|
if (source < 0 || target < 0) return;
|
|
|
|
if (source >= streams.size() || target >= streams.size()) return;
|
|
|
|
|
|
|
|
final int current = getIndex();
|
|
|
|
if (source == current) {
|
|
|
|
queueIndex.set(target);
|
|
|
|
} else if (source < current && target >= current) {
|
|
|
|
queueIndex.decrementAndGet();
|
|
|
|
} else if (source > current && target <= current) {
|
|
|
|
queueIndex.incrementAndGet();
|
|
|
|
}
|
|
|
|
|
2020-07-12 02:59:47 +02:00
|
|
|
final PlayQueueItem playQueueItem = streams.remove(source);
|
2018-11-11 11:54:49 +01:00
|
|
|
playQueueItem.setAutoQueued(false);
|
|
|
|
streams.add(target, playQueueItem);
|
2017-10-09 07:43:07 +02:00
|
|
|
broadcast(new MoveEvent(source, target));
|
|
|
|
}
|
|
|
|
|
2017-10-23 03:58:01 +02:00
|
|
|
/**
|
|
|
|
* Sets the recovery record of the item at the index.
|
|
|
|
*
|
|
|
|
* Broadcasts a recovery event.
|
|
|
|
* */
|
|
|
|
public synchronized void setRecovery(final int index, final long position) {
|
|
|
|
if (index < 0 || index >= streams.size()) return;
|
|
|
|
|
|
|
|
streams.get(index).setRecoveryPosition(position);
|
|
|
|
broadcast(new RecoveryEvent(index, position));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Revoke the recovery record of the item at the index.
|
|
|
|
*
|
|
|
|
* Broadcasts a recovery event.
|
|
|
|
* */
|
|
|
|
public synchronized void unsetRecovery(final int index) {
|
|
|
|
setRecovery(index, PlayQueueItem.RECOVERY_UNSET);
|
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Shuffles the current play queue.
|
|
|
|
*
|
|
|
|
* This method first backs up the existing play queue and item being played.
|
2017-10-22 21:43:49 +02:00
|
|
|
* Then a newly shuffled play queue will be generated along with currently
|
|
|
|
* playing item placed at the beginning of the queue.
|
2017-09-29 04:37:04 +02:00
|
|
|
*
|
|
|
|
* Will emit a {@link ReorderEvent} in any context.
|
|
|
|
* */
|
2017-09-16 06:28:56 +02:00
|
|
|
public synchronized void shuffle() {
|
2017-10-10 03:20:11 +02:00
|
|
|
if (backup == null) {
|
|
|
|
backup = new ArrayList<>(streams);
|
|
|
|
}
|
2018-03-04 05:58:06 +01:00
|
|
|
final int originIndex = getIndex();
|
2017-10-08 06:39:34 +02:00
|
|
|
final PlayQueueItem current = getItem();
|
2017-09-16 06:28:56 +02:00
|
|
|
Collections.shuffle(streams);
|
2017-10-10 21:25:48 +02:00
|
|
|
|
|
|
|
final int newIndex = streams.indexOf(current);
|
|
|
|
if (newIndex != -1) {
|
2017-10-22 21:43:49 +02:00
|
|
|
streams.add(0, streams.remove(newIndex));
|
2017-10-10 21:25:48 +02:00
|
|
|
}
|
2017-10-22 21:43:49 +02:00
|
|
|
queueIndex.set(0);
|
2020-02-05 06:59:30 +01:00
|
|
|
if (streams.size() > 0) {
|
|
|
|
history.add(streams.get(0));
|
|
|
|
}
|
2017-09-16 06:28:56 +02:00
|
|
|
|
2018-03-04 05:58:06 +01:00
|
|
|
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
2017-09-16 06:28:56 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 04:37:04 +02:00
|
|
|
/**
|
|
|
|
* Unshuffles the current play queue if a backup play queue exists.
|
|
|
|
*
|
2017-10-10 21:25:48 +02:00
|
|
|
* This method undoes shuffling and index will be set to the previously playing item if found,
|
|
|
|
* otherwise, the index will reset to 0.
|
2017-09-29 04:37:04 +02:00
|
|
|
*
|
|
|
|
* Will emit a {@link ReorderEvent} if a backup exists.
|
|
|
|
* */
|
2017-09-16 06:28:56 +02:00
|
|
|
public synchronized void unshuffle() {
|
|
|
|
if (backup == null) return;
|
2018-03-04 05:58:06 +01:00
|
|
|
final int originIndex = getIndex();
|
2017-10-08 06:39:34 +02:00
|
|
|
final PlayQueueItem current = getItem();
|
2017-10-10 21:25:48 +02:00
|
|
|
|
2017-09-16 06:28:56 +02:00
|
|
|
streams.clear();
|
|
|
|
streams = backup;
|
2017-10-08 06:39:34 +02:00
|
|
|
backup = null;
|
2017-10-10 21:25:48 +02:00
|
|
|
|
|
|
|
final int newIndex = streams.indexOf(current);
|
|
|
|
if (newIndex != -1) {
|
|
|
|
queueIndex.set(newIndex);
|
|
|
|
} else {
|
|
|
|
queueIndex.set(0);
|
|
|
|
}
|
2020-02-05 06:59:30 +01:00
|
|
|
if (streams.size() > queueIndex.get()) {
|
|
|
|
history.add(streams.get(queueIndex.get()));
|
|
|
|
}
|
2017-09-16 06:28:56 +02:00
|
|
|
|
2018-03-04 05:58:06 +01:00
|
|
|
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
2017-09-16 06:28:56 +02:00
|
|
|
}
|
|
|
|
|
2020-01-06 11:39:01 +01:00
|
|
|
/**
|
|
|
|
* Selects previous played item
|
|
|
|
*
|
|
|
|
* This method removes currently playing item from history and
|
|
|
|
* starts playing the last item from history if it exists
|
|
|
|
*
|
|
|
|
* Returns true if history is not empty and the item can be played
|
|
|
|
* */
|
|
|
|
public synchronized boolean previous() {
|
|
|
|
if (history.size() <= 1) return false;
|
|
|
|
|
|
|
|
history.remove(history.size() - 1);
|
|
|
|
|
2020-07-12 02:59:47 +02:00
|
|
|
final PlayQueueItem last = history.remove(history.size() - 1);
|
2020-01-06 11:39:01 +01:00
|
|
|
setIndex(indexOf(last));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compares two PlayQueues. Useful when a user switches players but queue is the same so
|
|
|
|
* we don't have to do anything with new queue. This method also gives a chance to track history of items in a queue in
|
|
|
|
* VideoDetailFragment without duplicating items from two identical queues
|
|
|
|
* */
|
|
|
|
@Override
|
2020-07-12 02:59:47 +02:00
|
|
|
public boolean equals(@Nullable final Object obj) {
|
2020-01-06 11:39:01 +01:00
|
|
|
if (!(obj instanceof PlayQueue) || getStreams().size() != ((PlayQueue) obj).getStreams().size())
|
|
|
|
return false;
|
|
|
|
|
2020-07-12 02:59:47 +02:00
|
|
|
final PlayQueue other = (PlayQueue) obj;
|
2020-01-06 11:39:01 +01:00
|
|
|
for (int i = 0; i < getStreams().size(); i++) {
|
|
|
|
if (!getItem(i).getUrl().equals(other.getItem(i).getUrl()))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isDisposed() {
|
|
|
|
return disposed;
|
|
|
|
}
|
2017-09-04 20:05:13 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Rx Broadcast
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2018-03-01 08:25:45 +01:00
|
|
|
private void broadcast(@NonNull final PlayQueueEvent event) {
|
2017-10-08 06:39:34 +02:00
|
|
|
if (eventBroadcast != null) {
|
|
|
|
eventBroadcast.onNext(event);
|
|
|
|
}
|
2017-09-04 20:05:13 +02:00
|
|
|
}
|
|
|
|
|
2017-10-23 03:58:01 +02:00
|
|
|
private Subscriber<PlayQueueEvent> getSelfReporter() {
|
|
|
|
return new Subscriber<PlayQueueEvent>() {
|
2017-09-02 20:06:36 +02:00
|
|
|
@Override
|
|
|
|
public void onSubscribe(Subscription s) {
|
|
|
|
if (reportingReactor != null) reportingReactor.cancel();
|
|
|
|
reportingReactor = s;
|
|
|
|
reportingReactor.request(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2017-10-23 03:58:01 +02:00
|
|
|
public void onNext(PlayQueueEvent event) {
|
2017-09-02 20:06:36 +02:00
|
|
|
Log.d(TAG, "Received broadcast: " + event.type().name() + ". Current index: " + getIndex() + ", play queue length: " + size() + ".");
|
|
|
|
reportingReactor.request(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onError(Throwable t) {
|
|
|
|
Log.e(TAG, "Received broadcast error", t);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onComplete() {
|
2017-09-29 04:37:04 +02:00
|
|
|
Log.d(TAG, "Broadcast is shutting down.");
|
2017-09-02 20:06:36 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2017-08-29 17:00:11 +02:00
|
|
|
}
|
|
|
|
|