-Added MediaSourceManager and Playlist adapters.
This commit is contained in:
parent
e70dcdc642
commit
cbcd281784
13 changed files with 753 additions and 21 deletions
|
@ -79,6 +79,11 @@ public class PlaylistFragment extends BaseFragment {
|
||||||
private ImageView headerAvatarView;
|
private ImageView headerAvatarView;
|
||||||
private TextView headerTitleView;
|
private TextView headerTitleView;
|
||||||
|
|
||||||
|
/*////////////////////////////////////////////////////////////////////////*/
|
||||||
|
// Reactors
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
private Disposable loadingReactor;
|
||||||
|
|
||||||
/*////////////////////////////////////////////////////////////////////////*/
|
/*////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public PlaylistFragment() {
|
public PlaylistFragment() {
|
||||||
|
@ -153,8 +158,8 @@ public class PlaylistFragment extends BaseFragment {
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
if (DEBUG) Log.d(TAG, "onStop() called");
|
if (DEBUG) Log.d(TAG, "onStop() called");
|
||||||
|
|
||||||
disposable.dispose();
|
if (loadingReactor != null) loadingReactor.dispose();
|
||||||
disposable = null;
|
loadingReactor = null;
|
||||||
|
|
||||||
super.onStop();
|
super.onStop();
|
||||||
}
|
}
|
||||||
|
@ -221,7 +226,7 @@ public class PlaylistFragment extends BaseFragment {
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
playlistStreams = (RecyclerView) rootView.findViewById(R.id.channel_streams_view);
|
playlistStreams = rootView.findViewById(R.id.channel_streams_view);
|
||||||
|
|
||||||
playlistStreams.setLayoutManager(new LinearLayoutManager(activity));
|
playlistStreams.setLayoutManager(new LinearLayoutManager(activity));
|
||||||
if (infoListAdapter == null) {
|
if (infoListAdapter == null) {
|
||||||
|
@ -238,9 +243,9 @@ public class PlaylistFragment extends BaseFragment {
|
||||||
infoListAdapter.setHeader(headerRootLayout);
|
infoListAdapter.setHeader(headerRootLayout);
|
||||||
infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, playlistStreams, false));
|
infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, playlistStreams, false));
|
||||||
|
|
||||||
headerBannerView = (ImageView) headerRootLayout.findViewById(R.id.playlist_banner_image);
|
headerBannerView = headerRootLayout.findViewById(R.id.playlist_banner_image);
|
||||||
headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.playlist_avatar_view);
|
headerAvatarView = headerRootLayout.findViewById(R.id.playlist_avatar_view);
|
||||||
headerTitleView = (TextView) headerRootLayout.findViewById(R.id.playlist_title_view);
|
headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initListeners() {
|
protected void initListeners() {
|
||||||
|
@ -280,7 +285,71 @@ public class PlaylistFragment extends BaseFragment {
|
||||||
return NewPipe.getService(serviceId);
|
return NewPipe.getService(serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Disposable disposable;
|
private void loadAll() {
|
||||||
|
final Callable<PlayListInfo> task = new Callable<PlayListInfo>() {
|
||||||
|
@Override
|
||||||
|
public PlayListInfo call() throws Exception {
|
||||||
|
int pageCount = 0;
|
||||||
|
|
||||||
|
final PlayListExtractor extractor = getService(serviceId)
|
||||||
|
.getPlayListExtractorInstance(playlistUrl, 0);
|
||||||
|
|
||||||
|
final PlayListInfo info = PlayListInfo.getInfo(extractor);
|
||||||
|
|
||||||
|
boolean hasNext = info.hasNextPage;
|
||||||
|
while(hasNext) {
|
||||||
|
pageCount++;
|
||||||
|
|
||||||
|
final PlayListExtractor moreExtractor = getService(serviceId)
|
||||||
|
.getPlayListExtractorInstance(playlistUrl, pageCount);
|
||||||
|
|
||||||
|
final PlayListInfo moreInfo = PlayListInfo.getInfo(moreExtractor);
|
||||||
|
|
||||||
|
info.related_streams.addAll(moreInfo.related_streams);
|
||||||
|
hasNext = moreInfo.hasNextPage;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Observable.fromCallable(task)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new Observer<PlayListInfo>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
|
if (loadingReactor == null || loadingReactor.isDisposed()) {
|
||||||
|
loadingReactor = d;
|
||||||
|
isLoading.set(true);
|
||||||
|
} else {
|
||||||
|
d.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(@NonNull PlayListInfo playListInfo) {
|
||||||
|
if (DEBUG) Log.d(TAG, "onReceive() called with: info = [" + playListInfo + "]");
|
||||||
|
if (playListInfo == null || isRemoving() || !isVisible()) return;
|
||||||
|
|
||||||
|
handlePlayListInfo(playListInfo, false, true);
|
||||||
|
isLoading.set(false);
|
||||||
|
pageNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull Throwable e) {
|
||||||
|
onRxError(e, "Observer failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
if (loadingReactor != null) {
|
||||||
|
loadingReactor.dispose();
|
||||||
|
loadingReactor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void loadMore(final boolean onlyVideos) {
|
private void loadMore(final boolean onlyVideos) {
|
||||||
final Callable<PlayListInfo> task = new Callable<PlayListInfo>() {
|
final Callable<PlayListInfo> task = new Callable<PlayListInfo>() {
|
||||||
|
@ -300,8 +369,8 @@ public class PlaylistFragment extends BaseFragment {
|
||||||
.subscribe(new Observer<PlayListInfo>() {
|
.subscribe(new Observer<PlayListInfo>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(@NonNull Disposable d) {
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
if (disposable == null || disposable.isDisposed()) {
|
if (loadingReactor == null || loadingReactor.isDisposed()) {
|
||||||
disposable = d;
|
loadingReactor = d;
|
||||||
isLoading.set(true);
|
isLoading.set(true);
|
||||||
} else {
|
} else {
|
||||||
d.dispose();
|
d.dispose();
|
||||||
|
@ -325,9 +394,9 @@ public class PlaylistFragment extends BaseFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete() {
|
public void onComplete() {
|
||||||
if (disposable != null) {
|
if (loadingReactor != null) {
|
||||||
disposable.dispose();
|
loadingReactor.dispose();
|
||||||
disposable = null;
|
loadingReactor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
package org.schabi.newpipe.fragments.search;
|
|
||||||
|
|
||||||
public class PlaylistService {
|
|
||||||
}
|
|
|
@ -344,13 +344,15 @@ public class BackgroundPlayer extends Service {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFastRewind() {
|
public void onFastRewind() {
|
||||||
super.onFastRewind();
|
// super.onFastRewind();
|
||||||
|
simpleExoPlayer.seekTo(0, 0);
|
||||||
triggerProgressUpdate();
|
triggerProgressUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFastForward() {
|
public void onFastForward() {
|
||||||
super.onFastForward();
|
// super.onFastForward();
|
||||||
|
simpleExoPlayer.seekTo(2, 0);
|
||||||
triggerProgressUpdate();
|
triggerProgressUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,9 @@ import com.google.android.exoplayer2.RenderersFactory;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
|
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.LoopingMediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||||
|
@ -248,7 +250,12 @@ public abstract class BasePlayer implements Player.EventListener, AudioManager.O
|
||||||
changeState(STATE_LOADING);
|
changeState(STATE_LOADING);
|
||||||
|
|
||||||
isPrepared = false;
|
isPrepared = false;
|
||||||
mediaSource = buildMediaSource(url, format);
|
|
||||||
|
final MediaSource ms = buildMediaSource(url, format);
|
||||||
|
final DynamicConcatenatingMediaSource dcms = new DynamicConcatenatingMediaSource();
|
||||||
|
dcms.addMediaSource(ms);
|
||||||
|
mediaSource = dcms;
|
||||||
|
dcms.addMediaSource(new LoopingMediaSource(ms, 2));
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.playlist.Playlist;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MediaSourceManager {
|
||||||
|
|
||||||
|
private DynamicConcatenatingMediaSource source;
|
||||||
|
|
||||||
|
private Playlist playlist;
|
||||||
|
private List<MediaSource> sources;
|
||||||
|
|
||||||
|
public MediaSourceManager(Playlist playlist) {
|
||||||
|
this.source = new DynamicConcatenatingMediaSource();
|
||||||
|
this.playlist = playlist;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package org.schabi.newpipe.playlist;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
import org.schabi.newpipe.extractor.playlist.PlayListExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.playlist.PlayListInfo;
|
||||||
|
import org.schabi.newpipe.extractor.playlist.PlayListInfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import io.reactivex.Maybe;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.functions.Consumer;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class ExternalPlaylist extends Playlist {
|
||||||
|
|
||||||
|
private AtomicInteger pageNumber;
|
||||||
|
|
||||||
|
private StreamingService service;
|
||||||
|
|
||||||
|
public ExternalPlaylist(final PlayListInfoItem playlist) {
|
||||||
|
super();
|
||||||
|
service = getService(playlist.serviceId);
|
||||||
|
pageNumber = new AtomicInteger(0);
|
||||||
|
|
||||||
|
load(playlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load(final PlayListInfoItem playlist) {
|
||||||
|
final int page = pageNumber.getAndIncrement();
|
||||||
|
|
||||||
|
final Callable<PlayListInfo> task = new Callable<PlayListInfo>() {
|
||||||
|
@Override
|
||||||
|
public PlayListInfo call() throws Exception {
|
||||||
|
PlayListExtractor extractor = service.getPlayListExtractorInstance(playlist.getLink(), page);
|
||||||
|
return PlayListInfo.getInfo(extractor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Consumer<PlayListInfo> onSuccess = new Consumer<PlayListInfo>() {
|
||||||
|
@Override
|
||||||
|
public void accept(PlayListInfo playListInfo) throws Exception {
|
||||||
|
streams.addAll(extractPlaylistItems(playListInfo));
|
||||||
|
changeBroadcast.onNext(streams);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Maybe.fromCallable(task)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.onErrorComplete()
|
||||||
|
.subscribe(onSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PlaylistItem> extractPlaylistItems(final PlayListInfo info) {
|
||||||
|
List<PlaylistItem> result = new ArrayList<>();
|
||||||
|
for (final InfoItem stream : info.related_streams) {
|
||||||
|
if (stream instanceof StreamInfoItem) {
|
||||||
|
result.add(new PlaylistItem((StreamInfoItem) stream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isComplete() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void load(int index) {
|
||||||
|
while (streams.size() < index) {
|
||||||
|
pageNumber.incrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Observable<StreamInfo> get(int index) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StreamingService getService(final int serviceId) {
|
||||||
|
try {
|
||||||
|
return NewPipe.getService(serviceId);
|
||||||
|
} catch (ExtractionException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
app/src/main/java/org/schabi/newpipe/playlist/Playlist.java
Normal file
38
app/src/main/java/org/schabi/newpipe/playlist/Playlist.java
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package org.schabi.newpipe.playlist;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.subjects.PublishSubject;
|
||||||
|
|
||||||
|
public abstract class Playlist {
|
||||||
|
private final String TAG = "Playlist@" + Integer.toHexString(hashCode());
|
||||||
|
|
||||||
|
private final int LOAD_BOUND = 2;
|
||||||
|
|
||||||
|
List<PlaylistItem> streams;
|
||||||
|
PublishSubject<List<PlaylistItem>> changeBroadcast;
|
||||||
|
|
||||||
|
Playlist() {
|
||||||
|
streams = Collections.synchronizedList(new ArrayList<PlaylistItem>());
|
||||||
|
changeBroadcast = PublishSubject.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public PublishSubject<List<PlaylistItem>> getChangeBroadcast() {
|
||||||
|
return changeBroadcast;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract boolean isComplete();
|
||||||
|
|
||||||
|
abstract void load(int index);
|
||||||
|
|
||||||
|
abstract Observable<StreamInfo> get(int index);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
package org.schabi.newpipe.playlist;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.info_list.StreamInfoItemHolder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Christian Schabesberger on 01.08.16.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
|
* InfoListAdapter.java is part of NewPipe.
|
||||||
|
*
|
||||||
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class PlaylistAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
private static final String TAG = PlaylistAdapter.class.toString();
|
||||||
|
|
||||||
|
private final PlaylistItemBuilder playlistItemBuilder;
|
||||||
|
private final List<PlaylistItem> playlistItems;
|
||||||
|
private boolean showFooter = false;
|
||||||
|
private View header = null;
|
||||||
|
private View footer = null;
|
||||||
|
|
||||||
|
public class HFHolder extends RecyclerView.ViewHolder {
|
||||||
|
public HFHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
view = v;
|
||||||
|
}
|
||||||
|
public View view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showFooter(boolean show) {
|
||||||
|
showFooter = show;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlaylistAdapter(List<PlaylistItem> data) {
|
||||||
|
playlistItemBuilder = new PlaylistItemBuilder();
|
||||||
|
playlistItems = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedListener(PlaylistItemBuilder.OnSelectedListener listener) {
|
||||||
|
playlistItemBuilder.setOnSelectedListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInfoItemList(List<PlaylistItem> data) {
|
||||||
|
if(data != null) {
|
||||||
|
playlistItems.addAll(data);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInfoItem(PlaylistItem data) {
|
||||||
|
if (data != null) {
|
||||||
|
playlistItems.add(data);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearStreamItemList() {
|
||||||
|
if(playlistItems.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
playlistItems.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeader(View header) {
|
||||||
|
this.header = header;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFooter(View footer) {
|
||||||
|
this.footer = footer;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PlaylistItem> getItemsList() {
|
||||||
|
return playlistItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
int count = playlistItems.size();
|
||||||
|
if(header != null) count++;
|
||||||
|
if(footer != null && showFooter) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't ask why we have to do that this way... it's android accept it -.-
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if(header != null && position == 0) {
|
||||||
|
return 0;
|
||||||
|
} else if(header != null) {
|
||||||
|
position--;
|
||||||
|
}
|
||||||
|
if(footer != null && position == playlistItems.size() && showFooter) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||||
|
switch(type) {
|
||||||
|
case 0:
|
||||||
|
return new HFHolder(header);
|
||||||
|
case 1:
|
||||||
|
return new HFHolder(footer);
|
||||||
|
case 2:
|
||||||
|
return new StreamInfoItemHolder(LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.playlist_stream_item, parent, false));
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Trollolo");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
|
||||||
|
if(holder instanceof PlaylistItemHolder) {
|
||||||
|
if(header != null) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
playlistItemBuilder.buildStreamInfoItem((PlaylistItemHolder) holder, playlistItems.get(i));
|
||||||
|
} else if(holder instanceof HFHolder && i == 0 && header != null) {
|
||||||
|
((HFHolder) holder).view = header;
|
||||||
|
} else if(holder instanceof HFHolder && i == playlistItems.size() && footer != null && showFooter) {
|
||||||
|
((HFHolder) holder).view = footer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
app/src/main/java/org/schabi/newpipe/playlist/PlaylistItem.java
Normal file
109
app/src/main/java/org/schabi/newpipe/playlist/PlaylistItem.java
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package org.schabi.newpipe.playlist;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import io.reactivex.Maybe;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.functions.Action;
|
||||||
|
import io.reactivex.functions.Consumer;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class PlaylistItem implements Serializable {
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
private String url;
|
||||||
|
private int serviceId;
|
||||||
|
private int duration;
|
||||||
|
|
||||||
|
private boolean isDone;
|
||||||
|
private Throwable error;
|
||||||
|
private Maybe<StreamInfo> stream;
|
||||||
|
|
||||||
|
public PlaylistItem(final StreamInfoItem streamInfoItem) {
|
||||||
|
this.title = streamInfoItem.getTitle();
|
||||||
|
this.url = streamInfoItem.getLink();
|
||||||
|
this.serviceId = streamInfoItem.service_id;
|
||||||
|
this.duration = streamInfoItem.duration;
|
||||||
|
|
||||||
|
this.isDone = false;
|
||||||
|
this.stream = getInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDone() {
|
||||||
|
return isDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Throwable getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Maybe<StreamInfo> getStream() {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
stream.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Maybe<StreamInfo> getInfo() {
|
||||||
|
final Callable<StreamInfo> task = new Callable<StreamInfo>() {
|
||||||
|
@Override
|
||||||
|
public StreamInfo call() throws Exception {
|
||||||
|
final StreamExtractor extractor = NewPipe.getService(serviceId).getExtractorInstance(url);
|
||||||
|
return StreamInfo.getVideoInfo(extractor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||||
|
@Override
|
||||||
|
public void accept(Throwable throwable) throws Exception {
|
||||||
|
error = throwable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Action onComplete = new Action() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
isDone = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Maybe.fromCallable(task)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnError(onError)
|
||||||
|
.onErrorComplete()
|
||||||
|
.doOnComplete(onComplete)
|
||||||
|
.cache();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package org.schabi.newpipe.playlist;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.ImageErrorLoadingListener;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.AbstractStreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.playlist.PlayListInfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
|
||||||
|
import org.schabi.newpipe.info_list.ChannelInfoItemHolder;
|
||||||
|
import org.schabi.newpipe.info_list.InfoItemHolder;
|
||||||
|
import org.schabi.newpipe.info_list.PlaylistInfoItemHolder;
|
||||||
|
import org.schabi.newpipe.info_list.StreamInfoItemHolder;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Christian Schabesberger on 26.09.16.
|
||||||
|
* <p>
|
||||||
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
|
* InfoItemBuilder.java is part of NewPipe.
|
||||||
|
* <p>
|
||||||
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* <p>
|
||||||
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
* <p>
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class PlaylistItemBuilder {
|
||||||
|
|
||||||
|
private static final String TAG = PlaylistItemBuilder.class.toString();
|
||||||
|
|
||||||
|
public interface OnSelectedListener {
|
||||||
|
void selected(int serviceId, String url, String title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnSelectedListener onStreamInfoItemSelectedListener;
|
||||||
|
|
||||||
|
public PlaylistItemBuilder() {}
|
||||||
|
|
||||||
|
public void setOnSelectedListener(OnSelectedListener listener) {
|
||||||
|
this.onStreamInfoItemSelectedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View buildView(ViewGroup parent, final PlaylistItem item) {
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
|
final View itemView = inflater.inflate(R.layout.stream_item, parent, false);
|
||||||
|
final PlaylistItemHolder holder = new PlaylistItemHolder(itemView);
|
||||||
|
|
||||||
|
buildStreamInfoItem(holder, item);
|
||||||
|
|
||||||
|
return itemView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void buildStreamInfoItem(PlaylistItemHolder holder, final PlaylistItem item) {
|
||||||
|
if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle());
|
||||||
|
|
||||||
|
if (item.getDuration() > 0) {
|
||||||
|
holder.itemDurationView.setText(getDurationString(item.getDuration()));
|
||||||
|
} else {
|
||||||
|
holder.itemDurationView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if(onStreamInfoItemSelectedListener != null) {
|
||||||
|
onStreamInfoItemSelectedListener.selected(item.getServiceId(), item.getUrl(), item.getTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getDurationString(int duration) {
|
||||||
|
if(duration < 0) {
|
||||||
|
duration = 0;
|
||||||
|
}
|
||||||
|
String output;
|
||||||
|
int days = duration / (24 * 60 * 60); /* greater than a day */
|
||||||
|
duration %= (24 * 60 * 60);
|
||||||
|
int hours = duration / (60 * 60); /* greater than an hour */
|
||||||
|
duration %= (60 * 60);
|
||||||
|
int minutes = duration / 60;
|
||||||
|
int seconds = duration % 60;
|
||||||
|
|
||||||
|
//handle days
|
||||||
|
if (days > 0) {
|
||||||
|
output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds);
|
||||||
|
} else if(hours > 0) {
|
||||||
|
output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
|
||||||
|
} else {
|
||||||
|
output = String.format(Locale.US, "%d:%02d", minutes, seconds);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.schabi.newpipe.playlist;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
import org.schabi.newpipe.info_list.InfoItemHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Christian Schabesberger on 01.08.16.
|
||||||
|
* <p>
|
||||||
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
|
* StreamInfoItemHolder.java is part of NewPipe.
|
||||||
|
* <p>
|
||||||
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* <p>
|
||||||
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
* <p>
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class PlaylistItemHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
public final TextView itemVideoTitleView, itemDurationView;
|
||||||
|
public final View itemRoot;
|
||||||
|
|
||||||
|
public PlaylistItemHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
itemRoot = v.findViewById(R.id.itemRoot);
|
||||||
|
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
|
||||||
|
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<RelativeLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -328,4 +328,12 @@
|
||||||
tools:visibility="visible"/>
|
tools:visibility="visible"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/video_playlist"
|
||||||
|
android:layout_width="480dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:background="#64000000"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
70
app/src/main/res/layout/playlist_stream_item.xml
Normal file
70
app/src/main/res/layout/playlist_stream_item.xml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/itemRoot"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="6dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/itemThumbnailView"
|
||||||
|
android:layout_width="62dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
|
||||||
|
android:contentDescription="@string/list_thumbnail_view_description"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/dummy_thumbnail"
|
||||||
|
tools:ignore="RtlHardcoded"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemDurationView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignBottom="@id/itemThumbnailView"
|
||||||
|
android:layout_alignRight="@id/itemThumbnailView"
|
||||||
|
android:layout_marginBottom="@dimen/video_item_search_duration_margin"
|
||||||
|
android:layout_marginRight="@dimen/video_item_search_duration_margin"
|
||||||
|
android:background="@color/duration_background_color"
|
||||||
|
android:paddingBottom="@dimen/video_item_search_duration_vertical_padding"
|
||||||
|
android:paddingLeft="@dimen/video_item_search_duration_horizontal_padding"
|
||||||
|
android:paddingRight="@dimen/video_item_search_duration_horizontal_padding"
|
||||||
|
android:paddingTop="@dimen/video_item_search_duration_vertical_padding"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="@color/duration_text_color"
|
||||||
|
android:textSize="@dimen/video_item_search_duration_text_size"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="1:09:10"/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemVideoTitleView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
|
android:layout_toEndOf="@id/itemThumbnailView"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:textSize="@dimen/video_item_search_title_text_size"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemAdditionalDetails"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
|
android:layout_toEndOf="@id/itemThumbnailView"
|
||||||
|
android:lines="1"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||||
|
tools:text="Uploader • 2 years ago • 10M views"/>
|
||||||
|
</RelativeLayout>
|
Loading…
Reference in a new issue