1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-24 09:00:31 +00:00

-Added play queue dropdown to channel info items.

-Added play queue dropdown to playlist info items.
-Added Channel Play Queue.
-Renamed External Play Queue to Playlist Play Queue.
-Modified Playlist Play Queue to allow loading from initial state.
This commit is contained in:
John Zhen Mo 2017-11-01 23:38:18 -07:00
parent 87febf8679
commit b8a17580c5
12 changed files with 431 additions and 47 deletions

View File

@ -1,6 +1,5 @@
package org.schabi.newpipe.fragments.list.playlist; package org.schabi.newpipe.fragments.list.playlist;
import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -24,10 +23,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.player.BackgroundPlayer; import org.schabi.newpipe.playlist.PlaylistPlayQueue;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
@ -185,7 +181,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
} }
private PlayQueue getPlayQueue() { private PlayQueue getPlayQueue() {
return new ExternalPlayQueue( return new PlaylistPlayQueue(
currentInfo.service_id, currentInfo.service_id,
currentInfo.url, currentInfo.url,
currentInfo.next_streams_url, currentInfo.next_streams_url,

View File

@ -1,7 +1,11 @@
package org.schabi.newpipe.info_list.holder; package org.schabi.newpipe.info_list.holder;
import android.content.Context;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
@ -10,7 +14,9 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.playlist.ChannelPlayQueue;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
@ -18,6 +24,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
public final CircleImageView itemThumbnailView; public final CircleImageView itemThumbnailView;
public final TextView itemTitleView; public final TextView itemTitleView;
public final TextView itemAdditionalDetailView; public final TextView itemAdditionalDetailView;
public final ImageButton itemActionDropdown;
ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
@ -25,6 +32,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemTitleView = itemView.findViewById(R.id.itemTitleView); itemTitleView = itemView.findViewById(R.id.itemTitleView);
itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails); itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails);
itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown);
} }
public ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
@ -50,6 +58,55 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
} }
} }
}); });
enableActionDropdown(item);
}
private void enableActionDropdown(final ChannelInfoItem item) {
itemActionDropdown.setVisibility(View.VISIBLE);
itemActionDropdown.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final PopupMenu actionMenu = getStreamDropdown(itemBuilder.getContext(), itemActionDropdown, item);
if (itemBuilder.getOnChannelSelectedListener() != null) {
itemBuilder.getOnChannelSelectedListener().dropdownClicked(item, actionMenu);
}
actionMenu.show();
}
});
}
private PopupMenu getStreamDropdown(final Context context, final View anchor, final ChannelInfoItem infoItem) {
PopupMenu actionMenu = new PopupMenu(context, anchor);
final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all);
mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.playOnMainPlayer(context, new ChannelPlayQueue(infoItem));
return true;
}
});
final MenuItem popupPlay = actionMenu.getMenu().add(R.string.controls_popup_title);
popupPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.playOnPopupPlayer(context, new ChannelPlayQueue(infoItem));
return true;
}
});
final MenuItem backgroundPlay = actionMenu.getMenu().add(R.string.controls_background_title);
backgroundPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.playOnBackgroundPlayer(context, new ChannelPlayQueue(infoItem));
return true;
}
});
return actionMenu;
} }
protected String getDetailLine(final ChannelInfoItem item) { protected String getDetailLine(final ChannelInfoItem item) {

View File

@ -1,8 +1,12 @@
package org.schabi.newpipe.info_list.holder; package org.schabi.newpipe.info_list.holder;
import android.content.Context;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
@ -11,12 +15,15 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.playlist.PlaylistPlayQueue;
import org.schabi.newpipe.util.NavigationHelper;
public class PlaylistInfoItemHolder extends InfoItemHolder { public class PlaylistInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView; public final ImageView itemThumbnailView;
public final TextView itemStreamCountView; public final TextView itemStreamCountView;
public final TextView itemTitleView; public final TextView itemTitleView;
public final TextView itemUploaderView; public final TextView itemUploaderView;
public final ImageButton itemActionDropdown;
public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_item, parent); super(infoItemBuilder, R.layout.list_playlist_item, parent);
@ -25,6 +32,7 @@ public class PlaylistInfoItemHolder extends InfoItemHolder {
itemTitleView = itemView.findViewById(R.id.itemTitleView); itemTitleView = itemView.findViewById(R.id.itemTitleView);
itemStreamCountView = itemView.findViewById(R.id.itemStreamCountView); itemStreamCountView = itemView.findViewById(R.id.itemStreamCountView);
itemUploaderView = itemView.findViewById(R.id.itemUploaderView); itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown);
} }
@Override @Override
@ -47,8 +55,56 @@ public class PlaylistInfoItemHolder extends InfoItemHolder {
} }
} }
}); });
enableActionDropdown(item);
} }
private void enableActionDropdown(final PlaylistInfoItem item) {
itemActionDropdown.setVisibility(View.VISIBLE);
itemActionDropdown.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final PopupMenu actionMenu = getStreamDropdown(itemBuilder.getContext(), itemActionDropdown, item);
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
itemBuilder.getOnPlaylistSelectedListener().dropdownClicked(item, actionMenu);
}
actionMenu.show();
}
});
}
private PopupMenu getStreamDropdown(final Context context, final View anchor, final PlaylistInfoItem infoItem) {
PopupMenu actionMenu = new PopupMenu(context, anchor);
final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all);
mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.playOnMainPlayer(context, new PlaylistPlayQueue(infoItem));
return true;
}
});
final MenuItem popupPlay = actionMenu.getMenu().add(R.string.controls_popup_title);
popupPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.playOnPopupPlayer(context, new PlaylistPlayQueue(infoItem));
return true;
}
});
final MenuItem backgroundPlay = actionMenu.getMenu().add(R.string.controls_background_title);
backgroundPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.playOnBackgroundPlayer(context, new PlaylistPlayQueue(infoItem));
return true;
}
});
return actionMenu;
}
/** /**
* Display options for playlist thumbnails * Display options for playlist thumbnails
*/ */

