-Changed intents to start all players, including player swap.

-Make play queue and items serializable
-Removed now deprecated code for playing url in exoplayer
This commit is contained in:
John Zhen M 2017-09-05 12:27:12 -07:00 committed by John Zhen Mo
parent 705028c79d
commit b54d18d888
12 changed files with 368 additions and 261 deletions

View File

@ -20,15 +20,12 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.List;
import io.reactivex.Single;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@ -141,7 +138,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
}
imageLoader.displayImage(result.uploader_avatar_url, headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS);
headerStreamCount.setText(result.stream_count + " videos");
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, (int) result.stream_count));
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.service_id), result.url, 0);
@ -150,19 +147,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
play();
final Intent intent = NavigationHelper.getExternalPlaylistIntent(
activity, MainVideoPlayer.class, currentInfo, infoListAdapter.getItemsList(), 0
);
startActivity(intent);
}
});
}
private void play() {
Intent mIntent = new Intent(activity, MainVideoPlayer.class)
.putExtra("serviceId", serviceId)
.putExtra("index", 0)
.putExtra("streams", infoListAdapter.getItemsList())
.putExtra("nextPageUrl", currentInfo.next_streams_url);
startActivity(mIntent);
}
@Override
public void handleNextItems(ListExtractor.NextItemsResult result) {

View File

@ -268,9 +268,9 @@ public class BackgroundPlayer extends Service {
@Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
Serializable serializable = intent.getSerializableExtra(BackgroundPlayer.AUDIO_STREAM);
if (serializable instanceof AudioStream) audioStream = (AudioStream) serializable;
playUrl(audioStream.url, MediaFormat.getSuffixById(audioStream.format), true);
notBuilder = createNotification();
startForeground(NOTIFICATION_ID, notBuilder.build());
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
@ -294,14 +294,6 @@ public class BackgroundPlayer extends Service {
}
}
@Override
public void playUrl(String url, String format, boolean autoPlay) {
super.playUrl(url, format, autoPlay);
notBuilder = createNotification();
startForeground(NOTIFICATION_ID, notBuilder.build());
}
@Override
public void onPrepared(boolean playWhenReady) {
super.onPrepared(playWhenReady);
@ -348,15 +340,13 @@ public class BackgroundPlayer extends Service {
@Override
public void onFastRewind() {
// super.onFastRewind();
simpleExoPlayer.seekTo(0, 0);
playQueue.setIndex(playQueue.getIndex() - 1);
triggerProgressUpdate();
}
@Override
public void onFastForward() {
// super.onFastForward();
simpleExoPlayer.seekTo(2, 0);
playQueue.setIndex(playQueue.getIndex() + 1);
triggerProgressUpdate();
}
@ -380,6 +370,15 @@ public class BackgroundPlayer extends Service {
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void shutdown() {
super.shutdown();
stopSelf();
}

View File

@ -70,13 +70,21 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import java.io.File;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
@ -90,7 +98,7 @@ public abstract class BasePlayer implements Player.EventListener,
AudioManager.OnAudioFocusChangeListener, MediaSourceManager.PlaybackListener {
// TODO: Check api version for deprecated audio manager methods
public static final boolean DEBUG = false;
public static final boolean DEBUG = true;
public static final String TAG = "BasePlayer";
protected Context context;
@ -104,6 +112,11 @@ public abstract class BasePlayer implements Player.EventListener,
// Intent
//////////////////////////////////////////////////////////////////////////*/
public static final String INTENT_TYPE = "intent_type";
public static final String SINGLE_STREAM = "single";
public static final String EXTERNAL_PLAYLIST = "external";
public static final String INTERNAL_PLAYLIST = "internal";
public static final String VIDEO_URL = "video_url";
public static final String VIDEO_TITLE = "video_title";
public static final String VIDEO_THUMBNAIL_URL = "video_thumbnail_url";
@ -111,6 +124,9 @@ public abstract class BasePlayer implements Player.EventListener,
public static final String CHANNEL_NAME = "channel_name";
public static final String PLAYBACK_SPEED = "playback_speed";
public static final String RESTORE_QUEUE_INDEX = "restore_queue_index";
public static final String RESTORE_WINDOW_POS = "restore_window_pos";
protected Bitmap videoThumbnail = null;
protected String videoUrl = "";
protected String videoTitle = "";
@ -125,8 +141,8 @@ public abstract class BasePlayer implements Player.EventListener,
protected MediaSourceManager playbackManager;
protected PlayQueue playQueue;
private int windowIndex;
private long windowPos;
protected int restoreQueueIndex;
protected long restoreWindowPos;
/*//////////////////////////////////////////////////////////////////////////
// Player
@ -219,15 +235,54 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return;
videoUrl = intent.getStringExtra(VIDEO_URL);
videoTitle = intent.getStringExtra(VIDEO_TITLE);
videoThumbnailUrl = intent.getStringExtra(VIDEO_THUMBNAIL_URL);
videoStartPos = intent.getLongExtra(START_POSITION, -1L);
uploaderName = intent.getStringExtra(CHANNEL_NAME);
restoreQueueIndex = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0);
restoreWindowPos = intent.getLongExtra(START_POSITION, 0);
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
switch (intent.getStringExtra(INTENT_TYPE)) {
case SINGLE_STREAM:
handleSinglePlaylistIntent(intent);
break;
case EXTERNAL_PLAYLIST:
handleExternalPlaylistIntent(intent);
break;
default:
break;
}
initThumbnail();
//play(getSelectedVideoStream(), true);
}
@SuppressWarnings("unchecked")
public void handleExternalPlaylistIntent(Intent intent) {
final int serviceId = intent.getIntExtra(ExternalPlayQueue.SERVICE_ID, -1);
final int index = intent.getIntExtra(ExternalPlayQueue.INDEX, 0);
final Serializable serializable = intent.getSerializableExtra(ExternalPlayQueue.STREAMS);
final String nextPageUrl = intent.getStringExtra(ExternalPlayQueue.NEXT_PAGE_URL);
List<InfoItem> info = new ArrayList<>();
if (serializable instanceof List) {
for (final Object o : (List) serializable) {
if (o instanceof InfoItem) info.add((StreamInfoItem) o);
}
}
playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index);
playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue);
}
@SuppressWarnings("unchecked")
public void handleSinglePlaylistIntent(Intent intent) {
final Serializable serializable = intent.getSerializableExtra(SinglePlayQueue.STREAM);
if (!(serializable instanceof StreamInfo)) return;
playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY);
playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue);
}
public void initThumbnail() {
@ -247,27 +302,6 @@ public abstract class BasePlayer implements Player.EventListener,
});
}
public void playUrl(String url, String format, boolean autoPlay) {
if (DEBUG) {
Log.d(TAG, "play() called with: url = [" + url + "], autoPlay = [" + autoPlay + "]");
}
if (url == null || simpleExoPlayer == null) {
RuntimeException runtimeException = new RuntimeException((url == null ? "Url " : "Player ") + " null");
onError(runtimeException);
throw runtimeException;
}
changeState(STATE_LOADING);
isPrepared = false;
if (simpleExoPlayer.getPlaybackState() != Player.STATE_IDLE) simpleExoPlayer.stop();
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
simpleExoPlayer.prepare(mediaSource);
simpleExoPlayer.setPlayWhenReady(autoPlay);
}
public void destroyPlayer() {
if (DEBUG) Log.d(TAG, "destroyPlayer() called");
if (simpleExoPlayer != null) {
@ -466,7 +500,6 @@ public abstract class BasePlayer implements Player.EventListener,
public void onRepeatClicked() {
if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
// TODO: implement repeat all when playlist is implemented
final int mode;
@ -482,7 +515,7 @@ public abstract class BasePlayer implements Player.EventListener,
mode = Player.REPEAT_MODE_OFF;
break;
}
// Switch the modes between DISABLED and REPEAT_ONE, till playlist is implemented
simpleExoPlayer.setRepeatMode(mode);
if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + simpleExoPlayer.getRepeatMode());
}
@ -566,8 +599,6 @@ public abstract class BasePlayer implements Player.EventListener,
Log.d(TAG, "Blocking...");
simpleExoPlayer.stop();
windowIndex = simpleExoPlayer.getCurrentWindowIndex();
windowPos = Math.max(0, simpleExoPlayer.getContentPosition());
changeState(STATE_BUFFERING);
}
@ -576,42 +607,51 @@ public abstract class BasePlayer implements Player.EventListener,
public void unblock() {
Log.d(TAG, "Unblocking...");
if (windowIndex != playbackManager.getCurrentSourceIndex()) {
windowIndex = playbackManager.getCurrentSourceIndex();
windowPos = 0;
if (restoreQueueIndex != playQueue.getIndex()) {
restoreQueueIndex = playQueue.getIndex();
restoreWindowPos = 0;
}
simpleExoPlayer.prepare(playbackManager.getMediaSource());
simpleExoPlayer.seekTo(windowIndex, windowPos);
simpleExoPlayer.setPlayWhenReady(true);
simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), restoreWindowPos);
simpleExoPlayer.setPlayWhenReady(false);
}
@Override
public void sync(final int windowIndex, final StreamInfo info) {
public void sync(final StreamInfo info, final int sortedStreamsIndex) {
Log.d(TAG, "Syncing...");
videoUrl = info.url;
videoThumbnailUrl = info.thumbnail_url;
videoTitle = info.name;
if (simpleExoPlayer.getCurrentWindowIndex() != windowIndex) {
if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) {
Log.w(TAG, "Rewinding to correct window");
simpleExoPlayer.seekTo(windowIndex, 0L);
simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), 0L);
}
simpleExoPlayer.setPlayWhenReady(true);
}
@Override
public MediaSource sourceOf(final StreamInfo info) {
public MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex) {
return null;
}
@Override
public void shutdown() {
Log.d(TAG, "Shutting down...");
playbackManager.dispose();
playQueue.dispose();
destroy();
}
/*//////////////////////////////////////////////////////////////////////////
// General Player
//////////////////////////////////////////////////////////////////////////*/
public void onError(Exception exception){
destroy();
}
public abstract void onError(Exception exception);
public void onPrepared(boolean playWhenReady) {
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
@ -840,4 +880,12 @@ public abstract class BasePlayer implements Player.EventListener,
public void setPlaybackSpeed(float speed) {
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, 1f));
}
public int getCurrentQueueIndex() {
return playQueue != null ? playQueue.getIndex() : -1;
}
public PlayQueue getPlayQueue() {
return playQueue;
}
}

View File

@ -123,7 +123,8 @@ public class MainVideoPlayer extends Activity {
if (activityPaused) {
playerImpl.initPlayer();
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
playerImpl.play(false);
playerImpl.playQueue.init();
//playerImpl.play(false);
activityPaused = false;
}
}
@ -230,21 +231,25 @@ public class MainVideoPlayer extends Activity {
channelTextView.setText(getUploaderName());
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void sync(final int windowIndex, final StreamInfo info) {
super.sync(windowIndex, info);
public void shutdown() {
super.shutdown();
finish();
}
@Override
public void sync(final StreamInfo info, final int sortedStreamsIndex) {
super.sync(info, sortedStreamsIndex);
titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName());
playPauseButton.setImageResource(R.drawable.ic_pause_white);
}
@Override
public void playUrl(String url, String format, boolean autoPlay) {
super.playUrl(url, format, autoPlay);
playPauseButton.setImageResource(autoPlay ? R.drawable.ic_pause_white : R.drawable.ic_play_arrow_white);
}
@Override
public void onFullScreenButtonClicked() {
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
@ -331,7 +336,6 @@ public class MainVideoPlayer extends Activity {
public void onError(Exception exception) {
exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
//finish();
}
/*//////////////////////////////////////////////////////////////////////////

View File

@ -1,7 +1,6 @@
package org.schabi.newpipe.player;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
@ -19,7 +18,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.MaybeObserver;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
@ -67,13 +65,15 @@ class MediaSourceManager {
* Signals to the listener to synchronize the player's window to the manager's
* window.
* */
void sync(final int windowIndex, final StreamInfo info);
void sync(final StreamInfo info, final int sortedStreamsIndex);
/*
* Requests the listener to resolve a stream info into a media source respective
* of the listener's implementation (background, popup or main video player),
* */
MediaSource sourceOf(final StreamInfo info);
MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex);
void shutdown();
}
MediaSourceManager(@NonNull final MediaSourceManager.PlaybackListener listener,
@ -118,9 +118,7 @@ class MediaSourceManager {
void report(final Exception error) {
// ignore error checking for now, just remove the current index
if (error != null) {
tryBlock();
}
if (error == null || !tryBlock()) return;
final int index = playQueue.getIndex();
playQueue.remove(index);
@ -129,6 +127,19 @@ class MediaSourceManager {
load();
}
int queueIndexOf(final int sourceIndex) {
return sourceIndex < sourceToQueueIndex.size() ? sourceToQueueIndex.get(sourceIndex) : -1;
}
void updateCurrent(final int newSortedStreamsIndex) {
if (!tryBlock()) return;
PlayQueueItem item = playQueue.getCurrent();
item.setSortedQualityIndex(newSortedStreamsIndex);
resetSources();
load();
}
void dispose() {
if (loadingReactor != null) loadingReactor.cancel();
if (playQueueReactor != null) playQueueReactor.cancel();
@ -180,7 +191,10 @@ class MediaSourceManager {
if (!isPlayQueueReady()) {
tryBlock();
playQueue.fetch();
} else if (playQueue.isEmpty()) {
playbackListener.shutdown();
}
if (playQueueReactor != null) playQueueReactor.request(1);
}
@ -240,14 +254,16 @@ class MediaSourceManager {
}
private void sync() {
final PlayQueueItem currentItem = playQueue.getCurrent();
final Consumer<StreamInfo> onSuccess = new Consumer<StreamInfo>() {
@Override
public void accept(StreamInfo streamInfo) throws Exception {
playbackListener.sync(getCurrentSourceIndex(), streamInfo);
playbackListener.sync(streamInfo, currentItem.getSortedQualityIndex());
}
};
playQueue.getCurrent().getStream().subscribe(onSuccess);
currentItem.getStream().subscribe(onSuccess);
}
private void load() {
@ -280,7 +296,7 @@ class MediaSourceManager {
@Override
public void onSuccess(@NonNull StreamInfo streamInfo) {
final MediaSource source = playbackListener.sourceOf(streamInfo);
final MediaSource source = playbackListener.sourceOf(streamInfo, item.getSortedQualityIndex());
insert(playQueue.indexOf(item), source);
if (tryUnblock()) sync();
}
@ -305,13 +321,12 @@ class MediaSourceManager {
// Media Source List Manipulation
//////////////////////////////////////////////////////////////////////////*/
public void replace(final int queueIndex, final MediaSource source) {
private void reset(final int queueIndex) {
if (queueIndex < 0) return;
final int sourceIndex = sourceToQueueIndex.indexOf(queueIndex);
if (sourceIndex != -1) {
// Add the source after the one to remove, so the window will remain the same in the player
sources.addMediaSource(sourceIndex + 1, source);
sourceToQueueIndex.remove(sourceIndex);
sources.removeMediaSource(sourceIndex);
}
}

View File

@ -272,10 +272,11 @@ public class PopupVideoPlayer extends Service {
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
break;
case Player.REPEAT_MODE_ONE:
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
//todo change image
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 168);
break;
case Player.REPEAT_MODE_ALL:
// Waiting :)
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
break;
}
@ -390,18 +391,6 @@ public class PopupVideoPlayer extends Service {
super("VideoPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
}
@Override
public void playUrl(String url, String format, boolean autoPlay) {
super.playUrl(url, format, autoPlay);
windowLayoutParams.width = (int) popupWidth;
windowLayoutParams.height = (int) getMinimumVideoHeight(popupWidth);
windowManager.updateViewLayout(getRootView(), windowLayoutParams);
notBuilder = createNotification();
startForeground(NOTIFICATION_ID, notBuilder.build());
}
@Override
public void initViews(View rootView) {
super.initViews(rootView);
@ -475,7 +464,6 @@ public class PopupVideoPlayer extends Service {
public void onError(Exception exception) {
exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
stopSelf();
}
@Override
@ -486,6 +474,16 @@ public class PopupVideoPlayer extends Service {
}
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void shutdown() {
super.shutdown();
stopSelf();
}
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
@ -583,8 +581,14 @@ public class PopupVideoPlayer extends Service {
if (DEBUG)
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
if (!playerImpl.isPlaying()) return false;
if (e.getX() > popupWidth / 2) playerImpl.onFastForward();
else playerImpl.onFastRewind();
if (e.getX() > popupWidth / 2) {
//playerImpl.onFastForward();
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1);
} else {
//playerImpl.onFastRewind();
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1);
}
return true;
}
@ -766,7 +770,7 @@ public class PopupVideoPlayer extends Service {
mainHandler.post(new Runnable() {
@Override
public void run() {
playerImpl.play(true);
playerImpl.playQueue.init();
}
});

View File

@ -53,13 +53,13 @@ import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ListHelper;
@ -89,7 +89,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
private int selectedIndexStream;
public static final String PLAY_QUEUE = "play_queue";
public static final String PLAYER_INTENT = "player_intent";
private int selectedIndexStream = -1;
private ArrayList<VideoStream> videoStreamsList = new ArrayList<>();
private AudioStream videoOnlyAudioStream;
@ -202,65 +205,55 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
simpleExoPlayer.setVideoListener(this);
}
@SuppressWarnings("unchecked")
public void handleSingleStreamIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return;
selectedIndexStream = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
Serializable serializable = intent.getSerializableExtra(VIDEO_STREAMS_LIST);
if (serializable instanceof ArrayList) videoStreamsList = (ArrayList<VideoStream>) serializable;
if (serializable instanceof Vector) videoStreamsList = new ArrayList<>((List<VideoStream>) serializable);
Serializable audioStream = intent.getSerializableExtra(VIDEO_ONLY_AUDIO_STREAM);
if (audioStream != null) videoOnlyAudioStream = (AudioStream) audioStream;
startedFromNewPipe = intent.getBooleanExtra(STARTED_FROM_NEWPIPE, true);
play(true);
}
@SuppressWarnings("unchecked")
public void handleIntent(Intent intent) {
super.handleIntent(intent);
if (intent == null) return;
handleExternalPlaylistIntent(intent);
if (intent.getStringExtra(INTENT_TYPE).equals(PLAYER_INTENT)) {
handlePlayerIntent(intent);
}
}
@SuppressWarnings("unchecked")
public void handleExternalPlaylistIntent(Intent intent) {
selectedIndexStream = 0;
public void handleSinglePlaylistIntent(Intent intent) {
final Serializable serializable = intent.getSerializableExtra(SinglePlayQueue.STREAM);
if (!(serializable instanceof StreamInfo)) return;
final int serviceId = intent.getIntExtra("serviceId", -1);
final int index = intent.getIntExtra("index", 0);
final Serializable serializable = intent.getSerializableExtra("streams");
final String nextPageUrl = intent.getStringExtra("nextPageUrl");
final int sortedStreamsIndex = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
List<InfoItem> info = new ArrayList<>();
if (serializable instanceof List) {
for (final Object o : (List) serializable) {
if (o instanceof InfoItem) info.add((StreamInfoItem) o);
}
}
playQueue = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex);
playQueue.init();
playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index);
playbackManager = new MediaSourceManager(this, playQueue);
}
public void play(boolean autoPlay) {
playUrl(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format), autoPlay);
@SuppressWarnings("unchecked")
public void handlePlayerIntent(Intent intent) {
final Serializable serializable = intent.getSerializableExtra(PLAY_QUEUE);
if (!(serializable instanceof PlayQueue)) return;
selectedIndexStream = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
playQueue = (PlayQueue) serializable;
playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue);
}
@Override
public void sync(final int windowIndex, final StreamInfo info) {
super.sync(windowIndex, info);
public void sync(final StreamInfo info, final int sortedStreamsIndex) {
super.sync(info, sortedStreamsIndex);
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
videoStreamsList = new ArrayList<>(videos);
selectedIndexStream = ListHelper.getDefaultResolutionIndex(context, videos);
if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) {
selectedIndexStream = ListHelper.getDefaultResolutionIndex(context, videos);
} else {
selectedIndexStream = sortedStreamsIndex;
}
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
buildQualityMenu(qualityPopupMenu);
@ -270,9 +263,16 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
}
@Override
public MediaSource sourceOf(final StreamInfo info) {
public MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex) {
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
final VideoStream video = videos.get(ListHelper.getDefaultResolutionIndex(context, videos));
final VideoStream video;
if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) {
final int index = ListHelper.getDefaultResolutionIndex(context, videos);
video = videos.get(index);
} else {
video = videos.get(sortedStreamsIndex);
}
final MediaSource mediaSource = super.buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
if (!video.isVideoOnly) return mediaSource;
@ -282,26 +282,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
return new MergingMediaSource(mediaSource, new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null));
}
@Override
public void playUrl(String url, String format, boolean autoPlay) {
if (DEBUG) Log.d(TAG, "play() called with: url = [" + url + "], autoPlay = [" + autoPlay + "]");
qualityChanged = false;
if (url == null || simpleExoPlayer == null) {
RuntimeException runtimeException = new RuntimeException((url == null ? "Url " : "Player ") + " null");
onError(runtimeException);
throw runtimeException;
}
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
buildQualityMenu(qualityPopupMenu);
playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
buildPlaybackSpeedMenu(playbackSpeedPopupMenu);
super.playUrl(url, format, autoPlay);
}
@Override
public MediaSource buildMediaSource(String url, String overrideExtension) {
MediaSource mediaSource = super.buildMediaSource(url, overrideExtension);
@ -460,6 +440,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (duration != playbackSeekBar.getMax()) {
playbackEndTime.setText(getTimeString(duration));
playbackSeekBar.setMax(duration);
}
if (currentState != STATE_PAUSED) {
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
@ -478,7 +459,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
if (qualityChanged) {
setVideoStartPos(0);
play(true);
//play(true);
} else super.onVideoPlayPauseRepeat();
}
@ -528,11 +509,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
if (selectedIndexStream == menuItem.getItemId()) return true;
setVideoStartPos(simpleExoPlayer.getCurrentPosition());
selectedIndexStream = menuItem.getItemId();
if (!(getCurrentState() == STATE_COMPLETED)) play(wasPlaying);
else qualityChanged = true;
restoreQueueIndex = playQueue.getIndex();
restoreWindowPos = simpleExoPlayer.getCurrentPosition();
playbackManager.updateCurrent(menuItem.getItemId());
qualityTextView.setText(menuItem.getTitle());
return true;

View File

@ -4,7 +4,6 @@ import android.util.Log;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
@ -18,8 +17,14 @@ import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class ExternalPlayQueue extends PlayQueue {
public final class ExternalPlayQueue extends PlayQueue {
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode());
public static final String SERVICE_ID = "service_id";
public static final String INDEX = "index";
public static final String STREAMS = "streams";
public static final String NEXT_PAGE_URL = "next_page_url";
private static final int RETRY_COUNT = 2;
private boolean isComplete;
@ -27,7 +32,7 @@ public class ExternalPlayQueue extends PlayQueue {
private int serviceId;
private String playlistUrl;
private Disposable fetchReactor;
private transient Disposable fetchReactor;
public ExternalPlayQueue(final int serviceId,
final String nextPageUrl,
@ -46,12 +51,6 @@ public class ExternalPlayQueue extends PlayQueue {
return isComplete;
}
@Override
public PlayQueueItem get(int index) {
if (index > getStreams().size() || getStreams().get(index) == null) return null;
return getStreams().get(index);
}
@Override
public void fetch() {
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.playlistUrl)

View File

@ -16,6 +16,7 @@ import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.SelectEvent;
import org.schabi.newpipe.playlist.events.SwapEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -26,27 +27,33 @@ import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.subjects.BehaviorSubject;
public abstract class PlayQueue {
public abstract class PlayQueue implements Serializable {
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
public static final boolean DEBUG = true;
private final List<PlayQueueItem> streams;
private final ArrayList<PlayQueueItem> streams;
private final AtomicInteger queueIndex;
private final BehaviorSubject<PlayQueueMessage> eventBroadcast;
private final Flowable<PlayQueueMessage> broadcastReceiver;
private Subscription reportingReactor;
private transient BehaviorSubject<PlayQueueMessage> eventBroadcast;
private transient Flowable<PlayQueueMessage> broadcastReceiver;
private transient Subscription reportingReactor;
PlayQueue() {
this(0, Collections.<PlayQueueItem>emptyList());
}
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
streams = Collections.synchronizedList(new ArrayList<PlayQueueItem>());
streams = new ArrayList<>();
streams.addAll(startWith);
queueIndex = new AtomicInteger(index);
}
/*//////////////////////////////////////////////////////////////////////////
// Playlist actions
//////////////////////////////////////////////////////////////////////////*/
public void init() {
eventBroadcast = BehaviorSubject.create();
broadcastReceiver = eventBroadcast
.startWith(new InitEvent())
@ -55,9 +62,12 @@ public abstract class PlayQueue {
if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
}
/*//////////////////////////////////////////////////////////////////////////
// Playlist actions
//////////////////////////////////////////////////////////////////////////*/
public void dispose() {
eventBroadcast.onComplete();
if (reportingReactor != null) reportingReactor.cancel();
reportingReactor = null;
}
// a queue is complete if it has loaded all items in an external playlist
// single stream or local queues are always complete
@ -66,21 +76,27 @@ public abstract class PlayQueue {
// load partial queue in the background, does nothing if the queue is complete
public abstract void fetch();
public abstract PlayQueueItem get(int index);
public void dispose() {
eventBroadcast.onComplete();
if (reportingReactor != null) reportingReactor.cancel();
reportingReactor = null;
}
/*//////////////////////////////////////////////////////////////////////////
// Readonly ops
//////////////////////////////////////////////////////////////////////////*/
public int getIndex() {
return queueIndex.get();
}
public PlayQueueItem getCurrent() {
return streams.get(getIndex());
return get(getIndex());
}
public PlayQueueItem get(int index) {
if (index >= streams.size() || streams.get(index) == null) return null;
return streams.get(index);
}
public int indexOf(final PlayQueueItem item) {
// reference equality, can't think of a better way to do this
// todo: better than this
return streams.indexOf(item);
}
public int size() {
@ -101,36 +117,26 @@ public abstract class PlayQueue {
return broadcastReceiver;
}
public int indexOf(final PlayQueueItem item) {
// reference equality, can't think of a better way to do this
// todo: better than this
return streams.indexOf(item);
}
public int getIndex() {
return queueIndex.get();
}
/*//////////////////////////////////////////////////////////////////////////
// Write ops
//////////////////////////////////////////////////////////////////////////*/
public void setIndex(final int index) {
public synchronized void setIndex(final int index) {
queueIndex.set(Math.min(Math.max(0, index), streams.size() - 1));
broadcast(new SelectEvent(index));
}
protected void append(final PlayQueueItem item) {
protected synchronized void append(final PlayQueueItem item) {
streams.add(item);
broadcast(new AppendEvent(1));
}
protected void append(final Collection<PlayQueueItem> items) {
protected synchronized void append(final Collection<PlayQueueItem> items) {
streams.addAll(items);
broadcast(new AppendEvent(items.size()));
}
public void remove(final int index) {
public synchronized void remove(final int index) {
if (index >= streams.size()) return;
streams.remove(index);
@ -142,7 +148,7 @@ public abstract class PlayQueue {
broadcast(new RemoveEvent(index));
}
protected void swap(final int source, final int target) {
protected synchronized void swap(final int source, final int target) {
final List<PlayQueueItem> items = streams;
if (source < items.size() && target < items.size()) {
// Swap two items

View File

@ -7,20 +7,35 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.io.Serializable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
public class PlayQueueItem {
public class PlayQueueItem implements Serializable {
final public static int DEFAULT_QUALITY = -1;
final private String title;
final private String url;
final private int serviceId;
final private long duration;
// Externally mutable, not sure if this is a good idea here
private int sortedQualityIndex;
private Throwable error;
private Single<StreamInfo> stream;
PlayQueueItem(final StreamInfo streamInfo, final int sortedQualityIndex) {
this.title = streamInfo.name;
this.url = streamInfo.url;
this.serviceId = streamInfo.service_id;
this.duration = streamInfo.duration;
this.sortedQualityIndex = sortedQualityIndex;
}
PlayQueueItem(final StreamInfoItem streamInfoItem) {
this.title = streamInfoItem.name;
@ -28,9 +43,10 @@ public class PlayQueueItem {
this.serviceId = streamInfoItem.service_id;
this.duration = streamInfoItem.duration;
this.stream = getInfo();
this.sortedQualityIndex = DEFAULT_QUALITY;
}
@NonNull
public String getTitle() {
return title;
@ -49,6 +65,14 @@ public class PlayQueueItem {
return duration;
}
public int getSortedQualityIndex() {
return sortedQualityIndex;
}
public void setSortedQualityIndex(int sortedQualityIndex) {
this.sortedQualityIndex = sortedQualityIndex;
}
@Nullable
public Throwable getError() {
return error;
@ -56,11 +80,6 @@ public class PlayQueueItem {
@NonNull
public Single<StreamInfo> getStream() {
return stream;
}
@NonNull
private Single<StreamInfo> getInfo() {
final Consumer<Throwable> onError = new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {

View File

@ -0,0 +1,21 @@
package org.schabi.newpipe.playlist;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import java.util.Collections;
public final class SinglePlayQueue extends PlayQueue {
public static final String STREAM = "stream";
public SinglePlayQueue(final StreamInfo info, final int selectedQualityIndex) {
super(0, Collections.singletonList(new PlayQueueItem(info, selectedQualityIndex)));
}
@Override
public boolean isComplete() {
return true;
}
@Override
public void fetch() {}
}

View File

@ -13,10 +13,12 @@ import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.about.AboutActivity;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.MainFragment;
@ -30,9 +32,12 @@ import org.schabi.newpipe.history.HistoryActivity;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.settings.SettingsActivity;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings({"unused", "WeakerAccess"})
public class NavigationHelper {
@ -43,46 +48,60 @@ public class NavigationHelper {
//////////////////////////////////////////////////////////////////////////*/
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
Intent mIntent = new Intent(context, targetClazz)
.putExtra(BasePlayer.VIDEO_TITLE, info.name)
.putExtra(BasePlayer.VIDEO_URL, info.url)
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader_name)
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false)))
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, ListHelper.getHighestQualityAudio(info.audio_streams));
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000L);
return mIntent;
return new Intent(context, targetClazz)
.putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.SINGLE_STREAM)
.putExtra(SinglePlayQueue.STREAM, info)
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex);
}
public static Intent getExternalPlaylistIntent(Context context,
Class targetClazz,
PlaylistInfo info,
ArrayList<InfoItem> streams,
int index) {
return new Intent(context, targetClazz)
.putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.EXTERNAL_PLAYLIST)
.putExtra(ExternalPlayQueue.SERVICE_ID, info.service_id)
.putExtra(ExternalPlayQueue.INDEX, index)
.putExtra(ExternalPlayQueue.STREAMS, streams)
.putExtra(ExternalPlayQueue.NEXT_PAGE_URL, info.next_streams_url);
}
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, VideoPlayer instance) {
return new Intent(context, targetClazz)
.putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle())
.putExtra(BasePlayer.VIDEO_URL, instance.getVideoUrl())
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, instance.getVideoThumbnailUrl())
.putExtra(BasePlayer.CHANNEL_NAME, instance.getUploaderName())
.putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.PLAYER_INTENT)
.putExtra(VideoPlayer.PLAY_QUEUE, instance.getPlayQueue())
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
.putExtra(VideoPlayer.RESTORE_QUEUE_INDEX, instance.getCurrentQueueIndex())
.putExtra(BasePlayer.START_POSITION, instance.getPlayer().getCurrentPosition())
.putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed());
}
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
return getOpenBackgroundPlayerIntent(context, info, info.audio_streams.get(ListHelper.getDefaultAudioFormat(context, info.audio_streams)));
return new Intent(context, BackgroundPlayer.class)
.putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.SINGLE_STREAM)
.putExtra(SinglePlayQueue.STREAM, info);
}
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info, AudioStream audioStream) {
Intent mIntent = new Intent(context, BackgroundPlayer.class)
.putExtra(BasePlayer.VIDEO_TITLE, info.name)
.putExtra(BasePlayer.VIDEO_URL, info.url)
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader_name)
.putExtra(BackgroundPlayer.AUDIO_STREAM, audioStream);
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000L);
return mIntent;
return getOpenBackgroundPlayerIntent(context, info);
}
// public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
// return getOpenBackgroundPlayerIntent(context, info, info.audio_streams.get(ListHelper.getDefaultAudioFormat(context, info.audio_streams)));
// }
//
// public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info, AudioStream audioStream) {
// Intent mIntent = new Intent(context, BackgroundPlayer.class)
// .putExtra(BasePlayer.VIDEO_TITLE, info.name)
// .putExtra(BasePlayer.VIDEO_URL, info.url)
// .putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
// .putExtra(BasePlayer.CHANNEL_NAME, info.uploader_name)
// .putExtra(BackgroundPlayer.AUDIO_STREAM, audioStream);
// if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000L);
// return mIntent;
// }
/*//////////////////////////////////////////////////////////////////////////
// Through FragmentManager
//////////////////////////////////////////////////////////////////////////*/