From 906824785622fc639b36d31308b7bdd65a6548db Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sun, 22 Oct 2017 18:58:01 -0700 Subject: [PATCH] -Reverted manual track selection from exoplayer track selector. -Added quality record to play queue items. -Added quality and recovery record play queue events. -Added landscape view for ServicePlayerActivity. -Moved repeat and shuffle button to play queue panel in main video player. -Fixed potential NPE in MediaSourceManager by no longer nulling play queue on dispose. -Renamed PlayQueueEvent to PlayQueueEventType. -Renamed PlayQueueMessage to PlayQueueEvent. --- .../fragments/detail/VideoDetailFragment.java | 16 +- .../list/playlist/PlaylistFragment.java | 11 + .../newpipe/player/BackgroundPlayer.java | 6 +- .../org/schabi/newpipe/player/BasePlayer.java | 26 +- .../newpipe/player/MainVideoPlayer.java | 69 +++-- .../newpipe/player/PopupVideoPlayer.java | 11 +- .../newpipe/player/ServicePlayerActivity.java | 8 +- .../schabi/newpipe/player/VideoPlayer.java | 192 ++++-------- .../mediasource/DeferredMediaSource.java | 17 +- .../player/playback/MediaSourceManager.java | 18 +- .../player/playback/PlaybackListener.java | 2 +- .../schabi/newpipe/playlist/PlayQueue.java | 63 +++- .../newpipe/playlist/PlayQueueAdapter.java | 12 +- .../newpipe/playlist/PlayQueueItem.java | 41 ++- .../newpipe/playlist/SinglePlayQueue.java | 4 + .../newpipe/playlist/events/AppendEvent.java | 6 +- .../newpipe/playlist/events/ErrorEvent.java | 6 +- .../newpipe/playlist/events/InitEvent.java | 6 +- .../newpipe/playlist/events/MoveEvent.java | 6 +- .../playlist/events/PlayQueueEvent.java | 23 +- .../playlist/events/PlayQueueEventType.java | 30 ++ .../playlist/events/PlayQueueMessage.java | 7 - .../newpipe/playlist/events/QualityEvent.java | 31 ++ .../playlist/events/RecoveryEvent.java | 25 ++ .../newpipe/playlist/events/RemoveEvent.java | 6 +- .../newpipe/playlist/events/ReorderEvent.java | 6 +- .../newpipe/playlist/events/SelectEvent.java | 6 +- .../org/schabi/newpipe/util/Localization.java | 9 - .../schabi/newpipe/util/NavigationHelper.java | 26 +- .../activity_player_queue_control.xml | 274 ++++++++++++++++++ .../main/res/layout/activity_main_player.xml | 87 ++++-- .../layout/activity_player_queue_control.xml | 4 +- 32 files changed, 696 insertions(+), 358 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/events/PlayQueueEventType.java delete mode 100644 app/src/main/java/org/schabi/newpipe/playlist/events/PlayQueueMessage.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/events/QualityEvent.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/events/RecoveryEvent.java create mode 100644 app/src/main/res/layout-land/activity_player_queue_control.xml diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 1e5e4f5d2..72fb47cde 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -788,16 +788,14 @@ public class VideoDetailFragment extends BaseStateFragment implement ((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream()); } - final PlayQueue playQueue = new SinglePlayQueue(currentInfo); - final VideoStream candidate = sortedStreamVideosList.get(actionBarHandler.getSelectedVideoStream()); - + final PlayQueue playQueue = new SinglePlayQueue(currentInfo, actionBarHandler.getSelectedVideoStream()); final Intent intent; if (append) { Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); - intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, true); + intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue); } else { Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, Localization.resolutionOf(candidate.resolution)); + intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue); } activity.startService(intent); } @@ -819,10 +817,11 @@ public class VideoDetailFragment extends BaseStateFragment implement private void openNormalBackgroundPlayer(final boolean append) { final PlayQueue playQueue = new SinglePlayQueue(currentInfo); - activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue, append)); if (append) { + activity.startService(NavigationHelper.getPlayerEnqueueIntent(activity, BackgroundPlayer.class, playQueue)); Toast.makeText(activity, R.string.background_player_append, Toast.LENGTH_SHORT).show(); } else { + activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue)); Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show(); } } @@ -867,9 +866,8 @@ public class VideoDetailFragment extends BaseStateFragment implement || (Build.VERSION.SDK_INT < 16); if (!useOldPlayer) { // ExoPlayer - final PlayQueue playQueue = new SinglePlayQueue(currentInfo); - final VideoStream candidate = sortedStreamVideosList.get(actionBarHandler.getSelectedVideoStream()); - mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, Localization.resolutionOf(candidate.resolution)); + final PlayQueue playQueue = new SinglePlayQueue(currentInfo, actionBarHandler.getSelectedVideoStream()); + mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue); } else { // Internal Player mIntent = new Intent(activity, PlayVideoActivity.class) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index af7e10435..b88d54524 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -1,11 +1,13 @@ package org.schabi.newpipe.fragments.list.playlist; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -14,6 +16,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.ListExtractor; @@ -29,6 +32,7 @@ import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PermissionHelper; import io.reactivex.Single; @@ -162,6 +166,13 @@ public class PlaylistFragment extends BaseListInfoFragment { headerPopupButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) { + Toast toast = Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG); + TextView messageView = toast.getView().findViewById(android.R.id.message); + if (messageView != null) messageView.setGravity(Gravity.CENTER); + toast.show(); + return; + } activity.startService(buildPlaylistIntent(PopupVideoPlayer.class)); } }); diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 9f7cbe58f..ce13b02fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -290,7 +290,9 @@ public final class BackgroundPlayer extends Service { } @Override - protected void postProcess(@NonNull final Intent intent) { + public void handleIntent(final Intent intent) { + super.handleIntent(intent); + resetNotification(); startForeground(NOTIFICATION_ID, notBuilder.build()); @@ -437,7 +439,7 @@ public final class BackgroundPlayer extends Service { } @Override - public MediaSource sourceOf(final StreamInfo info) { + public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams); if (index < 0) return null; diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 20ad90aa1..cffd024b5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -122,6 +122,8 @@ public abstract class BasePlayer implements Player.EventListener, // Intent //////////////////////////////////////////////////////////////////////////*/ + public static final String REPEAT_MODE = "repeat_mode"; + public static final String PLAYBACK_PITCH = "playback_pitch"; public static final String PLAYBACK_SPEED = "playback_speed"; public static final String PLAY_QUEUE = "play_queue"; public static final String APPEND_ONLY = "append_only"; @@ -234,8 +236,6 @@ public abstract class BasePlayer implements Player.EventListener, }); } - protected abstract void postProcess(@NonNull final Intent intent); - public void handleIntent(Intent intent) { if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); if (intent == null) return; @@ -253,6 +253,7 @@ public abstract class BasePlayer implements Player.EventListener, } setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed())); + setPlaybackPitch(intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch())); // Re-initialization destroyPlayer(); @@ -262,7 +263,6 @@ public abstract class BasePlayer implements Player.EventListener, // Good to go... initPlayback(this, queue); - postProcess(intent); } protected void initPlayback(@NonNull final PlaybackListener listener, @NonNull final PlayQueue queue) { @@ -288,7 +288,6 @@ public abstract class BasePlayer implements Player.EventListener, }); } - public void onThumbnailReceived(Bitmap thumbnail) { if (DEBUG) Log.d(TAG, "onThumbnailReceived() called with: thumbnail = [" + thumbnail + "]"); } @@ -470,7 +469,6 @@ public abstract class BasePlayer implements Player.EventListener, public static final int STATE_PAUSED_SEEK = 127; public static final int STATE_COMPLETED = 128; - protected int currentState = -1; public void changeState(int state) { @@ -577,15 +575,13 @@ public abstract class BasePlayer implements Player.EventListener, // Check if recovering if (isCurrentWindowCorrect && currentSourceItem != null && currentSourceItem.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) { + /* Recovering with sub-second position may cause a long buffer delay in ExoPlayer, + * rounding this position to the nearest second will help alleviate this.*/ + final long position = currentSourceItem.getRecoveryPosition(); - // 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 = (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)position)); simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition()); - currentSourceItem.resetRecoveryPosition(); + playQueue.unsetRecovery(currentSourceIndex); } } @@ -995,10 +991,6 @@ public abstract class BasePlayer implements Player.EventListener, simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch)); } - public int getCurrentResolutionTarget() { - return trackSelector != null ? trackSelector.getParameters().maxVideoHeight : Integer.MAX_VALUE; - } - public PlayQueue getPlayQueue() { return playQueue; } @@ -1024,6 +1016,6 @@ public abstract class BasePlayer implements Player.EventListener, if (playQueue.size() <= queuePos) return; if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos); - playQueue.getItem(queuePos).setRecoveryPosition(windowPos); + playQueue.setRecovery(queuePos, windowPos); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 2b3a7cef3..e180c6d0d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -28,7 +28,6 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; @@ -52,7 +51,6 @@ import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItemBuilder; import org.schabi.newpipe.playlist.PlayQueueItemHolder; import org.schabi.newpipe.util.AnimationUtils; -import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -208,6 +206,15 @@ public final class MainVideoPlayer extends Activity { } } + protected void setShuffleButton(final ImageButton shuffleButton, final boolean shuffled) { + final int shuffleAlpha = shuffled ? 255 : 77; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + shuffleButton.setImageAlpha(shuffleAlpha); + } else { + shuffleButton.setAlpha(shuffleAlpha); + } + } + /////////////////////////////////////////////////////////////////////////// @SuppressWarnings({"unused", "WeakerAccess"}) @@ -216,8 +223,9 @@ public final class MainVideoPlayer extends Activity { private TextView channelTextView; private TextView volumeTextView; private TextView brightnessTextView; - private ImageButton repeatButton; private ImageButton queueButton; + private ImageButton repeatButton; + private ImageButton shuffleButton; private ImageButton screenRotationButton; private ImageButton playPauseButton; @@ -242,8 +250,9 @@ public final class MainVideoPlayer extends Activity { this.channelTextView = rootView.findViewById(R.id.channelTextView); this.volumeTextView = rootView.findViewById(R.id.volumeTextView); this.brightnessTextView = rootView.findViewById(R.id.brightnessTextView); - this.repeatButton = rootView.findViewById(R.id.repeatButton); this.queueButton = rootView.findViewById(R.id.queueButton); + this.repeatButton = rootView.findViewById(R.id.repeatButton); + this.shuffleButton = rootView.findViewById(R.id.shuffleButton); this.screenRotationButton = rootView.findViewById(R.id.screenRotationButton); this.playPauseButton = rootView.findViewById(R.id.playPauseButton); @@ -264,18 +273,14 @@ public final class MainVideoPlayer extends Activity { queueButton.setOnClickListener(this); repeatButton.setOnClickListener(this); + shuffleButton.setOnClickListener(this); + playPauseButton.setOnClickListener(this); playPreviousButton.setOnClickListener(this); playNextButton.setOnClickListener(this); screenRotationButton.setOnClickListener(this); } - @Override - public int getPreferredResolution() { - if (sharedPreferences == null || context == null) return Integer.MAX_VALUE; - return Localization.resolutionOf(sharedPreferences.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value))); - } - /*////////////////////////////////////////////////////////////////////////// // ExoPlayer Video Listener //////////////////////////////////////////////////////////////////////////*/ @@ -283,7 +288,7 @@ public final class MainVideoPlayer extends Activity { @Override public void onRepeatModeChanged(int i) { super.onRepeatModeChanged(i); - setRepeatModeButton(repeatButton, simpleExoPlayer.getRepeatMode()); + updatePlaybackButtons(); } /*////////////////////////////////////////////////////////////////////////// @@ -305,6 +310,12 @@ public final class MainVideoPlayer extends Activity { playPauseButton.setImageResource(R.drawable.ic_pause_white); } + @Override + public void onShuffleClicked() { + super.onShuffleClicked(); + updatePlaybackButtons(); + } + @Override public void onFullScreenButtonClicked() { super.onFullScreenButtonClicked(); @@ -323,8 +334,9 @@ public final class MainVideoPlayer extends Activity { context, PopupVideoPlayer.class, this.getPlayQueue(), - this.getCurrentResolutionTarget(), - this.getPlaybackSpeed() + this.simpleExoPlayer.getRepeatMode(), + this.getPlaybackSpeed(), + this.getPlaybackPitch() ); context.startService(intent); destroyPlayer(); @@ -336,10 +348,7 @@ public final class MainVideoPlayer extends Activity { @Override public void onClick(View v) { super.onClick(v); - if (v.getId() == repeatButton.getId()) { - onRepeatClicked(); - - } else if (v.getId() == playPauseButton.getId()) { + if (v.getId() == playPauseButton.getId()) { onVideoPlayPause(); } else if (v.getId() == playPreviousButton.getId()) { @@ -354,6 +363,12 @@ public final class MainVideoPlayer extends Activity { } else if (v.getId() == queueButton.getId()) { onQueueClicked(); return; + } else if (v.getId() == repeatButton.getId()) { + onRepeatClicked(); + return; + } else if (v.getId() == shuffleButton.getId()) { + onShuffleClicked(); + return; } if (getCurrentState() != STATE_COMPLETED) { @@ -371,10 +386,14 @@ public final class MainVideoPlayer extends Activity { private void onQueueClicked() { queueVisible = true; - buildQueue(); hideSystemUi(); + + buildQueue(); + updatePlaybackButtons(); + getControlsRoot().setVisibility(View.INVISIBLE); queueLayout.setVisibility(View.VISIBLE); + itemsList.smoothScrollToPosition(playQueue.getIndex()); } @@ -527,12 +546,20 @@ public final class MainVideoPlayer extends Activity { }, delay); } + private void updatePlaybackButtons() { + if (repeatButton == null || shuffleButton == null || + simpleExoPlayer == null || playQueue == null) return; + + setRepeatModeButton(repeatButton, simpleExoPlayer.getRepeatMode()); + setShuffleButton(shuffleButton, playQueue.isShuffled()); + } + private void buildQueue() { - queueLayout = findViewById(R.id.play_queue_control); + queueLayout = findViewById(R.id.playQueuePanel); - itemsListCloseButton = findViewById(R.id.play_queue_close_area); + itemsListCloseButton = findViewById(R.id.playQueueClose); - itemsList = findViewById(R.id.play_queue); + itemsList = findViewById(R.id.playQueue); itemsList.setAdapter(playQueueAdapter); itemsList.setClickable(true); itemsList.setLongClickable(true); diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index c48e3e3b2..fad03adba 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -405,12 +405,6 @@ public final class PopupVideoPlayer extends Service { resizingIndicator = rootView.findViewById(R.id.resizing_indicator); } - @Override - public int getPreferredResolution() { - if (sharedPreferences == null || context == null) return Integer.MAX_VALUE; - return Localization.resolutionOf(sharedPreferences.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value))); - } - @Override public void destroy() { super.destroy(); @@ -443,8 +437,9 @@ public final class PopupVideoPlayer extends Service { context, MainVideoPlayer.class, this.getPlayQueue(), - this.getCurrentResolutionTarget(), - this.getPlaybackSpeed() + this.simpleExoPlayer.getRepeatMode(), + this.getPlaybackSpeed(), + this.getPlaybackPitch() ); if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 2c20e0740..2da095b3a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -115,6 +115,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity bind(); } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_play_queue, menu); @@ -164,7 +169,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity serviceBound = false; stopPlayerListener(); player = null; - finish(); } } @@ -181,6 +185,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity player = playerFrom(service); if (player == null || player.playQueue == null || player.playQueueAdapter == null || player.simpleExoPlayer == null) { unbind(); + finish(); } else { buildComponents(); startPlayerListener(); @@ -460,6 +465,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity @Override public void onServiceStopped() { unbind(); + finish(); } //////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index e61fc2975..196be8229 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -25,7 +25,6 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; @@ -47,18 +46,10 @@ import android.widget.SeekBar; import android.widget.TextView; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; -import com.google.android.exoplayer2.source.TrackGroup; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.FixedTrackSelection; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import org.schabi.newpipe.R; @@ -90,7 +81,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. //////////////////////////////////////////////////////////////////////////*/ public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe"; - public static final String MAX_RESOLUTION = "max_resolution"; private ArrayList availableStreams; private int selectedStreamIndex; @@ -101,11 +91,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds - private static final TrackSelection.Factory FIXED_FACTORY = new FixedTrackSelection.Factory(); - private List trackGroupInfos; - private TrackGroupArray videoTrackGroups; - private TrackGroup selectedVideoTrackGroup; - private boolean startedFromNewPipe = true; protected boolean wasPlaying = false; @@ -130,7 +115,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. private SeekBar playbackSeekBar; private TextView playbackCurrentTime; private TextView playbackEndTime; - private TextView playbackSpeed; + private TextView playbackSpeedTextView; private View topControlsRoot; private TextView qualityTextView; @@ -173,7 +158,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. this.playbackSeekBar = rootView.findViewById(R.id.playbackSeekBar); this.playbackCurrentTime = rootView.findViewById(R.id.playbackCurrentTime); this.playbackEndTime = rootView.findViewById(R.id.playbackEndTime); - this.playbackSpeed = rootView.findViewById(R.id.playbackSpeed); + this.playbackSpeedTextView = rootView.findViewById(R.id.playbackSpeed); this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls); this.topControlsRoot = rootView.findViewById(R.id.topControls); this.qualityTextView = rootView.findViewById(R.id.qualityTextView); @@ -186,7 +171,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY); this.qualityPopupMenu = new PopupMenu(context, qualityTextView); - this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeed); + this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView); ((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)).getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY); @@ -196,7 +181,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. public void initListeners() { super.initListeners(); playbackSeekBar.setOnSeekBarChangeListener(this); - playbackSpeed.setOnClickListener(this); + playbackSpeedTextView.setOnClickListener(this); fullScreenButton.setOnClickListener(this); qualityTextView.setOnClickListener(this); } @@ -212,78 +197,21 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. } } - @Override - protected void postProcess(@NonNull final Intent intent) { - final int resolutionTarget = intent.getIntExtra(MAX_RESOLUTION, getPreferredResolution()); - trackSelector.setParameters( - // Assume video is horizontal - new DefaultTrackSelector.Parameters().withMaxVideoSize(Integer.MAX_VALUE, resolutionTarget) - ); - } - - public abstract int getPreferredResolution(); - /*////////////////////////////////////////////////////////////////////////// // UI Builders //////////////////////////////////////////////////////////////////////////*/ - private final class TrackGroupInfo { - final int track; - final int group; - final Format format; - - TrackGroupInfo(final int track, final int group, final Format format) { - this.track = track; - this.group = group; - this.format = format; - } - } - - private void buildQualityMenu() { - if (qualityPopupMenu == null || videoTrackGroups == null || selectedVideoTrackGroup == null - || availableStreams == null || videoTrackGroups.length != availableStreams.size()) return; + public void buildQualityMenu() { + if (qualityPopupMenu == null) return; qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId); - trackGroupInfos = new ArrayList<>(); - int acc = 0; - - // Each group represent a source in sorted order of how the media source was built - for (int groupIndex = 0; groupIndex < videoTrackGroups.length; groupIndex++) { - final TrackGroup group = videoTrackGroups.get(groupIndex); - final VideoStream stream = availableStreams.get(groupIndex); - - // For each source, there may be one or multiple tracks depending on the source type - for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { - final Format format = group.getFormat(trackIndex); - final boolean isSetCurrent = selectedVideoTrackGroup.indexOf(format) != -1; - - if (group.length == 1 && videoTrackGroups.length == availableStreams.size()) { - // If the source is non-adaptive (extractor source), then we use the resolution contained in the stream - if (isSetCurrent) qualityTextView.setText(stream.resolution); - - final String menuItem = MediaFormat.getNameById(stream.format) + " " + - stream.resolution + " (" + format.width + "x" + format.height + ")"; - qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem); - } else { - // Otherwise, we have an adaptive source, which contains multiple formats and - // thus have no inherent quality format - if (isSetCurrent) qualityTextView.setText(resolutionStringOf(format)); - - final MediaFormat mediaFormat = MediaFormat.getFromMimeType(format.sampleMimeType); - final String mediaName = mediaFormat == null ? format.sampleMimeType : mediaFormat.name; - - final String menuItem = mediaName + " " + format.width + "x" + format.height; - qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem); - } - - trackGroupInfos.add(new TrackGroupInfo(trackIndex, groupIndex, format)); - acc++; - } + for (int i = 0; i < availableStreams.size(); i++) { + VideoStream videoStream = availableStreams.get(i); + qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); } - + qualityTextView.setText(getSelectedVideoStream().resolution); qualityPopupMenu.setOnMenuItemClickListener(this); qualityPopupMenu.setOnDismissListener(this); - qualityTextView.setVisibility(View.VISIBLE); } private void buildPlaybackSpeedMenu() { @@ -293,7 +221,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i])); } - playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); + playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed())); playbackSpeedPopupMenu.setOnMenuItemClickListener(this); playbackSpeedPopupMenu.setOnDismissListener(this); } @@ -305,27 +233,46 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. @Override public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { super.sync(item, info); + qualityTextView.setVisibility(View.GONE); + playbackSpeedTextView.setVisibility(View.GONE); if (info != null) { final List videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); availableStreams = new ArrayList<>(videos); - selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, videos); - } + final int qualityIndex = item.getQualityIndex(); + if (qualityIndex == PlayQueueItem.DEFAULT_QUALITY) { + selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, videos); + } else { + selectedStreamIndex = qualityIndex; + } - buildPlaybackSpeedMenu(); - buildQualityMenu(); + buildQualityMenu(); + buildPlaybackSpeedMenu(); + qualityTextView.setVisibility(View.VISIBLE); + playbackSpeedTextView.setVisibility(View.VISIBLE); + } } - public MediaSource sourceOf(final StreamInfo info) { + public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { final List videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); - List sources = new ArrayList<>(); + final int sortedStreamsIndex = item.getQualityIndex(); + if (videos.isEmpty() || sortedStreamsIndex >= videos.size()) return null; - for (final VideoStream video : videos) { - final MediaSource mediaSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format)); - sources.add(mediaSource); + final VideoStream video; + if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) { + final int index = ListHelper.getDefaultResolutionIndex(context, videos); + video = videos.get(index); + } else { + video = videos.get(sortedStreamsIndex); } - return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()])); + final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format)); + final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams); + if (!video.isVideoOnly || audio == null) return streamSource; + + // Merge with audio stream in case if video does not contain audio + final MediaSource audioSource = buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format)); + return new MergingMediaSource(streamSource, audioSource); } /*////////////////////////////////////////////////////////////////////////// @@ -403,24 +350,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. // ExoPlayer Video Listener //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - super.onTracksChanged(trackGroups, trackSelections); - - if (trackSelector.getCurrentMappedTrackInfo() == null) return; - qualityTextView.setVisibility(View.GONE); - - final int videoRendererIndex = getVideoRendererIndex(); - if (videoRendererIndex == -1) return; - - videoTrackGroups = trackSelector.getCurrentMappedTrackInfo().getTrackGroups(videoRendererIndex); - final TrackSelection trackSelection = trackSelections.get(videoRendererIndex); - if (trackSelection != null) { - selectedVideoTrackGroup = trackSelection.getTrackGroup(); - buildQualityMenu(); - } - } - @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (DEBUG) { @@ -444,7 +373,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. playbackSeekBar.setMax((int) simpleExoPlayer.getDuration()); playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration())); - playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); + playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed())); super.onPrepared(playWhenReady); } @@ -510,7 +439,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. onFullScreenButtonClicked(); } else if (v.getId() == qualityTextView.getId()) { onQualitySelectorClicked(); - } else if (v.getId() == playbackSpeed.getId()) { + } else if (v.getId() == playbackSpeedTextView.getId()) { onPlaybackSpeedClicked(); } } @@ -524,34 +453,19 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]"); if (qualityPopupMenuGroupId == menuItem.getGroupId()) { - final int itemId = menuItem.getItemId(); - final TrackGroupInfo info = trackGroupInfos.get(itemId); + if (selectedStreamIndex == menuItem.getItemId()) return true; - // Set selected quality as player lifecycle persistent parameters - DefaultTrackSelector.Parameters parameters; - if (info.format.width > info.format.height) { - // Check if video horizontal - parameters = new DefaultTrackSelector.Parameters().withMaxVideoSize(Integer.MAX_VALUE, info.format.height); - } else { - // Or if vertical - parameters = new DefaultTrackSelector.Parameters().withMaxVideoSize(info.format.width, Integer.MAX_VALUE); - } - trackSelector.setParameters(parameters); - - final int videoRendererIndex = getVideoRendererIndex(); - if (videoRendererIndex != -1) { - // Override the selection with the selected quality in case of different frame rate - final MappingTrackSelector.SelectionOverride override = new MappingTrackSelector.SelectionOverride(FIXED_FACTORY, info.group, info.track); - trackSelector.setSelectionOverride(videoRendererIndex, videoTrackGroups, override); - } + setRecovery(); + playQueue.setQuality(playQueue.getIndex(), menuItem.getItemId()); + qualityTextView.setText(menuItem.getTitle()); return true; } else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) { int speedIndex = menuItem.getItemId(); float speed = PLAYBACK_SPEEDS[speedIndex]; setPlaybackSpeed(speed); - playbackSpeed.setText(formatSpeed(speed)); + playbackSpeedTextView.setText(formatSpeed(speed)); } return false; @@ -564,6 +478,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. public void onDismiss(PopupMenu menu) { if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); isSomePopupMenuVisible = false; + qualityTextView.setText(getSelectedVideoStream().resolution); } public void onQualitySelectorClicked() { @@ -572,6 +487,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. isSomePopupMenuVisible = true; showControls(300); + VideoStream videoStream = getSelectedVideoStream(); + qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); wasPlaying = simpleExoPlayer.getPlayWhenReady(); } @@ -635,11 +552,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. return -1; } - public String resolutionStringOf(final Format format) { - final String frameRate = format.frameRate > 0 ? String.valueOf((int) format.frameRate) : ""; - return Math.min(format.width, format.height) + "p" + frameRate; - } - public boolean isControlsVisible() { return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE; } @@ -746,10 +658,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. return wasPlaying; } - public int getQualityPopupMenuGroupId() { - return qualityPopupMenuGroupId; - } - public VideoStream getSelectedVideoStream() { return availableStreams.get(selectedStreamIndex); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/DeferredMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/DeferredMediaSource.java index eb6d0da82..c7e1c5754 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/DeferredMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/DeferredMediaSource.java @@ -51,7 +51,7 @@ public final class DeferredMediaSource implements MediaSource { * Player-specific {@link com.google.android.exoplayer2.source.MediaSource} resolution * from a given StreamInfo. * */ - MediaSource sourceOf(final StreamInfo info); + MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info); } private PlayQueueItem stream; @@ -102,8 +102,8 @@ public final class DeferredMediaSource implements MediaSource { * called once only. * * If loading fails here, an error will be propagated out and result in an - * {@link com.google.android.exoplayer2.ExoPlaybackException ExoPlaybackException}, which is delegated - * to the player. + * {@link com.google.android.exoplayer2.ExoPlaybackException ExoPlaybackException}, + * which is delegated to the player. * */ public synchronized void load() { if (stream == null) { @@ -117,7 +117,7 @@ public final class DeferredMediaSource implements MediaSource { final Function onReceive = new Function() { @Override public MediaSource apply(StreamInfo streamInfo) throws Exception { - return onStreamInfoReceived(streamInfo); + return onStreamInfoReceived(stream, streamInfo); } }; @@ -142,17 +142,18 @@ public final class DeferredMediaSource implements MediaSource { .subscribe(onSuccess, onError); } - private MediaSource onStreamInfoReceived(final StreamInfo streamInfo) throws Exception { + private MediaSource onStreamInfoReceived(@NonNull final PlayQueueItem item, + @NonNull final StreamInfo info) throws Exception { if (callback == null) { throw new Exception("No available callback for resolving stream info."); } - final MediaSource mediaSource = callback.sourceOf(streamInfo); + final MediaSource mediaSource = callback.sourceOf(item, info); if (mediaSource == null) { throw new Exception("Unable to resolve source from stream info. URL: " + stream.getUrl() + - ", audio count: " + streamInfo.audio_streams.size() + - ", video count: " + streamInfo.video_only_streams.size() + streamInfo.video_streams.size()); + ", audio count: " + info.audio_streams.size() + + ", video count: " + info.video_only_streams.size() + info.video_streams.size()); } return mediaSource; diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 65d04c33c..dd4c6fac6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -13,7 +13,7 @@ import org.schabi.newpipe.player.mediasource.DeferredMediaSource; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.events.MoveEvent; -import org.schabi.newpipe.playlist.events.PlayQueueMessage; +import org.schabi.newpipe.playlist.events.PlayQueueEvent; import org.schabi.newpipe.playlist.events.RemoveEvent; import java.util.ArrayList; @@ -65,8 +65,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { //////////////////////////////////////////////////////////////////////////*/ @Override - public MediaSource sourceOf(StreamInfo info) { - return playbackListener.sourceOf(info); + public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { + return playbackListener.sourceOf(item, info); } /*////////////////////////////////////////////////////////////////////////// @@ -83,8 +83,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { playQueueReactor = null; syncReactor = null; sources = null; - playbackListener = null; - playQueue = null; } /** @@ -130,8 +128,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { // Event Reactor //////////////////////////////////////////////////////////////////////////*/ - private Subscriber getReactor() { - return new Subscriber() { + private Subscriber getReactor() { + return new Subscriber() { @Override public void onSubscribe(@NonNull Subscription d) { if (playQueueReactor != null) playQueueReactor.cancel(); @@ -140,7 +138,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { } @Override - public void onNext(@NonNull PlayQueueMessage playQueueMessage) { + public void onNext(@NonNull PlayQueueEvent playQueueMessage) { onPlayQueueChanged(playQueueMessage); } @@ -152,7 +150,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { }; } - private void onPlayQueueChanged(final PlayQueueMessage event) { + private void onPlayQueueChanged(final PlayQueueEvent event) { if (playQueue.isEmpty()) { playbackListener.shutdown(); } @@ -160,6 +158,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { // why no pattern matching in Java =( switch (event.type()) { case INIT: + case QUALITY: case REORDER: reset(); break; @@ -179,6 +178,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { move(moveEvent.getFromIndex(), moveEvent.getToIndex()); break; case ERROR: + case RECOVERY: default: break; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index 391c15bbe..226c643d5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -43,7 +43,7 @@ public interface PlaybackListener { * * May be called at any time. * */ - MediaSource sourceOf(final StreamInfo info); + MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info); /** * Called when the play queue can no longer to played or used. diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java index 89a074b94..a7de95721 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java @@ -9,10 +9,12 @@ import org.schabi.newpipe.playlist.events.AppendEvent; import org.schabi.newpipe.playlist.events.ErrorEvent; import org.schabi.newpipe.playlist.events.InitEvent; import org.schabi.newpipe.playlist.events.MoveEvent; -import org.schabi.newpipe.playlist.events.PlayQueueMessage; +import org.schabi.newpipe.playlist.events.PlayQueueEvent; +import org.schabi.newpipe.playlist.events.RecoveryEvent; import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.ReorderEvent; import org.schabi.newpipe.playlist.events.SelectEvent; +import org.schabi.newpipe.playlist.events.QualityEvent; import java.io.Serializable; import java.util.ArrayList; @@ -46,8 +48,8 @@ public abstract class PlayQueue implements Serializable { private ArrayList streams; private final AtomicInteger queueIndex; - private transient BehaviorSubject eventBroadcast; - private transient Flowable broadcastReceiver; + private transient BehaviorSubject eventBroadcast; + private transient Flowable broadcastReceiver; private transient Subscription reportingReactor; PlayQueue(final int index, final List startWith) { @@ -171,7 +173,7 @@ public abstract class PlayQueue implements Serializable { * May be null if the play queue message bus is not initialized. * */ @NonNull - public Flowable getBroadcastReceiver() { + public Flowable getBroadcastReceiver() { return broadcastReceiver; } @@ -273,6 +275,15 @@ public abstract class PlayQueue implements Serializable { streams.remove(index); } + /** + * Moves a queue item at the source index to the target index. + * + * If the item being moved is the currently playing, then the current playing index is set + * to that of the target. + * If the moved item is not the currently playing and moves to an index AFTER the + * current playing index, then the current playing index is decremented. + * Vice versa if the an item after the currently playing is moved BEFORE. + * */ public synchronized void move(final int source, final int target) { if (source < 0 || target < 0) return; if (source >= streams.size() || target >= streams.size()) return; @@ -290,6 +301,42 @@ public abstract class PlayQueue implements Serializable { broadcast(new MoveEvent(source, target)); } + /** + * Updates the quality index at the given item index. + * + * Broadcasts an update event, signalling to all recipients that they should reset. + * */ + public synchronized void setQuality(final int queueIndex, final int qualityIndex) { + if (queueIndex < 0 || queueIndex >= streams.size()) return; + + final PlayQueueItem item = streams.get(queueIndex); + final int oldQualityIndex = item.getQualityIndex(); + + item.setQualityIndex(qualityIndex); + broadcast(new QualityEvent(queueIndex, oldQualityIndex, qualityIndex)); + } + + /** + * Sets the recovery record of the item at the index. + * + * Broadcasts a recovery event. + * */ + public synchronized void setRecovery(final int index, final long position) { + if (index < 0 || index >= streams.size()) return; + + streams.get(index).setRecoveryPosition(position); + broadcast(new RecoveryEvent(index, position)); + } + + /** + * Revoke the recovery record of the item at the index. + * + * Broadcasts a recovery event. + * */ + public synchronized void unsetRecovery(final int index) { + setRecovery(index, PlayQueueItem.RECOVERY_UNSET); + } + /** * Shuffles the current play queue. * @@ -345,14 +392,14 @@ public abstract class PlayQueue implements Serializable { // Rx Broadcast //////////////////////////////////////////////////////////////////////////*/ - private void broadcast(final PlayQueueMessage event) { + private void broadcast(final PlayQueueEvent event) { if (eventBroadcast != null) { eventBroadcast.onNext(event); } } - private Subscriber getSelfReporter() { - return new Subscriber() { + private Subscriber getSelfReporter() { + return new Subscriber() { @Override public void onSubscribe(Subscription s) { if (reportingReactor != null) reportingReactor.cancel(); @@ -361,7 +408,7 @@ public abstract class PlayQueue implements Serializable { } @Override - public void onNext(PlayQueueMessage event) { + public void onNext(PlayQueueEvent event) { Log.d(TAG, "Received broadcast: " + event.type().name() + ". Current index: " + getIndex() + ", play queue length: " + size() + "."); reportingReactor.request(1); } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java index f60ca1185..cabcca157 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueAdapter.java @@ -10,7 +10,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.playlist.events.AppendEvent; import org.schabi.newpipe.playlist.events.ErrorEvent; import org.schabi.newpipe.playlist.events.MoveEvent; -import org.schabi.newpipe.playlist.events.PlayQueueMessage; +import org.schabi.newpipe.playlist.events.PlayQueueEvent; import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.SelectEvent; @@ -73,7 +73,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter observer = new Observer() { + final Observer observer = new Observer() { @Override public void onSubscribe(@NonNull Disposable d) { if (playQueueReactor != null) playQueueReactor.dispose(); @@ -81,7 +81,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter 0 && TextUtils.isDigitsOnly(candidates[0])) { - return Integer.parseInt(candidates[0]); - } else { - return Integer.MAX_VALUE; - } - } } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index b5a2db8cf..9df9b3055 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -60,26 +60,20 @@ public class NavigationHelper { public static Intent getPlayerIntent(final Context context, final Class targetClazz, final PlayQueue playQueue, - final boolean isAppending) { + final int repeatMode, + final float playbackSpeed, + final float playbackPitch) { return getPlayerIntent(context, targetClazz, playQueue) - .putExtra(BasePlayer.APPEND_ONLY, isAppending); + .putExtra(BasePlayer.REPEAT_MODE, repeatMode) + .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed) + .putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch); } - public static Intent getPlayerIntent(final Context context, - final Class targetClazz, - final PlayQueue playQueue, - final int maxResolution) { + public static Intent getPlayerEnqueueIntent(final Context context, + final Class targetClazz, + final PlayQueue playQueue) { return getPlayerIntent(context, targetClazz, playQueue) - .putExtra(VideoPlayer.MAX_RESOLUTION, maxResolution); - } - - public static Intent getPlayerIntent(final Context context, - final Class targetClazz, - final PlayQueue playQueue, - final int maxResolution, - final float playbackSpeed) { - return getPlayerIntent(context, targetClazz, playQueue, maxResolution) - .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed); + .putExtra(BasePlayer.APPEND_ONLY, true); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/res/layout-land/activity_player_queue_control.xml b/app/src/main/res/layout-land/activity_player_queue_control.xml new file mode 100644 index 000000000..9d0c0c41e --- /dev/null +++ b/app/src/main/res/layout-land/activity_player_queue_control.xml @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index 30146f928..d3dd2ed83 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -43,26 +43,72 @@ tools:visibility="visible"/> - + android:id="@+id/playQueueControl"> + + + + + + + + @@ -164,7 +210,7 @@ android:layout_height="35dp" android:layout_marginLeft="2dp" android:layout_marginRight="2dp" - android:layout_toLeftOf="@+id/repeatButton" + android:layout_toLeftOf="@+id/queueButton" android:background="#00ffffff" android:clickable="true" android:focusable="true" @@ -173,21 +219,6 @@ android:src="@drawable/ic_screen_rotation_white" tools:ignore="ContentDescription,RtlHardcoded"/> - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_player_queue_control.xml b/app/src/main/res/layout/activity_player_queue_control.xml index 0a0f3bb96..fc9d7a36e 100644 --- a/app/src/main/res/layout/activity_player_queue_control.xml +++ b/app/src/main/res/layout/activity_player_queue_control.xml @@ -163,7 +163,7 @@ 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"/>