2017-08-29 17:00:11 +02:00
|
|
|
package org.schabi.newpipe.playlist;
|
|
|
|
|
|
|
|
import android.support.annotation.NonNull;
|
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;
|
|
|
|
import org.schabi.newpipe.playlist.events.AppendEvent;
|
|
|
|
import org.schabi.newpipe.playlist.events.InitEvent;
|
|
|
|
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
|
|
|
|
import org.schabi.newpipe.playlist.events.RemoveEvent;
|
|
|
|
import org.schabi.newpipe.playlist.events.SelectEvent;
|
2017-09-11 02:43:21 +02:00
|
|
|
import org.schabi.newpipe.playlist.events.UpdateEvent;
|
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-08-31 19:07:18 +02:00
|
|
|
import java.util.Collection;
|
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-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
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
public static final boolean DEBUG = true;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
private final ArrayList<PlayQueueItem> streams;
|
2017-09-03 04:30:34 +02:00
|
|
|
private final AtomicInteger queueIndex;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-09-06 08:49:00 +02:00
|
|
|
private transient BehaviorSubject<PlayQueueMessage> streamsEventBroadcast;
|
|
|
|
private transient BehaviorSubject<PlayQueueMessage> indexEventBroadcast;
|
2017-09-05 21:27:12 +02:00
|
|
|
private transient Flowable<PlayQueueMessage> broadcastReceiver;
|
|
|
|
private transient Subscription reportingReactor;
|
2017-08-29 17:00:11 +02:00
|
|
|
|
2017-09-01 21:10:36 +02:00
|
|
|
PlayQueue() {
|
|
|
|
this(0, Collections.<PlayQueueItem>emptyList());
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2017-09-04 20:05:13 +02:00
|
|
|
queueIndex = new AtomicInteger(index);
|
2017-09-05 21:27:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Playlist actions
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
2017-08-31 19:07:18 +02:00
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
public void init() {
|
2017-09-06 08:49:00 +02:00
|
|
|
streamsEventBroadcast = BehaviorSubject.create();
|
|
|
|
indexEventBroadcast = BehaviorSubject.create();
|
|
|
|
|
|
|
|
broadcastReceiver = Flowable.merge(
|
|
|
|
streamsEventBroadcast.toFlowable(BackpressureStrategy.BUFFER),
|
2017-09-10 01:11:45 +02:00
|
|
|
indexEventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
|
2017-09-14 20:02:18 +02:00
|
|
|
).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-05 21:27:12 +02:00
|
|
|
public void dispose() {
|
2017-09-06 08:49:00 +02:00
|
|
|
streamsEventBroadcast.onComplete();
|
2017-09-05 21:27:12 +02:00
|
|
|
|
|
|
|
if (reportingReactor != null) reportingReactor.cancel();
|
|
|
|
reportingReactor = null;
|
|
|
|
}
|
2017-09-04 20:05:13 +02:00
|
|
|
|
2017-08-31 19:07:18 +02:00
|
|
|
// 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 partial queue in the background, does nothing if the queue is complete
|
|
|
|
public abstract void fetch();
|
|
|
|
|
2017-09-04 20:05:13 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Readonly ops
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
public int getIndex() {
|
|
|
|
return queueIndex.get();
|
|
|
|
}
|
|
|
|
|
2017-09-03 04:30:34 +02:00
|
|
|
public PlayQueueItem getCurrent() {
|
2017-09-05 21:27:12 +02:00
|
|
|
return get(getIndex());
|
|
|
|
}
|
|
|
|
|
|
|
|
public PlayQueueItem get(int index) {
|
|
|
|
if (index >= streams.size() || streams.get(index) == null) return null;
|
|
|
|
return streams.get(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
public int indexOf(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-08-31 19:07:18 +02:00
|
|
|
public int size() {
|
|
|
|
return streams.size();
|
2017-08-29 17:00:11 +02:00
|
|
|
}
|
|
|
|
|
2017-09-05 00:38:58 +02:00
|
|
|
public boolean isEmpty() {
|
|
|
|
return streams.isEmpty();
|
|
|
|
}
|
|
|
|
|
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-08-31 19:07:18 +02:00
|
|
|
@NonNull
|
2017-09-03 04:30:34 +02:00
|
|
|
public Flowable<PlayQueueMessage> getBroadcastReceiver() {
|
|
|
|
return broadcastReceiver;
|
2017-08-29 17:00:11 +02:00
|
|
|
}
|
|
|
|
|
2017-09-04 20:05:13 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Write ops
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-09-08 16:52:38 +02:00
|
|
|
public synchronized void setIndex(final int index) {
|
2017-09-06 08:49:00 +02:00
|
|
|
if (index < 0 || index >= streams.size()) return;
|
|
|
|
|
2017-09-03 04:30:34 +02:00
|
|
|
queueIndex.set(Math.min(Math.max(0, index), streams.size() - 1));
|
2017-09-06 08:49:00 +02:00
|
|
|
indexEventBroadcast.onNext(new SelectEvent(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
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-11 02:43:21 +02:00
|
|
|
public synchronized void updateIndex(final int index, final int selectedQuality) {
|
|
|
|
if (index < 0 || index >= streams.size()) return;
|
|
|
|
|
|
|
|
get(index).setSortedQualityIndex(selectedQuality);
|
|
|
|
broadcast(new UpdateEvent(index));
|
|
|
|
}
|
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
protected synchronized void append(final PlayQueueItem item) {
|
2017-08-31 19:07:18 +02:00
|
|
|
streams.add(item);
|
2017-09-02 20:06:36 +02:00
|
|
|
broadcast(new AppendEvent(1));
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
protected synchronized void append(final Collection<PlayQueueItem> items) {
|
2017-08-31 19:07:18 +02:00
|
|
|
streams.addAll(items);
|
2017-09-02 20:06:36 +02:00
|
|
|
broadcast(new AppendEvent(items.size()));
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
final boolean isCurrent = index == getIndex();
|
2017-09-01 02:47:56 +02:00
|
|
|
|
|
|
|
streams.remove(index);
|
2017-09-04 20:05:13 +02:00
|
|
|
// Nudge the index if it becomes larger than the queue size
|
|
|
|
if (queueIndex.get() > size()) {
|
|
|
|
queueIndex.set(size() - 1);
|
|
|
|
}
|
2017-09-04 19:23:56 +02:00
|
|
|
|
2017-09-06 08:49:00 +02:00
|
|
|
broadcast(new RemoveEvent(index, isCurrent));
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
2017-09-04 20:05:13 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Rx Broadcast
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
private void broadcast(final PlayQueueMessage event) {
|
2017-09-06 08:49:00 +02:00
|
|
|
streamsEventBroadcast.onNext(event);
|
2017-09-04 20:05:13 +02:00
|
|
|
}
|
|
|
|
|
2017-09-02 20:06:36 +02:00
|
|
|
private Subscriber<PlayQueueMessage> getSelfReporter() {
|
|
|
|
return new Subscriber<PlayQueueMessage>() {
|
|
|
|
@Override
|
|
|
|
public void onSubscribe(Subscription s) {
|
|
|
|
if (reportingReactor != null) reportingReactor.cancel();
|
|
|
|
reportingReactor = s;
|
|
|
|
reportingReactor.request(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onNext(PlayQueueMessage event) {
|
|
|
|
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() {
|
|
|
|
Log.d(TAG, "Broadcast is shut down.");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2017-08-29 17:00:11 +02:00
|
|
|
}
|
|
|
|
|