-Baked recovery records into play queue items.

-Added previous and next button on main video player.
-Reverted double tap to seek for popup and main video players.
-Improved shuffling to use recovery record.
-Changed shuffling to place current playing stream to top of queue.
-Fixed exception when removing last item on queue.
-Changed fast forward and rewind button to previous and next on background notification.
-Changed background notification to not update when screen is off and update immediately when screen is turned back on.
-Removed unused intent strings.
-Changed "Append" to "Enqueue" for append text.
This commit is contained in:
John Zhen Mo 2017-10-22 12:43:49 -07:00
parent 21d42c92e5
commit 4553850412
14 changed files with 193 additions and 276 deletions

View File

@ -93,6 +93,7 @@ public final class BackgroundPlayer extends Service {
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Notification // Notification
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private static final int NOTIFICATION_ID = 123789; private static final int NOTIFICATION_ID = 123789;
private NotificationManager notificationManager; private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder; private NotificationCompat.Builder notBuilder;
@ -101,6 +102,8 @@ public final class BackgroundPlayer extends Service {
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha"; private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
private final String setImageResourceMethodName = "setImageResource"; private final String setImageResourceMethodName = "setImageResource";
private boolean shouldUpdateOnProgress;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle // Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -117,6 +120,7 @@ public final class BackgroundPlayer extends Service {
basePlayerImpl.setup(); basePlayerImpl.setup();
mBinder = new LocalBinder(); mBinder = new LocalBinder();
shouldUpdateOnProgress = true;
} }
@Override @Override
@ -166,12 +170,9 @@ public final class BackgroundPlayer extends Service {
private void onScreenOnOff(boolean on) { private void onScreenOnOff(boolean on) {
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]"); if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
shouldUpdateOnProgress = on;
if (on) { if (on) {
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning()) { basePlayerImpl.triggerProgressUpdate();
basePlayerImpl.startProgressLoop();
}
} else {
basePlayerImpl.stopProgressLoop();
} }
} }
@ -324,14 +325,6 @@ public final class BackgroundPlayer extends Service {
@Override @Override
public void onPrepared(boolean playWhenReady) { public void onPrepared(boolean playWhenReady) {
super.onPrepared(playWhenReady); super.onPrepared(playWhenReady);
if (simpleExoPlayer.getDuration() < 15000) {
FAST_FORWARD_REWIND_AMOUNT = 2000;
} else if (simpleExoPlayer.getDuration() > 60 * 60 * 1000) {
FAST_FORWARD_REWIND_AMOUNT = 60000;
} else {
FAST_FORWARD_REWIND_AMOUNT = 10000;
}
PROGRESS_LOOP_INTERVAL = 1000;
simpleExoPlayer.setVolume(1f); simpleExoPlayer.setVolume(1f);
} }
@ -343,6 +336,10 @@ public final class BackgroundPlayer extends Service {
@Override @Override
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
updateProgress(currentProgress, duration, bufferPercent);
if (!shouldUpdateOnProgress) return;
resetNotification(); resetNotification();
if (bigNotRemoteView != null) { if (bigNotRemoteView != null) {
if (currentItem != null) { if (currentItem != null) {
@ -361,7 +358,6 @@ public final class BackgroundPlayer extends Service {
} }
updateNotification(-1); updateNotification(-1);
updateProgress(currentProgress, duration, bufferPercent);
} }
@Override @Override

View File

@ -122,21 +122,8 @@ public abstract class BasePlayer implements Player.EventListener,
// Intent // Intent
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static final String INTENT_TYPE = "intent_type";
public static final String SINGLE_STREAM = "single";
public static final String EXTERNAL_PLAYLIST = "external";
public static final String INTERNAL_PLAYLIST = "internal";
public static final String VIDEO_URL = "video_url";
public static final String VIDEO_TITLE = "video_title";
public static final String VIDEO_THUMBNAIL_URL = "video_thumbnail_url";
public static final String START_POSITION = "start_position";
public static final String CHANNEL_NAME = "channel_name";
public static final String PLAYBACK_SPEED = "playback_speed"; public static final String PLAYBACK_SPEED = "playback_speed";
public static final String PLAY_QUEUE = "play_queue"; public static final String PLAY_QUEUE = "play_queue";
public static final String RESTORE_QUEUE_INDEX = "restore_queue_index";
public static final String RESTORE_WINDOW_POS = "restore_window_pos";
public static final String APPEND_ONLY = "append_only"; public static final String APPEND_ONLY = "append_only";
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -149,10 +136,6 @@ public abstract class BasePlayer implements Player.EventListener,
protected MediaSourceManager playbackManager; protected MediaSourceManager playbackManager;
protected PlayQueue playQueue; protected PlayQueue playQueue;
private boolean isRecovery = false;
private int queuePos = 0;
private long videoPos = -1;
protected StreamInfo currentInfo; protected StreamInfo currentInfo;
protected PlayQueueItem currentItem; protected PlayQueueItem currentItem;
@ -160,9 +143,10 @@ public abstract class BasePlayer implements Player.EventListener,
// Player // Player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds protected final static int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
public int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds protected final static int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds
public static final String CACHE_FOLDER_NAME = "exoplayer"; protected final static int PROGRESS_LOOP_INTERVAL = 500;
protected final static String CACHE_FOLDER_NAME = "exoplayer";
protected SimpleExoPlayer simpleExoPlayer; protected SimpleExoPlayer simpleExoPlayer;
protected boolean isPrepared = false; protected boolean isPrepared = false;
@ -172,7 +156,6 @@ public abstract class BasePlayer implements Player.EventListener,
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
protected int PROGRESS_LOOP_INTERVAL = 500;
protected Disposable progressUpdateReactor; protected Disposable progressUpdateReactor;
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -269,13 +252,6 @@ public abstract class BasePlayer implements Player.EventListener,
return; return;
} }
// Resolve playback details
if (intent.hasExtra(RESTORE_QUEUE_INDEX) && intent.hasExtra(START_POSITION)) {
setRecovery(
intent.getIntExtra(RESTORE_QUEUE_INDEX, 0),
intent.getLongExtra(START_POSITION, 0)
);
}
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed())); setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
// Re-initialization // Re-initialization
@ -579,6 +555,7 @@ public abstract class BasePlayer implements Player.EventListener,
if (playQueue == null) return; if (playQueue == null) return;
setRecovery();
if (playQueue.isShuffled()) { if (playQueue.isShuffled()) {
playQueue.unshuffle(); playQueue.unshuffle();
} else { } else {
@ -590,26 +567,31 @@ public abstract class BasePlayer implements Player.EventListener,
// ExoPlayer Listener // ExoPlayer Listener
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override private void recover() {
public void onTimelineChanged(Timeline timeline, Object manifest) {
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
final int currentSourceIndex = playQueue.getIndex(); final int currentSourceIndex = playQueue.getIndex();
final PlayQueueItem currentSourceItem = playQueue.getItem();
// Check if already playing correct window // Check if already playing correct window
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex; final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
// Check if recovering // Check if recovering
if (isCurrentWindowCorrect && isRecovery && queuePos == playQueue.getIndex()) { if (isCurrentWindowCorrect && currentSourceItem != null &&
currentSourceItem.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
// todo: figure out exactly why this is the case // todo: figure out exactly why this is the case
/* Rounding time to nearest second as certain media cannot guarantee a sub-second seek /* 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 */ will complete and the player might get stuck in buffering state forever */
final long roundedPos = (videoPos / 1000) * 1000; final long roundedPos = (currentSourceItem.getRecoveryPosition() / 1000) * 1000;
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)roundedPos)); if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)roundedPos));
simpleExoPlayer.seekTo(roundedPos); simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition());
isRecovery = false; currentSourceItem.resetRecoveryPosition();
} }
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
if (playbackManager != null) { if (playbackManager != null) {
playbackManager.load(); playbackManager.load();
@ -653,6 +635,8 @@ public abstract class BasePlayer implements Player.EventListener,
} }
break; break;
case Player.STATE_READY: //3 case Player.STATE_READY: //3
recover();
if (!isPrepared) { if (!isPrepared) {
isPrepared = true; isPrepared = true;
onPrepared(playWhenReady); onPrepared(playWhenReady);
@ -664,8 +648,7 @@ public abstract class BasePlayer implements Player.EventListener,
case Player.STATE_ENDED: // 4 case Player.STATE_ENDED: // 4
// Ensure the current window has actually ended // Ensure the current window has actually ended
// since single windows that are still loading may produce an ended state // since single windows that are still loading may produce an ended state
if (simpleExoPlayer.isCurrentWindowSeekable() && if (simpleExoPlayer.getDuration() > 0 && simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()) {
simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()) {
changeState(STATE_COMPLETED); changeState(STATE_COMPLETED);
isPrepared = false; isPrepared = false;
} }
@ -812,11 +795,8 @@ public abstract class BasePlayer implements Player.EventListener,
else audioManager.abandonAudioFocus(this); else audioManager.abandonAudioFocus(this);
if (getCurrentState() == STATE_COMPLETED) { if (getCurrentState() == STATE_COMPLETED) {
if (playQueue.getIndex() == 0) { playQueue.setIndex(0);
simpleExoPlayer.seekToDefaultPosition(); simpleExoPlayer.seekToDefaultPosition();
} else {
playQueue.setIndex(0);
}
} }
simpleExoPlayer.setPlayWhenReady(!isPlaying()); simpleExoPlayer.setPlayWhenReady(!isPlaying());
@ -877,10 +857,6 @@ public abstract class BasePlayer implements Player.EventListener,
simpleExoPlayer.seekTo(progress); simpleExoPlayer.seekTo(progress);
} }
public boolean isPlaying() {
return simpleExoPlayer.getPlaybackState() == Player.STATE_READY && simpleExoPlayer.getPlayWhenReady();
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -921,24 +897,6 @@ public abstract class BasePlayer implements Player.EventListener,
progressUpdateReactor = null; progressUpdateReactor = null;
} }
protected void tryDeleteCacheFiles(Context context) {
File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (cacheDir.exists()) {
try {
if (cacheDir.isDirectory()) {
for (File file : cacheDir.listFiles()) {
try {
if (DEBUG) Log.d(TAG, "tryDeleteCacheFiles: " + file.getAbsolutePath() + " deleted = " + file.delete());
} catch (Exception ignored) {
}
}
}
} catch (Exception ignored) {
}
}
}
public void triggerProgressUpdate() { public void triggerProgressUpdate() {
onUpdateProgress( onUpdateProgress(
(int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getCurrentPosition(),
@ -992,10 +950,6 @@ public abstract class BasePlayer implements Player.EventListener,
return currentState; return currentState;
} }
public long getVideoPos() {
return videoPos;
}
public String getVideoUrl() { public String getVideoUrl() {
return currentItem == null ? null : currentItem.getUrl(); return currentItem == null ? null : currentItem.getUrl();
} }
@ -1012,12 +966,8 @@ public abstract class BasePlayer implements Player.EventListener,
return simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == Player.STATE_ENDED; return simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == Player.STATE_ENDED;
} }
public boolean isPrepared() { public boolean isPlaying() {
return isPrepared; return simpleExoPlayer.getPlaybackState() == Player.STATE_READY && simpleExoPlayer.getPlayWhenReady();
}
public void setPrepared(boolean prepared) {
isPrepared = prepared;
} }
public float getPlaybackSpeed() { public float getPlaybackSpeed() {
@ -1045,18 +995,10 @@ public abstract class BasePlayer implements Player.EventListener,
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch)); simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch));
} }
public int getCurrentQueueIndex() {
return playQueue != null ? playQueue.getIndex() : -1;
}
public int getCurrentResolutionTarget() { public int getCurrentResolutionTarget() {
return trackSelector != null ? trackSelector.getParameters().maxVideoHeight : Integer.MAX_VALUE; return trackSelector != null ? trackSelector.getParameters().maxVideoHeight : Integer.MAX_VALUE;
} }
public long getPlayerCurrentPosition() {
return simpleExoPlayer != null ? simpleExoPlayer.getCurrentPosition() : 0L;
}
public PlayQueue getPlayQueue() { public PlayQueue getPlayQueue() {
return playQueue; return playQueue;
} }
@ -1069,14 +1011,19 @@ public abstract class BasePlayer implements Player.EventListener,
return progressUpdateReactor != null && !progressUpdateReactor.isDisposed(); return progressUpdateReactor != null && !progressUpdateReactor.isDisposed();
} }
public boolean getRecovery() { public void setRecovery() {
return isRecovery; if (playQueue == null || simpleExoPlayer == null) return;
final int queuePos = playQueue.getIndex();
final long windowPos = simpleExoPlayer.getCurrentPosition();
setRecovery(queuePos, windowPos);
} }
public void setRecovery(final int queuePos, final long windowPos) { public void setRecovery(final int queuePos, final long windowPos) {
if (playQueue.size() <= queuePos) return;
if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos); if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
this.isRecovery = true; playQueue.getItem(queuePos).setRecoveryPosition(windowPos);
this.queuePos = queuePos;
this.videoPos = windowPos;
} }
} }

