-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:
parent
21d42c92e5
commit
4553850412
|
@ -93,6 +93,7 @@ public final class BackgroundPlayer extends Service {
|
|||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Notification
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static final int NOTIFICATION_ID = 123789;
|
||||
private NotificationManager notificationManager;
|
||||
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 setImageResourceMethodName = "setImageResource";
|
||||
|
||||
private boolean shouldUpdateOnProgress;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service's LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -117,6 +120,7 @@ public final class BackgroundPlayer extends Service {
|
|||
basePlayerImpl.setup();
|
||||
|
||||
mBinder = new LocalBinder();
|
||||
shouldUpdateOnProgress = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -166,12 +170,9 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
private void onScreenOnOff(boolean on) {
|
||||
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||
shouldUpdateOnProgress = on;
|
||||
if (on) {
|
||||
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning()) {
|
||||
basePlayerImpl.startProgressLoop();
|
||||
}
|
||||
} else {
|
||||
basePlayerImpl.stopProgressLoop();
|
||||
basePlayerImpl.triggerProgressUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,14 +325,6 @@ public final class BackgroundPlayer extends Service {
|
|||
@Override
|
||||
public void onPrepared(boolean 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);
|
||||
}
|
||||
|
||||
|
@ -343,6 +336,10 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||
updateProgress(currentProgress, duration, bufferPercent);
|
||||
|
||||
if (!shouldUpdateOnProgress) return;
|
||||
|
||||
resetNotification();
|
||||
if (bigNotRemoteView != null) {
|
||||
if (currentItem != null) {
|
||||
|
@ -361,7 +358,6 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
updateNotification(-1);
|
||||
updateProgress(currentProgress, duration, bufferPercent);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -122,21 +122,8 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
// 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 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";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -149,10 +136,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
protected MediaSourceManager playbackManager;
|
||||
protected PlayQueue playQueue;
|
||||
|
||||
private boolean isRecovery = false;
|
||||
private int queuePos = 0;
|
||||
private long videoPos = -1;
|
||||
|
||||
protected StreamInfo currentInfo;
|
||||
protected PlayQueueItem currentItem;
|
||||
|
||||
|
@ -160,9 +143,10 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
// Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
|
||||
public int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds
|
||||
public static final String CACHE_FOLDER_NAME = "exoplayer";
|
||||
protected final static int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
|
||||
protected final static int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds
|
||||
protected final static int PROGRESS_LOOP_INTERVAL = 500;
|
||||
protected final static String CACHE_FOLDER_NAME = "exoplayer";
|
||||
|
||||
protected SimpleExoPlayer simpleExoPlayer;
|
||||
protected boolean isPrepared = false;
|
||||
|
@ -172,7 +156,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
|
||||
protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
|
||||
protected int PROGRESS_LOOP_INTERVAL = 500;
|
||||
protected Disposable progressUpdateReactor;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -269,13 +252,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
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()));
|
||||
|
||||
// Re-initialization
|
||||
|
@ -579,6 +555,7 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
|
||||
if (playQueue == null) return;
|
||||
|
||||
setRecovery();
|
||||
if (playQueue.isShuffled()) {
|
||||
playQueue.unshuffle();
|
||||
} else {
|
||||
|
@ -590,26 +567,31 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
// ExoPlayer Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
||||
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
|
||||
|
||||
private void recover() {
|
||||
final int currentSourceIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentSourceItem = playQueue.getItem();
|
||||
|
||||
// Check if already playing correct window
|
||||
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
|
||||
|
||||
// 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
|
||||
/* 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;
|
||||
final long roundedPos = (currentSourceItem.getRecoveryPosition() / 1000) * 1000;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)roundedPos));
|
||||
simpleExoPlayer.seekTo(roundedPos);
|
||||
isRecovery = false;
|
||||
simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition());
|
||||
currentSourceItem.resetRecoveryPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
||||
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
|
||||
|
||||
if (playbackManager != null) {
|
||||
playbackManager.load();
|
||||
|
@ -653,6 +635,8 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
}
|
||||
break;
|
||||
case Player.STATE_READY: //3
|
||||
recover();
|
||||
|
||||
if (!isPrepared) {
|
||||
isPrepared = true;
|
||||
onPrepared(playWhenReady);
|
||||
|
@ -664,8 +648,7 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
case Player.STATE_ENDED: // 4
|
||||
// Ensure the current window has actually ended
|
||||
// since single windows that are still loading may produce an ended state
|
||||
if (simpleExoPlayer.isCurrentWindowSeekable() &&
|
||||
simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()) {
|
||||
if (simpleExoPlayer.getDuration() > 0 && simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()) {
|
||||
changeState(STATE_COMPLETED);
|
||||
isPrepared = false;
|
||||
}
|
||||
|
@ -812,11 +795,8 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
else audioManager.abandonAudioFocus(this);
|
||||
|
||||
if (getCurrentState() == STATE_COMPLETED) {
|
||||
if (playQueue.getIndex() == 0) {
|
||||
simpleExoPlayer.seekToDefaultPosition();
|
||||
} else {
|
||||
playQueue.setIndex(0);
|
||||
}
|
||||
simpleExoPlayer.seekToDefaultPosition();
|
||||
}
|
||||
|
||||
simpleExoPlayer.setPlayWhenReady(!isPlaying());
|
||||
|
@ -877,10 +857,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
simpleExoPlayer.seekTo(progress);
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return simpleExoPlayer.getPlaybackState() == Player.STATE_READY && simpleExoPlayer.getPlayWhenReady();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -921,24 +897,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
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() {
|
||||
onUpdateProgress(
|
||||
(int) simpleExoPlayer.getCurrentPosition(),
|
||||
|
@ -992,10 +950,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
return currentState;
|
||||
}
|
||||
|
||||
public long getVideoPos() {
|
||||
return videoPos;
|
||||
}
|
||||
|
||||
public String getVideoUrl() {
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isPrepared() {
|
||||
return isPrepared;
|
||||
}
|
||||
|
||||
public void setPrepared(boolean prepared) {
|
||||
isPrepared = prepared;
|
||||
public boolean isPlaying() {
|
||||
return simpleExoPlayer.getPlaybackState() == Player.STATE_READY && simpleExoPlayer.getPlayWhenReady();
|
||||
}
|
||||
|
||||
public float getPlaybackSpeed() {
|
||||
|
@ -1045,18 +995,10 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch));
|
||||
}
|
||||
|
||||
public int getCurrentQueueIndex() {
|
||||
return playQueue != null ? playQueue.getIndex() : -1;
|
||||
}
|
||||
|
||||
public int getCurrentResolutionTarget() {
|
||||
return trackSelector != null ? trackSelector.getParameters().maxVideoHeight : Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public long getPlayerCurrentPosition() {
|
||||
return simpleExoPlayer != null ? simpleExoPlayer.getCurrentPosition() : 0L;
|
||||
}
|
||||
|
||||
public PlayQueue getPlayQueue() {
|
||||
return playQueue;
|
||||
}
|
||||
|
@ -1069,14 +1011,19 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
return progressUpdateReactor != null && !progressUpdateReactor.isDisposed();
|
||||
}
|
||||
|
||||
public boolean getRecovery() {
|
||||
return isRecovery;
|
||||
public void setRecovery() {
|
||||
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) {
|
||||
if (playQueue.size() <= queuePos) return;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
|
||||
this.isRecovery = true;
|
||||
this.queuePos = queuePos;
|
||||
this.videoPos = windowPos;
|
||||
playQueue.getItem(queuePos).setRecoveryPosition(windowPos);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,10 +126,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
|
||||
if (playerImpl.getPlayer() != null) {
|
||||
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
|
||||
playerImpl.setRecovery(
|
||||
playerImpl.getCurrentQueueIndex(),
|
||||
(int) playerImpl.getPlayer().getCurrentPosition()
|
||||
);
|
||||
playerImpl.setRecovery();
|
||||
playerImpl.destroyPlayer();
|
||||
}
|
||||
}
|
||||
|
@ -224,6 +221,8 @@ public final class MainVideoPlayer extends Activity {
|
|||
|
||||
private ImageButton screenRotationButton;
|
||||
private ImageButton playPauseButton;
|
||||
private ImageButton playPreviousButton;
|
||||
private ImageButton playNextButton;
|
||||
|
||||
private RelativeLayout queueLayout;
|
||||
private ImageButton itemsListCloseButton;
|
||||
|
@ -248,6 +247,8 @@ public final class MainVideoPlayer extends Activity {
|
|||
|
||||
this.screenRotationButton = rootView.findViewById(R.id.screenRotationButton);
|
||||
this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
|
||||
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
|
||||
this.playNextButton = rootView.findViewById(R.id.playNextButton);
|
||||
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
@ -264,6 +265,8 @@ public final class MainVideoPlayer extends Activity {
|
|||
queueButton.setOnClickListener(this);
|
||||
repeatButton.setOnClickListener(this);
|
||||
playPauseButton.setOnClickListener(this);
|
||||
playPreviousButton.setOnClickListener(this);
|
||||
playNextButton.setOnClickListener(this);
|
||||
screenRotationButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
|
@ -315,13 +318,12 @@ public final class MainVideoPlayer extends Activity {
|
|||
return;
|
||||
}
|
||||
|
||||
setRecovery();
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
context,
|
||||
PopupVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getCurrentResolutionTarget(),
|
||||
this.getCurrentQueueIndex(),
|
||||
this.getPlayerCurrentPosition(),
|
||||
this.getPlaybackSpeed()
|
||||
);
|
||||
context.startService(intent);
|
||||
|
@ -340,6 +342,12 @@ public final class MainVideoPlayer extends Activity {
|
|||
} else if (v.getId() == playPauseButton.getId()) {
|
||||
onVideoPlayPause();
|
||||
|
||||
} else if (v.getId() == playPreviousButton.getId()) {
|
||||
onPlayPrevious();
|
||||
|
||||
} else if (v.getId() == playNextButton.getId()) {
|
||||
onPlayNext();
|
||||
|
||||
} else if (v.getId() == screenRotationButton.getId()) {
|
||||
onScreenRotationClicked();
|
||||
|
||||
|
@ -367,6 +375,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
hideSystemUi();
|
||||
getControlsRoot().setVisibility(View.INVISIBLE);
|
||||
queueLayout.setVisibility(View.VISIBLE);
|
||||
itemsList.smoothScrollToPosition(playQueue.getIndex());
|
||||
}
|
||||
|
||||
private void onQueueClosed() {
|
||||
|
@ -410,18 +419,24 @@ public final class MainVideoPlayer extends Activity {
|
|||
// 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
|
||||
public void onBlocked() {
|
||||
super.onBlocked();
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100);
|
||||
animatePlayButtons(false, 100);
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuffering() {
|
||||
super.onBuffering();
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100);
|
||||
animatePlayButtons(false, 100);
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
|
@ -432,7 +447,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
@Override
|
||||
public void run() {
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 200);
|
||||
animatePlayButtons(true, 200);
|
||||
}
|
||||
});
|
||||
showSystemUi();
|
||||
|
@ -446,7 +461,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
@Override
|
||||
public void run() {
|
||||
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
|
||||
public void onPausedSeek() {
|
||||
super.onPausedSeek();
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100);
|
||||
animatePlayButtons(false, 100);
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
|
@ -469,7 +484,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
@Override
|
||||
public void run() {
|
||||
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
|
||||
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 (!playerImpl.isPlaying()) return false;
|
||||
if (!playerImpl.isPlayerReady()) return false;
|
||||
if (!playerImpl.isPlaying()) return false;
|
||||
|
||||
if (e.getX() > playerImpl.getRootView().getWidth() / 2)
|
||||
playerImpl.onPlayNext();
|
||||
//playerImpl.onFastForward();
|
||||
playerImpl.onFastForward();
|
||||
else
|
||||
playerImpl.onPlayPrevious();
|
||||
//playerImpl.onFastRewind();
|
||||
playerImpl.onFastRewind();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -435,6 +435,8 @@ public final class PopupVideoPlayer extends Service {
|
|||
super.onFullScreenButtonClicked();
|
||||
|
||||
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||
|
||||
playerImpl.setRecovery();
|
||||
Intent intent;
|
||||
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
|
||||
intent = NavigationHelper.getPlayerIntent(
|
||||
|
@ -442,8 +444,6 @@ public final class PopupVideoPlayer extends Service {
|
|||
MainVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getCurrentResolutionTarget(),
|
||||
this.getCurrentQueueIndex(),
|
||||
this.getPlayerCurrentPosition(),
|
||||
this.getPlaybackSpeed()
|
||||
);
|
||||
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 (e.getX() > popupWidth / 2) {
|
||||
//playerImpl.onFastForward();
|
||||
playerImpl.onPlayNext();
|
||||
playerImpl.onFastForward();
|
||||
} else {
|
||||
//playerImpl.onFastRewind();
|
||||
playerImpl.onPlayPrevious();
|
||||
playerImpl.onFastRewind();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -382,11 +382,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
} else if (view.getId() == backwardButton.getId()) {
|
||||
player.onPlayPrevious();
|
||||
scrollToSelected();
|
||||
|
||||
} else if (view.getId() == playPauseButton.getId()) {
|
||||
player.onVideoPlayPause();
|
||||
scrollToSelected();
|
||||
|
||||
} else if (view.getId() == forwardButton.getId()) {
|
||||
player.onPlayNext();
|
||||
|
|
|
@ -89,16 +89,12 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
// 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 PLAYER_INTENT = "player_intent";
|
||||
public static final String MAX_RESOLUTION = "max_resolution";
|
||||
|
||||
private ArrayList<VideoStream> availableStreams;
|
||||
private int selectedStreamIndex;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -153,6 +153,10 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueMessage event) {
|
||||
if (playQueue.isEmpty()) {
|
||||
playbackListener.shutdown();
|
||||
}
|
||||
|
||||
// why no pattern matching in Java =(
|
||||
switch (event.type()) {
|
||||
case INIT:
|
||||
|
@ -168,6 +172,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
case REMOVE:
|
||||
final RemoveEvent removeEvent = (RemoveEvent) event;
|
||||
remove(removeEvent.index());
|
||||
sync();
|
||||
break;
|
||||
case MOVE:
|
||||
final MoveEvent moveEvent = (MoveEvent) event;
|
||||
|
@ -181,8 +186,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
if (!isPlayQueueReady()) {
|
||||
tryBlock();
|
||||
playQueue.fetch();
|
||||
} else if (playQueue.isEmpty()) {
|
||||
playbackListener.shutdown();
|
||||
} else {
|
||||
load(); // All event warrants a load
|
||||
}
|
||||
|
@ -219,6 +222,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
|
||||
private void sync() {
|
||||
final PlayQueueItem currentItem = playQueue.getItem();
|
||||
if (currentItem == null) return;
|
||||
|
||||
final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() {
|
||||
@Override
|
||||
|
|
|
@ -50,10 +50,6 @@ public abstract class PlayQueue implements Serializable {
|
|||
private transient Flowable<PlayQueueMessage> broadcastReceiver;
|
||||
private transient Subscription reportingReactor;
|
||||
|
||||
PlayQueue() {
|
||||
this(0, Collections.<PlayQueueItem>emptyList());
|
||||
}
|
||||
|
||||
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
||||
streams = new ArrayList<>();
|
||||
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() {
|
||||
if (backup != null) backup.clear();
|
||||
if (streams != null) streams.clear();
|
||||
|
||||
if (eventBroadcast != null) eventBroadcast.onComplete();
|
||||
if (reportingReactor != null) reportingReactor.cancel();
|
||||
|
||||
|
@ -265,11 +258,12 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
private synchronized void removeInternal(final int index) {
|
||||
final int currentIndex = queueIndex.get();
|
||||
final int size = size();
|
||||
|
||||
if (currentIndex > index) {
|
||||
queueIndex.decrementAndGet();
|
||||
} else if (currentIndex >= size()) {
|
||||
queueIndex.set(0);
|
||||
} else if (currentIndex >= size) {
|
||||
queueIndex.set(currentIndex % (size - 1));
|
||||
}
|
||||
|
||||
if (backup != null) {
|
||||
|
@ -300,9 +294,8 @@ public abstract class PlayQueue implements Serializable {
|
|||
* Shuffles the current play queue.
|
||||
*
|
||||
* This method first backs up the existing play queue and item being played.
|
||||
* Then a newly shuffled play queue will be generated along with the index of
|
||||
* the previously playing item if it is found in the shuffled play queue. If
|
||||
* not found, the current index will reset to 0.
|
||||
* Then a newly shuffled play queue will be generated along with currently
|
||||
* playing item placed at the beginning of the queue.
|
||||
*
|
||||
* Will emit a {@link ReorderEvent} in any context.
|
||||
* */
|
||||
|
@ -315,10 +308,9 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
final int newIndex = streams.indexOf(current);
|
||||
if (newIndex != -1) {
|
||||
queueIndex.set(newIndex);
|
||||
} else {
|
||||
queueIndex.set(0);
|
||||
streams.add(0, streams.remove(newIndex));
|
||||
}
|
||||
queueIndex.set(0);
|
||||
|
||||
broadcast(new ReorderEvent());
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import io.reactivex.functions.Consumer;
|
|||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
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 url;
|
||||
|
@ -23,28 +25,32 @@ public class PlayQueueItem implements Serializable {
|
|||
final private String thumbnailUrl;
|
||||
final private String uploader;
|
||||
|
||||
private int qualityIndex;
|
||||
private long recoveryPosition;
|
||||
private Throwable error;
|
||||
|
||||
private transient Single<StreamInfo> stream;
|
||||
|
||||
PlayQueueItem(final StreamInfo streamInfo) {
|
||||
this.title = streamInfo.name;
|
||||
this.url = streamInfo.url;
|
||||
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(@NonNull final StreamInfo info) {
|
||||
this(info.name, info.url, info.service_id, info.duration, info.thumbnail_url, info.uploader_name);
|
||||
this.stream = Single.just(info);
|
||||
}
|
||||
|
||||
PlayQueueItem(final StreamInfoItem streamInfoItem) {
|
||||
this.title = streamInfoItem.name;
|
||||
this.url = streamInfoItem.url;
|
||||
this.serviceId = streamInfoItem.service_id;
|
||||
this.duration = streamInfoItem.duration;
|
||||
this.thumbnailUrl = streamInfoItem.thumbnail_url;
|
||||
this.uploader = streamInfoItem.uploader_name;
|
||||
PlayQueueItem(@NonNull final StreamInfoItem item) {
|
||||
this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name);
|
||||
}
|
||||
|
||||
private PlayQueueItem(final String name, final String url, final int serviceId,
|
||||
final long duration, final String thumbnailUrl, final String uploader) {
|
||||
this.title = name;
|
||||
this.url = url;
|
||||
this.serviceId = serviceId;
|
||||
this.duration = duration;
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
this.uploader = uploader;
|
||||
|
||||
resetQualityIndex();
|
||||
resetRecoveryPosition();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -97,4 +103,32 @@ public class PlayQueueItem implements Serializable {
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,12 +77,8 @@ public class NavigationHelper {
|
|||
final Class targetClazz,
|
||||
final PlayQueue playQueue,
|
||||
final int maxResolution,
|
||||
final int restoringIndex,
|
||||
final long startPosition,
|
||||
final float playbackSpeed) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue, maxResolution)
|
||||
.putExtra(VideoPlayer.RESTORE_QUEUE_INDEX, restoringIndex)
|
||||
.putExtra(BasePlayer.START_POSITION, startPosition)
|
||||
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed);
|
||||
}
|
||||
|
||||
|
|
|
@ -275,6 +275,34 @@
|
|||
android:src="@drawable/ic_pause_white"
|
||||
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>
|
||||
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
android:layout_height="64dp"
|
||||
android:background="@color/background_notification_color"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
@ -58,6 +59,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_repeat_white"
|
||||
|
@ -69,9 +71,10 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_rewind"
|
||||
android:src="@drawable/exo_controls_previous"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
|
@ -80,6 +83,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_pause_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
|
@ -89,9 +93,10 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_forward"
|
||||
android:src="@drawable/exo_controls_next"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
|
@ -101,6 +106,7 @@
|
|||
android:layout_marginLeft="5dp"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="128dp"
|
||||
android:background="@color/background_notification_color"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
@ -26,6 +27,7 @@
|
|||
android:layout_alignParentRight="true"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
|
@ -82,9 +84,11 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_alignTop="@+id/notificationProgressBar"
|
||||
android:layout_toRightOf="@+id/notificationCover"
|
||||
android:layout_toEndOf="@+id/notificationCover"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="12sp"
|
||||
|
@ -109,6 +113,7 @@
|
|||
android:layout_centerVertical="true"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_repeat_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
@ -122,9 +127,10 @@
|
|||
android:layout_toLeftOf="@+id/notificationPlayPause"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_rewind"
|
||||
android:src="@drawable/exo_controls_previous"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
|
@ -137,6 +143,7 @@
|
|||
android:background="#00000000"
|
||||
android:padding="2dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_pause_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
@ -150,107 +157,10 @@
|
|||
android:layout_marginRight="8dp"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_forward"
|
||||
android:src="@drawable/exo_controls_next"
|
||||
tools:ignore="ContentDescription"/>
|
||||
</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>-->
|
||||
|
|
|
@ -303,5 +303,5 @@
|
|||
<string name="play_queue_remove">Remove</string>
|
||||
<string name="play_queue_stream_detail">Details</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>
|
||||
|
|
Loading…
Reference in New Issue