From cbcd281784ce07c9def74bf44676f65169ea0d11 Mon Sep 17 00:00:00 2001 From: John Zhen M Date: Mon, 28 Aug 2017 17:38:37 -0700 Subject: [PATCH] -Added MediaSourceManager and Playlist adapters. --- .../fragments/playlist/PlaylistFragment.java | 93 +++++++++-- .../fragments/search/PlaylistService.java | 4 - .../newpipe/player/BackgroundPlayer.java | 6 +- .../org/schabi/newpipe/player/BasePlayer.java | 9 +- .../newpipe/player/MediaSourceManager.java | 21 +++ .../newpipe/playlist/ExternalPlaylist.java | 99 +++++++++++ .../org/schabi/newpipe/playlist/Playlist.java | 38 +++++ .../newpipe/playlist/PlaylistAdapter.java | 154 ++++++++++++++++++ .../schabi/newpipe/playlist/PlaylistItem.java | 109 +++++++++++++ .../newpipe/playlist/PlaylistItemBuilder.java | 116 +++++++++++++ .../newpipe/playlist/PlaylistItemHolder.java | 43 +++++ .../main/res/layout/activity_main_player.xml | 12 +- .../main/res/layout/playlist_stream_item.xml | 70 ++++++++ 13 files changed, 753 insertions(+), 21 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/fragments/search/PlaylistService.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/MediaSourceManager.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/ExternalPlaylist.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/Playlist.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/PlaylistAdapter.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/PlaylistItem.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemBuilder.java create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemHolder.java create mode 100644 app/src/main/res/layout/playlist_stream_item.xml diff --git a/app/src/main/java/org/schabi/newpipe/fragments/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/playlist/PlaylistFragment.java index 0493fd525..032b227e8 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/playlist/PlaylistFragment.java @@ -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 task = new Callable() { + @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() { + @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 task = new Callable() { @@ -300,8 +369,8 @@ public class PlaylistFragment extends BaseFragment { .subscribe(new Observer() { @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; } } }); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/PlaylistService.java b/app/src/main/java/org/schabi/newpipe/fragments/search/PlaylistService.java deleted file mode 100644 index b43c7e356..000000000 --- a/app/src/main/java/org/schabi/newpipe/fragments/search/PlaylistService.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.schabi.newpipe.fragments.search; - -public class PlaylistService { -} diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index be9247569..8659ca645 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -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(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index f90352fa1..4d5631bc9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -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); diff --git a/app/src/main/java/org/schabi/newpipe/player/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/MediaSourceManager.java new file mode 100644 index 000000000..ee4ef5df4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/MediaSourceManager.java @@ -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 sources; + + public MediaSourceManager(Playlist playlist) { + this.source = new DynamicConcatenatingMediaSource(); + this.playlist = playlist; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ExternalPlaylist.java b/app/src/main/java/org/schabi/newpipe/playlist/ExternalPlaylist.java new file mode 100644 index 000000000..9ffc71303 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/ExternalPlaylist.java @@ -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 task = new Callable() { + @Override + public PlayListInfo call() throws Exception { + PlayListExtractor extractor = service.getPlayListExtractorInstance(playlist.getLink(), page); + return PlayListInfo.getInfo(extractor); + } + }; + + final Consumer onSuccess = new Consumer() { + @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 extractPlaylistItems(final PlayListInfo info) { + List 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 get(int index) { + return null; + } + + private StreamingService getService(final int serviceId) { + try { + return NewPipe.getService(serviceId); + } catch (ExtractionException e) { + return null; + } + } + +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/Playlist.java b/app/src/main/java/org/schabi/newpipe/playlist/Playlist.java new file mode 100644 index 000000000..02faaa755 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/Playlist.java @@ -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 streams; + PublishSubject> changeBroadcast; + + Playlist() { + streams = Collections.synchronizedList(new ArrayList()); + changeBroadcast = PublishSubject.create(); + } + + @NonNull + public PublishSubject> getChangeBroadcast() { + return changeBroadcast; + } + + abstract boolean isComplete(); + + abstract void load(int index); + + abstract Observable get(int index); +} + diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistAdapter.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistAdapter.java new file mode 100644 index 000000000..6f6c924d7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistAdapter.java @@ -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 + * 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 . + */ + +public class PlaylistAdapter extends RecyclerView.Adapter { + private static final String TAG = PlaylistAdapter.class.toString(); + + private final PlaylistItemBuilder playlistItemBuilder; + private final List 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 data) { + playlistItemBuilder = new PlaylistItemBuilder(); + playlistItems = data; + } + + public void setSelectedListener(PlaylistItemBuilder.OnSelectedListener listener) { + playlistItemBuilder.setOnSelectedListener(listener); + } + + public void addInfoItemList(List 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 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; + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItem.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItem.java new file mode 100644 index 000000000..cd65b335b --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItem.java @@ -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 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 getStream() { + return stream; + } + + public void load() { + stream.subscribe(); + } + + @NonNull + private Maybe getInfo() { + final Callable task = new Callable() { + @Override + public StreamInfo call() throws Exception { + final StreamExtractor extractor = NewPipe.getService(serviceId).getExtractorInstance(url); + return StreamInfo.getVideoInfo(extractor); + } + }; + + final Consumer onError = new Consumer() { + @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(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemBuilder.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemBuilder.java new file mode 100644 index 000000000..1a74240ce --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemBuilder.java @@ -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. + *

+ * Copyright (C) Christian Schabesberger 2016 + * InfoItemBuilder.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 . + */ + +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; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemHolder.java new file mode 100644 index 000000000..d1251c535 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistItemHolder.java @@ -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. + *

+ * Copyright (C) Christian Schabesberger 2016 + * StreamInfoItemHolder.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 . + */ + +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); + } +} diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index e3ef022f9..5b9246f8c 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/layout/playlist_stream_item.xml b/app/src/main/res/layout/playlist_stream_item.xml new file mode 100644 index 000000000..52fac4e31 --- /dev/null +++ b/app/src/main/res/layout/playlist_stream_item.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + \ No newline at end of file