View File

@ -2,7 +2,6 @@ package org.schabi.newpipe.info_list.holder;
import android.content.Context; import android.content.Context;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -10,7 +9,6 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
@ -19,9 +17,6 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
@ -91,11 +86,13 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
case AUDIO_LIVE_STREAM: case AUDIO_LIVE_STREAM:
case NONE: case NONE:
default: default:
disableActionDropdown();
break; break;
} }
} }
private void enableActionDropdown(final StreamInfoItem item) { private void enableActionDropdown(final StreamInfoItem item) {
itemActionDropdown.setClickable(true);
itemActionDropdown.setVisibility(View.VISIBLE); itemActionDropdown.setVisibility(View.VISIBLE);
itemActionDropdown.setOnClickListener(new View.OnClickListener() { itemActionDropdown.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -109,10 +106,34 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}); });
} }
private void disableActionDropdown() {
itemActionDropdown.setVisibility(View.GONE);
itemActionDropdown.setClickable(false);
itemActionDropdown.setOnClickListener(null);
}
private PopupMenu getStreamDropdown(final Context context, final View anchor, final StreamInfoItem infoItem) { private PopupMenu getStreamDropdown(final Context context, final View anchor, final StreamInfoItem infoItem) {
PopupMenu actionMenu = new PopupMenu(context, anchor); PopupMenu actionMenu = new PopupMenu(context, anchor);
final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_btn_text); final MenuItem backgroundEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_background);
backgroundEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem));
return true;
}
});
final MenuItem popupEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_popup);
popupEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(infoItem));
return true;
}
});
final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all);
mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override @Override
public boolean onMenuItemClick(MenuItem menuItem) { public boolean onMenuItemClick(MenuItem menuItem) {
@ -139,24 +160,6 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
} }
}); });
final MenuItem backgroundEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_background);
backgroundEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem));
return true;
}
});
final MenuItem popupEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_popup);
popupEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(infoItem));
return true;
}
});
return actionMenu; return actionMenu;
} }

