-Fixed Deferred Media Source not working on non-extractor (e.g. dash) sources.
-Fixed NPE when extracting streams with no audio.
This commit is contained in:
parent
9bc95f030c
commit
8e3be3826f
|
@ -551,6 +551,8 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void refreshTimeline() {
|
private void refreshTimeline() {
|
||||||
|
playbackManager.load();
|
||||||
|
|
||||||
final int currentSourceIndex = playbackManager.getCurrentSourceIndex();
|
final int currentSourceIndex = playbackManager.getCurrentSourceIndex();
|
||||||
|
|
||||||
// Sanity checks
|
// Sanity checks
|
||||||
|
@ -558,15 +560,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
|
|
||||||
// Check if already playing correct window
|
// Check if already playing correct window
|
||||||
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
|
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
|
||||||
if (isCurrentWindowCorrect && getCurrentState() == STATE_PLAYING) return;
|
|
||||||
|
|
||||||
// Check timeline is up-to-date and has window
|
|
||||||
if (playbackManager.expectedTimelineSize() != simpleExoPlayer.getCurrentTimeline().getWindowCount()) return;
|
|
||||||
|
|
||||||
// Check if window is ready
|
|
||||||
Timeline.Window window = new Timeline.Window();
|
|
||||||
simpleExoPlayer.getCurrentTimeline().getWindow(currentSourceIndex, window);
|
|
||||||
if (window.isDynamic) return;
|
|
||||||
|
|
||||||
// Check if on wrong window
|
// Check if on wrong window
|
||||||
if (!isCurrentWindowCorrect) {
|
if (!isCurrentWindowCorrect) {
|
||||||
|
@ -576,14 +569,16 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if recovering
|
// Check if recovering
|
||||||
if (isRecovery && queuePos == playQueue.getIndex() && isCurrentWindowCorrect) {
|
if (isCurrentWindowCorrect && isRecovery && queuePos == playQueue.getIndex()) {
|
||||||
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)videoPos));
|
// todo: figure out exactly why this is the case
|
||||||
simpleExoPlayer.seekTo(videoPos);
|
/* Rounding time to nearest second as certain media cannot guarantee a sub-second seek
|
||||||
|
will complete and the player might get stuck in buffering state forever */
|
||||||
|
final long roundedPos = (videoPos / 1000) * 1000;
|
||||||
|
|
||||||
|
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)roundedPos));
|
||||||
|
simpleExoPlayer.seekTo(roundedPos);
|
||||||
isRecovery = false;
|
isRecovery = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Good to go...
|
|
||||||
simpleExoPlayer.setPlayWhenReady(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -628,7 +623,9 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
isPrepared = false;
|
isPrepared = false;
|
||||||
break;
|
break;
|
||||||
case Player.STATE_BUFFERING: // 2
|
case Player.STATE_BUFFERING: // 2
|
||||||
if (isPrepared) changeState(STATE_BUFFERING);
|
if (isPrepared) {
|
||||||
|
changeState(STATE_BUFFERING);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Player.STATE_READY: //3
|
case Player.STATE_READY: //3
|
||||||
if (!isPrepared) {
|
if (!isPrepared) {
|
||||||
|
|
|
@ -265,9 +265,11 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
}
|
}
|
||||||
|
|
||||||
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
|
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
|
||||||
final Uri audioUri = Uri.parse(audio.url);
|
if (audio != null) {
|
||||||
final MediaSource audioSource = new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null);
|
final Uri audioUri = Uri.parse(audio.url);
|
||||||
sources.add(audioSource);
|
final MediaSource audioSource = new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null);
|
||||||
|
sources.add(audioSource);
|
||||||
|
}
|
||||||
|
|
||||||
return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()]));
|
return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,107 @@
|
||||||
package org.schabi.newpipe.player.mediasource;
|
package org.schabi.newpipe.player.mediasource;
|
||||||
|
|
||||||
import android.os.Looper;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
|
import io.reactivex.SingleObserver;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.functions.Consumer;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
public final class DeferredMediaSource implements MediaSource {
|
public final class DeferredMediaSource implements MediaSource {
|
||||||
|
private final String TAG = "DeferredMediaSource@" + Integer.toHexString(hashCode());
|
||||||
|
|
||||||
|
private int state = -1;
|
||||||
|
|
||||||
|
public final static int STATE_INIT = 0;
|
||||||
|
public final static int STATE_PREPARED = 1;
|
||||||
|
public final static int STATE_LOADED = 2;
|
||||||
|
public final static int STATE_DISPOSED = 3;
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
MediaSource sourceOf(final StreamInfo info);
|
MediaSource sourceOf(final StreamInfo info);
|
||||||
}
|
}
|
||||||
|
|
||||||
final private PlayQueueItem stream;
|
private PlayQueueItem stream;
|
||||||
final private Callback callback;
|
private Callback callback;
|
||||||
|
|
||||||
private StreamInfo info;
|
|
||||||
private MediaSource mediaSource;
|
private MediaSource mediaSource;
|
||||||
|
|
||||||
private ExoPlayer exoPlayer;
|
private Disposable loader;
|
||||||
private boolean isTopLevel;
|
|
||||||
private Listener listener;
|
|
||||||
|
|
||||||
public DeferredMediaSource(final PlayQueueItem stream, final Callback callback) {
|
private ExoPlayer exoPlayer;
|
||||||
|
private Listener listener;
|
||||||
|
private Throwable error;
|
||||||
|
|
||||||
|
public DeferredMediaSource(@NonNull final PlayQueueItem stream,
|
||||||
|
@NonNull final Callback callback) {
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.state = STATE_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource(ExoPlayer exoPlayer, boolean isTopLevelSource, Listener listener) {
|
public void prepareSource(ExoPlayer exoPlayer, boolean isTopLevelSource, Listener listener) {
|
||||||
this.exoPlayer = exoPlayer;
|
this.exoPlayer = exoPlayer;
|
||||||
this.isTopLevel = isTopLevelSource;
|
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
this.state = STATE_PREPARED;
|
||||||
|
}
|
||||||
|
|
||||||
listener.onSourceInfoRefreshed(new SinglePeriodTimeline(C.TIME_UNSET, false), null);
|
public int state() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void load() {
|
||||||
|
if (state != STATE_PREPARED || stream == null || loader != null) return;
|
||||||
|
Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
||||||
|
|
||||||
|
final Consumer<StreamInfo> onSuccess = new Consumer<StreamInfo>() {
|
||||||
|
@Override
|
||||||
|
public void accept(StreamInfo streamInfo) throws Exception {
|
||||||
|
if (exoPlayer == null && listener == null) {
|
||||||
|
error = new Throwable("Stream info loading failed. URL: " + stream.getUrl());
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, " Loaded: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
||||||
|
|
||||||
|
mediaSource = callback.sourceOf(streamInfo);
|
||||||
|
mediaSource.prepareSource(exoPlayer, false, listener);
|
||||||
|
state = STATE_LOADED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||||
|
@Override
|
||||||
|
public void accept(Throwable throwable) throws Exception {
|
||||||
|
Log.e(TAG, "Loading error:", throwable);
|
||||||
|
error = throwable;
|
||||||
|
state = STATE_LOADED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loader = stream.getStream()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(onSuccess, onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||||
|
if (error != null) {
|
||||||
|
throw new IOException(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (mediaSource != null) {
|
if (mediaSource != null) {
|
||||||
mediaSource.maybeThrowSourceInfoRefreshError();
|
mediaSource.maybeThrowSourceInfoRefreshError();
|
||||||
}
|
}
|
||||||
|
@ -55,28 +109,33 @@ public final class DeferredMediaSource implements MediaSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(MediaPeriodId mediaPeriodId, Allocator allocator) {
|
public MediaPeriod createPeriod(MediaPeriodId mediaPeriodId, Allocator allocator) {
|
||||||
// This must be called on a non-main thread
|
|
||||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
||||||
throw new UnsupportedOperationException("Source preparation is blocking, it must be run on non-UI thread.");
|
|
||||||
}
|
|
||||||
|
|
||||||
info = stream.getStream().blockingGet();
|
|
||||||
|
|
||||||
mediaSource = callback.sourceOf(info);
|
|
||||||
mediaSource.prepareSource(exoPlayer, isTopLevel, listener);
|
|
||||||
|
|
||||||
return mediaSource.createPeriod(mediaPeriodId, allocator);
|
return mediaSource.createPeriod(mediaPeriodId, allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||||
mediaSource.releasePeriod(mediaPeriod);
|
if (mediaSource == null) {
|
||||||
|
Log.e(TAG, "releasePeriod() called when media source is null, memory leak may have occurred.");
|
||||||
|
} else {
|
||||||
|
mediaSource.releasePeriod(mediaPeriod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseSource() {
|
public void releaseSource() {
|
||||||
if (mediaSource != null) mediaSource.releaseSource();
|
state = STATE_DISPOSED;
|
||||||
info = null;
|
|
||||||
mediaSource = null;
|
if (mediaSource != null) {
|
||||||
|
mediaSource.releaseSource();
|
||||||
|
}
|
||||||
|
if (loader != null) {
|
||||||
|
loader.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do not set mediaSource as null here as it may be called through releasePeriod */
|
||||||
|
stream = null;
|
||||||
|
callback = null;
|
||||||
|
exoPlayer = null;
|
||||||
|
listener = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.schabi.newpipe.player.playback;
|
package org.schabi.newpipe.player.playback;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
|
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
@ -13,16 +14,13 @@ import org.schabi.newpipe.playlist.PlayQueue;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||||
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
|
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
|
||||||
import org.schabi.newpipe.playlist.events.RemoveEvent;
|
import org.schabi.newpipe.playlist.events.RemoveEvent;
|
||||||
import org.schabi.newpipe.playlist.events.UpdateEvent;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.SingleObserver;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.annotations.NonNull;
|
import io.reactivex.annotations.NonNull;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.functions.Consumer;
|
||||||
|
|
||||||
|
@ -44,7 +42,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
|
|
||||||
private Subscription playQueueReactor;
|
private Subscription playQueueReactor;
|
||||||
private Disposable syncReactor;
|
private Disposable syncReactor;
|
||||||
private CompositeDisposable disposables;
|
|
||||||
|
|
||||||
private boolean isBlocked;
|
private boolean isBlocked;
|
||||||
|
|
||||||
|
@ -53,8 +50,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
this.playbackListener = listener;
|
this.playbackListener = listener;
|
||||||
this.playQueue = playQueue;
|
this.playQueue = playQueue;
|
||||||
|
|
||||||
this.disposables = new CompositeDisposable();
|
|
||||||
|
|
||||||
this.sources = new DynamicConcatenatingMediaSource();
|
this.sources = new DynamicConcatenatingMediaSource();
|
||||||
this.sourceToQueueIndex = Collections.synchronizedList(new ArrayList<Integer>());
|
this.sourceToQueueIndex = Collections.synchronizedList(new ArrayList<Integer>());
|
||||||
|
|
||||||
|
@ -85,18 +80,35 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if (playQueueReactor != null) playQueueReactor.cancel();
|
if (playQueueReactor != null) playQueueReactor.cancel();
|
||||||
if (disposables != null) disposables.dispose();
|
|
||||||
if (syncReactor != null) syncReactor.dispose();
|
if (syncReactor != null) syncReactor.dispose();
|
||||||
if (sources != null) sources.releaseSource();
|
if (sources != null) sources.releaseSource();
|
||||||
if (sourceToQueueIndex != null) sourceToQueueIndex.clear();
|
if (sourceToQueueIndex != null) sourceToQueueIndex.clear();
|
||||||
|
|
||||||
playQueueReactor = null;
|
playQueueReactor = null;
|
||||||
disposables = null;
|
|
||||||
syncReactor = null;
|
syncReactor = null;
|
||||||
sources = null;
|
sources = null;
|
||||||
sourceToQueueIndex = null;
|
sourceToQueueIndex = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
// The current item has higher priority
|
||||||
|
final int currentIndex = playQueue.getIndex();
|
||||||
|
final PlayQueueItem currentItem = playQueue.get(currentIndex);
|
||||||
|
if (currentItem == null) return;
|
||||||
|
load(currentItem);
|
||||||
|
|
||||||
|
// The rest are just for seamless playback
|
||||||
|
final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE);
|
||||||
|
final int rightLimit = currentIndex + WINDOW_SIZE + 1;
|
||||||
|
final int rightBound = Math.min(playQueue.size(), rightLimit);
|
||||||
|
final List<PlayQueueItem> items = new ArrayList<>(playQueue.getStreams().subList(leftBound, rightBound));
|
||||||
|
|
||||||
|
final int excess = rightLimit - playQueue.size();
|
||||||
|
if (excess >= 0) items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess)));
|
||||||
|
|
||||||
|
for (final PlayQueueItem item: items) load(item);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Event Reactor
|
// Event Reactor
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -115,30 +127,26 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
// why no pattern matching in Java =(
|
// why no pattern matching in Java =(
|
||||||
switch (event.type()) {
|
switch (event.type()) {
|
||||||
case APPEND:
|
case APPEND:
|
||||||
|
populateSources();
|
||||||
break;
|
break;
|
||||||
case SELECT:
|
case SELECT:
|
||||||
if (isBlocked) break;
|
|
||||||
if (isCurrentIndexLoaded()) {
|
if (isCurrentIndexLoaded()) {
|
||||||
sync();
|
sync();
|
||||||
} else {
|
|
||||||
tryBlock();
|
|
||||||
resetSources();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case REMOVE:
|
case REMOVE:
|
||||||
final RemoveEvent removeEvent = (RemoveEvent) event;
|
final RemoveEvent removeEvent = (RemoveEvent) event;
|
||||||
if (!removeEvent.isCurrent()) {
|
if (!removeEvent.isCurrent()) {
|
||||||
remove(removeEvent.index());
|
remove(removeEvent.index());
|
||||||
} else {
|
break;
|
||||||
tryBlock();
|
|
||||||
resetSources();
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case INIT:
|
case INIT:
|
||||||
case UPDATE:
|
case UPDATE:
|
||||||
case REORDER:
|
case REORDER:
|
||||||
tryBlock();
|
tryBlock();
|
||||||
resetSources();
|
resetSources();
|
||||||
|
populateSources();
|
||||||
|
if (tryUnblock()) sync();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -204,31 +212,46 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
currentItem.getStream().subscribe(syncPlayback);
|
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||||
|
@Override
|
||||||
|
public void accept(Throwable throwable) throws Exception {
|
||||||
|
Log.e(TAG, "Sync error:", throwable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
currentItem.getStream().subscribe(syncPlayback, onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private void load(@Nullable final PlayQueueItem item) {
|
||||||
for (final PlayQueueItem item : playQueue.getStreams()) {
|
if (item == null) return;
|
||||||
insert(playQueue.indexOf(item), new DeferredMediaSource(item, this));
|
|
||||||
if (tryUnblock()) sync();
|
final int index = playQueue.indexOf(item);
|
||||||
}
|
if (index > sources.getSize() - 1) return;
|
||||||
|
|
||||||
|
final DeferredMediaSource mediaSource = (DeferredMediaSource) sources.getMediaSource(playQueue.indexOf(item));
|
||||||
|
if (mediaSource.state() == DeferredMediaSource.STATE_PREPARED) mediaSource.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetSources() {
|
private void resetSources() {
|
||||||
if (this.disposables != null) this.disposables.clear();
|
|
||||||
if (this.sources != null) this.sources.releaseSource();
|
if (this.sources != null) this.sources.releaseSource();
|
||||||
if (this.sourceToQueueIndex != null) this.sourceToQueueIndex.clear();
|
if (this.sourceToQueueIndex != null) this.sourceToQueueIndex.clear();
|
||||||
|
|
||||||
this.sources = new DynamicConcatenatingMediaSource();
|
this.sources = new DynamicConcatenatingMediaSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void populateSources() {
|
||||||
|
for (final PlayQueueItem item : playQueue.getStreams()) {
|
||||||
|
insert(playQueue.indexOf(item), new DeferredMediaSource(item, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Media Source List Manipulation
|
// Media Source List Manipulation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
// Insert source into playlist with position in respect to the play queue
|
// Insert source into playlist with position in respect to the play queue
|
||||||
// If the play queue index already exists, then the insert is ignored
|
// If the play queue index already exists, then the insert is ignored
|
||||||
private void insert(final int queueIndex, final MediaSource source) {
|
private void insert(final int queueIndex, final DeferredMediaSource source) {
|
||||||
if (queueIndex < 0) return;
|
if (queueIndex < 0) return;
|
||||||
|
|
||||||
int pos = Collections.binarySearch(sourceToQueueIndex, queueIndex);
|
int pos = Collections.binarySearch(sourceToQueueIndex, queueIndex);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.schabi.newpipe.playlist.events.UpdateEvent;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -147,9 +148,9 @@ public abstract class PlayQueue implements Serializable {
|
||||||
broadcast(new UpdateEvent(index));
|
broadcast(new UpdateEvent(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected synchronized void append(final PlayQueueItem item) {
|
protected synchronized void append(final PlayQueueItem... items) {
|
||||||
streams.add(item);
|
streams.addAll(Arrays.asList(items));
|
||||||
broadcast(new AppendEvent(1));
|
broadcast(new AppendEvent(items.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected synchronized void append(final Collection<PlayQueueItem> items) {
|
protected synchronized void append(final Collection<PlayQueueItem> items) {
|
||||||
|
|
Loading…
Reference in New Issue