rename playlist to player.playqueue
This commit is contained in:
parent
8ed9d71e14
commit
bcfd8a2450
47 changed files with 88 additions and 88 deletions
|
|
@ -48,7 +48,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
|
|
|||
|
|
@ -69,9 +69,9 @@ import org.schabi.newpipe.player.playback.BasePlayerMediaSession;
|
|||
import org.schabi.newpipe.player.playback.CustomTrackSelector;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
|||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemTouchCallback;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItemTouchCallback;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import android.util.Log;
|
|||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playqueue.PlayQueue;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ import org.schabi.newpipe.player.event.PlayerEventListener;
|
|||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
|||
import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||
import org.schabi.newpipe.playlist.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemTouchCallback;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItemTouchCallback;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesFormat;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.SinglePlayQueue;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.google.android.exoplayer2.ExoPlayer;
|
|||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
|
|||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.support.annotation.NonNull;
|
|||
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
|
||||
public interface ManagedMediaSource extends MediaSource {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.google.android.exoplayer2.ExoPlayer;
|
|||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import android.support.v4.media.MediaDescriptionCompat;
|
|||
|
||||
import org.schabi.newpipe.player.BasePlayer;
|
||||
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
|
||||
public class BasePlayerMediaSession implements MediaSessionCallback {
|
||||
private BasePlayer player;
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ import org.schabi.newpipe.player.mediasource.LoadedMediaSource;
|
|||
import org.schabi.newpipe.player.mediasource.ManagedMediaSource;
|
||||
import org.schabi.newpipe.player.mediasource.ManagedMediaSourcePlaylist;
|
||||
import org.schabi.newpipe.player.mediasource.PlaceholderMediaSource;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.events.MoveEvent;
|
||||
import org.schabi.newpipe.playlist.events.PlayQueueEvent;
|
||||
import org.schabi.newpipe.playlist.events.RemoveEvent;
|
||||
import org.schabi.newpipe.playlist.events.ReorderEvent;
|
||||
import org.schabi.newpipe.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.events.MoveEvent;
|
||||
import org.schabi.newpipe.playqueue.events.PlayQueueEvent;
|
||||
import org.schabi.newpipe.playqueue.events.RemoveEvent;
|
||||
import org.schabi.newpipe.playqueue.events.ReorderEvent;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -45,7 +45,7 @@ import io.reactivex.subjects.PublishSubject;
|
|||
|
||||
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
|
||||
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;
|
||||
import static org.schabi.newpipe.playlist.PlayQueue.DEBUG;
|
||||
import static org.schabi.newpipe.playqueue.PlayQueue.DEBUG;
|
||||
|
||||
public class MediaSourceManager {
|
||||
@NonNull private final String TAG = "MediaSourceManager@" + hashCode();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
|
|||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playqueue.PlayQueueItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
|
||||
boolean isInitial;
|
||||
boolean isComplete;
|
||||
|
||||
int serviceId;
|
||||
String baseUrl;
|
||||
String nextUrl;
|
||||
|
||||
transient Disposable fetchReactor;
|
||||
|
||||
AbstractInfoPlayQueue(final U item) {
|
||||
this(item.getServiceId(), item.getUrl(), null, Collections.<StreamInfoItem>emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
super(index, extractListItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isInitial = streams.isEmpty();
|
||||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
}
|
||||
|
||||
abstract protected String getTag();
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
SingleObserver<T> getHeadListObserver() {
|
||||
return new SingleObserver<T>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || !isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull T result) {
|
||||
isInitial = false;
|
||||
if (!result.hasNextPage()) isComplete = true;
|
||||
nextUrl = result.getNextPageUrl();
|
||||
|
||||
append(extractListItems(result.getRelatedItems()));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SingleObserver<ListExtractor.InfoItemsPage> getNextPageObserver() {
|
||||
return new SingleObserver<ListExtractor.InfoItemsPage>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull ListExtractor.InfoItemsPage result) {
|
||||
if (!result.hasNextPage()) isComplete = true;
|
||||
nextUrl = result.getNextPageUrl();
|
||||
|
||||
append(extractListItems(result.getItems()));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (fetchReactor != null) fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> extractListItems(final List<StreamInfoItem> infos) {
|
||||
List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
if (stream instanceof StreamInfoItem) {
|
||||
result.add(new PlayQueueItem((StreamInfoItem) stream));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, ChannelInfoItem> {
|
||||
public ChannelPlayQueue(final ChannelInfoItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final ChannelInfo info) {
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTag() {
|
||||
return "ChannelPlayQueue@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
if (this.isInitial) {
|
||||
ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextPageObserver());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,431 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.playqueue.events.AppendEvent;
|
||||
import org.schabi.newpipe.playqueue.events.ErrorEvent;
|
||||
import org.schabi.newpipe.playqueue.events.InitEvent;
|
||||
import org.schabi.newpipe.playqueue.events.MoveEvent;
|
||||
import org.schabi.newpipe.playqueue.events.PlayQueueEvent;
|
||||
import org.schabi.newpipe.playqueue.events.RecoveryEvent;
|
||||
import org.schabi.newpipe.playqueue.events.RemoveEvent;
|
||||
import org.schabi.newpipe.playqueue.events.ReorderEvent;
|
||||
import org.schabi.newpipe.playqueue.events.SelectEvent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
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 {
|
||||
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
private ArrayList<PlayQueueItem> backup;
|
||||
private ArrayList<PlayQueueItem> streams;
|
||||
@NonNull private final AtomicInteger queueIndex;
|
||||
|
||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||
private transient Subscription reportingReactor;
|
||||
|
||||
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
||||
streams = new ArrayList<>();
|
||||
streams.addAll(startWith);
|
||||
|
||||
queueIndex = new AtomicInteger(index);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playlist actions
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Initializes the play queue message buses.
|
||||
*
|
||||
* Also starts a self reporter for logging if debug mode is enabled.
|
||||
* */
|
||||
public void init() {
|
||||
eventBroadcast = BehaviorSubject.create();
|
||||
|
||||
broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.startWith(new InitEvent());
|
||||
|
||||
if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the play queue by stopping all message buses.
|
||||
* */
|
||||
public void dispose() {
|
||||
if (eventBroadcast != null) eventBroadcast.onComplete();
|
||||
if (reportingReactor != null) reportingReactor.cancel();
|
||||
|
||||
eventBroadcast = null;
|
||||
broadcastReceiver = null;
|
||||
reportingReactor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Load partial queue in the background, does nothing if the queue is complete.
|
||||
* */
|
||||
public abstract void fetch();
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Readonly ops
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Returns the current index that should be played.
|
||||
* */
|
||||
public int getIndex() {
|
||||
return queueIndex.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current item that should be played.
|
||||
* */
|
||||
public PlayQueueItem getItem() {
|
||||
return getItem(getIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item at the given index.
|
||||
* May throw {@link IndexOutOfBoundsException}.
|
||||
* */
|
||||
public PlayQueueItem getItem(int index) {
|
||||
if (index < 0 || index >= streams.size() || streams.get(index) == null) return null;
|
||||
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(@NonNull final PlayQueueItem item) {
|
||||
// referential equality, can't think of a better way to do this
|
||||
// todo: better than this
|
||||
return streams.indexOf(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current size of play queue.
|
||||
* */
|
||||
public int size() {
|
||||
return streams.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the play queue is empty.
|
||||
* */
|
||||
public boolean isEmpty() {
|
||||
return streams.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current play queue is shuffled.
|
||||
* */
|
||||
public boolean isShuffled() {
|
||||
return backup != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable view of the play queue.
|
||||
* */
|
||||
@NonNull
|
||||
public List<PlayQueueItem> getStreams() {
|
||||
return Collections.unmodifiableList(streams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the play queue's update broadcast.
|
||||
* May be null if the play queue message bus is not initialized.
|
||||
* */
|
||||
@Nullable
|
||||
public Flowable<PlayQueueEvent> getBroadcastReceiver() {
|
||||
return broadcastReceiver;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// 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) {
|
||||
final int oldIndex = getIndex();
|
||||
|
||||
int newIndex = index;
|
||||
if (index < 0) newIndex = 0;
|
||||
if (index >= streams.size()) newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
||||
|
||||
queueIndex.set(newIndex);
|
||||
broadcast(new SelectEvent(oldIndex, 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) {
|
||||
setIndex(getIndex() + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
||||
*
|
||||
* @see #append(List items)
|
||||
* */
|
||||
public synchronized void append(@NonNull final PlayQueueItem... items) {
|
||||
append(Arrays.asList(items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Will emit a {@link AppendEvent} on any given context.
|
||||
* */
|
||||
public synchronized void append(@NonNull final List<PlayQueueItem> items) {
|
||||
List<PlayQueueItem> itemList = new ArrayList<>(items);
|
||||
|
||||
if (isShuffled()) {
|
||||
backup.addAll(itemList);
|
||||
Collections.shuffle(itemList);
|
||||
}
|
||||
streams.addAll(itemList);
|
||||
|
||||
broadcast(new AppendEvent(itemList.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the item at the given index from the play queue.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Will emit a {@link RemoveEvent} if the index is within the play queue index range.
|
||||
* */
|
||||
public synchronized void remove(final int index) {
|
||||
if (index >= streams.size() || index < 0) return;
|
||||
removeInternal(index);
|
||||
broadcast(new RemoveEvent(index, getIndex()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This is done as a separate event as the underlying manager may have
|
||||
* different implementation regarding exceptions.
|
||||
* */
|
||||
public synchronized void error(final boolean skippable) {
|
||||
final int index = getIndex();
|
||||
|
||||
if (skippable) {
|
||||
queueIndex.incrementAndGet();
|
||||
} else {
|
||||
removeInternal(index);
|
||||
}
|
||||
|
||||
broadcast(new ErrorEvent(index, getIndex(), skippable));
|
||||
}
|
||||
|
||||
private synchronized void removeInternal(final int removeIndex) {
|
||||
final int currentIndex = queueIndex.get();
|
||||
final int size = size();
|
||||
|
||||
if (currentIndex > removeIndex) {
|
||||
queueIndex.decrementAndGet();
|
||||
|
||||
} else if (currentIndex >= size) {
|
||||
queueIndex.set(currentIndex % (size - 1));
|
||||
|
||||
} else if (currentIndex == removeIndex && currentIndex == size - 1){
|
||||
queueIndex.set(0);
|
||||
}
|
||||
|
||||
if (backup != null) {
|
||||
final int backupIndex = backup.indexOf(getItem(removeIndex));
|
||||
backup.remove(backupIndex);
|
||||
}
|
||||
streams.remove(removeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>.
|
||||
* */
|
||||
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();
|
||||
}
|
||||
|
||||
streams.add(target, streams.remove(source));
|
||||
broadcast(new MoveEvent(source, target));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 currently
|
||||
* playing item placed at the beginning of the queue.
|
||||
*
|
||||
* Will emit a {@link ReorderEvent} in any context.
|
||||
* */
|
||||
public synchronized void shuffle() {
|
||||
if (backup == null) {
|
||||
backup = new ArrayList<>(streams);
|
||||
}
|
||||
final int originIndex = getIndex();
|
||||
final PlayQueueItem current = getItem();
|
||||
Collections.shuffle(streams);
|
||||
|
||||
final int newIndex = streams.indexOf(current);
|
||||
if (newIndex != -1) {
|
||||
streams.add(0, streams.remove(newIndex));
|
||||
}
|
||||
queueIndex.set(0);
|
||||
|
||||
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 if found,
|
||||
* otherwise, the index will reset to 0.
|
||||
*
|
||||
* Will emit a {@link ReorderEvent} if a backup exists.
|
||||
* */
|
||||
public synchronized void unshuffle() {
|
||||
if (backup == null) return;
|
||||
final int originIndex = getIndex();
|
||||
final PlayQueueItem current = getItem();
|
||||
|
||||
streams.clear();
|
||||
streams = backup;
|
||||
backup = null;
|
||||
|
||||
final int newIndex = streams.indexOf(current);
|
||||
if (newIndex != -1) {
|
||||
queueIndex.set(newIndex);
|
||||
} else {
|
||||
queueIndex.set(0);
|
||||
}
|
||||
|
||||
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Rx Broadcast
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void broadcast(@NonNull final PlayQueueEvent event) {
|
||||
if (eventBroadcast != null) {
|
||||
eventBroadcast.onNext(event);
|
||||
}
|
||||
}
|
||||
|
||||
private Subscriber<PlayQueueEvent> getSelfReporter() {
|
||||
return new Subscriber<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
if (reportingReactor != null) reportingReactor.cancel();
|
||||
reportingReactor = s;
|
||||
reportingReactor.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(PlayQueueEvent 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 shutting down.");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.playqueue.events.AppendEvent;
|
||||
import org.schabi.newpipe.playqueue.events.ErrorEvent;
|
||||
import org.schabi.newpipe.playqueue.events.MoveEvent;
|
||||
import org.schabi.newpipe.playqueue.events.PlayQueueEvent;
|
||||
import org.schabi.newpipe.playqueue.events.RemoveEvent;
|
||||
import org.schabi.newpipe.playqueue.events.SelectEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* InfoListAdapter.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private static final String TAG = PlayQueueAdapter.class.toString();
|
||||
|
||||
private static final int ITEM_VIEW_TYPE_ID = 0;
|
||||
private static final int FOOTER_VIEW_TYPE_ID = 1;
|
||||
|
||||
private final PlayQueueItemBuilder playQueueItemBuilder;
|
||||
private final PlayQueue playQueue;
|
||||
private boolean showFooter = false;
|
||||
private View footer = null;
|
||||
|
||||
private Disposable playQueueReactor;
|
||||
|
||||
public class HFHolder extends RecyclerView.ViewHolder {
|
||||
public HFHolder(View v) {
|
||||
super(v);
|
||||
view = v;
|
||||
}
|
||||
public View view;
|
||||
}
|
||||
|
||||
public PlayQueueAdapter(final Context context, final PlayQueue playQueue) {
|
||||
if (playQueue.getBroadcastReceiver() == null) {
|
||||
throw new IllegalStateException("Play Queue has not been initialized.");
|
||||
}
|
||||
|
||||
this.playQueueItemBuilder = new PlayQueueItemBuilder(context);
|
||||
this.playQueue = playQueue;
|
||||
|
||||
playQueue.getBroadcastReceiver().toObservable().subscribe(getReactor());
|
||||
}
|
||||
|
||||
private Observer<PlayQueueEvent> getReactor() {
|
||||
return new Observer<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (playQueueReactor != null) playQueueReactor.dispose();
|
||||
playQueueReactor = d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull PlayQueueEvent playQueueMessage) {
|
||||
if (playQueueReactor != null) onPlayQueueChanged(playQueueMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueEvent message) {
|
||||
switch (message.type()) {
|
||||
case RECOVERY:
|
||||
// Do nothing.
|
||||
break;
|
||||
case SELECT:
|
||||
final SelectEvent selectEvent = (SelectEvent) message;
|
||||
notifyItemChanged(selectEvent.getOldIndex());
|
||||
notifyItemChanged(selectEvent.getNewIndex());
|
||||
break;
|
||||
case APPEND:
|
||||
final AppendEvent appendEvent = (AppendEvent) message;
|
||||
notifyItemRangeInserted(playQueue.size(), appendEvent.getAmount());
|
||||
break;
|
||||
case ERROR:
|
||||
final ErrorEvent errorEvent = (ErrorEvent) message;
|
||||
if (!errorEvent.isSkippable()) {
|
||||
notifyItemRemoved(errorEvent.getErrorIndex());
|
||||
}
|
||||
notifyItemChanged(errorEvent.getErrorIndex());
|
||||
notifyItemChanged(errorEvent.getQueueIndex());
|
||||
break;
|
||||
case REMOVE:
|
||||
final RemoveEvent removeEvent = (RemoveEvent) message;
|
||||
notifyItemRemoved(removeEvent.getRemoveIndex());
|
||||
notifyItemChanged(removeEvent.getQueueIndex());
|
||||
break;
|
||||
case MOVE:
|
||||
final MoveEvent moveEvent = (MoveEvent) message;
|
||||
notifyItemMoved(moveEvent.getFromIndex(), moveEvent.getToIndex());
|
||||
break;
|
||||
case INIT:
|
||||
case REORDER:
|
||||
default:
|
||||
notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (playQueueReactor != null) playQueueReactor.dispose();
|
||||
playQueueReactor = null;
|
||||
}
|
||||
|
||||
public void setSelectedListener(final PlayQueueItemBuilder.OnSelectedListener listener) {
|
||||
playQueueItemBuilder.setOnSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void unsetSelectedListener() {
|
||||
playQueueItemBuilder.setOnSelectedListener(null);
|
||||
}
|
||||
|
||||
public void setFooter(View footer) {
|
||||
this.footer = footer;
|
||||
notifyItemChanged(playQueue.size());
|
||||
}
|
||||
|
||||
public void showFooter(final boolean show) {
|
||||
showFooter = show;
|
||||
notifyItemChanged(playQueue.size());
|
||||
}
|
||||
|
||||
public List<PlayQueueItem> getItems() {
|
||||
return playQueue.getStreams();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int count = playQueue.getStreams().size();
|
||||
if(footer != null && showFooter) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if(footer != null && position == playQueue.getStreams().size() && showFooter) {
|
||||
return FOOTER_VIEW_TYPE_ID;
|
||||
}
|
||||
|
||||
return ITEM_VIEW_TYPE_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||
switch(type) {
|
||||
case FOOTER_VIEW_TYPE_ID:
|
||||
return new HFHolder(footer);
|
||||
case ITEM_VIEW_TYPE_ID:
|
||||
return new PlayQueueItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.play_queue_item, parent, false));
|
||||
default:
|
||||
Log.e(TAG, "Attempting to create view holder with undefined type: " + type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
if(holder instanceof PlayQueueItemHolder) {
|
||||
final PlayQueueItemHolder itemHolder = (PlayQueueItemHolder) holder;
|
||||
|
||||
// Build the list item
|
||||
playQueueItemBuilder.buildStreamInfoItem(itemHolder, playQueue.getStreams().get(position));
|
||||
|
||||
// Check if the current item should be selected/highlighted
|
||||
final boolean isSelected = playQueue.getIndex() == position;
|
||||
itemHolder.itemSelected.setVisibility(isSelected ? View.VISIBLE : View.INVISIBLE);
|
||||
itemHolder.itemView.setSelected(isSelected);
|
||||
} else if(holder instanceof HFHolder && position == playQueue.getStreams().size() && footer != null && showFooter) {
|
||||
((HFHolder) holder).view = footer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class PlayQueueItem implements Serializable {
|
||||
public final static long RECOVERY_UNSET = Long.MIN_VALUE;
|
||||
private final static String EMPTY_STRING = "";
|
||||
|
||||
@NonNull final private String title;
|
||||
@NonNull final private String url;
|
||||
final private int serviceId;
|
||||
final private long duration;
|
||||
@NonNull final private String thumbnailUrl;
|
||||
@NonNull final private String uploader;
|
||||
@NonNull final private StreamType streamType;
|
||||
|
||||
private long recoveryPosition;
|
||||
private Throwable error;
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfo info) {
|
||||
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
|
||||
info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType());
|
||||
|
||||
if (info.getStartPosition() > 0)
|
||||
setRecoveryPosition(info.getStartPosition() * 1000);
|
||||
}
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfoItem item) {
|
||||
this(item.getName(), item.getUrl(), item.getServiceId(), item.getDuration(),
|
||||
item.getThumbnailUrl(), item.getUploaderName(), item.getStreamType());
|
||||
}
|
||||
|
||||
private PlayQueueItem(@Nullable final String name, @Nullable final String url,
|
||||
final int serviceId, final long duration,
|
||||
@Nullable final String thumbnailUrl, @Nullable final String uploader,
|
||||
@NonNull final StreamType streamType) {
|
||||
this.title = name != null ? name : EMPTY_STRING;
|
||||
this.url = url != null ? url : EMPTY_STRING;
|
||||
this.serviceId = serviceId;
|
||||
this.duration = duration;
|
||||
this.thumbnailUrl = thumbnailUrl != null ? thumbnailUrl : EMPTY_STRING;
|
||||
this.uploader = uploader != null ? uploader : EMPTY_STRING;
|
||||
this.streamType = streamType;
|
||||
|
||||
this.recoveryPosition = RECOVERY_UNSET;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public int getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public StreamType getStreamType() {
|
||||
return streamType;
|
||||
}
|
||||
|
||||
public long getRecoveryPosition() {
|
||||
return recoveryPosition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Single<StreamInfo> getStream() {
|
||||
return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.doOnError(throwable -> error = throwable);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Item States, keep external access out
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*package-private*/ void setRecoveryPosition(final long recoveryPosition) {
|
||||
this.recoveryPosition = recoveryPosition;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
public class PlayQueueItemBuilder {
|
||||
|
||||
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
||||
|
||||
public interface OnSelectedListener {
|
||||
void selected(PlayQueueItem item, View view);
|
||||
void held(PlayQueueItem item, View view);
|
||||
void onStartDrag(PlayQueueItemHolder viewHolder);
|
||||
}
|
||||
|
||||
private OnSelectedListener onItemClickListener;
|
||||
|
||||
public PlayQueueItemBuilder(final Context context) {}
|
||||
|
||||
public void setOnSelectedListener(OnSelectedListener listener) {
|
||||
this.onItemClickListener = listener;
|
||||
}
|
||||
|
||||
public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueueItem item) {
|
||||
if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle());
|
||||
holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(),
|
||||
NewPipe.getNameOfService(item.getServiceId())));
|
||||
|
||||
if (item.getDuration() > 0) {
|
||||
holder.itemDurationView.setText(Localization.getDurationString(item.getDuration()));
|
||||
} else {
|
||||
holder.itemDurationView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
holder.itemRoot.setOnClickListener(view -> {
|
||||
if (onItemClickListener != null) {
|
||||
onItemClickListener.selected(item, view);
|
||||
}
|
||||
});
|
||||
|
||||
holder.itemRoot.setOnLongClickListener(view -> {
|
||||
if (onItemClickListener != null) {
|
||||
onItemClickListener.held(item, view);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
holder.itemThumbnailView.setOnTouchListener(getOnTouchListener(holder));
|
||||
holder.itemHandle.setOnTouchListener(getOnTouchListener(holder));
|
||||
}
|
||||
|
||||
private View.OnTouchListener getOnTouchListener(final PlayQueueItemHolder holder) {
|
||||
return (view, motionEvent) -> {
|
||||
view.performClick();
|
||||
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN
|
||||
&& onItemClickListener != null) {
|
||||
onItemClickListener.onStartDrag(holder);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
* <p>
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* StreamInfoItemHolder.java is part of NewPipe.
|
||||
* <p>
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class PlayQueueItemHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final TextView itemVideoTitleView, itemDurationView, itemAdditionalDetailsView;
|
||||
public final ImageView itemSelected, itemThumbnailView, itemHandle;
|
||||
|
||||
public final View itemRoot;
|
||||
|
||||
public PlayQueueItemHolder(View v) {
|
||||
super(v);
|
||||
itemRoot = v.findViewById(R.id.itemRoot);
|
||||
itemVideoTitleView = v.findViewById(R.id.itemVideoTitleView);
|
||||
itemDurationView = v.findViewById(R.id.itemDurationView);
|
||||
itemAdditionalDetailsView = v.findViewById(R.id.itemAdditionalDetails);
|
||||
itemSelected = v.findViewById(R.id.itemSelected);
|
||||
itemThumbnailView = v.findViewById(R.id.itemThumbnailView);
|
||||
itemHandle = v.findViewById(R.id.itemHandle);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
|
||||
public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleCallback {
|
||||
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 10;
|
||||
private static final int MAXIMUM_INITIAL_DRAG_VELOCITY = 25;
|
||||
|
||||
public PlayQueueItemTouchCallback() {
|
||||
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
|
||||
}
|
||||
|
||||
public abstract void onMove(final int sourceIndex, final int targetIndex);
|
||||
|
||||
@Override
|
||||
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
|
||||
int viewSizeOutOfBounds, int totalSize,
|
||||
long msSinceStartScroll) {
|
||||
final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize,
|
||||
viewSizeOutOfBounds, totalSize, msSinceStartScroll);
|
||||
final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY,
|
||||
Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY));
|
||||
return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
|
||||
RecyclerView.ViewHolder target) {
|
||||
if (source.getItemViewType() != target.getItemViewType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int sourceIndex = source.getLayoutPosition();
|
||||
final int targetIndex = target.getLayoutPosition();
|
||||
onMove(sourceIndex, targetIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemViewSwipeEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo, PlaylistInfoItem> {
|
||||
public PlaylistPlayQueue(final PlaylistInfoItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
public PlaylistPlayQueue(final PlaylistInfo info) {
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
|
||||
}
|
||||
|
||||
public PlaylistPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTag() {
|
||||
return "PlaylistPlayQueue@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
if (this.isInitial) {
|
||||
ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextPageObserver());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class SinglePlayQueue extends PlayQueue {
|
||||
public SinglePlayQueue(final StreamInfoItem item) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(item)));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final StreamInfo info) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final List<StreamInfoItem> items, final int index) {
|
||||
super(index, playQueueItemsOf(items));
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> playQueueItemsOf(List<StreamInfoItem> items) {
|
||||
List<PlayQueueItem> playQueueItems = new ArrayList<>(items.size());
|
||||
for (final StreamInfoItem item : items) {
|
||||
playQueueItems.add(new PlayQueueItem(item));
|
||||
}
|
||||
return playQueueItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
package org.schabi.newpipe.playqueue;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public abstract class StatisticsPlaylistFragment
|
||||
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
|
||||
|
||||
private View headerPlayAllButton;
|
||||
private View headerPopupButton;
|
||||
private View headerBackgroundButton;
|
||||
|
||||
@State
|
||||
protected Parcelable itemsListState;
|
||||
|
||||
/* Used for independent events */
|
||||
private Subscription databaseSubscription;
|
||||
private HistoryRecordManager recordManager;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Abstracts
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected abstract String getName();
|
||||
|
||||
protected abstract List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Creation
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
recordManager = new HistoryRecordManager(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Views
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
setTitle(getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getListHeader() {
|
||||
final View headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_control,
|
||||
itemsList, false);
|
||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
|
||||
return headerRootLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||
@Override
|
||||
public void selected(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof StreamStatisticsEntry) {
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
|
||||
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
|
||||
item.serviceId, item.url, item.title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof StreamStatisticsEntry) {
|
||||
showStreamDialog((StreamStatisticsEntry) selectedItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Loading
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void startLoading(boolean forceLoad) {
|
||||
super.startLoading(forceLoad);
|
||||
recordManager.getStreamStatistics()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHistoryObserver());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Destruction
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (itemListAdapter != null) itemListAdapter.unsetSelectedListener();
|
||||
if (headerBackgroundButton != null) headerBackgroundButton.setOnClickListener(null);
|
||||
if (headerPlayAllButton != null) headerPlayAllButton.setOnClickListener(null);
|
||||
if (headerPopupButton != null) headerPopupButton.setOnClickListener(null);
|
||||
|
||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||
databaseSubscription = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
recordManager = null;
|
||||
itemsListState = null;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Statistics Loader
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Subscriber<List<StreamStatisticsEntry>> getHistoryObserver() {
|
||||
return new Subscriber<List<StreamStatisticsEntry>>() {
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
showLoading();
|
||||
|
||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||
databaseSubscription = s;
|
||||
databaseSubscription.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(List<StreamStatisticsEntry> streams) {
|
||||
handleResult(streams);
|
||||
if (databaseSubscription != null) databaseSubscription.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable exception) {
|
||||
StatisticsPlaylistFragment.this.onError(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull List<StreamStatisticsEntry> result) {
|
||||
super.handleResult(result);
|
||||
if (itemListAdapter == null) return;
|
||||
|
||||
itemListAdapter.clearStreamItemList();
|
||||
|
||||
if (result.isEmpty()) {
|
||||
showEmptyState();
|
||||
return;
|
||||
}
|
||||
|
||||
itemListAdapter.addItems(processResult(result));
|
||||
if (itemsListState != null) {
|
||||
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||
itemsListState = null;
|
||||
}
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||
|
||||
hideLoading();
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment Error Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void resetFragment() {
|
||||
super.resetFragment();
|
||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
||||
"none", "History Statistics", R.string.general_error);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void showStreamDialog(final StreamStatisticsEntry item) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 3:
|
||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), infoItem, commands, actions).show();
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
return getPlayQueue(0);
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
if (itemListAdapter == null) {
|
||||
return new SinglePlayQueue(Collections.emptyList(), 0);
|
||||
}
|
||||
|
||||
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
||||
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||
for (final LocalItem item : infoItems) {
|
||||
if (item instanceof StreamStatisticsEntry) {
|
||||
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());
|
||||
}
|
||||
}
|
||||
return new SinglePlayQueue(streamInfoItems, index);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
|
||||
public class AppendEvent implements PlayQueueEvent {
|
||||
final private int amount;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.APPEND;
|
||||
}
|
||||
|
||||
public AppendEvent(final int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
|
||||
public class ErrorEvent implements PlayQueueEvent {
|
||||
final private int errorIndex;
|
||||
final private int queueIndex;
|
||||
final private boolean skippable;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.ERROR;
|
||||
}
|
||||
|
||||
public ErrorEvent(final int errorIndex, final int queueIndex, final boolean skippable) {
|
||||
this.errorIndex = errorIndex;
|
||||
this.queueIndex = queueIndex;
|
||||
this.skippable = skippable;
|
||||
}
|
||||
|
||||
public int getErrorIndex() {
|
||||
return errorIndex;
|
||||
}
|
||||
|
||||
public int getQueueIndex() {
|
||||
return queueIndex;
|
||||
}
|
||||
|
||||
public boolean isSkippable() {
|
||||
return skippable;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
public class InitEvent implements PlayQueueEvent {
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.INIT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
public class MoveEvent implements PlayQueueEvent {
|
||||
final private int fromIndex;
|
||||
final private int toIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.MOVE;
|
||||
}
|
||||
|
||||
public MoveEvent(final int oldIndex, final int newIndex) {
|
||||
this.fromIndex = oldIndex;
|
||||
this.toIndex = newIndex;
|
||||
}
|
||||
|
||||
public int getFromIndex() {
|
||||
return fromIndex;
|
||||
}
|
||||
|
||||
public int getToIndex() {
|
||||
return toIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface PlayQueueEvent extends Serializable {
|
||||
PlayQueueEventType type();
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
public enum PlayQueueEventType {
|
||||
INIT,
|
||||
|
||||
// sent when the index is changed
|
||||
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 two streams swap place in the play queue
|
||||
MOVE,
|
||||
|
||||
// sent when queue is shuffled
|
||||
REORDER,
|
||||
|
||||
// sent when recovery record is set on a stream
|
||||
RECOVERY,
|
||||
|
||||
// sent when the item at index has caused an exception
|
||||
ERROR
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
|
||||
public class RecoveryEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
final private long position;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.RECOVERY;
|
||||
}
|
||||
|
||||
public RecoveryEvent(final int index, final long position) {
|
||||
this.index = index;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
|
||||
public class RemoveEvent implements PlayQueueEvent {
|
||||
final private int removeIndex;
|
||||
final private int queueIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REMOVE;
|
||||
}
|
||||
|
||||
public RemoveEvent(final int removeIndex, final int queueIndex) {
|
||||
this.removeIndex = removeIndex;
|
||||
this.queueIndex = queueIndex;
|
||||
}
|
||||
|
||||
public int getQueueIndex() {
|
||||
return queueIndex;
|
||||
}
|
||||
|
||||
public int getRemoveIndex() {
|
||||
return removeIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
public class ReorderEvent implements PlayQueueEvent {
|
||||
private final int fromSelectedIndex;
|
||||
private final int toSelectedIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REORDER;
|
||||
}
|
||||
|
||||
public ReorderEvent(final int fromSelectedIndex, final int toSelectedIndex) {
|
||||
this.fromSelectedIndex = fromSelectedIndex;
|
||||
this.toSelectedIndex = toSelectedIndex;
|
||||
}
|
||||
|
||||
public int getFromSelectedIndex() {
|
||||
return fromSelectedIndex;
|
||||
}
|
||||
|
||||
public int getToSelectedIndex() {
|
||||
return toSelectedIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package org.schabi.newpipe.playqueue.events;
|
||||
|
||||
|
||||
public class SelectEvent implements PlayQueueEvent {
|
||||
final private int oldIndex;
|
||||
final private int newIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.SELECT;
|
||||
}
|
||||
|
||||
public SelectEvent(final int oldIndex, final int newIndex) {
|
||||
this.oldIndex = oldIndex;
|
||||
this.newIndex = newIndex;
|
||||
}
|
||||
|
||||
public int getOldIndex() {
|
||||
return oldIndex;
|
||||
}
|
||||
|
||||
public int getNewIndex() {
|
||||
return newIndex;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue