mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 16:40:32 +00:00
-Baked stream info resolution into custom media source, allowing for simpler playlist control.
-Added track merging on different stream qualities, allowing for implementation of smooth transition on A/V quality and captions change.
This commit is contained in:
parent
9576d5bd89
commit
9bc95f030c
@ -37,6 +37,7 @@ import android.widget.RemoteViews;
|
||||
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
@ -49,6 +50,9 @@ import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Base players joining the common properties
|
||||
@ -390,9 +394,14 @@ public final class BackgroundPlayer extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex) {
|
||||
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
|
||||
return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
||||
public MediaSource sourceOf(final StreamInfo info) {
|
||||
List<MediaSource> sources = new ArrayList<>();
|
||||
for (final AudioStream audio : info.audio_streams) {
|
||||
final MediaSource audioSource = buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
||||
sources.add(audioSource);
|
||||
}
|
||||
|
||||
return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,8 +72,8 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.playback.PlaybackManager;
|
||||
import org.schabi.newpipe.playlist.ExternalPlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
@ -139,7 +139,7 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
// Playback
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected PlaybackManager playbackManager;
|
||||
protected MediaSourceManager playbackManager;
|
||||
protected PlayQueue playQueue;
|
||||
|
||||
private boolean isRecovery = false;
|
||||
@ -158,7 +158,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
|
||||
protected SimpleExoPlayer simpleExoPlayer;
|
||||
protected boolean isPrepared = false;
|
||||
protected boolean wasPlaying = false;
|
||||
|
||||
protected CacheDataSourceFactory cacheDataSourceFactory;
|
||||
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
|
||||
@ -297,7 +296,7 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
|
||||
playQueue = queue;
|
||||
playQueue.init();
|
||||
playbackManager = new PlaybackManager(this, playQueue);
|
||||
playbackManager = new MediaSourceManager(this, playQueue);
|
||||
}
|
||||
|
||||
public void initThumbnail(final String url) {
|
||||
@ -442,14 +441,12 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
|
||||
if (isResumeAfterAudioFocusGain()) {
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
wasPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onAudioFocusLoss() {
|
||||
if (DEBUG) Log.d(TAG, "onAudioFocusLoss() called");
|
||||
simpleExoPlayer.setPlayWhenReady(false);
|
||||
wasPlaying = false;
|
||||
}
|
||||
|
||||
protected void onAudioFocusLossCanDuck() {
|
||||
@ -586,7 +583,7 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
}
|
||||
|
||||
// Good to go...
|
||||
simpleExoPlayer.setPlayWhenReady(wasPlaying);
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -669,11 +666,10 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with: " +
|
||||
"window index = [" + newWindowIndex + "], queue index = [" + newQueueIndex + "]");
|
||||
|
||||
if (newQueueIndex == -1) {
|
||||
playQueue.offsetIndex(+1);
|
||||
} else {
|
||||
playQueue.setIndex(newQueueIndex);
|
||||
}
|
||||
// If the user selects a new track, then the discontinuity occurs after the index is changed.
|
||||
// Therefore, the only source that causes a discrepancy would be autoplay,
|
||||
// which can only offset the current track by +1.
|
||||
if (newQueueIndex != playQueue.getIndex()) playQueue.offsetIndex(+1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -690,31 +686,20 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Blocking...");
|
||||
|
||||
simpleExoPlayer.removeListener(this);
|
||||
changeState(STATE_BLOCKED);
|
||||
|
||||
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
||||
simpleExoPlayer.setPlayWhenReady(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(final MediaSource mediaSource) {
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Preparing...");
|
||||
|
||||
simpleExoPlayer.stop();
|
||||
isPrepared = false;
|
||||
|
||||
simpleExoPlayer.prepare(mediaSource);
|
||||
changeState(STATE_BLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unblock() {
|
||||
public void unblock(final MediaSource mediaSource) {
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Unblocking...");
|
||||
|
||||
if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING);
|
||||
simpleExoPlayer.addListener(this);
|
||||
|
||||
simpleExoPlayer.prepare(mediaSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -762,7 +747,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
else playQueue.setIndex(0);
|
||||
}
|
||||
simpleExoPlayer.setPlayWhenReady(!isPlaying());
|
||||
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
||||
}
|
||||
|
||||
public void onFastRewind() {
|
||||
|
@ -63,7 +63,7 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.playback.PlaybackManager;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
@ -743,7 +743,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
public void run() {
|
||||
playerImpl.playQueue = new SinglePlayQueue(info, PlayQueueItem.DEFAULT_QUALITY);
|
||||
playerImpl.playQueue.init();
|
||||
playerImpl.playbackManager = new PlaybackManager(playerImpl, playerImpl.playQueue);
|
||||
playerImpl.playbackManager = new MediaSourceManager(playerImpl, playerImpl.playQueue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.playback.PlaybackManager;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
@ -104,6 +103,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||
private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
|
||||
|
||||
private boolean startedFromNewPipe = true;
|
||||
protected boolean wasPlaying = false;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
@ -255,24 +255,21 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||
buildPlaybackSpeedMenu(playbackSpeedPopupMenu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex) {
|
||||
public MediaSource sourceOf(final StreamInfo info) {
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
|
||||
List<MediaSource> sources = new ArrayList<>();
|
||||
|
||||
final VideoStream video;
|
||||
if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) {
|
||||
final int index = ListHelper.getDefaultResolutionIndex(context, videos);
|
||||
video = videos.get(index);
|
||||
} else {
|
||||
video = videos.get(sortedStreamsIndex);
|
||||
}
|
||||
|
||||
for (final VideoStream video : videos) {
|
||||
final MediaSource mediaSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
|
||||
if (!video.isVideoOnly) return mediaSource;
|
||||
sources.add(mediaSource);
|
||||
}
|
||||
|
||||
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
|
||||
final Uri audioUri = Uri.parse(audio.url);
|
||||
return new MergingMediaSource(mediaSource, new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null));
|
||||
final MediaSource audioSource = new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null);
|
||||
sources.add(audioSource);
|
||||
|
||||
return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()]));
|
||||
}
|
||||
|
||||
public void buildQualityMenu(PopupMenu popupMenu) {
|
||||
|
@ -0,0 +1,82 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Looper;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
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 org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public final class DeferredMediaSource implements MediaSource {
|
||||
|
||||
public interface Callback {
|
||||
MediaSource sourceOf(final StreamInfo info);
|
||||
}
|
||||
|
||||
final private PlayQueueItem stream;
|
||||
final private Callback callback;
|
||||
|
||||
private StreamInfo info;
|
||||
private MediaSource mediaSource;
|
||||
|
||||
private ExoPlayer exoPlayer;
|
||||
private boolean isTopLevel;
|
||||
private Listener listener;
|
||||
|
||||
public DeferredMediaSource(final PlayQueueItem stream, final Callback callback) {
|
||||
this.stream = stream;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(ExoPlayer exoPlayer, boolean isTopLevelSource, Listener listener) {
|
||||
this.exoPlayer = exoPlayer;
|
||||
this.isTopLevel = isTopLevelSource;
|
||||
this.listener = listener;
|
||||
|
||||
listener.onSourceInfoRefreshed(new SinglePeriodTimeline(C.TIME_UNSET, false), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||
if (mediaSource != null) {
|
||||
mediaSource.maybeThrowSourceInfoRefreshError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
mediaSource.releasePeriod(mediaPeriod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
if (mediaSource != null) mediaSource.releaseSource();
|
||||
info = null;
|
||||
mediaSource = null;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import com.google.android.exoplayer2.source.MediaSource;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.mediasource.DeferredMediaSource;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
|
||||
@ -25,12 +26,12 @@ import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
|
||||
public class PlaybackManager {
|
||||
private final String TAG = "PlaybackManager@" + Integer.toHexString(hashCode());
|
||||
public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode());
|
||||
// One-side rolling window size for default loading
|
||||
// Effectively loads WINDOW_SIZE * 2 + 1 streams, should be at least 1
|
||||
// Effectively loads WINDOW_SIZE * 2 + 1 streams, should be at least 1 to ensure gapless playback
|
||||
// todo: inject this parameter, allow user settings perhaps
|
||||
private static final int WINDOW_SIZE = 3;
|
||||
private static final int WINDOW_SIZE = 1;
|
||||
|
||||
private final PlaybackListener playbackListener;
|
||||
private final PlayQueue playQueue;
|
||||
@ -46,9 +47,8 @@ public class PlaybackManager {
|
||||
private CompositeDisposable disposables;
|
||||
|
||||
private boolean isBlocked;
|
||||
private boolean hasReset;
|
||||
|
||||
public PlaybackManager(@NonNull final PlaybackListener listener,
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this.playbackListener = listener;
|
||||
this.playQueue = playQueue;
|
||||
@ -114,22 +114,27 @@ public class PlaybackManager {
|
||||
public void onNext(@NonNull PlayQueueMessage event) {
|
||||
// why no pattern matching in Java =(
|
||||
switch (event.type()) {
|
||||
case INIT:
|
||||
tryBlock();
|
||||
resetSources();
|
||||
break;
|
||||
case APPEND:
|
||||
break;
|
||||
case SELECT:
|
||||
if (isBlocked) break;
|
||||
if (isCurrentIndexLoaded()) sync(); else tryBlock();
|
||||
if (isCurrentIndexLoaded()) {
|
||||
sync();
|
||||
} else {
|
||||
tryBlock();
|
||||
resetSources();
|
||||
}
|
||||
break;
|
||||
case REMOVE:
|
||||
final RemoveEvent removeEvent = (RemoveEvent) event;
|
||||
if (!removeEvent.isCurrent()) {
|
||||
remove(removeEvent.index());
|
||||
break;
|
||||
} else {
|
||||
tryBlock();
|
||||
resetSources();
|
||||
}
|
||||
break;
|
||||
case INIT:
|
||||
case UPDATE:
|
||||
case REORDER:
|
||||
tryBlock();
|
||||
@ -182,13 +187,8 @@ public class PlaybackManager {
|
||||
|
||||
private boolean tryUnblock() {
|
||||
if (isPlayQueueReady() && isCurrentIndexLoaded() && isBlocked) {
|
||||
if (hasReset) {
|
||||
playbackListener.prepare(sources);
|
||||
hasReset = false;
|
||||
}
|
||||
|
||||
isBlocked = false;
|
||||
playbackListener.unblock();
|
||||
playbackListener.unblock(sources);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -208,53 +208,10 @@ public class PlaybackManager {
|
||||
}
|
||||
|
||||
private 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);
|
||||
|
||||
// Load boundaries to ensure correct looping
|
||||
if (sourceToQueueIndex.indexOf(0) == -1) load(playQueue.get(0));
|
||||
if (sourceToQueueIndex.indexOf(playQueue.size() - 1) == -1) load(playQueue.get(playQueue.size() - 1));
|
||||
|
||||
// The rest are just for seamless playback
|
||||
final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE);
|
||||
final int rightBound = Math.min(playQueue.size(), currentIndex + WINDOW_SIZE + 1);
|
||||
final List<PlayQueueItem> items = new ArrayList<>(playQueue.getStreams().subList(leftBound, rightBound));
|
||||
|
||||
for (final PlayQueueItem item: items) load(item);
|
||||
}
|
||||
|
||||
private void load(@Nullable final PlayQueueItem item) {
|
||||
if (item == null) return;
|
||||
|
||||
item.getStream().subscribe(new SingleObserver<StreamInfo>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (disposables == null) {
|
||||
d.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
disposables.add(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull StreamInfo streamInfo) {
|
||||
final MediaSource source = playbackListener.sourceOf(streamInfo, item.getSortedQualityIndex());
|
||||
final int itemIndex = playQueue.indexOf(item);
|
||||
// replace all except the currently playing
|
||||
insert(itemIndex, source, itemIndex != playQueue.getIndex());
|
||||
for (final PlayQueueItem item : playQueue.getStreams()) {
|
||||
insert(playQueue.indexOf(item), new DeferredMediaSource(item, this));
|
||||
if (tryUnblock()) sync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
playQueue.remove(playQueue.indexOf(item));
|
||||
load();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void resetSources() {
|
||||
@ -263,7 +220,6 @@ public class PlaybackManager {
|
||||
if (this.sourceToQueueIndex != null) this.sourceToQueueIndex.clear();
|
||||
|
||||
this.sources = new DynamicConcatenatingMediaSource();
|
||||
this.hasReset = true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -272,7 +228,7 @@ public class PlaybackManager {
|
||||
|
||||
// Insert source into playlist with position in respect to the play queue
|
||||
// If the play queue index already exists, then the insert is ignored
|
||||
private void insert(final int queueIndex, final MediaSource source, final boolean replace) {
|
||||
private void insert(final int queueIndex, final MediaSource source) {
|
||||
if (queueIndex < 0) return;
|
||||
|
||||
int pos = Collections.binarySearch(sourceToQueueIndex, queueIndex);
|
||||
@ -280,9 +236,6 @@ public class PlaybackManager {
|
||||
final int sourceIndex = -pos-1;
|
||||
sourceToQueueIndex.add(sourceIndex, queueIndex);
|
||||
sources.addMediaSource(sourceIndex, source);
|
||||
} else if (replace) {
|
||||
sources.addMediaSource(pos + 1, source);
|
||||
sources.removeMediaSource(pos);
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,4 +253,9 @@ public class PlaybackManager {
|
||||
sourceToQueueIndex.set(i, sourceToQueueIndex.get(i) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSource sourceOf(StreamInfo info) {
|
||||
return playbackListener.sourceOf(info);
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PlaybackListener {
|
||||
/*
|
||||
* Called when the stream at the current queue index is not ready yet.
|
||||
@ -13,23 +15,13 @@ public interface PlaybackListener {
|
||||
* */
|
||||
void block();
|
||||
|
||||
|
||||
/*
|
||||
* Called when the media source is rebuilt.
|
||||
* Signals to the listener to prepare the media source again.
|
||||
* The provided media source is always non-empty.
|
||||
*
|
||||
* May be called only after blocking and before unblocking.
|
||||
* */
|
||||
void prepare(final MediaSource mediaSource);
|
||||
|
||||
/*
|
||||
* Called when the stream at the current queue index is ready.
|
||||
* Signals to the listener to resume the player.
|
||||
*
|
||||
* May be called only when the player is blocked.
|
||||
* */
|
||||
void unblock();
|
||||
void unblock(final MediaSource mediaSource);
|
||||
|
||||
/*
|
||||
* Called when the queue index is refreshed.
|
||||
@ -46,7 +38,7 @@ public interface PlaybackListener {
|
||||
*
|
||||
* May be called at any time.
|
||||
* */
|
||||
MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex);
|
||||
MediaSource sourceOf(final StreamInfo info);
|
||||
|
||||
/*
|
||||
* Called when the play queue can no longer to played or used.
|
||||
|
Loading…
Reference in New Issue
Block a user