Fourth block of fixes for review
- wrote more methods to PlayQueue. Now it supports internal history of played items with ability to play previous() item. Also it has equals() to check whether queues has the same content or not - backstack in fragment is more powerful now with help of PlayQueue's history and able to work great with playlists' PlayQueue and SinglePlayQueue at the same time - simplified logic inside fragment. Easy to understand. New PlayQueue will be added in backstack from only one place; less number of setInitialData() calls - BasePlayer now able to check PlayQueue and compare it with currently playing. And if it is the same queue it tries to not init() it twice. It gives possibility to have a great backstack in fragment since the same queue will not be played from two different instances and will not be added to backstack twice with duplicated history inside - better support of Player.STATE_IDLE - worked with layouts of player and made them better and more universal - service will be stopped when activity finishes by a user decision - fixed a problem related to ChannelPlayQueue and PlaylistPlayQueue in initial start of fragment - fixed crash in popup
This commit is contained in:
parent
e063967734
commit
a2d5314cf7
10 changed files with 184 additions and 65 deletions
|
@ -7,7 +7,7 @@ import java.io.Serializable;
|
||||||
class StackItem implements Serializable {
|
class StackItem implements Serializable {
|
||||||
private final int serviceId;
|
private final int serviceId;
|
||||||
private String title;
|
private String title;
|
||||||
private final String url;
|
private String url;
|
||||||
private final PlayQueue playQueue;
|
private final PlayQueue playQueue;
|
||||||
|
|
||||||
StackItem(int serviceId, String url, String title, PlayQueue playQueue) {
|
StackItem(int serviceId, String url, String title, PlayQueue playQueue) {
|
||||||
|
@ -21,6 +21,10 @@ class StackItem implements Serializable {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
public int getServiceId() {
|
public int getServiceId() {
|
||||||
return serviceId;
|
return serviceId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ public class VideoDetailFragment
|
||||||
@State
|
@State
|
||||||
protected PlayQueue playQueue;
|
protected PlayQueue playQueue;
|
||||||
@State
|
@State
|
||||||
int bottomSheetState = BottomSheetBehavior.STATE_HIDDEN;
|
int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED;
|
||||||
|
|
||||||
private StreamInfo currentInfo;
|
private StreamInfo currentInfo;
|
||||||
private Disposable currentWorker;
|
private Disposable currentWorker;
|
||||||
|
@ -398,7 +398,8 @@ public class VideoDetailFragment
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
unbind();
|
if (!activity.isFinishing()) unbind();
|
||||||
|
else stopService();
|
||||||
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
.unregisterOnSharedPreferenceChangeListener(this);
|
.unregisterOnSharedPreferenceChangeListener(this);
|
||||||
|
@ -850,26 +851,6 @@ public class VideoDetailFragment
|
||||||
*/
|
*/
|
||||||
protected final LinkedList<StackItem> stack = new LinkedList<>();
|
protected final LinkedList<StackItem> stack = new LinkedList<>();
|
||||||
|
|
||||||
public void pushToStack(int serviceId, String videoUrl, String name, PlayQueue playQueue) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "pushToStack() called with: serviceId = ["
|
|
||||||
+ serviceId + "], videoUrl = [" + videoUrl + "], name = [" + name + "], playQueue = [" + playQueue + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stack.size() > 0
|
|
||||||
&& stack.peek().getServiceId() == serviceId
|
|
||||||
&& stack.peek().getUrl().equals(videoUrl)
|
|
||||||
&& stack.peek().getPlayQueue().getClass().equals(playQueue.getClass())) {
|
|
||||||
Log.d(TAG, "pushToStack() called with: serviceId == peek.serviceId = ["
|
|
||||||
+ serviceId + "], videoUrl == peek.getUrl = [" + videoUrl + "]");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "pushToStack() wasn't equal");
|
|
||||||
}
|
|
||||||
|
|
||||||
stack.push(new StackItem(serviceId, videoUrl, name, playQueue));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitleToUrl(int serviceId, String videoUrl, String name) {
|
public void setTitleToUrl(int serviceId, String videoUrl, String name) {
|
||||||
if (name != null && !name.isEmpty()) {
|
if (name != null && !name.isEmpty()) {
|
||||||
for (StackItem stackItem : stack) {
|
for (StackItem stackItem : stack) {
|
||||||
|
@ -885,12 +866,17 @@ public class VideoDetailFragment
|
||||||
public boolean onBackPressed() {
|
public boolean onBackPressed() {
|
||||||
if (DEBUG) Log.d(TAG, "onBackPressed() called");
|
if (DEBUG) Log.d(TAG, "onBackPressed() called");
|
||||||
|
|
||||||
|
// If we are in fullscreen mode just exit from it via first back press
|
||||||
if (player != null && player.isInFullscreen()) {
|
if (player != null && player.isInFullscreen()) {
|
||||||
player.onPause();
|
player.onPause();
|
||||||
restoreDefaultOrientation();
|
restoreDefaultOrientation();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have something in history of played items we replay it here
|
||||||
|
if (player != null && player.getPlayQueue().previous()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// That means that we are on the start of the stack,
|
// That means that we are on the start of the stack,
|
||||||
// return false to let the MainActivity handle the onBack
|
// return false to let the MainActivity handle the onBack
|
||||||
if (stack.size() <= 1) {
|
if (stack.size() <= 1) {
|
||||||
|
@ -928,15 +914,15 @@ public class VideoDetailFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectAndLoadVideo(int serviceId, String videoUrl, String name, PlayQueue playQueue) {
|
public void selectAndLoadVideo(int serviceId, String videoUrl, String name, PlayQueue playQueue) {
|
||||||
boolean streamIsTheSame = videoUrl.equals(url) && currentInfo != null;
|
boolean streamIsTheSame = this.playQueue != null && this.playQueue.equals(playQueue);
|
||||||
setInitialData(serviceId, videoUrl, name, playQueue);
|
|
||||||
|
|
||||||
// Situation when user switches from players to main player. All needed data is here, we can start watching
|
// Situation when user switches from players to main player. All needed data is here, we can start watching
|
||||||
if (streamIsTheSame) {
|
if (streamIsTheSame) {
|
||||||
handleResult(currentInfo);
|
//TODO not sure about usefulness of this line in the case when user switches from one player to another
|
||||||
|
// handleResult(currentInfo);
|
||||||
openVideoPlayer();
|
openVideoPlayer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setInitialData(serviceId, videoUrl, name, playQueue);
|
||||||
startLoading(false);
|
startLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,7 +930,6 @@ public class VideoDetailFragment
|
||||||
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = ["
|
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = ["
|
||||||
+ info + "], scrollToTop = [" + scrollToTop + "]");
|
+ info + "], scrollToTop = [" + scrollToTop + "]");
|
||||||
|
|
||||||
setInitialData(info.getServiceId(), info.getUrl(), info.getName(), new SinglePlayQueue(info));
|
|
||||||
showLoading();
|
showLoading();
|
||||||
initTabs();
|
initTabs();
|
||||||
|
|
||||||
|
@ -1390,8 +1375,6 @@ public class VideoDetailFragment
|
||||||
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName(),
|
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName(),
|
||||||
playQueue == null ? new SinglePlayQueue(info) : playQueue);
|
playQueue == null ? new SinglePlayQueue(info) : playQueue);
|
||||||
|
|
||||||
pushToStack(serviceId, url, name, playQueue);
|
|
||||||
|
|
||||||
if(showRelatedStreams){
|
if(showRelatedStreams){
|
||||||
if(null == relatedStreamsLayout){ //phone
|
if(null == relatedStreamsLayout){ //phone
|
||||||
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info));
|
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info));
|
||||||
|
@ -1627,6 +1610,20 @@ public class VideoDetailFragment
|
||||||
// Player event listener
|
// Player event listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onQueueUpdate(PlayQueue queue) {
|
||||||
|
playQueue = queue;
|
||||||
|
// This should be the only place where we push data to stack. It will allow to have live instance of PlayQueue with actual
|
||||||
|
// information about deleted/added items inside Channel/Playlist queue and makes possible to have a history of played items
|
||||||
|
if (stack.isEmpty() || !stack.peek().getPlayQueue().equals(queue))
|
||||||
|
stack.push(new StackItem(serviceId, url, name, playQueue));
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onQueueUpdate() called with: serviceId = ["
|
||||||
|
+ serviceId + "], videoUrl = [" + url + "], name = [" + name + "], playQueue = [" + playQueue + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) {
|
public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) {
|
||||||
setOverlayPlayPauseImage();
|
setOverlayPlayPauseImage();
|
||||||
|
@ -1647,11 +1644,6 @@ public class VideoDetailFragment
|
||||||
public void onProgressUpdate(int currentProgress, int duration, int bufferPercent) {
|
public void onProgressUpdate(int currentProgress, int duration, int bufferPercent) {
|
||||||
// Progress updates every second even if media is paused. It's useless until playing
|
// Progress updates every second even if media is paused. It's useless until playing
|
||||||
if (!player.getPlayer().isPlaying() || playQueue == null) return;
|
if (!player.getPlayer().isPlaying() || playQueue == null) return;
|
||||||
|
|
||||||
// Update current progress in cached playQueue because playQueue in popup and background players
|
|
||||||
// are different instances
|
|
||||||
playQueue.setRecovery(playQueue.getIndex(), currentProgress);
|
|
||||||
|
|
||||||
showPlaybackProgress(currentProgress, duration);
|
showPlaybackProgress(currentProgress, duration);
|
||||||
|
|
||||||
// We don't want to interrupt playback and don't want to see notification if player is stopped
|
// We don't want to interrupt playback and don't want to see notification if player is stopped
|
||||||
|
@ -1672,6 +1664,14 @@ public class VideoDetailFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadataUpdate(StreamInfo info) {
|
public void onMetadataUpdate(StreamInfo info) {
|
||||||
|
if (!stack.isEmpty()) {
|
||||||
|
// When PlayQueue can have multiple streams (PlaylistPlayQueue or ChannelPlayQueue) every new played stream gives
|
||||||
|
// new title and url. StackItem contains information about first played stream. Let's update it here
|
||||||
|
StackItem peek = stack.peek();
|
||||||
|
peek.setTitle(info.getName());
|
||||||
|
peek.setUrl(info.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
if (currentInfo == info) return;
|
if (currentInfo == info) return;
|
||||||
|
|
||||||
currentInfo = info;
|
currentInfo = info;
|
||||||
|
@ -1865,7 +1865,7 @@ public class VideoDetailFragment
|
||||||
case BottomSheetBehavior.STATE_COLLAPSED:
|
case BottomSheetBehavior.STATE_COLLAPSED:
|
||||||
// Re-enable clicks
|
// Re-enable clicks
|
||||||
setOverlayElementsClickable(true);
|
setOverlayElementsClickable(true);
|
||||||
if (player != null && player.isInFullscreen() && player.isPlaying()) showSystemUi();
|
if (player != null && player.isInFullscreen()) showSystemUi();
|
||||||
break;
|
break;
|
||||||
case BottomSheetBehavior.STATE_DRAGGING:
|
case BottomSheetBehavior.STATE_DRAGGING:
|
||||||
if (player != null && player.isControlsVisible()) player.hideControls(0, 0);
|
if (player != null && player.isControlsVisible()) player.hideControls(0, 0);
|
||||||
|
@ -1873,7 +1873,6 @@ public class VideoDetailFragment
|
||||||
case BottomSheetBehavior.STATE_SETTLING:
|
case BottomSheetBehavior.STATE_SETTLING:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "onStateChanged: " + newState);
|
|
||||||
}
|
}
|
||||||
@Override public void onSlide(@NonNull View bottomSheet, float slideOffset) {
|
@Override public void onSlide(@NonNull View bottomSheet, float slideOffset) {
|
||||||
setOverlayLook(appBarLayout, behavior, slideOffset);
|
setOverlayLook(appBarLayout, behavior, slideOffset);
|
||||||
|
|
|
@ -274,6 +274,16 @@ public abstract class BasePlayer implements
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean same = playQueue != null && playQueue.equals(queue);
|
||||||
|
|
||||||
|
// Do not re-init the same PlayQueue. Save time
|
||||||
|
if (same && !playQueue.isDisposed()) {
|
||||||
|
// Player can have state = IDLE when playback is stopped or failed and we should retry() in this case
|
||||||
|
if (simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE)
|
||||||
|
simpleExoPlayer.retry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
||||||
final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed());
|
final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed());
|
||||||
final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch());
|
final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch());
|
||||||
|
@ -284,14 +294,17 @@ public abstract class BasePlayer implements
|
||||||
if (simpleExoPlayer != null
|
if (simpleExoPlayer != null
|
||||||
&& queue.size() == 1
|
&& queue.size() == 1
|
||||||
&& playQueue != null
|
&& playQueue != null
|
||||||
|
&& playQueue.size() == 1
|
||||||
&& playQueue.getItem() != null
|
&& playQueue.getItem() != null
|
||||||
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
||||||
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET
|
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET
|
||||||
) {
|
&& !same) {
|
||||||
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
|
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) {
|
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
|
||||||
|
&& isPlaybackResumeEnabled()
|
||||||
|
&& !same) {
|
||||||
final PlayQueueItem item = queue.getItem();
|
final PlayQueueItem item = queue.getItem();
|
||||||
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
|
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
|
||||||
stateLoader = recordManager.loadStreamState(item)
|
stateLoader = recordManager.loadStreamState(item)
|
||||||
|
@ -321,7 +334,8 @@ public abstract class BasePlayer implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Good to go...
|
// Good to go...
|
||||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
|
||||||
|
initPlayback(same ? playQueue : queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||||
/*playOnInit=*/true);
|
/*playOnInit=*/true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,7 @@ public final class MainPlayer extends Service {
|
||||||
|
|
||||||
if (playerImpl.getPlayer() != null) {
|
if (playerImpl.getPlayer() != null) {
|
||||||
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
|
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
|
||||||
|
// We can't pause the player here because it will make transition from one stream to a new stream not smooth
|
||||||
playerImpl.getPlayer().stop(false);
|
playerImpl.getPlayer().stop(false);
|
||||||
playerImpl.setRecovery();
|
playerImpl.setRecovery();
|
||||||
}
|
}
|
||||||
|
|
|
@ -557,6 +557,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||||
// Binding Service Listener
|
// Binding Service Listener
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onQueueUpdate(PlayQueue queue) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) {
|
public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) {
|
||||||
onStateChanged(state);
|
onStateChanged(state);
|
||||||
|
|
|
@ -281,6 +281,7 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
private void setupElementsVisibility() {
|
private void setupElementsVisibility() {
|
||||||
if (popupPlayerSelected()) {
|
if (popupPlayerSelected()) {
|
||||||
fullscreenButton.setVisibility(View.VISIBLE);
|
fullscreenButton.setVisibility(View.VISIBLE);
|
||||||
|
getRootView().findViewById(R.id.spaceBeforeControls).setVisibility(View.GONE);
|
||||||
getRootView().findViewById(R.id.metadataView).setVisibility(View.GONE);
|
getRootView().findViewById(R.id.metadataView).setVisibility(View.GONE);
|
||||||
queueButton.setVisibility(View.GONE);
|
queueButton.setVisibility(View.GONE);
|
||||||
moreOptionsButton.setVisibility(View.GONE);
|
moreOptionsButton.setVisibility(View.GONE);
|
||||||
|
@ -294,10 +295,11 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
openInBrowser.setVisibility(View.GONE);
|
openInBrowser.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
fullscreenButton.setVisibility(View.GONE);
|
fullscreenButton.setVisibility(View.GONE);
|
||||||
|
getRootView().findViewById(R.id.spaceBeforeControls).setVisibility(View.VISIBLE);
|
||||||
getRootView().findViewById(R.id.metadataView).setVisibility(View.VISIBLE);
|
getRootView().findViewById(R.id.metadataView).setVisibility(View.VISIBLE);
|
||||||
moreOptionsButton.setVisibility(View.VISIBLE);
|
moreOptionsButton.setVisibility(View.VISIBLE);
|
||||||
getTopControlsRoot().setOrientation(LinearLayout.VERTICAL);
|
getTopControlsRoot().setOrientation(LinearLayout.VERTICAL);
|
||||||
primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.MATCH_PARENT;
|
primaryControls.getLayoutParams().width = secondaryControls.getLayoutParams().width;
|
||||||
secondaryControls.setVisibility(View.GONE);
|
secondaryControls.setVisibility(View.GONE);
|
||||||
moreOptionsButton.setImageDrawable(service.getResources().getDrawable(
|
moreOptionsButton.setImageDrawable(service.getResources().getDrawable(
|
||||||
R.drawable.ic_expand_more_white_24dp));
|
R.drawable.ic_expand_more_white_24dp));
|
||||||
|
@ -500,7 +502,13 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
triggerProgressUpdate();
|
triggerProgressUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
@Override
|
||||||
|
protected void initPlayback(@NonNull PlayQueue queue, int repeatMode, float playbackSpeed, float playbackPitch, boolean playbackSkipSilence, boolean playOnReady) {
|
||||||
|
super.initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, playOnReady);
|
||||||
|
updateQueue(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Player Overrides
|
// Player Overrides
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@ -1088,7 +1096,7 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSystemUIPartially() {
|
private void showSystemUIPartially() {
|
||||||
if (isInFullscreen()) {
|
if (isInFullscreen() && getParentActivity() != null) {
|
||||||
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
|
@ -1106,7 +1114,7 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
* This method measures width and height of controls visible on screen. It ensures that controls will be side-by-side with
|
* This method measures width and height of controls visible on screen. It ensures that controls will be side-by-side with
|
||||||
* NavigationBar and notches but not under them. Tablets have only bottom NavigationBar
|
* NavigationBar and notches but not under them. Tablets have only bottom NavigationBar
|
||||||
* */
|
* */
|
||||||
void setControlsSize() {
|
private void setControlsSize() {
|
||||||
Point size = new Point();
|
Point size = new Point();
|
||||||
Display display = getRootView().getDisplay();
|
Display display = getRootView().getDisplay();
|
||||||
if (display == null) return;
|
if (display == null) return;
|
||||||
|
@ -1479,6 +1487,15 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateQueue(PlayQueue queue) {
|
||||||
|
if (fragmentListener != null) {
|
||||||
|
fragmentListener.onQueueUpdate(queue);
|
||||||
|
}
|
||||||
|
if (activityListener != null) {
|
||||||
|
activityListener.onQueueUpdate(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateMetadata() {
|
private void updateMetadata() {
|
||||||
if (fragmentListener != null && getCurrentMetadata() != null) {
|
if (fragmentListener != null && getCurrentMetadata() != null) {
|
||||||
fragmentListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
|
fragmentListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
|
||||||
|
|
|
@ -4,8 +4,10 @@ package org.schabi.newpipe.player.event;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
|
|
||||||
public interface PlayerEventListener {
|
public interface PlayerEventListener {
|
||||||
|
void onQueueUpdate(PlayQueue queue);
|
||||||
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters);
|
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters);
|
||||||
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
||||||
void onMetadataUpdate(StreamInfo info);
|
void onMetadataUpdate(StreamInfo info);
|
||||||
|
|
|
@ -46,17 +46,23 @@ public abstract class PlayQueue implements Serializable {
|
||||||
|
|
||||||
private ArrayList<PlayQueueItem> backup;
|
private ArrayList<PlayQueueItem> backup;
|
||||||
private ArrayList<PlayQueueItem> streams;
|
private ArrayList<PlayQueueItem> streams;
|
||||||
|
private ArrayList<PlayQueueItem> history;
|
||||||
@NonNull private final AtomicInteger queueIndex;
|
@NonNull private final AtomicInteger queueIndex;
|
||||||
|
|
||||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||||
private transient Subscription reportingReactor;
|
private transient Subscription reportingReactor;
|
||||||
|
|
||||||
|
private transient boolean disposed;
|
||||||
|
|
||||||
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
||||||
streams = new ArrayList<>();
|
streams = new ArrayList<>();
|
||||||
streams.addAll(startWith);
|
streams.addAll(startWith);
|
||||||
|
history = new ArrayList<>();
|
||||||
|
history.add(streams.get(index));
|
||||||
|
|
||||||
queueIndex = new AtomicInteger(index);
|
queueIndex = new AtomicInteger(index);
|
||||||
|
disposed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -88,6 +94,7 @@ public abstract class PlayQueue implements Serializable {
|
||||||
eventBroadcast = null;
|
eventBroadcast = null;
|
||||||
broadcastReceiver = null;
|
broadcastReceiver = null;
|
||||||
reportingReactor = null;
|
reportingReactor = null;
|
||||||
|
disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,6 +202,7 @@ public abstract class PlayQueue implements Serializable {
|
||||||
int newIndex = index;
|
int newIndex = index;
|
||||||
if (index < 0) newIndex = 0;
|
if (index < 0) newIndex = 0;
|
||||||
if (index >= streams.size()) newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
if (index >= streams.size()) newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
||||||
|
if (oldIndex != newIndex) history.add(streams.get(newIndex));
|
||||||
|
|
||||||
queueIndex.set(newIndex);
|
queueIndex.set(newIndex);
|
||||||
broadcast(new SelectEvent(oldIndex, newIndex));
|
broadcast(new SelectEvent(oldIndex, newIndex));
|
||||||
|
@ -267,6 +275,9 @@ public abstract class PlayQueue implements Serializable {
|
||||||
|
|
||||||
if (skippable) {
|
if (skippable) {
|
||||||
queueIndex.incrementAndGet();
|
queueIndex.incrementAndGet();
|
||||||
|
if (streams.size() > queueIndex.get()) {
|
||||||
|
history.add(streams.get(queueIndex.get()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
removeInternal(index);
|
removeInternal(index);
|
||||||
}
|
}
|
||||||
|
@ -292,7 +303,9 @@ public abstract class PlayQueue implements Serializable {
|
||||||
final int backupIndex = backup.indexOf(getItem(removeIndex));
|
final int backupIndex = backup.indexOf(getItem(removeIndex));
|
||||||
backup.remove(backupIndex);
|
backup.remove(backupIndex);
|
||||||
}
|
}
|
||||||
streams.remove(removeIndex);
|
|
||||||
|
history.remove(streams.remove(removeIndex));
|
||||||
|
history.add(streams.get(queueIndex.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -366,6 +379,7 @@ public abstract class PlayQueue implements Serializable {
|
||||||
streams.add(0, streams.remove(newIndex));
|
streams.add(0, streams.remove(newIndex));
|
||||||
}
|
}
|
||||||
queueIndex.set(0);
|
queueIndex.set(0);
|
||||||
|
history.add(streams.get(0));
|
||||||
|
|
||||||
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
||||||
}
|
}
|
||||||
|
@ -393,10 +407,52 @@ public abstract class PlayQueue implements Serializable {
|
||||||
} else {
|
} else {
|
||||||
queueIndex.set(0);
|
queueIndex.set(0);
|
||||||
}
|
}
|
||||||
|
history.add(streams.get(queueIndex.get()));
|
||||||
|
|
||||||
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects previous played item
|
||||||
|
*
|
||||||
|
* This method removes currently playing item from history and
|
||||||
|
* starts playing the last item from history if it exists
|
||||||
|
*
|
||||||
|
* Returns true if history is not empty and the item can be played
|
||||||
|
* */
|
||||||
|
public synchronized boolean previous() {
|
||||||
|
if (history.size() <= 1) return false;
|
||||||
|
|
||||||
|
history.remove(history.size() - 1);
|
||||||
|
|
||||||
|
PlayQueueItem last = history.remove(history.size() - 1);
|
||||||
|
setIndex(indexOf(last));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compares two PlayQueues. Useful when a user switches players but queue is the same so
|
||||||
|
* we don't have to do anything with new queue. This method also gives a chance to track history of items in a queue in
|
||||||
|
* VideoDetailFragment without duplicating items from two identical queues
|
||||||
|
* */
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (!(obj instanceof PlayQueue) || getStreams().size() != ((PlayQueue) obj).getStreams().size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
PlayQueue other = (PlayQueue) obj;
|
||||||
|
for (int i = 0; i < getStreams().size(); i++) {
|
||||||
|
if (!getItem(i).getUrl().equals(other.getItem(i).getUrl()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDisposed() {
|
||||||
|
return disposed;
|
||||||
|
}
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Rx Broadcast
|
// Rx Broadcast
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -141,17 +141,29 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:background="@drawable/player_top_controls_bg"
|
||||||
|
android:layout_alignParentTop="true" />
|
||||||
|
|
||||||
|
<!-- It will be hidden in popup -->
|
||||||
|
<Space
|
||||||
|
android:id="@+id/spaceBeforeControls"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/topControls"
|
android:id="@+id/topControls"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:background="@drawable/player_top_controls_bg"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:baselineAligned="false"
|
android:paddingEnd="6dp"
|
||||||
android:layout_toStartOf="@id/fullScreenButton">
|
android:layout_toEndOf="@id/spaceBeforeControls"
|
||||||
|
android:baselineAligned="false">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/primaryControls"
|
android:id="@+id/primaryControls"
|
||||||
|
@ -161,7 +173,6 @@
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:paddingBottom="7dp"
|
android:paddingBottom="7dp"
|
||||||
android:paddingLeft="2dp"
|
android:paddingLeft="2dp"
|
||||||
android:paddingRight="6dp"
|
|
||||||
tools:ignore="RtlHardcoded">
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -171,7 +182,6 @@
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingTop="6dp"
|
android:paddingTop="6dp"
|
||||||
android:paddingLeft="16dp"
|
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
@ -274,8 +284,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="6dp"
|
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
@ -302,6 +310,8 @@
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:gravity="center|left"
|
android:gravity="center|left"
|
||||||
android:minHeight="35dp"
|
android:minHeight="35dp"
|
||||||
|
android:lines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
android:minWidth="50dp"
|
android:minWidth="50dp"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
@ -365,10 +375,11 @@
|
||||||
android:id="@+id/fullScreenButton"
|
android:id="@+id/fullScreenButton"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:paddingEnd="8dp"
|
android:layout_marginTop="2dp"
|
||||||
android:paddingTop="8dp"
|
android:layout_marginEnd="2dp"
|
||||||
|
android:padding="6dp"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:background="@drawable/player_top_controls_bg"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
|
|
|
@ -139,17 +139,29 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:background="@drawable/player_top_controls_bg"
|
||||||
|
android:layout_alignParentTop="true" />
|
||||||
|
|
||||||
|
<!-- It will be hidden in popup -->
|
||||||
|
<Space
|
||||||
|
android:id="@+id/spaceBeforeControls"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/topControls"
|
android:id="@+id/topControls"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:background="@drawable/player_top_controls_bg"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:baselineAligned="false"
|
android:paddingEnd="6dp"
|
||||||
android:layout_toStartOf="@id/fullScreenButton">
|
android:layout_toEndOf="@id/spaceBeforeControls"
|
||||||
|
android:baselineAligned="false">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/primaryControls"
|
android:id="@+id/primaryControls"
|
||||||
|
@ -159,7 +171,6 @@
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:paddingBottom="7dp"
|
android:paddingBottom="7dp"
|
||||||
android:paddingLeft="2dp"
|
android:paddingLeft="2dp"
|
||||||
android:paddingRight="6dp"
|
|
||||||
tools:ignore="RtlHardcoded">
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -169,7 +180,6 @@
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingTop="6dp"
|
android:paddingTop="6dp"
|
||||||
android:paddingLeft="16dp"
|
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
@ -272,8 +282,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="6dp"
|
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
@ -300,6 +308,8 @@
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:gravity="center|left"
|
android:gravity="center|left"
|
||||||
android:minHeight="35dp"
|
android:minHeight="35dp"
|
||||||
|
android:lines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
android:minWidth="50dp"
|
android:minWidth="50dp"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
@ -363,10 +373,11 @@
|
||||||
android:id="@+id/fullScreenButton"
|
android:id="@+id/fullScreenButton"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:paddingEnd="8dp"
|
android:layout_marginTop="2dp"
|
||||||
android:paddingTop="8dp"
|
android:layout_marginEnd="2dp"
|
||||||
|
android:padding="6dp"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:background="@drawable/player_top_controls_bg"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
|
|
Loading…
Reference in a new issue