View File

@ -157,7 +157,7 @@ public class MediaSourceManager {
} }
private void onPlayQueueChanged(final PlayQueueEvent event) { private void onPlayQueueChanged(final PlayQueueEvent event) {
if (playQueue.isEmpty()) { if (playQueue.isEmpty() && playQueue.isComplete()) {
playbackListener.shutdown(); playbackListener.shutdown();
return; return;
} }

View File

@ -0,0 +1,151 @@
package org.schabi.newpipe.playlist;
import android.util.Log;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public final class ChannelPlayQueue extends PlayQueue {
private final String TAG = "ChannelPlayQueue@" + Integer.toHexString(hashCode());
private boolean isInitial;
private boolean isComplete;
private int serviceId;
private String baseUrl;
private String nextUrl;
private transient Disposable fetchReactor;
public ChannelPlayQueue(final ChannelInfoItem item) {
this(item.service_id, item.url, item.url, Collections.<InfoItem>emptyList(), 0);
}
public ChannelPlayQueue(final int serviceId,
final String url,
final String nextPageUrl,
final List<InfoItem> streams,
final int index) {
super(index, extractChannelItems(streams));
this.baseUrl = url;
this.nextUrl = nextPageUrl;
this.serviceId = serviceId;
this.isInitial = streams.isEmpty();
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
}
@Override
public boolean isComplete() {
return isComplete;
}
@Override
public void fetch() {
if (isInitial) {
ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getChannelInitialObserver());
} else {
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getChannelNextItemsObserver());
}
}
private SingleObserver<ChannelInfo> getChannelInitialObserver() {
return new SingleObserver<ChannelInfo>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) {
d.dispose();
} else {
fetchReactor = d;
}
}
@Override
public void onSuccess(@NonNull ChannelInfo result) {
if (!result.has_more_streams) isComplete = true;
nextUrl = result.next_streams_url;
append(extractChannelItems(result.related_streams));
isInitial = false;
fetchReactor.dispose();
fetchReactor = null;
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
isComplete = true;
append(); // Notify change
}
};
}
private SingleObserver<ListExtractor.NextItemsResult> getChannelNextItemsObserver() {
return new SingleObserver<ListExtractor.NextItemsResult>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) {
d.dispose();
} else {
fetchReactor = d;
}
}
@Override
public void onSuccess(@NonNull ListExtractor.NextItemsResult result) {
if (!result.hasMoreStreams()) isComplete = true;
nextUrl = result.nextItemsUrl;
append(extractChannelItems(result.nextItemsList));
fetchReactor.dispose();
fetchReactor = null;
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
isComplete = true;
append(); // Notify change
}
};
}
@Override
public void dispose() {
super.dispose();
if (fetchReactor != null) fetchReactor.dispose();
}
private static List<PlayQueueItem> extractChannelItems(final List<InfoItem> infos) {
List<PlayQueueItem> result = new ArrayList<>();
for (final InfoItem stream : infos) {
if (stream instanceof StreamInfoItem) {
result.add(new PlayQueueItem((StreamInfoItem) stream));
}
}
return result;
}
}

View File

