-Hooking playback manager and play queue into main video player.

This commit is contained in:
John Zhen M 2017-09-01 12:10:36 -07:00 committed by John Zhen Mo
parent 701320b100
commit b859823011
9 changed files with 147 additions and 44 deletions

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe.fragments.playlist; package org.schabi.newpipe.fragments.playlist;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -17,6 +18,7 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@ -29,14 +31,19 @@ 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.PlayListExtractor; import org.schabi.newpipe.extractor.playlist.PlayListExtractor;
import org.schabi.newpipe.extractor.playlist.PlayListInfo; import org.schabi.newpipe.extractor.playlist.PlayListInfo;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.fragments.BaseFragment; import org.schabi.newpipe.fragments.BaseFragment;
import org.schabi.newpipe.fragments.search.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.search.OnScrollBelowItemsListener;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter; import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.Utils;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
@ -78,6 +85,7 @@ public class PlaylistFragment extends BaseFragment {
private ImageView headerBannerView; private ImageView headerBannerView;
private ImageView headerAvatarView; private ImageView headerAvatarView;
private TextView headerTitleView; private TextView headerTitleView;
private Button headerPlayAllButton;
/*////////////////////////////////////////////////////////////////////////*/ /*////////////////////////////////////////////////////////////////////////*/
// Reactors // Reactors
@ -95,6 +103,15 @@ public class PlaylistFragment extends BaseFragment {
return instance; return instance;
} }
public void play(Context context, Class targetClazz) {
Intent mIntent = new Intent(context, targetClazz)
.putExtra("url", playlistUrl)
.putExtra("nextPage", 1)
.putExtra("index", 0)
.putExtra("stream", currentPlaylistInfo);
startActivity(mIntent);
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle // Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -246,6 +263,9 @@ public class PlaylistFragment extends BaseFragment {
headerBannerView = headerRootLayout.findViewById(R.id.playlist_banner_image); headerBannerView = headerRootLayout.findViewById(R.id.playlist_banner_image);
headerAvatarView = headerRootLayout.findViewById(R.id.playlist_avatar_view); headerAvatarView = headerRootLayout.findViewById(R.id.playlist_avatar_view);
headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view); headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_play_all_button);
headerPlayAllButton.setVisibility(View.VISIBLE);
} }
protected void initListeners() { protected void initListeners() {
@ -266,6 +286,13 @@ public class PlaylistFragment extends BaseFragment {
loadMore(true); loadMore(true);
} }
}); });
headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
play(activity, MainVideoPlayer.class);
}
});
} }
@ -434,7 +461,7 @@ public class PlaylistFragment extends BaseFragment {
} }
private void handlePlayListInfo(PlayListInfo info, boolean onlyVideos, boolean addVideos) { private void handlePlayListInfo(PlayListInfo info, boolean onlyVideos, boolean addVideos) {
currentPlaylistInfo = info; if (currentPlaylistInfo == null) currentPlaylistInfo = info;
animateView(errorPanel, false, 300); animateView(errorPanel, false, 300);
animateView(playlistStreams, true, 200); animateView(playlistStreams, true, 200);
@ -468,7 +495,10 @@ public class PlaylistFragment extends BaseFragment {
if (!hasNextPage) infoListAdapter.showFooter(false); if (!hasNextPage) infoListAdapter.showFooter(false);
//if (!listRestored) { //if (!listRestored) {
if (addVideos) infoListAdapter.addInfoItemList(info.related_streams); if (addVideos) {
infoListAdapter.addInfoItemList(info.related_streams);
currentPlaylistInfo.related_streams.addAll(info.related_streams);
}
//} //}
} }

View File

@ -72,6 +72,7 @@ import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream_info.StreamInfo; import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.util.Utils;
import java.io.File; import java.io.File;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@ -257,7 +258,6 @@ public abstract class BasePlayer implements Player.EventListener,
changeState(STATE_LOADING); changeState(STATE_LOADING);
isPrepared = false; isPrepared = false;
mediaSource = buildMediaSource(url, format);
if (simpleExoPlayer.getPlaybackState() != Player.STATE_IDLE) simpleExoPlayer.stop(); if (simpleExoPlayer.getPlaybackState() != Player.STATE_IDLE) simpleExoPlayer.stop();
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos); if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
@ -548,7 +548,7 @@ public abstract class BasePlayer implements Player.EventListener,
@Override @Override
public void onPositionDiscontinuity() { public void onPositionDiscontinuity() {
int newIndex = simpleExoPlayer.getCurrentWindowIndex(); int newIndex = simpleExoPlayer.getCurrentWindowIndex();
playbackManager.refreshMedia(newIndex);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -567,12 +567,12 @@ public abstract class BasePlayer implements Player.EventListener,
@Override @Override
public void sync(final StreamInfo info) { public void sync(final StreamInfo info) {
videoTitle = info.title;
channelName = info.uploader;
} }
@Override @Override
public MediaSource sourceOf(final StreamInfo info) { public MediaSource sourceOf(final StreamInfo info) {
return null; return null;
} }

View File

@ -454,9 +454,15 @@ public class MainVideoPlayer extends Activity {
@Override @Override
public boolean onDoubleTap(MotionEvent e) { public boolean onDoubleTap(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); 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 (!playerImpl.isPlaying()) return false;
if (e.getX() > playerImpl.getRootView().getWidth() / 2) playerImpl.onFastForward();
else playerImpl.onFastRewind(); if (e.getX() > playerImpl.getRootView().getWidth() / 2)
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1);
//playerImpl.onFastForward();
else
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1);
//playerImpl.onFastRewind();
return true; return true;
} }

