-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.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo; 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.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import java.util.List;
import io.reactivex.Single; import io.reactivex.Single;
import static org.schabi.newpipe.util.AnimationUtils.animateView; 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); 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()) { if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.service_id), result.url, 0); 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() { headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { 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 @Override
public void handleNextItems(ListExtractor.NextItemsResult result) { public void handleNextItems(ListExtractor.NextItemsResult result) {

View file

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

View file

@ -70,13 +70,21 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
import org.schabi.newpipe.Downloader; import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfo; 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.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import java.io.File; import java.io.File;
import java.io.Serializable;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Formatter; import java.util.Formatter;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -90,7 +98,7 @@ public abstract class BasePlayer implements Player.EventListener,
AudioManager.OnAudioFocusChangeListener, MediaSourceManager.PlaybackListener { AudioManager.OnAudioFocusChangeListener, MediaSourceManager.PlaybackListener {
// TODO: Check api version for deprecated audio manager methods // 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"; public static final String TAG = "BasePlayer";
protected Context context; protected Context context;
@ -104,6 +112,11 @@ public abstract class BasePlayer implements Player.EventListener,
// Intent // 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_URL = "video_url";
public static final String VIDEO_TITLE = "video_title"; public static final String VIDEO_TITLE = "video_title";
public static final String VIDEO_THUMBNAIL_URL = "video_thumbnail_url"; 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 CHANNEL_NAME = "channel_name";
public static final String PLAYBACK_SPEED = "playback_speed"; 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 Bitmap videoThumbnail = null;
protected String videoUrl = ""; protected String videoUrl = "";
protected String videoTitle = ""; protected String videoTitle = "";
@ -125,8 +141,8 @@ public abstract class BasePlayer implements Player.EventListener,
protected MediaSourceManager playbackManager; protected MediaSourceManager playbackManager;
protected PlayQueue playQueue; protected PlayQueue playQueue;
private int windowIndex; protected int restoreQueueIndex;
private long windowPos; protected long restoreWindowPos;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Player // Player
@ -219,15 +235,54 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return; if (intent == null) return;
videoUrl = intent.getStringExtra(VIDEO_URL); restoreQueueIndex = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0);
videoTitle = intent.getStringExtra(VIDEO_TITLE); restoreWindowPos = intent.getLongExtra(START_POSITION, 0);
videoThumbnailUrl = intent.getStringExtra(VIDEO_THUMBNAIL_URL);
videoStartPos = intent.getLongExtra(START_POSITION, -1L);
uploaderName = intent.getStringExtra(CHANNEL_NAME);
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed())); 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(); 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() { 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() { public void destroyPlayer() {
if (DEBUG) Log.d(TAG, "destroyPlayer() called"); if (DEBUG) Log.d(TAG, "destroyPlayer() called");
if (simpleExoPlayer != null) { if (simpleExoPlayer != null) {
@ -466,7 +500,6 @@ public abstract class BasePlayer implements Player.EventListener,
public void onRepeatClicked() { public void onRepeatClicked() {
if (DEBUG) Log.d(TAG, "onRepeatClicked() called"); if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
// TODO: implement repeat all when playlist is implemented
final int mode; final int mode;
@ -482,7 +515,7 @@ public abstract class BasePlayer implements Player.EventListener,
mode = Player.REPEAT_MODE_OFF; mode = Player.REPEAT_MODE_OFF;
break; break;
} }
// Switch the modes between DISABLED and REPEAT_ONE, till playlist is implemented
simpleExoPlayer.setRepeatMode(mode); simpleExoPlayer.setRepeatMode(mode);
if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + simpleExoPlayer.getRepeatMode()); if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + simpleExoPlayer.getRepeatMode());
} }
@ -566,8 +599,6 @@ public abstract class BasePlayer implements Player.EventListener,
Log.d(TAG, "Blocking..."); Log.d(TAG, "Blocking...");
simpleExoPlayer.stop(); simpleExoPlayer.stop();
windowIndex = simpleExoPlayer.getCurrentWindowIndex();
windowPos = Math.max(0, simpleExoPlayer.getContentPosition());
changeState(STATE_BUFFERING); changeState(STATE_BUFFERING);
} }
@ -576,42 +607,51 @@ public abstract class BasePlayer implements Player.EventListener,
public void unblock() { public void unblock() {
Log.d(TAG, "Unblocking..."); Log.d(TAG, "Unblocking...");
if (windowIndex != playbackManager.getCurrentSourceIndex()) { if (restoreQueueIndex != playQueue.getIndex()) {
windowIndex = playbackManager.getCurrentSourceIndex(); restoreQueueIndex = playQueue.getIndex();
windowPos = 0; restoreWindowPos = 0;
} }
simpleExoPlayer.prepare(playbackManager.getMediaSource()); simpleExoPlayer.prepare(playbackManager.getMediaSource());
simpleExoPlayer.seekTo(windowIndex, windowPos); simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), restoreWindowPos);
simpleExoPlayer.setPlayWhenReady(true); simpleExoPlayer.setPlayWhenReady(false);
} }
@Override @Override
public void sync(final int windowIndex, final StreamInfo info) { public void sync(final StreamInfo info, final int sortedStreamsIndex) {
Log.d(TAG, "Syncing..."); Log.d(TAG, "Syncing...");
videoUrl = info.url; videoUrl = info.url;
videoThumbnailUrl = info.thumbnail_url; videoThumbnailUrl = info.thumbnail_url;
videoTitle = info.name; videoTitle = info.name;
if (simpleExoPlayer.getCurrentWindowIndex() != windowIndex) { if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) {
Log.w(TAG, "Rewinding to correct window"); Log.w(TAG, "Rewinding to correct window");
simpleExoPlayer.seekTo(windowIndex, 0L); simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), 0L);
} }
simpleExoPlayer.setPlayWhenReady(true);
} }
@Override @Override
public MediaSource sourceOf(final StreamInfo info) { public MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex) {
return null; return null;
} }
@Override
public void shutdown() {
Log.d(TAG, "Shutting down...");
playbackManager.dispose();
playQueue.dispose();
destroy();
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// General Player // General Player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void onError(Exception exception){ public abstract void onError(Exception exception);
destroy();
}
public void onPrepared(boolean playWhenReady) { public void onPrepared(boolean playWhenReady) {
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + 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) { public void setPlaybackSpeed(float speed) {
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, 1f)); 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) { if (activityPaused) {
playerImpl.initPlayer(); playerImpl.initPlayer();
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white); playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
playerImpl.play(false); playerImpl.playQueue.init();
//playerImpl.play(false);
activityPaused = false; activityPaused = false;
} }
} }
@ -230,21 +231,25 @@ public class MainVideoPlayer extends Activity {
channelTextView.setText(getUploaderName()); channelTextView.setText(getUploaderName());
} }
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void sync(final int windowIndex, final StreamInfo info) { public void shutdown() {
super.sync(windowIndex, info); super.shutdown();
finish();
}
@Override
public void sync(final StreamInfo info, final int sortedStreamsIndex) {
super.sync(info, sortedStreamsIndex);
titleTextView.setText(getVideoTitle()); titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName()); channelTextView.setText(getUploaderName());
playPauseButton.setImageResource(R.drawable.ic_pause_white); 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 @Override
public void onFullScreenButtonClicked() { public void onFullScreenButtonClicked() {
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
@ -331,7 +336,6 @@ public class MainVideoPlayer extends Activity {
public void onError(Exception exception) { public void onError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
//finish();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View file

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

View file

@ -272,10 +272,11 @@ public class PopupVideoPlayer extends Service {
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77); notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
break; break;
case Player.REPEAT_MODE_ONE: case Player.REPEAT_MODE_ONE:
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255); //todo change image
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 168);
break; break;
case Player.REPEAT_MODE_ALL: case Player.REPEAT_MODE_ALL:
// Waiting :) notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
break; break;
} }
@ -390,18 +391,6 @@ public class PopupVideoPlayer extends Service {
super("VideoPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this); 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 @Override
public void initViews(View rootView) { public void initViews(View rootView) {
super.initViews(rootView); super.initViews(rootView);
@ -475,7 +464,6 @@ public class PopupVideoPlayer extends Service {
public void onError(Exception exception) { public void onError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
stopSelf();
} }
@Override @Override
@ -486,6 +474,16 @@ public class PopupVideoPlayer extends Service {
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void shutdown() {
super.shutdown();
stopSelf();
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver // Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -583,8 +581,14 @@ public class PopupVideoPlayer extends Service {
if (DEBUG) if (DEBUG)
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
if (!playerImpl.isPlaying()) return false; if (!playerImpl.isPlaying()) return false;
if (e.getX() > popupWidth / 2) playerImpl.onFastForward(); if (e.getX() > popupWidth / 2) {
else playerImpl.onFastRewind(); //playerImpl.onFastForward();
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1);
} else {
//playerImpl.onFastRewind();
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1);
}
return true; return true;
} }
@ -766,7 +770,7 @@ public class PopupVideoPlayer extends Service {
mainHandler.post(new Runnable() { mainHandler.post(new Runnable() {
@Override @Override
public void run() { 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 com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream; 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.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.AnimationUtils;
import org.schabi.newpipe.util.ListHelper; 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 INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe"; 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 ArrayList<VideoStream> videoStreamsList = new ArrayList<>();
private AudioStream videoOnlyAudioStream; private AudioStream videoOnlyAudioStream;
@ -202,65 +205,55 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
simpleExoPlayer.setVideoListener(this); 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") @SuppressWarnings("unchecked")
public void handleIntent(Intent intent) { public void handleIntent(Intent intent) {
super.handleIntent(intent); super.handleIntent(intent);
if (intent == null) return; if (intent == null) return;
handleExternalPlaylistIntent(intent); if (intent.getStringExtra(INTENT_TYPE).equals(PLAYER_INTENT)) {
handlePlayerIntent(intent);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void handleExternalPlaylistIntent(Intent intent) { public void handleSinglePlaylistIntent(Intent intent) {
selectedIndexStream = 0; final Serializable serializable = intent.getSerializableExtra(SinglePlayQueue.STREAM);
if (!(serializable instanceof StreamInfo)) return;
final int serviceId = intent.getIntExtra("serviceId", -1); final int sortedStreamsIndex = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
final int index = intent.getIntExtra("index", 0);
final Serializable serializable = intent.getSerializableExtra("streams");
final String nextPageUrl = intent.getStringExtra("nextPageUrl");
List<InfoItem> info = new ArrayList<>(); playQueue = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex);
if (serializable instanceof List) { playQueue.init();
for (final Object o : (List) serializable) {
if (o instanceof InfoItem) info.add((StreamInfoItem) o);
}
}
playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index);
playbackManager = new MediaSourceManager(this, playQueue); playbackManager = new MediaSourceManager(this, playQueue);
} }
public void play(boolean autoPlay) { @SuppressWarnings("unchecked")
playUrl(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format), autoPlay); 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 @Override
public void sync(final int windowIndex, final StreamInfo info) { public void sync(final StreamInfo info, final int sortedStreamsIndex) {
super.sync(windowIndex, info); super.sync(info, sortedStreamsIndex);
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
videoStreamsList = new ArrayList<>(videos); videoStreamsList = new ArrayList<>(videos);
if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) {
selectedIndexStream = ListHelper.getDefaultResolutionIndex(context, videos); selectedIndexStream = ListHelper.getDefaultResolutionIndex(context, videos);
} else {
selectedIndexStream = sortedStreamsIndex;
}
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId); qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
buildQualityMenu(qualityPopupMenu); buildQualityMenu(qualityPopupMenu);
@ -270,9 +263,16 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
} }
@Override @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 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)); final MediaSource mediaSource = super.buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
if (!video.isVideoOnly) return mediaSource; 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)); 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 @Override
public MediaSource buildMediaSource(String url, String overrideExtension) { public MediaSource buildMediaSource(String url, String overrideExtension) {
MediaSource mediaSource = super.buildMediaSource(url, overrideExtension); MediaSource mediaSource = super.buildMediaSource(url, overrideExtension);
@ -460,6 +440,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (duration != playbackSeekBar.getMax()) { if (duration != playbackSeekBar.getMax()) {
playbackEndTime.setText(getTimeString(duration)); playbackEndTime.setText(getTimeString(duration));
playbackSeekBar.setMax(duration);
} }
if (currentState != STATE_PAUSED) { if (currentState != STATE_PAUSED) {
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress); 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 (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
if (qualityChanged) { if (qualityChanged) {
setVideoStartPos(0); setVideoStartPos(0);
play(true); //play(true);
} else super.onVideoPlayPauseRepeat(); } else super.onVideoPlayPauseRepeat();
} }
@ -528,11 +509,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (qualityPopupMenuGroupId == menuItem.getGroupId()) { if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
if (selectedIndexStream == menuItem.getItemId()) return true; if (selectedIndexStream == menuItem.getItemId()) return true;
setVideoStartPos(simpleExoPlayer.getCurrentPosition());
selectedIndexStream = menuItem.getItemId(); restoreQueueIndex = playQueue.getIndex();
if (!(getCurrentState() == STATE_COMPLETED)) play(wasPlaying); restoreWindowPos = simpleExoPlayer.getCurrentPosition();
else qualityChanged = true; playbackManager.updateCurrent(menuItem.getItemId());
qualityTextView.setText(menuItem.getTitle()); qualityTextView.setText(menuItem.getTitle());
return true; return true;

View file

@ -4,7 +4,6 @@ import android.util.Log;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
@ -18,8 +17,14 @@ import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
public class ExternalPlayQueue extends PlayQueue { public final class ExternalPlayQueue extends PlayQueue {
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode()); 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 static final int RETRY_COUNT = 2;
private boolean isComplete; private boolean isComplete;
@ -27,7 +32,7 @@ public class ExternalPlayQueue extends PlayQueue {
private int serviceId; private int serviceId;
private String playlistUrl; private String playlistUrl;
private Disposable fetchReactor; private transient Disposable fetchReactor;
public ExternalPlayQueue(final int serviceId, public ExternalPlayQueue(final int serviceId,
final String nextPageUrl, final String nextPageUrl,
@ -46,12 +51,6 @@ public class ExternalPlayQueue extends PlayQueue {
return isComplete; return isComplete;
} }
@Override
public PlayQueueItem get(int index) {
if (index > getStreams().size() || getStreams().get(index) == null) return null;
return getStreams().get(index);
}
@Override @Override
public void fetch() { public void fetch() {
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.playlistUrl) 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.SelectEvent;
import org.schabi.newpipe.playlist.events.SwapEvent; import org.schabi.newpipe.playlist.events.SwapEvent;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -26,27 +27,33 @@ import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable; import io.reactivex.Flowable;
import io.reactivex.subjects.BehaviorSubject; import io.reactivex.subjects.BehaviorSubject;
public abstract class PlayQueue { public abstract class PlayQueue implements Serializable {
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
public static final boolean DEBUG = true; public static final boolean DEBUG = true;
private final List<PlayQueueItem> streams; private final ArrayList<PlayQueueItem> streams;
private final AtomicInteger queueIndex; private final AtomicInteger queueIndex;
private final BehaviorSubject<PlayQueueMessage> eventBroadcast; private transient BehaviorSubject<PlayQueueMessage> eventBroadcast;
private final Flowable<PlayQueueMessage> broadcastReceiver; private transient Flowable<PlayQueueMessage> broadcastReceiver;
private Subscription reportingReactor; private transient Subscription reportingReactor;
PlayQueue() { PlayQueue() {
this(0, Collections.<PlayQueueItem>emptyList()); this(0, Collections.<PlayQueueItem>emptyList());
} }
PlayQueue(final int index, final List<PlayQueueItem> startWith) { PlayQueue(final int index, final List<PlayQueueItem> startWith) {
streams = Collections.synchronizedList(new ArrayList<PlayQueueItem>()); streams = new ArrayList<>();
streams.addAll(startWith); streams.addAll(startWith);
queueIndex = new AtomicInteger(index); queueIndex = new AtomicInteger(index);
}
/*//////////////////////////////////////////////////////////////////////////
// Playlist actions
//////////////////////////////////////////////////////////////////////////*/
public void init() {
eventBroadcast = BehaviorSubject.create(); eventBroadcast = BehaviorSubject.create();
broadcastReceiver = eventBroadcast broadcastReceiver = eventBroadcast
.startWith(new InitEvent()) .startWith(new InitEvent())
@ -55,9 +62,12 @@ public abstract class PlayQueue {
if (DEBUG) broadcastReceiver.subscribe(getSelfReporter()); if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
} }
/*////////////////////////////////////////////////////////////////////////// public void dispose() {
// Playlist actions eventBroadcast.onComplete();
//////////////////////////////////////////////////////////////////////////*/
if (reportingReactor != null) reportingReactor.cancel();
reportingReactor = null;
}
// a queue is complete if it has loaded all items in an external playlist // a queue is complete if it has loaded all items in an external playlist
// single stream or local queues are always complete // 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 // load partial queue in the background, does nothing if the queue is complete
public abstract void fetch(); public abstract void fetch();
public abstract PlayQueueItem get(int index);
public void dispose() {
eventBroadcast.onComplete();
if (reportingReactor != null) reportingReactor.cancel();
reportingReactor = null;
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Readonly ops // Readonly ops
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public int getIndex() {
return queueIndex.get();
}
public PlayQueueItem getCurrent() { 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() { public int size() {
@ -101,36 +117,26 @@ public abstract class PlayQueue {
return broadcastReceiver; 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 // 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)); queueIndex.set(Math.min(Math.max(0, index), streams.size() - 1));
broadcast(new SelectEvent(index)); broadcast(new SelectEvent(index));
} }
protected void append(final PlayQueueItem item) { protected synchronized void append(final PlayQueueItem item) {
streams.add(item); streams.add(item);
broadcast(new AppendEvent(1)); broadcast(new AppendEvent(1));
} }
protected void append(final Collection<PlayQueueItem> items) { protected synchronized void append(final Collection<PlayQueueItem> items) {
streams.addAll(items); streams.addAll(items);
broadcast(new AppendEvent(items.size())); broadcast(new AppendEvent(items.size()));
} }
public void remove(final int index) { public synchronized void remove(final int index) {
if (index >= streams.size()) return; if (index >= streams.size()) return;
streams.remove(index); streams.remove(index);
@ -142,7 +148,7 @@ public abstract class PlayQueue {
broadcast(new RemoveEvent(index)); 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; final List<PlayQueueItem> items = streams;
if (source < items.size() && target < items.size()) { if (source < items.size() && target < items.size()) {
// Swap two items // 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.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import java.io.Serializable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer; import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers; 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 title;
final private String url; final private String url;
final private int serviceId; final private int serviceId;
final private long duration; final private long duration;
// Externally mutable, not sure if this is a good idea here
private int sortedQualityIndex;
private Throwable error; 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) { PlayQueueItem(final StreamInfoItem streamInfoItem) {
this.title = streamInfoItem.name; this.title = streamInfoItem.name;
@ -28,9 +43,10 @@ public class PlayQueueItem {
this.serviceId = streamInfoItem.service_id; this.serviceId = streamInfoItem.service_id;
this.duration = streamInfoItem.duration; this.duration = streamInfoItem.duration;
this.stream = getInfo(); this.sortedQualityIndex = DEFAULT_QUALITY;
} }
@NonNull @NonNull
public String getTitle() { public String getTitle() {
return title; return title;
@ -49,6 +65,14 @@ public class PlayQueueItem {
return duration; return duration;
} }
public int getSortedQualityIndex() {
return sortedQualityIndex;
}
public void setSortedQualityIndex(int sortedQualityIndex) {
this.sortedQualityIndex = sortedQualityIndex;
}
@Nullable @Nullable
public Throwable getError() { public Throwable getError() {
return error; return error;
@ -56,11 +80,6 @@ public class PlayQueueItem {
@NonNull @NonNull
public Single<StreamInfo> getStream() { public Single<StreamInfo> getStream() {
return stream;
}
@NonNull
private Single<StreamInfo> getInfo() {
final Consumer<Throwable> onError = new Consumer<Throwable>() { final Consumer<Throwable> onError = new Consumer<Throwable>() {
@Override @Override
public void accept(Throwable throwable) throws Exception { 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.R;
import org.schabi.newpipe.about.AboutActivity; import org.schabi.newpipe.about.AboutActivity;
import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; 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.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.MainFragment; 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.BackgroundPlayer;
import org.schabi.newpipe.player.BasePlayer; import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.VideoPlayer; 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 org.schabi.newpipe.settings.SettingsActivity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
@SuppressWarnings({"unused", "WeakerAccess"}) @SuppressWarnings({"unused", "WeakerAccess"})
public class NavigationHelper { public class NavigationHelper {
@ -43,46 +48,60 @@ public class NavigationHelper {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) { public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
Intent mIntent = new Intent(context, targetClazz) return new Intent(context, targetClazz)
.putExtra(BasePlayer.VIDEO_TITLE, info.name) .putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.SINGLE_STREAM)
.putExtra(BasePlayer.VIDEO_URL, info.url) .putExtra(SinglePlayQueue.STREAM, info)
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url) .putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex);
.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))) public static Intent getExternalPlaylistIntent(Context context,
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, ListHelper.getHighestQualityAudio(info.audio_streams)); Class targetClazz,
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000L); PlaylistInfo info,
return mIntent; 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) { public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, VideoPlayer instance) {
return new Intent(context, targetClazz) return new Intent(context, targetClazz)
.putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle()) .putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.PLAYER_INTENT)
.putExtra(BasePlayer.VIDEO_URL, instance.getVideoUrl()) .putExtra(VideoPlayer.PLAY_QUEUE, instance.getPlayQueue())
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, instance.getVideoThumbnailUrl())
.putExtra(BasePlayer.CHANNEL_NAME, instance.getUploaderName())
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex()) .putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList()) .putExtra(VideoPlayer.RESTORE_QUEUE_INDEX, instance.getCurrentQueueIndex())
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
.putExtra(BasePlayer.START_POSITION, instance.getPlayer().getCurrentPosition()) .putExtra(BasePlayer.START_POSITION, instance.getPlayer().getCurrentPosition())
.putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed()); .putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed());
} }
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) { 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) { public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info, AudioStream audioStream) {
Intent mIntent = new Intent(context, BackgroundPlayer.class) return getOpenBackgroundPlayerIntent(context, info);
.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;
} }
// 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 // Through FragmentManager
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/