@ -4,6 +4,8 @@ import android.util.Log;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
@ -17,9 +19,10 @@ import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
public final class ExternalPlayQueue extends PlayQueue { public final class PlaylistPlayQueue extends PlayQueue {
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode()); private final String TAG = "PlaylistPlayQueue@" + Integer.toHexString(hashCode());
private boolean isInitial;
private boolean isComplete; private boolean isComplete;
private int serviceId; private int serviceId;
@ -28,7 +31,11 @@ public final class ExternalPlayQueue extends PlayQueue {
private transient Disposable fetchReactor; private transient Disposable fetchReactor;
public ExternalPlayQueue(final int serviceId, public PlaylistPlayQueue(final PlaylistInfoItem item) {
this(item.service_id, item.url, item.url, Collections.<InfoItem>emptyList(), 0);
}
public PlaylistPlayQueue(final int serviceId,
final String url, final String url,
final String nextPageUrl, final String nextPageUrl,
final List<InfoItem> streams, final List<InfoItem> streams,
@ -39,7 +46,8 @@ public final class ExternalPlayQueue extends PlayQueue {
this.nextUrl = nextPageUrl; this.nextUrl = nextPageUrl;
this.serviceId = serviceId; this.serviceId = serviceId;
this.isComplete = nextPageUrl == null || nextPageUrl.isEmpty(); this.isInitial = streams.isEmpty();
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
} }
@Override @Override
@ -49,13 +57,51 @@ public final class ExternalPlayQueue extends PlayQueue {
@Override @Override
public void fetch() { public void fetch() {
if (isInitial) {
ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getInitialPlaylistObserver());
} else {
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl) ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistObserver()); .subscribe(getPlaylistNextItemsObserver());
}
} }
private SingleObserver<ListExtractor.NextItemsResult> getPlaylistObserver() { private SingleObserver<PlaylistInfo> getInitialPlaylistObserver() {
return new SingleObserver<PlaylistInfo>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) {
d.dispose();
} else {
fetchReactor = d;
}
}
@Override
public void onSuccess(@NonNull PlaylistInfo result) {
if (!result.has_more_streams) isComplete = true;
nextUrl = result.next_streams_url;
append(extractPlaylistItems(result.related_streams));
fetchReactor.dispose();
fetchReactor = null;
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
isComplete = true;
append(); // Notify change
}
};
}
private SingleObserver<ListExtractor.NextItemsResult> getPlaylistNextItemsObserver() {
return new SingleObserver<ListExtractor.NextItemsResult>() { return new SingleObserver<ListExtractor.NextItemsResult>() {
@Override @Override
public void onSubscribe(@NonNull Disposable d) { public void onSubscribe(@NonNull Disposable d) {

View File

@ -7,7 +7,9 @@
android:layout_height="@dimen/video_item_search_height" android:layout_height="@dimen/video_item_search_height"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:padding="@dimen/video_item_search_padding"> android:focusable="true"
android:paddingTop="@dimen/video_item_search_padding"
android:paddingBottom="@dimen/video_item_search_padding">
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/itemThumbnailView" android:id="@+id/itemThumbnailView"
@ -16,18 +18,37 @@
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description" android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy" android:src="@drawable/buddy"
tools:ignore="RtlHardcoded"/> tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/itemActionDropdown"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginStart="@dimen/video_item_search_image_right_margin"
android:background="?attr/selectableItemBackground"
android:src="?attr/more_vertical"
android:visibility="gone"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/itemTitleView" android:id="@+id/itemTitleView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginBottom="@dimen/video_item_search_image_right_margin" android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:layout_toRightOf="@+id/itemThumbnailView" android:layout_toRightOf="@+id/itemThumbnailView"
android:layout_toEndOf="@+id/itemThumbnailView"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge" android:textAppearance="?android:attr/textAppearanceLarge"
@ -40,7 +61,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@+id/itemAdditionalDetails" android:layout_above="@+id/itemAdditionalDetails"
android:layout_marginBottom="@dimen/channel_item_description_to_details_margin" android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:layout_toRightOf="@+id/itemThumbnailView" android:layout_toRightOf="@+id/itemThumbnailView"
android:layout_toEndOf="@+id/itemThumbnailView"
android:ellipsize="end" android:ellipsize="end"
android:lines="2" android:lines="2"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
@ -52,7 +76,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:layout_toRightOf="@+id/itemThumbnailView" android:layout_toRightOf="@+id/itemThumbnailView"
android:layout_toEndOf="@+id/itemThumbnailView"
android:lines="1" android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size" android:textSize="@dimen/video_item_search_upload_date_text_size"

View File

@ -7,23 +7,43 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:padding="@dimen/video_item_search_padding"> android:focusable="true"
android:paddingTop="@dimen/video_item_search_padding"
android:paddingBottom="@dimen/video_item_search_padding">
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/itemThumbnailView" android:id="@+id/itemThumbnailView"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="42dp" android:layout_height="42dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginRight="12dp" android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description" android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy_channel_item" android:src="@drawable/buddy_channel_item"
tools:ignore="RtlHardcoded"/> tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/itemActionDropdown"
android:layout_width="wrap_content"
android:layout_height="42dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginStart="@dimen/video_item_search_image_right_margin"
android:background="?attr/selectableItemBackground"
android:src="?attr/more_vertical"
android:visibility="gone"
tools:visibility="visible"
tools:ignore="ContentDescription"/>
<TextView <TextView
android:id="@+id/itemTitleView" android:id="@+id/itemTitleView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:layout_marginBottom="2dp" android:layout_marginBottom="2dp"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:layout_toRightOf="@+id/itemThumbnailView" android:layout_toRightOf="@+id/itemThumbnailView"
@ -40,6 +60,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/itemTitleView" android:layout_below="@+id/itemTitleView"
android:layout_toRightOf="@+id/itemThumbnailView" android:layout_toRightOf="@+id/itemThumbnailView"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:lines="1" android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size" android:textSize="@dimen/video_item_search_upload_date_text_size"

View File

@ -7,7 +7,9 @@
android:layout_height="@dimen/video_item_search_height" android:layout_height="@dimen/video_item_search_height"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:padding="@dimen/video_item_search_padding"> android:focusable="true"
android:paddingTop="@dimen/video_item_search_padding"
android:paddingBottom="@dimen/video_item_search_padding">
<ImageView <ImageView
android:id="@+id/itemThumbnailView" android:id="@+id/itemThumbnailView"
@ -16,17 +18,35 @@
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description" android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="fitEnd" android:scaleType="fitEnd"
android:src="@drawable/dummy_thumbnail_playlist" android:src="@drawable/dummy_thumbnail_playlist"
tools:ignore="RtlHardcoded"/> tools:ignore="RtlHardcoded"/>
<ImageButton
android:id="@+id/itemActionDropdown"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginStart="@dimen/video_item_search_image_right_margin"
android:background="?attr/selectableItemBackground"
android:src="?attr/more_vertical"
android:visibility="gone"
tools:visibility="visible"
tools:ignore="ContentDescription"/>
<TextView <TextView
android:id="@+id/itemStreamCountView" android:id="@+id/itemStreamCountView"
android:layout_width="@dimen/playlist_item_thumbnail_stream_count_width" android:layout_width="@dimen/playlist_item_thumbnail_stream_count_width"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignBottom="@id/itemThumbnailView" android:layout_alignBottom="@id/itemThumbnailView"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:layout_alignRight="@id/itemThumbnailView" android:layout_alignRight="@id/itemThumbnailView"
android:layout_alignTop="@id/itemThumbnailView" android:layout_alignTop="@id/itemThumbnailView"
android:background="@color/playlist_stream_count_background_color" android:background="@color/playlist_stream_count_background_color"
@ -46,6 +66,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:layout_toRightOf="@+id/itemThumbnailView" android:layout_toRightOf="@+id/itemThumbnailView"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="2" android:maxLines="2"
@ -59,6 +81,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/itemTitleView" android:layout_below="@+id/itemTitleView"
android:layout_toLeftOf="@id/itemActionDropdown"
android:layout_toStartOf="@id/itemActionDropdown"
android:layout_toRightOf="@+id/itemThumbnailView" android:layout_toRightOf="@+id/itemThumbnailView"
android:lines="1" android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"

View File

@ -18,7 +18,7 @@
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/video_item_search_padding" android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description" android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop" android:scaleType="centerCrop"

View File

@ -51,6 +51,8 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginLeft="@dimen/video_item_search_image_right_margin"
android:layout_marginStart="@dimen/video_item_search_image_right_margin"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:src="?attr/more_vertical" android:src="?attr/more_vertical"
android:visibility="gone" android:visibility="gone"