mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 16:40:32 +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:
parent
87febf8679
commit
b8a17580c5
@ -1,6 +1,5 @@
|
||||
package org.schabi.newpipe.fragments.list.playlist;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
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.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.playlist.ExternalPlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlaylistPlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
@ -185,7 +181,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
return new ExternalPlayQueue(
|
||||
return new PlaylistPlayQueue(
|
||||
currentInfo.service_id,
|
||||
currentInfo.url,
|
||||
currentInfo.next_streams_url,
|
||||
|
@ -1,7 +1,11 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.playlist.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
|
||||
@ -18,6 +24,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||
public final CircleImageView itemThumbnailView;
|
||||
public final TextView itemTitleView;
|
||||
public final TextView itemAdditionalDetailView;
|
||||
public final ImageButton itemActionDropdown;
|
||||
|
||||
ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
@ -25,6 +32,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||
itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||
itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -1,8 +1,12 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.playlist.PlaylistInfoItem;
|
||||
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 final ImageView itemThumbnailView;
|
||||
public final TextView itemStreamCountView;
|
||||
public final TextView itemTitleView;
|
||||
public final TextView itemUploaderView;
|
||||
public final ImageButton itemActionDropdown;
|
||||
|
||||
public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_playlist_item, parent);
|
||||
@ -25,6 +32,7 @@ public class PlaylistInfoItemHolder extends InfoItemHolder {
|
||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||
itemStreamCountView = itemView.findViewById(R.id.itemStreamCountView);
|
||||
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
||||
itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown);
|
||||
}
|
||||
|
||||
@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
|
||||
*/
|
||||
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -10,7 +9,6 @@ import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
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.StreamType;
|
||||
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.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
@ -91,11 +86,13 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
case AUDIO_LIVE_STREAM:
|
||||
case NONE:
|
||||
default:
|
||||
disableActionDropdown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void enableActionDropdown(final StreamInfoItem item) {
|
||||
itemActionDropdown.setClickable(true);
|
||||
itemActionDropdown.setVisibility(View.VISIBLE);
|
||||
itemActionDropdown.setOnClickListener(new View.OnClickListener() {
|
||||
@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) {
|
||||
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() {
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -157,7 +157,7 @@ public class MediaSourceManager {
|
||||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueEvent event) {
|
||||
if (playQueue.isEmpty()) {
|
||||
if (playQueue.isEmpty() && playQueue.isComplete()) {
|
||||
playbackListener.shutdown();
|
||||
return;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
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.util.ExtractorHelper;
|
||||
|
||||
@ -17,9 +19,10 @@ import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class ExternalPlayQueue extends PlayQueue {
|
||||
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode());
|
||||
public final class PlaylistPlayQueue extends PlayQueue {
|
||||
private final String TAG = "PlaylistPlayQueue@" + Integer.toHexString(hashCode());
|
||||
|
||||
private boolean isInitial;
|
||||
private boolean isComplete;
|
||||
|
||||
private int serviceId;
|
||||
@ -28,7 +31,11 @@ public final class ExternalPlayQueue extends PlayQueue {
|
||||
|
||||
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 nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
@ -39,7 +46,8 @@ public final class ExternalPlayQueue extends PlayQueue {
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isComplete = nextPageUrl == null || nextPageUrl.isEmpty();
|
||||
this.isInitial = streams.isEmpty();
|
||||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,13 +57,51 @@ public final class ExternalPlayQueue extends PlayQueue {
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistObserver());
|
||||
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)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.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>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
@ -7,7 +7,9 @@
|
||||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
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
|
||||
android:id="@+id/itemThumbnailView"
|
||||
@ -16,23 +18,42 @@
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="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:contentDescription="@string/list_thumbnail_view_description"
|
||||
android:src="@drawable/buddy"
|
||||
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
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
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_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Channel Title, Lorem ipsum"/>
|
||||
tools:text="Channel Title, Lorem ipsum" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemChannelDescriptionView"
|
||||
@ -40,19 +61,25 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/itemAdditionalDetails"
|
||||
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_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:lines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit"/>
|
||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemAdditionalDetails"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toLeftOf="@id/itemActionDropdown"
|
||||
android:layout_toStartOf="@id/itemActionDropdown"
|
||||
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"
|
||||
|
@ -7,23 +7,43 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
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
|
||||
android:id="@+id/itemThumbnailView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="42dp"
|
||||
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:src="@drawable/buddy_channel_item"
|
||||
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
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toLeftOf="@id/itemActionDropdown"
|
||||
android:layout_toStartOf="@id/itemActionDropdown"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
@ -40,6 +60,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemTitleView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toLeftOf="@id/itemActionDropdown"
|
||||
android:layout_toStartOf="@id/itemActionDropdown"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
|
@ -7,7 +7,9 @@
|
||||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
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
|
||||
android:id="@+id/itemThumbnailView"
|
||||
@ -16,17 +18,35 @@
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="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:contentDescription="@string/list_thumbnail_view_description"
|
||||
android:scaleType="fitEnd"
|
||||
android:src="@drawable/dummy_thumbnail_playlist"
|
||||
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
|
||||
android:id="@+id/itemStreamCountView"
|
||||
android:layout_width="@dimen/playlist_item_thumbnail_stream_count_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignBottom="@id/itemThumbnailView"
|
||||
android:layout_toLeftOf="@id/itemActionDropdown"
|
||||
android:layout_toStartOf="@id/itemActionDropdown"
|
||||
android:layout_alignRight="@id/itemThumbnailView"
|
||||
android:layout_alignTop="@id/itemThumbnailView"
|
||||
android:background="@color/playlist_stream_count_background_color"
|
||||
@ -46,6 +66,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toLeftOf="@id/itemActionDropdown"
|
||||
android:layout_toStartOf="@id/itemActionDropdown"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
@ -59,6 +81,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemTitleView"
|
||||
android:layout_toLeftOf="@id/itemActionDropdown"
|
||||
android:layout_toStartOf="@id/itemActionDropdown"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
|
@ -18,7 +18,7 @@
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="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:contentDescription="@string/list_thumbnail_view_description"
|
||||
android:scaleType="centerCrop"
|
||||
|
@ -51,6 +51,8 @@
|
||||
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"
|
||||
|
Loading…
Reference in New Issue
Block a user