mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-10-19 01:17:39 +00:00
-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.
This commit is contained in:
@@ -788,16 +788,14 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> 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<StreamInfo> 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<StreamInfo> 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)
|
||||
|
@@ -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<PlaylistInfo> {
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -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<VideoStream> 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<TrackGroupInfo> 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<VideoStream> 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<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
|
||||
List<MediaSource> 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);
|
||||
}
|
||||
|
@@ -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<StreamInfo, MediaSource> onReceive = new Function<StreamInfo, MediaSource>() {
|
||||
@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;
|
||||
|
@@ -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<PlayQueueMessage> getReactor() {
|
||||
return new Subscriber<PlayQueueMessage>() {
|
||||
private Subscriber<PlayQueueEvent> getReactor() {
|
||||
return new Subscriber<PlayQueueEvent>() {
|
||||
@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;
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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<PlayQueueItem> streams;
|
||||
private final AtomicInteger queueIndex;
|
||||
|
||||
private transient BehaviorSubject<PlayQueueMessage> eventBroadcast;
|
||||
private transient Flowable<PlayQueueMessage> broadcastReceiver;
|
||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||
private transient Subscription reportingReactor;
|
||||
|
||||
PlayQueue(final int index, final List<PlayQueueItem> 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<PlayQueueMessage> getBroadcastReceiver() {
|
||||
public Flowable<PlayQueueEvent> 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 <b>AFTER</b> the
|
||||
* current playing index, then the current playing index is decremented.
|
||||
* Vice versa if the an item after the currently playing is moved <b>BEFORE</b>.
|
||||
* */
|
||||
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<PlayQueueMessage> getSelfReporter() {
|
||||
return new Subscriber<PlayQueueMessage>() {
|
||||
private Subscriber<PlayQueueEvent> getSelfReporter() {
|
||||
return new Subscriber<PlayQueueEvent>() {
|
||||
@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);
|
||||
}
|
||||
|
@@ -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<RecyclerView.ViewHold
|
||||
}
|
||||
|
||||
private void startReactor() {
|
||||
final Observer<PlayQueueMessage> observer = new Observer<PlayQueueMessage>() {
|
||||
final Observer<PlayQueueEvent> observer = new Observer<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (playQueueReactor != null) playQueueReactor.dispose();
|
||||
@@ -81,7 +81,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull PlayQueueMessage playQueueMessage) {
|
||||
public void onNext(@NonNull PlayQueueEvent playQueueMessage) {
|
||||
onPlayQueueChanged(playQueueMessage);
|
||||
}
|
||||
|
||||
@@ -99,8 +99,12 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||
.subscribe(observer);
|
||||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueMessage message) {
|
||||
private void onPlayQueueChanged(final PlayQueueEvent message) {
|
||||
switch (message.type()) {
|
||||
case RECOVERY:
|
||||
case QUALITY:
|
||||
// Do nothing.
|
||||
break;
|
||||
case SELECT:
|
||||
final SelectEvent selectEvent = (SelectEvent) message;
|
||||
notifyItemChanged(selectEvent.getOldIndex());
|
||||
|
@@ -36,6 +36,11 @@ public class PlayQueueItem implements Serializable {
|
||||
this.stream = Single.just(info);
|
||||
}
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfo info, final int qualityIndex) {
|
||||
this(info);
|
||||
this.qualityIndex = qualityIndex;
|
||||
}
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfoItem item) {
|
||||
this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name);
|
||||
}
|
||||
@@ -49,8 +54,8 @@ public class PlayQueueItem implements Serializable {
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
this.uploader = uploader;
|
||||
|
||||
resetQualityIndex();
|
||||
resetRecoveryPosition();
|
||||
this.qualityIndex = DEFAULT_QUALITY;
|
||||
this.recoveryPosition = RECOVERY_UNSET;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -71,14 +76,24 @@ public class PlayQueueItem implements Serializable {
|
||||
return duration;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public int getQualityIndex() {
|
||||
return qualityIndex;
|
||||
}
|
||||
|
||||
public long getRecoveryPosition() {
|
||||
return recoveryPosition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
@@ -105,30 +120,14 @@ public class PlayQueueItem implements Serializable {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Item States
|
||||
// Item States, keep external access out
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public int getQualityIndex() {
|
||||
return qualityIndex;
|
||||
}
|
||||
|
||||
public long getRecoveryPosition() {
|
||||
return recoveryPosition;
|
||||
}
|
||||
|
||||
public void setQualityIndex(int qualityIndex) {
|
||||
/*package-private*/ void setQualityIndex(final int qualityIndex) {
|
||||
this.qualityIndex = qualityIndex;
|
||||
}
|
||||
|
||||
public void setRecoveryPosition(long recoveryPosition) {
|
||||
/*package-private*/ void setRecoveryPosition(final long recoveryPosition) {
|
||||
this.recoveryPosition = recoveryPosition;
|
||||
}
|
||||
|
||||
public void resetQualityIndex() {
|
||||
this.qualityIndex = DEFAULT_QUALITY;
|
||||
}
|
||||
|
||||
public void resetRecoveryPosition() {
|
||||
this.recoveryPosition = RECOVERY_UNSET;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,10 @@ public final class SinglePlayQueue extends PlayQueue {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final StreamInfo info, final int qualityIndex) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info, qualityIndex)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return true;
|
||||
|
@@ -1,12 +1,12 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class AppendEvent implements PlayQueueMessage {
|
||||
public class AppendEvent implements PlayQueueEvent {
|
||||
final private int amount;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.APPEND;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.APPEND;
|
||||
}
|
||||
|
||||
public AppendEvent(final int amount) {
|
||||
|
@@ -1,12 +1,12 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class ErrorEvent implements PlayQueueMessage {
|
||||
public class ErrorEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.ERROR;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.ERROR;
|
||||
}
|
||||
|
||||
public ErrorEvent(final int index) {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public class InitEvent implements PlayQueueMessage {
|
||||
public class InitEvent implements PlayQueueEvent {
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.INIT;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.INIT;
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public class MoveEvent implements PlayQueueMessage {
|
||||
public class MoveEvent implements PlayQueueEvent {
|
||||
final private int fromIndex;
|
||||
final private int toIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.MOVE;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.MOVE;
|
||||
}
|
||||
|
||||
public MoveEvent(final int oldIndex, final int newIndex) {
|
||||
|
@@ -1,24 +1,7 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public enum PlayQueueEvent {
|
||||
INIT,
|
||||
import java.io.Serializable;
|
||||
|
||||
// sent when the index is changed
|
||||
SELECT,
|
||||
|
||||
// sent when more streams are added to the play queue
|
||||
APPEND,
|
||||
|
||||
// sent when a pending stream is removed from the play queue
|
||||
REMOVE,
|
||||
|
||||
// sent when two streams swap place in the play queue
|
||||
MOVE,
|
||||
|
||||
// sent when queue is shuffled
|
||||
REORDER,
|
||||
|
||||
// sent when the item at index has caused an exception
|
||||
ERROR
|
||||
public interface PlayQueueEvent extends Serializable {
|
||||
PlayQueueEventType type();
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,30 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public enum PlayQueueEventType {
|
||||
INIT,
|
||||
|
||||
// sent when the index is changed
|
||||
SELECT,
|
||||
|
||||
// sent when more streams are added to the play queue
|
||||
APPEND,
|
||||
|
||||
// sent when a pending stream is removed from the play queue
|
||||
REMOVE,
|
||||
|
||||
// sent when two streams swap place in the play queue
|
||||
MOVE,
|
||||
|
||||
// sent when queue is shuffled
|
||||
REORDER,
|
||||
|
||||
// sent when quality index is set on a stream
|
||||
QUALITY,
|
||||
|
||||
// sent when recovery record is set on a stream
|
||||
RECOVERY,
|
||||
|
||||
// sent when the item at index has caused an exception
|
||||
ERROR
|
||||
}
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface PlayQueueMessage extends Serializable {
|
||||
PlayQueueEvent type();
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class QualityEvent implements PlayQueueEvent {
|
||||
final private int streamIndex;
|
||||
final private int oldQualityIndex;
|
||||
final private int newQualityIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.QUALITY;
|
||||
}
|
||||
|
||||
public QualityEvent(final int streamIndex, final int oldQualityIndex, final int newQualityIndex) {
|
||||
this.streamIndex = streamIndex;
|
||||
this.oldQualityIndex = oldQualityIndex;
|
||||
this.newQualityIndex = newQualityIndex;
|
||||
}
|
||||
|
||||
public int getStreamIndex() {
|
||||
return streamIndex;
|
||||
}
|
||||
|
||||
public int getOldQualityIndex() {
|
||||
return oldQualityIndex;
|
||||
}
|
||||
|
||||
public int getNewQualityIndex() {
|
||||
return newQualityIndex;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class RecoveryEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
final private long position;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.RECOVERY;
|
||||
}
|
||||
|
||||
public RecoveryEvent(final int index, final long position) {
|
||||
this.index = index;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return position;
|
||||
}
|
||||
}
|
@@ -1,12 +1,12 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class RemoveEvent implements PlayQueueMessage {
|
||||
public class RemoveEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.REMOVE;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REMOVE;
|
||||
}
|
||||
|
||||
public RemoveEvent(final int index) {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public class ReorderEvent implements PlayQueueMessage {
|
||||
public class ReorderEvent implements PlayQueueEvent {
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.REORDER;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REORDER;
|
||||
}
|
||||
|
||||
public ReorderEvent() {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class SelectEvent implements PlayQueueMessage {
|
||||
public class SelectEvent implements PlayQueueEvent {
|
||||
final private int oldIndex;
|
||||
final private int newIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.SELECT;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.SELECT;
|
||||
}
|
||||
|
||||
public SelectEvent(final int oldIndex, final int newIndex) {
|
||||
|
@@ -152,13 +152,4 @@ public class Localization {
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static int resolutionOf(final String resolution) {
|
||||
final String[] candidates = TextUtils.split(resolution, "p");
|
||||
if (candidates.length > 0 && TextUtils.isDigitsOnly(candidates[0])) {
|
||||
return Integer.parseInt(candidates[0]);
|
||||
} else {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
Reference in New Issue
Block a user