View File

@ -15,10 +15,14 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.Maybe; import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull; import io.reactivex.annotations.NonNull;
import io.reactivex.schedulers.Schedulers;
public class PlaybackManager { public class PlaybackManager {
private static final int WINDOW_SIZE = 5;
private DynamicConcatenatingMediaSource mediaSource; private DynamicConcatenatingMediaSource mediaSource;
private List<PlayQueueItem> queueSource; private List<PlayQueueItem> queueSource;
private int sourceIndex; private int sourceIndex;
@ -58,8 +62,11 @@ public class PlaybackManager {
load(0); load(0);
} }
public void changeSource(final int index) { public void changeSource(final MediaSource newSource) {
listener.block();
this.mediaSource.removeMediaSource(0);
this.mediaSource.addMediaSource(0, newSource);
listener.unblock();
} }
public void refreshMedia(final int newMediaIndex) { public void refreshMedia(final int newMediaIndex) {
@ -71,7 +78,7 @@ public class PlaybackManager {
queueSource.remove(0); queueSource.remove(0);
} else { } else {
//something went wrong //something went wrong
init(); reload();
} }
} }
@ -85,7 +92,8 @@ public class PlaybackManager {
private Subscription loaderReactor; private Subscription loaderReactor;
private void load() { private void load() {
if (mediaSource.getSize() < 5 && queueSource.size() < 5) load(mediaSource.getSize()); if (mediaSource.getSize() < WINDOW_SIZE && queueSource.size() < WINDOW_SIZE)
load(mediaSource.getSize());
} }
private void load(final int from) { private void load(final int from) {
@ -94,23 +102,33 @@ public class PlaybackManager {
if (loaderReactor != null) loaderReactor.cancel(); if (loaderReactor != null) loaderReactor.cancel();
List<Maybe<StreamInfo>> maybes = new ArrayList<>(); List<Maybe<StreamInfo>> maybes = new ArrayList<>();
for (int i = from; i < 5; i++) { for (int i = from; i < WINDOW_SIZE; i++) {
final int index = playQueue.getIndex() + i; final int index = playQueue.getIndex() + i;
final PlayQueueItem item = playQueue.get(index); final PlayQueueItem item = playQueue.get(index);
queueSource.set(i, item);
if (queueSource.size() > i) queueSource.set(i, item);
else queueSource.add(item);
maybes.add(item.getStream()); maybes.add(item.getStream());
} }
Maybe.concat(maybes).subscribe(new Subscriber<StreamInfo>() { Maybe.concat(maybes).subscribe(getSubscriber());
}
private Subscriber<StreamInfo> getSubscriber() {
return new Subscriber<StreamInfo>() {
@Override @Override
public void onSubscribe(Subscription s) { public void onSubscribe(Subscription s) {
if (loaderReactor != null) loaderReactor.cancel();
loaderReactor = s; loaderReactor = s;
s.request(1);
} }
@Override @Override
public void onNext(StreamInfo streamInfo) { public void onNext(StreamInfo streamInfo) {
mediaSource.addMediaSource(listener.sourceOf(streamInfo)); mediaSource.addMediaSource(listener.sourceOf(streamInfo));
onLoaded(); tryUnblock();
loaderReactor.request(1);
} }
@Override @Override
@ -120,11 +138,13 @@ public class PlaybackManager {
@Override @Override
public void onComplete() { public void onComplete() {
if (loaderReactor != null) loaderReactor.cancel();
loaderReactor = null;
} }
}); };
} }
private void onLoaded() { private void tryUnblock() {
if (mediaSource.getSize() > 0 && queueSource.size() > 0) listener.unblock(); if (mediaSource.getSize() > 0 && queueSource.size() > 0) listener.unblock();
} }
@ -134,11 +154,15 @@ public class PlaybackManager {
} }
private void clear(int from) { private void clear(int from) {
listener.block();
while (mediaSource.getSize() > from) { while (mediaSource.getSize() > from) {
queueSource.remove(from); queueSource.remove(from);
mediaSource.removeMediaSource(from); mediaSource.removeMediaSource(from);
} }
}
private void clear() {
listener.block();
clear(0);
listener.unblock(); listener.unblock();
} }
@ -153,7 +177,7 @@ public class PlaybackManager {
@Override @Override
public void onNext(@NonNull PlayQueueEvent event) { public void onNext(@NonNull PlayQueueEvent event) {
if (playQueue.getStreams().size() - playQueue.getIndex() < 10 && !playQueue.isComplete()) { if (playQueue.getStreams().size() - playQueue.getIndex() < WINDOW_SIZE && !playQueue.isComplete()) {
listener.block(); listener.block();
playQueue.fetch(); playQueue.fetch();
} }
@ -177,14 +201,14 @@ public class PlaybackManager {
load(1); load(1);
break; break;
case CLEAR: case CLEAR:
clear(0); clear();
break; break;
case NEXT: case NEXT:
default: default:
break; break;
} }
onLoaded(); tryUnblock();
if (playQueueReactor != null) playQueueReactor.request(1); if (playQueueReactor != null) playQueueReactor.request(1);
} }
@ -195,12 +219,13 @@ public class PlaybackManager {
@Override @Override
public void onComplete() { public void onComplete() {
// Never completes, only canceled dispose();
} }
}; };
} }
public void dispose() { public void dispose() {
if (playQueueReactor != null) playQueueReactor.cancel(); if (playQueueReactor != null) playQueueReactor.cancel();
playQueueReactor = null;
} }
} }

