-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:
John Zhen Mo 2017-10-22 18:58:01 -07:00
parent e1f73b70df
commit 7bc5473832
32 changed files with 696 additions and 358 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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