-Added MediaSourceManager and Playlist adapters.
This commit is contained in:
parent
e70dcdc642
commit
cbcd281784
|
@ -79,6 +79,11 @@ public class PlaylistFragment extends BaseFragment {
|
|||
private ImageView headerAvatarView;
|
||||
private TextView headerTitleView;
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////////*/
|
||||
// Reactors
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private Disposable loadingReactor;
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public PlaylistFragment() {
|
||||
|
@ -153,8 +158,8 @@ public class PlaylistFragment extends BaseFragment {
|
|||
public void onStop() {
|
||||
if (DEBUG) Log.d(TAG, "onStop() called");
|
||||
|
||||
disposable.dispose();
|
||||
disposable = null;
|
||||
if (loadingReactor != null) loadingReactor.dispose();
|
||||
loadingReactor = null;
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
@ -221,7 +226,7 @@ public class PlaylistFragment extends BaseFragment {
|
|||
protected void initViews(View rootView, Bundle 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));
|
||||
if (infoListAdapter == null) {
|
||||
|
@ -238,9 +243,9 @@ public class PlaylistFragment extends BaseFragment {
|
|||
infoListAdapter.setHeader(headerRootLayout);
|
||||
infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, playlistStreams, false));
|
||||
|
||||
headerBannerView = (ImageView) headerRootLayout.findViewById(R.id.playlist_banner_image);
|
||||
headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.playlist_avatar_view);
|
||||
headerTitleView = (TextView) headerRootLayout.findViewById(R.id.playlist_title_view);
|
||||
headerBannerView = headerRootLayout.findViewById(R.id.playlist_banner_image);
|
||||
headerAvatarView = headerRootLayout.findViewById(R.id.playlist_avatar_view);
|
||||
headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
|
||||
}
|
||||
|
||||
protected void initListeners() {
|
||||
|
@ -280,7 +285,71 @@ public class PlaylistFragment extends BaseFragment {
|
|||
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) {
|
||||
final Callable<PlayListInfo> task = new Callable<PlayListInfo>() {
|
||||
|
@ -300,8 +369,8 @@ public class PlaylistFragment extends BaseFragment {
|
|||
.subscribe(new Observer<PlayListInfo>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (disposable == null || disposable.isDisposed()) {
|
||||
disposable = d;
|
||||
if (loadingReactor == null || loadingReactor.isDisposed()) {
|
||||
loadingReactor = d;
|
||||
isLoading.set(true);
|
||||
} else {
|
||||
d.dispose();
|
||||
|
@ -325,9 +394,9 @@ public class PlaylistFragment extends BaseFragment {
|
|||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
disposable = null;
|
||||
if (loadingReactor != null) {
|
||||
loadingReactor.dispose();
|
||||
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
|
||||
public void onFastRewind() {
|
||||
super.onFastRewind();
|
||||
// super.onFastRewind();
|
||||
simpleExoPlayer.seekTo(0, 0);
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {
|
||||
super.onFastForward();
|
||||
// super.onFastForward();
|
||||
simpleExoPlayer.seekTo(2, 0);
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,9 @@ import com.google.android.exoplayer2.RenderersFactory;
|
|||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
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.LoopingMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||
|
@ -248,7 +250,12 @@ public abstract class BasePlayer implements Player.EventListener, AudioManager.O
|
|||
changeState(STATE_LOADING);
|
||||
|
||||
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 (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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"?>
|
||||
<FrameLayout
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -328,4 +328,12 @@
|
|||
tools:visibility="visible"/>
|
||||
</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>
|
|
@ -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 New Issue