View File

@ -56,7 +56,11 @@ import org.schabi.newpipe.R;
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.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.playlist.PlayListInfo;
import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Utils;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
@ -198,7 +202,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void handleIntent(Intent intent) { public void handleIntent2(Intent intent) {
super.handleIntent(intent); super.handleIntent(intent);
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;
@ -217,6 +221,38 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
play(true); play(true);
} }
@Override
public MediaSource sourceOf(final StreamInfo info) {
videoStreamsList = Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
videoOnlyAudioStream = Utils.getHighestQualityAudio(info.audio_streams);
return buildMediaSource(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format));
}
@Override
public void unblock() {
play(true);
super.unblock();
}
public void handleIntent(Intent intent) {
if (intent == null) return;
selectedIndexStream = 0;
String url = intent.getStringExtra("url");
int nextPage = intent.getIntExtra("nextPage", 0);
int index = intent.getIntExtra("index", 0);
PlayListInfo info;
Serializable serializable = intent.getSerializableExtra("stream");
if (serializable instanceof PlayListInfo) info = (PlayListInfo) serializable;
else return;
playQueue = new ExternalPlayQueue(url, info, nextPage, index);
playbackManager = new PlaybackManager(this, playQueue);
mediaSource = playbackManager.getMediaSource();
}
public void play(boolean autoPlay) { public void play(boolean autoPlay) {
playUrl(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format), autoPlay); playUrl(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format), autoPlay);