View File

@ -126,10 +126,7 @@ public final class MainVideoPlayer extends Activity {
if (playerImpl.getPlayer() != null) { if (playerImpl.getPlayer() != null) {
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady(); playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
playerImpl.setRecovery( playerImpl.setRecovery();
playerImpl.getCurrentQueueIndex(),
(int) playerImpl.getPlayer().getCurrentPosition()
);
playerImpl.destroyPlayer(); playerImpl.destroyPlayer();
} }
} }
@ -224,6 +221,8 @@ public final class MainVideoPlayer extends Activity {
private ImageButton screenRotationButton; private ImageButton screenRotationButton;
private ImageButton playPauseButton; private ImageButton playPauseButton;
private ImageButton playPreviousButton;
private ImageButton playNextButton;
private RelativeLayout queueLayout; private RelativeLayout queueLayout;
private ImageButton itemsListCloseButton; private ImageButton itemsListCloseButton;
@ -248,6 +247,8 @@ public final class MainVideoPlayer extends Activity {
this.screenRotationButton = rootView.findViewById(R.id.screenRotationButton); this.screenRotationButton = rootView.findViewById(R.id.screenRotationButton);
this.playPauseButton = rootView.findViewById(R.id.playPauseButton); this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
this.playNextButton = rootView.findViewById(R.id.playNextButton);
getRootView().setKeepScreenOn(true); getRootView().setKeepScreenOn(true);
} }
@ -264,6 +265,8 @@ public final class MainVideoPlayer extends Activity {
queueButton.setOnClickListener(this); queueButton.setOnClickListener(this);
repeatButton.setOnClickListener(this); repeatButton.setOnClickListener(this);
playPauseButton.setOnClickListener(this); playPauseButton.setOnClickListener(this);
playPreviousButton.setOnClickListener(this);
playNextButton.setOnClickListener(this);
screenRotationButton.setOnClickListener(this); screenRotationButton.setOnClickListener(this);
} }
@ -315,13 +318,12 @@ public final class MainVideoPlayer extends Activity {
return; return;
} }
setRecovery();
final Intent intent = NavigationHelper.getPlayerIntent( final Intent intent = NavigationHelper.getPlayerIntent(
context, context,
PopupVideoPlayer.class, PopupVideoPlayer.class,
this.getPlayQueue(), this.getPlayQueue(),
this.getCurrentResolutionTarget(), this.getCurrentResolutionTarget(),
this.getCurrentQueueIndex(),
this.getPlayerCurrentPosition(),
this.getPlaybackSpeed() this.getPlaybackSpeed()
); );
context.startService(intent); context.startService(intent);
@ -340,6 +342,12 @@ public final class MainVideoPlayer extends Activity {
} else if (v.getId() == playPauseButton.getId()) { } else if (v.getId() == playPauseButton.getId()) {
onVideoPlayPause(); onVideoPlayPause();
} else if (v.getId() == playPreviousButton.getId()) {
onPlayPrevious();
} else if (v.getId() == playNextButton.getId()) {
onPlayNext();
} else if (v.getId() == screenRotationButton.getId()) { } else if (v.getId() == screenRotationButton.getId()) {
onScreenRotationClicked(); onScreenRotationClicked();
@ -367,6 +375,7 @@ public final class MainVideoPlayer extends Activity {
hideSystemUi(); hideSystemUi();
getControlsRoot().setVisibility(View.INVISIBLE); getControlsRoot().setVisibility(View.INVISIBLE);
queueLayout.setVisibility(View.VISIBLE); queueLayout.setVisibility(View.VISIBLE);
itemsList.smoothScrollToPosition(playQueue.getIndex());
} }
private void onQueueClosed() { private void onQueueClosed() {
@ -410,18 +419,24 @@ public final class MainVideoPlayer extends Activity {
// States // States
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void animatePlayButtons(final boolean show, final int duration) {
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
animateView(playPreviousButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
animateView(playNextButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
}
@Override @Override
public void onBlocked() { public void onBlocked() {
super.onBlocked(); super.onBlocked();
playPauseButton.setImageResource(R.drawable.ic_pause_white); playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100); animatePlayButtons(false, 100);
getRootView().setKeepScreenOn(true); getRootView().setKeepScreenOn(true);
} }
@Override @Override
public void onBuffering() { public void onBuffering() {
super.onBuffering(); super.onBuffering();
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100); animatePlayButtons(false, 100);
getRootView().setKeepScreenOn(true); getRootView().setKeepScreenOn(true);
} }
@ -432,7 +447,7 @@ public final class MainVideoPlayer extends Activity {
@Override @Override
public void run() { public void run() {
playPauseButton.setImageResource(R.drawable.ic_pause_white); playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 200); animatePlayButtons(true, 200);
} }
}); });
showSystemUi(); showSystemUi();
@ -446,7 +461,7 @@ public final class MainVideoPlayer extends Activity {
@Override @Override
public void run() { public void run() {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white); playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 200); animatePlayButtons(true, 200);
} }
}); });
@ -457,7 +472,7 @@ public final class MainVideoPlayer extends Activity {
@Override @Override
public void onPausedSeek() { public void onPausedSeek() {
super.onPausedSeek(); super.onPausedSeek();
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100); animatePlayButtons(false, 100);
getRootView().setKeepScreenOn(true); getRootView().setKeepScreenOn(true);
} }
@ -469,7 +484,7 @@ public final class MainVideoPlayer extends Activity {
@Override @Override
public void run() { public void run() {
playPauseButton.setImageResource(R.drawable.ic_replay_white); playPauseButton.setImageResource(R.drawable.ic_replay_white);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 300); animatePlayButtons(true, 300);
} }
}); });
@ -619,15 +634,12 @@ public final class MainVideoPlayer extends Activity {
@Override @Override
public boolean onDoubleTap(MotionEvent e) { public boolean onDoubleTap(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
//if (!playerImpl.isPlaying()) return false; if (!playerImpl.isPlaying()) return false;
if (!playerImpl.isPlayerReady()) return false;
if (e.getX() > playerImpl.getRootView().getWidth() / 2) if (e.getX() > playerImpl.getRootView().getWidth() / 2)
playerImpl.onPlayNext(); playerImpl.onFastForward();
//playerImpl.onFastForward();
else else
playerImpl.onPlayPrevious(); playerImpl.onFastRewind();
//playerImpl.onFastRewind();
return true; return true;
} }

View File

@ -435,6 +435,8 @@ public final class PopupVideoPlayer extends Service {
super.onFullScreenButtonClicked(); super.onFullScreenButtonClicked();
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
playerImpl.setRecovery();
Intent intent; Intent intent;
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) { if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
intent = NavigationHelper.getPlayerIntent( intent = NavigationHelper.getPlayerIntent(
@ -442,8 +444,6 @@ public final class PopupVideoPlayer extends Service {
MainVideoPlayer.class, MainVideoPlayer.class,
this.getPlayQueue(), this.getPlayQueue(),
this.getCurrentResolutionTarget(), this.getCurrentResolutionTarget(),
this.getCurrentQueueIndex(),
this.getPlayerCurrentPosition(),
this.getPlaybackSpeed() this.getPlaybackSpeed()
); );
if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false); if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
@ -703,11 +703,9 @@ public final class PopupVideoPlayer extends Service {
if (!playerImpl.isPlaying() || !playerImpl.isPlayerReady()) return false; if (!playerImpl.isPlaying() || !playerImpl.isPlayerReady()) return false;
if (e.getX() > popupWidth / 2) { if (e.getX() > popupWidth / 2) {
//playerImpl.onFastForward(); playerImpl.onFastForward();
playerImpl.onPlayNext();
} else { } else {
//playerImpl.onFastRewind(); playerImpl.onFastRewind();
playerImpl.onPlayPrevious();
} }
return true; return true;

View File

@ -382,11 +382,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
} else if (view.getId() == backwardButton.getId()) { } else if (view.getId() == backwardButton.getId()) {
player.onPlayPrevious(); player.onPlayPrevious();
scrollToSelected();
} else if (view.getId() == playPauseButton.getId()) { } else if (view.getId() == playPauseButton.getId()) {
player.onVideoPlayPause(); player.onVideoPlayPause();
scrollToSelected();
} else if (view.getId() == forwardButton.getId()) { } else if (view.getId() == forwardButton.getId()) {
player.onPlayNext(); player.onPlayNext();

View File

@ -89,16 +89,12 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
// Intent // Intent
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static final String VIDEO_STREAMS_LIST = "video_streams_list";
public static final String VIDEO_ONLY_AUDIO_STREAM = "video_only_audio_stream";
public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe"; public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
public static final String PLAYER_INTENT = "player_intent";
public static final String MAX_RESOLUTION = "max_resolution"; public static final String MAX_RESOLUTION = "max_resolution";
private ArrayList<VideoStream> availableStreams; private ArrayList<VideoStream> availableStreams;
private int selectedStreamIndex; private int selectedStreamIndex;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Player // Player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/

View File

@ -153,6 +153,10 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
} }
private void onPlayQueueChanged(final PlayQueueMessage event) { private void onPlayQueueChanged(final PlayQueueMessage event) {
if (playQueue.isEmpty()) {
playbackListener.shutdown();
}
// why no pattern matching in Java =( // why no pattern matching in Java =(
switch (event.type()) { switch (event.type()) {
case INIT: case INIT:
@ -168,6 +172,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
case REMOVE: case REMOVE:
final RemoveEvent removeEvent = (RemoveEvent) event; final RemoveEvent removeEvent = (RemoveEvent) event;
remove(removeEvent.index()); remove(removeEvent.index());
sync();
break; break;
case MOVE: case MOVE:
final MoveEvent moveEvent = (MoveEvent) event; final MoveEvent moveEvent = (MoveEvent) event;
@ -181,8 +186,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
if (!isPlayQueueReady()) { if (!isPlayQueueReady()) {
tryBlock(); tryBlock();
playQueue.fetch(); playQueue.fetch();
} else if (playQueue.isEmpty()) {
playbackListener.shutdown();
} else { } else {
load(); // All event warrants a load load(); // All event warrants a load
} }
@ -219,6 +222,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
private void sync() { private void sync() {
final PlayQueueItem currentItem = playQueue.getItem(); final PlayQueueItem currentItem = playQueue.getItem();
if (currentItem == null) return;
final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() { final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() {
@Override @Override

View File

@ -50,10 +50,6 @@ public abstract class PlayQueue implements Serializable {
private transient Flowable<PlayQueueMessage> broadcastReceiver; private transient Flowable<PlayQueueMessage> broadcastReceiver;
private transient Subscription reportingReactor; private transient Subscription reportingReactor;
PlayQueue() {
this(0, Collections.<PlayQueueItem>emptyList());
}
PlayQueue(final int index, final List<PlayQueueItem> startWith) { PlayQueue(final int index, final List<PlayQueueItem> startWith) {
streams = new ArrayList<>(); streams = new ArrayList<>();
streams.addAll(startWith); streams.addAll(startWith);
@ -81,12 +77,9 @@ public abstract class PlayQueue implements Serializable {
} }
/** /**
* Dispose this play queue by stopping all message buses and clearing the playlist. * Dispose the play queue by stopping all message buses.
* */ * */
public void dispose() { public void dispose() {
if (backup != null) backup.clear();
if (streams != null) streams.clear();
if (eventBroadcast != null) eventBroadcast.onComplete(); if (eventBroadcast != null) eventBroadcast.onComplete();
if (reportingReactor != null) reportingReactor.cancel(); if (reportingReactor != null) reportingReactor.cancel();
@ -265,11 +258,12 @@ public abstract class PlayQueue implements Serializable {
private synchronized void removeInternal(final int index) { private synchronized void removeInternal(final int index) {
final int currentIndex = queueIndex.get(); final int currentIndex = queueIndex.get();
final int size = size();
if (currentIndex > index) { if (currentIndex > index) {
queueIndex.decrementAndGet(); queueIndex.decrementAndGet();
} else if (currentIndex >= size()) { } else if (currentIndex >= size) {
queueIndex.set(0); queueIndex.set(currentIndex % (size - 1));
} }
if (backup != null) { if (backup != null) {
@ -300,9 +294,8 @@ public abstract class PlayQueue implements Serializable {
* Shuffles the current play queue. * Shuffles the current play queue.
* *
* This method first backs up the existing play queue and item being played. * This method first backs up the existing play queue and item being played.
* Then a newly shuffled play queue will be generated along with the index of * Then a newly shuffled play queue will be generated along with currently
* the previously playing item if it is found in the shuffled play queue. If * playing item placed at the beginning of the queue.
* not found, the current index will reset to 0.
* *
* Will emit a {@link ReorderEvent} in any context. * Will emit a {@link ReorderEvent} in any context.
* */ * */
@ -315,10 +308,9 @@ public abstract class PlayQueue implements Serializable {
final int newIndex = streams.indexOf(current); final int newIndex = streams.indexOf(current);
if (newIndex != -1) { if (newIndex != -1) {
queueIndex.set(newIndex); streams.add(0, streams.remove(newIndex));
} else {
queueIndex.set(0);
} }
queueIndex.set(0);
broadcast(new ReorderEvent()); broadcast(new ReorderEvent());
} }

View File

@ -15,6 +15,8 @@ import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
public class PlayQueueItem implements Serializable { public class PlayQueueItem implements Serializable {
final public static int DEFAULT_QUALITY = Integer.MIN_VALUE;
final public static long RECOVERY_UNSET = Long.MIN_VALUE;
final private String title; final private String title;
final private String url; final private String url;
@ -23,28 +25,32 @@ public class PlayQueueItem implements Serializable {
final private String thumbnailUrl; final private String thumbnailUrl;
final private String uploader; final private String uploader;
private int qualityIndex;
private long recoveryPosition;
private Throwable error; private Throwable error;
private transient Single<StreamInfo> stream; private transient Single<StreamInfo> stream;
PlayQueueItem(final StreamInfo streamInfo) { PlayQueueItem(@NonNull final StreamInfo info) {
this.title = streamInfo.name; this(info.name, info.url, info.service_id, info.duration, info.thumbnail_url, info.uploader_name);
this.url = streamInfo.url; this.stream = Single.just(info);
this.serviceId = streamInfo.service_id;
this.duration = streamInfo.duration;
this.thumbnailUrl = streamInfo.thumbnail_url;
this.uploader = streamInfo.uploader_name;
this.stream = Single.just(streamInfo);
} }
PlayQueueItem(final StreamInfoItem streamInfoItem) { PlayQueueItem(@NonNull final StreamInfoItem item) {
this.title = streamInfoItem.name; this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name);
this.url = streamInfoItem.url; }
this.serviceId = streamInfoItem.service_id;
this.duration = streamInfoItem.duration; private PlayQueueItem(final String name, final String url, final int serviceId,
this.thumbnailUrl = streamInfoItem.thumbnail_url; final long duration, final String thumbnailUrl, final String uploader) {
this.uploader = streamInfoItem.uploader_name; this.title = name;
this.url = url;
this.serviceId = serviceId;
this.duration = duration;
this.thumbnailUrl = thumbnailUrl;
this.uploader = uploader;
resetQualityIndex();
resetRecoveryPosition();
} }
@NonNull @NonNull
@ -97,4 +103,32 @@ public class PlayQueueItem implements Serializable {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnError(onError); .doOnError(onError);
} }
////////////////////////////////////////////////////////////////////////////
// Item States
////////////////////////////////////////////////////////////////////////////
public int getQualityIndex() {
return qualityIndex;
}
public long getRecoveryPosition() {
return recoveryPosition;
}
public void setQualityIndex(int qualityIndex) {
this.qualityIndex = qualityIndex;
}
public void setRecoveryPosition(long recoveryPosition) {
this.recoveryPosition = recoveryPosition;
}
public void resetQualityIndex() {
this.qualityIndex = DEFAULT_QUALITY;
}
public void resetRecoveryPosition() {
this.recoveryPosition = RECOVERY_UNSET;
}
} }

View File

@ -77,12 +77,8 @@ public class NavigationHelper {
final Class targetClazz, final Class targetClazz,
final PlayQueue playQueue, final PlayQueue playQueue,
final int maxResolution, final int maxResolution,
final int restoringIndex,
final long startPosition,
final float playbackSpeed) { final float playbackSpeed) {
return getPlayerIntent(context, targetClazz, playQueue, maxResolution) return getPlayerIntent(context, targetClazz, playQueue, maxResolution)
.putExtra(VideoPlayer.RESTORE_QUEUE_INDEX, restoringIndex)
.putExtra(BasePlayer.START_POSITION, startPosition)
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed); .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed);
} }

View File

@ -275,6 +275,34 @@
android:src="@drawable/ic_pause_white" android:src="@drawable/ic_pause_white"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/playPreviousButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="30dp"
android:layout_marginEnd="30dp"
android:layout_centerInParent="true"
android:layout_toLeftOf="@id/playPauseButton"
android:layout_toStartOf="@id/playPauseButton"
android:background="#00000000"
android:scaleType="fitXY"
android:src="@drawable/exo_controls_previous"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/playNextButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="30dp"
android:layout_marginStart="30dp"
android:layout_centerInParent="true"
android:layout_toRightOf="@id/playPauseButton"
android:layout_toEndOf="@id/playPauseButton"
android:background="#00000000"
android:scaleType="fitXY"
android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/>
</RelativeLayout> </RelativeLayout>

View File

@ -12,6 +12,7 @@
android:layout_height="64dp" android:layout_height="64dp"
android:background="@color/background_notification_color" android:background="@color/background_notification_color"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
@ -58,6 +59,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:padding="5dp" android:padding="5dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_repeat_white" android:src="@drawable/ic_repeat_white"
@ -69,9 +71,10 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:padding="5dp" android:padding="5dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_rewind" android:src="@drawable/exo_controls_previous"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <ImageButton
@ -80,6 +83,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_pause_white" android:src="@drawable/ic_pause_white"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
@ -89,9 +93,10 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:padding="5dp" android:padding="5dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_forward" android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <ImageButton
@ -101,6 +106,7 @@
android:layout_marginLeft="5dp" android:layout_marginLeft="5dp"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:padding="5dp" android:padding="5dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_close_white_24dp" android:src="@drawable/ic_close_white_24dp"

View File

@ -7,6 +7,7 @@
android:layout_height="128dp" android:layout_height="128dp"
android:background="@color/background_notification_color" android:background="@color/background_notification_color"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
@ -26,6 +27,7 @@
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:padding="8dp" android:padding="8dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_close_white_24dp" android:src="@drawable/ic_close_white_24dp"
@ -82,9 +84,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:layout_alignTop="@+id/notificationProgressBar" android:layout_alignTop="@+id/notificationProgressBar"
android:layout_toRightOf="@+id/notificationCover" android:layout_toRightOf="@+id/notificationCover"
android:layout_toEndOf="@+id/notificationCover"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textSize="12sp" android:textSize="12sp"
@ -109,6 +113,7 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/ic_repeat_white" android:src="@drawable/ic_repeat_white"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
@ -122,9 +127,10 @@
android:layout_toLeftOf="@+id/notificationPlayPause" android:layout_toLeftOf="@+id/notificationPlayPause"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:padding="2dp" android:padding="2dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_rewind" android:src="@drawable/exo_controls_previous"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<ImageButton <ImageButton
@ -137,6 +143,7 @@
android:background="#00000000" android:background="#00000000"
android:padding="2dp" android:padding="2dp"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_pause_white" android:src="@drawable/ic_pause_white"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
@ -150,107 +157,10 @@
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:background="#00000000" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:padding="2dp" android:padding="2dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_forward" android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>
<!--
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notificationContent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:background="@color/background_notification_color">
<ImageView
android:id="@+id/notificationCover"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginRight="8dp"
android:src="@drawable/dummy_thumbnail"
android:scaleType="centerCrop"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="@+id/notificationButtons"
android:layout_toRightOf="@+id/notificationCover"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/notificationSongName"
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="40dp"
android:ellipsize="marquee"
android:singleLine="true"
android:text="title" />
<TextView
android:id="@+id/notificationArtist"
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text="artist" />
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/playbackProgress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_marginRight="8dp" />
</LinearLayout>
<ImageButton
android:id="@+id/notificationStop"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_margin="5dp"
android:background="#00ffffff"
android:clickable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_close_white" />
<RelativeLayout
android:id="@+id/notificationButtons"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignBottom="@id/notificationCover"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/notificationCover"
android:orientation="horizontal" >
<ImageButton
android:id="@+id/notificationPlayPause"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#00ffffff"
android:clickable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_pause_white"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<ImageButton
android:id="@+id/notificationRewind"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#00ffffff"
android:clickable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_action_av_fast_rewind"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>
</RelativeLayout>-->

View File

@ -303,5 +303,5 @@
<string name="play_queue_remove">Remove</string> <string name="play_queue_remove">Remove</string>
<string name="play_queue_stream_detail">Details</string> <string name="play_queue_stream_detail">Details</string>
<string name="play_queue_audio_settings">Audio Settings</string> <string name="play_queue_audio_settings">Audio Settings</string>
<string name="hold_to_append">Hold To Append</string> <string name="hold_to_append">Hold To Enqueue</string>
</resources> </resources>