-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
//////////////////////////////////////////////////////////////////////////*/
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

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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();

View File

@ -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
//////////////////////////////////////////////////////////////////////////*/

View File

@ -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

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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"

View File

@ -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>-->

View File

@ -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>