View File

@ -34,12 +34,11 @@ public class ExternalPlayQueue extends PlayQueue {
final PlayListInfo info, final PlayListInfo info,
final int nextPage, final int nextPage,
final int index) { final int index) {
super(index); super(index, extractPlaylistItems(info));
this.service = getService(info.service_id); this.service = getService(info.service_id);
this.pageNumber = new AtomicInteger(nextPage); this.pageNumber = new AtomicInteger(nextPage);
this.playlistUrl = playlistUrl; this.playlistUrl = playlistUrl;
getStreams().addAll(extractPlaylistItems(info));
} }
@Override @Override
@ -47,12 +46,6 @@ public class ExternalPlayQueue extends PlayQueue {
return isComplete; return isComplete;
} }
@Override
public void load(int index) {
if (index > getStreams().size() || getStreams().get(index) == null) return;
getStreams().get(index).load();
}
@Override @Override
public PlayQueueItem get(int index) { public PlayQueueItem get(int index) {
if (index > getStreams().size() || getStreams().get(index) == null) return null; if (index > getStreams().size() || getStreams().get(index) == null) return null;
@ -93,7 +86,7 @@ public class ExternalPlayQueue extends PlayQueue {
if (fetchReactor != null) fetchReactor.dispose(); if (fetchReactor != null) fetchReactor.dispose();
} }
private List<PlayQueueItem> extractPlaylistItems(final PlayListInfo info) { private static List<PlayQueueItem> extractPlaylistItems(final PlayListInfo info) {
List<PlayQueueItem> result = new ArrayList<>(); List<PlayQueueItem> result = new ArrayList<>();
for (final InfoItem stream : info.related_streams) { for (final InfoItem stream : info.related_streams) {
if (stream instanceof StreamInfoItem) { if (stream instanceof StreamInfoItem) {

View File

@ -25,8 +25,14 @@ public abstract class PlayQueue {
private BehaviorSubject<PlayQueueEvent> changeBroadcast; private BehaviorSubject<PlayQueueEvent> changeBroadcast;
private Flowable<PlayQueueEvent> playQueueFlowable; private Flowable<PlayQueueEvent> playQueueFlowable;
PlayQueue(final int index) { PlayQueue() {
this(0, Collections.<PlayQueueItem>emptyList());
}
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
streams = Collections.synchronizedList(new ArrayList<PlayQueueItem>()); streams = Collections.synchronizedList(new ArrayList<PlayQueueItem>());
streams.addAll(startWith);
queueIndex = new AtomicInteger(index); queueIndex = new AtomicInteger(index);
changeBroadcast = BehaviorSubject.create(); changeBroadcast = BehaviorSubject.create();
@ -37,9 +43,6 @@ public abstract class PlayQueue {
// single stream or local queues are always complete // single stream or local queues are always complete
public abstract boolean isComplete(); public abstract boolean isComplete();
// load in the background the item at index, may do nothing if the queue is incomplete
public abstract void load(int index);
// 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();

View File

@ -69,10 +69,6 @@ public class PlayQueueItem {
return stream; return stream;
} }
public void load() {
stream.subscribe();
}
@NonNull @NonNull
private Maybe<StreamInfo> getInfo() { private Maybe<StreamInfo> getInfo() {
final Callable<StreamInfo> task = new Callable<StreamInfo>() { final Callable<StreamInfo> task = new Callable<StreamInfo>() {
@ -101,7 +97,6 @@ public class PlayQueueItem {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnError(onError) .doOnError(onError)
.onErrorComplete()
.doOnComplete(onComplete) .doOnComplete(onComplete)
.cache(); .cache();
} }

View File

@ -78,4 +78,19 @@
tools:ignore="RtlHardcoded" tools:ignore="RtlHardcoded"
tools:text="234 videos"/> tools:text="234 videos"/>
<Button
android:id="@+id/playlist_play_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@+id/playlist_banner_image"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="2dp"
android:text="Play All"
android:textSize="@dimen/channel_rss_title_size"
android:theme="@style/RedButton"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
</RelativeLayout> </RelativeLayout>