-Refactored Playback manager to expose only readonly methods.

-Removed swap and move operations.
-Code clean up.
This commit is contained in:
John Zhen M 2017-09-10 17:43:21 -07:00 committed by John Zhen Mo
parent 9413856463
commit f8abf92a66
12 changed files with 128 additions and 226 deletions

View File

@ -382,9 +382,6 @@ public class BackgroundPlayer extends Service {
public void sync(final StreamInfo info, final int sortedStreamsIndex) {
super.sync(info, sortedStreamsIndex);
setVideoTitle(info.name);
setUploaderName(info.uploader_name);
notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());

View File

@ -93,7 +93,6 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
@ -136,11 +135,6 @@ public abstract class BasePlayer implements Player.EventListener,
public static final String RESTORE_QUEUE_INDEX = "restore_queue_index";
public static final String RESTORE_WINDOW_POS = "restore_window_pos";
protected String videoUrl = "";
protected String videoTitle = "";
protected String videoThumbnailUrl = "";
protected String uploaderName = "";
/*//////////////////////////////////////////////////////////////////////////
// Playlist
//////////////////////////////////////////////////////////////////////////*/
@ -148,8 +142,11 @@ public abstract class BasePlayer implements Player.EventListener,
protected PlaybackManager playbackManager;
protected PlayQueue playQueue;
protected int queueStartPos = 0;
protected long videoStartPos = -1;
private boolean isRecovery = false;
private int queuePos = 0;
private long videoPos = -1;
protected StreamInfo currentInfo;
/*//////////////////////////////////////////////////////////////////////////
// Player
@ -246,8 +243,10 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return;
queueStartPos = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0);
videoStartPos = intent.getLongExtra(START_POSITION, 0);
setRecovery(
intent.getIntExtra(RESTORE_QUEUE_INDEX, 0),
intent.getLongExtra(START_POSITION, 0)
);
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
switch (intent.getStringExtra(INTENT_TYPE)) {
@ -535,21 +534,37 @@ public abstract class BasePlayer implements Player.EventListener,
public void onTimelineChanged(Timeline timeline, Object manifest) {
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) {
if (timeline.getWindowCount() > playbackManager.getCurrentSourceIndex()) {
if (DEBUG) Log.d(TAG, "Rewinding to correct window");
simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex());
}
final int currentSourceIndex = playbackManager.getCurrentSourceIndex();
// Check timeline has window
if (simpleExoPlayer.getCurrentTimeline().getWindowCount() <= currentSourceIndex) return;
// Check if window is ready
Timeline.Window window = new Timeline.Window();
simpleExoPlayer.getCurrentTimeline().getWindow(currentSourceIndex, window);
if (window.isDynamic) return;
// Check if already playing correct window
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
if (isCurrentWindowCorrect && getCurrentState() == STATE_PLAYING) return;
// Check if recovering on correct item
if (isRecovery && queuePos == playQueue.getIndex() && isCurrentWindowCorrect) {
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)videoPos));
simpleExoPlayer.seekTo(currentSourceIndex, videoPos);
isRecovery = false;
} else if (!isCurrentWindowCorrect) { // Or if on wrong window
final long startPos = currentInfo != null ? currentInfo.start_position : 0;
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos));
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
}
if (!simpleExoPlayer.isCurrentWindowDynamic() && simpleExoPlayer.isCurrentWindowSeekable()) {
simpleExoPlayer.setPlayWhenReady(true);
}
// Good to go...
simpleExoPlayer.setPlayWhenReady(true);
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
Log.w(TAG, "onTracksChanged() called, unsupported operation. Is this expected?");
}
@Override
@ -604,8 +619,7 @@ public abstract class BasePlayer implements Player.EventListener,
@Override
public void onPlayerError(ExoPlaybackException error) {
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
playbackManager.report(error);
playQueue.remove(playQueue.getIndex());
onError(error);
}
@ -615,7 +629,9 @@ public abstract class BasePlayer implements Player.EventListener,
int newIndex = simpleExoPlayer.getCurrentWindowIndex();
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with: index = [" + newIndex + "]");
playbackManager.refresh(newIndex);
if (newIndex == playbackManager.getCurrentSourceIndex() + 1) {
playQueue.offsetIndex(+1);
}
}
@Override
@ -633,6 +649,7 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "Blocking...");
simpleExoPlayer.stop();
isPrepared = false;
changeState(STATE_BUFFERING);
}
@ -642,13 +659,8 @@ public abstract class BasePlayer implements Player.EventListener,
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Unblocking...");
if (queueStartPos != playQueue.getIndex()) {
queueStartPos = playQueue.getIndex();
videoStartPos = 0;
}
simpleExoPlayer.prepare(playbackManager.getMediaSource());
simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), videoStartPos);
simpleExoPlayer.prepare(playbackManager.getMediaSource(), true, true);
isPrepared = true;
}
@Override
@ -656,13 +668,10 @@ public abstract class BasePlayer implements Player.EventListener,
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Syncing...");
videoUrl = info.url;
videoThumbnailUrl = info.thumbnail_url;
videoTitle = info.name;
currentInfo = info;
onTimelineChanged(simpleExoPlayer.getCurrentTimeline(), null);
initThumbnail(videoThumbnailUrl);
initThumbnail(info.thumbnail_url);
}
@Override
@ -774,7 +783,11 @@ public abstract class BasePlayer implements Player.EventListener,
}
public void triggerProgressUpdate() {
onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
onUpdateProgress(
(int) simpleExoPlayer.getCurrentPosition(),
(int) simpleExoPlayer.getDuration(),
simpleExoPlayer.getBufferedPercentage()
);
}
public void animateAudio(final float from, final float to, int duration) {
@ -823,35 +836,19 @@ public abstract class BasePlayer implements Player.EventListener,
}
public String getVideoUrl() {
return videoUrl;
return currentInfo.url;
}
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public long getVideoStartPos() {
return videoStartPos;
}
public void setVideoStartPos(long videoStartPos) {
this.videoStartPos = videoStartPos;
public long getVideoPos() {
return videoPos;
}
public String getVideoTitle() {
return videoTitle;
}
public void setVideoTitle(String videoTitle) {
this.videoTitle = videoTitle;
return currentInfo.name;
}
public String getUploaderName() {
return uploaderName;
}
public void setUploaderName(String uploaderName) {
this.uploaderName = uploaderName;
return currentInfo.uploader_name;
}
public boolean isCompleted() {
@ -866,14 +863,6 @@ public abstract class BasePlayer implements Player.EventListener,
isPrepared = prepared;
}
public String getVideoThumbnailUrl() {
return videoThumbnailUrl;
}
public void setVideoThumbnailUrl(String videoThumbnailUrl) {
this.videoThumbnailUrl = videoThumbnailUrl;
}
public float getPlaybackSpeed() {
return simpleExoPlayer.getPlaybackParameters().speed;
}
@ -897,4 +886,15 @@ public abstract class BasePlayer implements Player.EventListener,
public boolean isProgressLoopRunning() {
return progressUpdateReactor != null && !progressUpdateReactor.isDisposed();
}
public boolean getRecovery() {
return isRecovery;
}
public void setRecovery(final int queuePos, final long windowPos) {
if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
this.isRecovery = true;
this.queuePos = queuePos;
this.videoPos = windowPos;
}
}

View File

@ -111,7 +111,10 @@ public class MainVideoPlayer extends Activity {
if (DEBUG) Log.d(TAG, "onStop() called");
activityPaused = true;
if (playerImpl.getPlayer() != null) {
playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
playerImpl.setRecovery(
playerImpl.getCurrentQueueIndex(),
(int) playerImpl.getPlayer().getCurrentPosition()
);
playerImpl.destroyPlayer();
}
}
@ -224,13 +227,6 @@ public class MainVideoPlayer extends Activity {
screenRotationButton.setOnClickListener(this);
}
@Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName());
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@ -263,7 +259,8 @@ public class MainVideoPlayer extends Activity {
return;
}
context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, this));
final Intent intent = NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, this);
context.startService(intent);
destroyPlayer();
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);

View File

@ -50,15 +50,11 @@ import android.widget.TextView;
import android.widget.Toast;
import com.google.android.exoplayer2.Player;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
@ -68,19 +64,16 @@ 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.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.IOException;
import java.util.ArrayList;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -745,9 +738,6 @@ public class PopupVideoPlayer extends Service {
}
public void onReceive(final StreamInfo info) {
if (info.start_position > 0) playerImpl.setVideoStartPos(info.start_position * 1000);
else playerImpl.setVideoStartPos(-1);
mainHandler.post(new Runnable() {
@Override
public void run() {

View File

@ -234,8 +234,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
final Serializable serializable = intent.getSerializableExtra(PLAY_QUEUE);
if (!(serializable instanceof PlayQueue)) return;
selectedIndexStream = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
playQueue = (PlayQueue) serializable;
playQueue.init();
@ -406,12 +404,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
public void onPrepared(boolean playWhenReady) {
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
if (videoStartPos > 0) {
playbackSeekBar.setProgress((int) videoStartPos);
playbackCurrentTime.setText(getTimeString((int) videoStartPos));
videoStartPos = -1;
}
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
@ -453,6 +445,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
protected void onFullScreenButtonClicked() {
if (!isPlayerReady()) return;
changeState(STATE_BUFFERING);
}
@Override
@ -492,11 +486,11 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
if (selectedIndexStream == menuItem.getItemId()) return true;
if (selectedIndexStream == menuItem.getItemId() || getRecovery()) return true;
queueStartPos = playQueue.getIndex();
videoStartPos = simpleExoPlayer.getCurrentPosition();
playbackManager.updateCurrent(menuItem.getItemId());
final int index = playQueue.getIndex();
setRecovery(index, simpleExoPlayer.getCurrentPosition());
playQueue.updateIndex(index, menuItem.getItemId());
qualityTextView.setText(menuItem.getTitle());
return true;

View File

@ -12,7 +12,6 @@ import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.MoveEvent;
import java.util.ArrayList;
import java.util.Collections;
@ -29,7 +28,7 @@ public class PlaybackManager {
private final String TAG = "PlaybackManager@" + Integer.toHexString(hashCode());
// One-side rolling window size for default loading
// Effectively loads WINDOW_SIZE * 2 streams
private static final int WINDOW_SIZE = 2;
private static final int WINDOW_SIZE = 3;
private final PlaybackListener playbackListener;
private final PlayQueue playQueue;
@ -77,37 +76,6 @@ public class PlaybackManager {
return sources;
}
/*
* Called when the player has transitioned to another stream.
* */
public void refresh(final int newSourceIndex) {
if (sourceToQueueIndex.indexOf(newSourceIndex) != -1 && newSourceIndex == getCurrentSourceIndex() + 1) {
playQueue.offsetIndex(+1);
}
}
public void report(final Exception error) {
// ignore error checking for now, just remove the current index
if (error == null) return;
tryBlock();
final int index = playQueue.getIndex();
playQueue.remove(index);
resetSources();
load();
}
public void updateCurrent(final int newSortedStreamsIndex) {
tryBlock();
PlayQueueItem item = playQueue.getCurrent();
item.setSortedQualityIndex(newSortedStreamsIndex);
resetSources();
load();
}
public void dispose() {
if (playQueueReactor != null) playQueueReactor.cancel();
@ -146,14 +114,19 @@ public class PlaybackManager {
case APPEND:
break;
case SELECT:
onSelect();
if (isBlocked) break;
if (isCurrentIndexLoaded()) sync(); else tryBlock();
break;
case REMOVE:
final RemoveEvent removeEvent = (RemoveEvent) event;
if (removeEvent.isCurrent()) tryBlock();
remove(removeEvent.index());
break;
case MOVE:
throw new UnsupportedOperationException("Move not yet supported");
case UPDATE:
case SHUFFLE:
tryBlock();
resetSources();
break;
default:
break;
}
@ -208,30 +181,6 @@ public class PlaybackManager {
return false;
}
/*
* Responds to a SELECT event.
*
* If the player is being blocked, then nothing should happen.
*
* Otherwise:
*
* When the selected item is already loaded, then we simply synchronize and
* start loading some more items.
*
* When the current item has not been fully loaded, then the player will be
* blocked. The sources will be reset and reloaded, to conserve memory.
* */
private void onSelect() {
if (isBlocked) return;
if (isCurrentIndexLoaded()) {
sync();
} else {
tryBlock();
resetSources();
}
}
private void sync() {
final PlayQueueItem currentItem = playQueue.getCurrent();
@ -319,13 +268,14 @@ public class PlaybackManager {
if (queueIndex < 0) return;
final int sourceIndex = sourceToQueueIndex.indexOf(queueIndex);
if (sourceIndex != -1) {
sourceToQueueIndex.remove(sourceIndex);
sources.removeMediaSource(sourceIndex);
// Will be slow on really large arrays, fast enough for typical use case
for (int i = sourceIndex; i < sourceToQueueIndex.size(); i++) {
sourceToQueueIndex.set(i, sourceToQueueIndex.get(i) - 1);
}
if (sourceIndex == -1) return;
sourceToQueueIndex.remove(sourceIndex);
sources.removeMediaSource(sourceIndex);
// Will be slow on really large arrays, fast enough for typical use case
for (int i = sourceIndex; i < sourceToQueueIndex.size(); i++) {
sourceToQueueIndex.set(i, sourceToQueueIndex.get(i) - 1);
}
}
}

View File

@ -10,14 +10,13 @@ import org.schabi.newpipe.playlist.events.InitEvent;
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.SelectEvent;
import org.schabi.newpipe.playlist.events.MoveEvent;
import org.schabi.newpipe.playlist.events.UpdateEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.BackpressureStrategy;
@ -96,7 +95,7 @@ public abstract class PlayQueue implements Serializable {
}
public int indexOf(final PlayQueueItem item) {
// reference equality, can't think of a better way to do this
// referential equality, can't think of a better way to do this
// todo: better than this
return streams.indexOf(item);
}
@ -134,6 +133,13 @@ public abstract class PlayQueue implements Serializable {
setIndex(getIndex() + offset);
}
public synchronized void updateIndex(final int index, final int selectedQuality) {
if (index < 0 || index >= streams.size()) return;
get(index).setSortedQualityIndex(selectedQuality);
broadcast(new UpdateEvent(index));
}
protected synchronized void append(final PlayQueueItem item) {
streams.add(item);
broadcast(new AppendEvent(1));
@ -158,29 +164,6 @@ public abstract class PlayQueue implements Serializable {
broadcast(new RemoveEvent(index, isCurrent));
}
protected synchronized void swap(final int source, final int target) {
if (source < 0 || target < 0) return;
final List<PlayQueueItem> items = streams;
if (source < items.size() && target < items.size()) {
// Swap two items
final PlayQueueItem sourceItem = items.get(source);
final PlayQueueItem targetItem = items.get(target);
items.set(target, sourceItem);
items.set(source, targetItem);
// If the current playing index is one of the swapped indices, change that as well
final int index = queueIndex.get();
if (index == source || index == target) {
final int newIndex = index == source ? target : source;
queueIndex.set(newIndex);
}
broadcast(new MoveEvent(source, target));
}
}
/*//////////////////////////////////////////////////////////////////////////
// Rx Broadcast
//////////////////////////////////////////////////////////////////////////*/

View File

@ -82,10 +82,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
playQueue.remove(index);
}
public void swap(final int source, final int target) {
playQueue.swap(source, target);
}
private void startReactor() {
final Observer<PlayQueueMessage> observer = new Observer<PlayQueueMessage>() {
@Override

View File

@ -1,25 +0,0 @@
package org.schabi.newpipe.playlist.events;
public class MoveEvent implements PlayQueueMessage {
final private int from;
final private int to;
@Override
public PlayQueueEvent type() {
return PlayQueueEvent.MOVE;
}
public MoveEvent(final int from, final int to) {
this.from = from;
this.to = to;
}
public int getFrom() {
return from;
}
public int getTo() {
return to;
}
}

View File

@ -3,10 +3,7 @@ package org.schabi.newpipe.playlist.events;
public enum PlayQueueEvent {
INIT,
// sent when the user is seamlessly transitioned by exoplayer to the next stream
NEXT,
// sent when the user transitions to an unbuffered period
// sent when the index is changed
SELECT,
// sent when more streams are added to the play queue
@ -16,6 +13,12 @@ public enum PlayQueueEvent {
REMOVE,
// sent when two streams swap place in the play queue
MOVE
MOVE,
// sent when a stream is updated
UPDATE,
// send when queue is shuffled
SHUFFLE
}

View File

@ -0,0 +1,18 @@
package org.schabi.newpipe.playlist.events;
public class UpdateEvent implements PlayQueueMessage {
final private int updatedIndex;
@Override
public PlayQueueEvent type() {
return PlayQueueEvent.UPDATE;
}
public UpdateEvent(final int updatedIndex) {
this.updatedIndex = updatedIndex;
}
public int index() {
return updatedIndex;
}
}

View File

@ -71,7 +71,6 @@ public class NavigationHelper {
return new Intent(context, targetClazz)
.putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.PLAYER_INTENT)
.putExtra(VideoPlayer.PLAY_QUEUE, instance.getPlayQueue())
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
.putExtra(VideoPlayer.RESTORE_QUEUE_INDEX, instance.getCurrentQueueIndex())
.putExtra(BasePlayer.START_POSITION, instance.getPlayer().getCurrentPosition())
.putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed());