-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:
parent
e1f73b70df
commit
7bc5473832
32 changed files with 696 additions and 358 deletions
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue