1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-23 00:20:32 +00:00

Channels are now an Info

The previous "main" tab is now just a normal tab returned in getTabs().
Various part of the code that used to handle channels as ListInfo now either take the first (playable, i.e. with streams) tab (e.g. the ChannelTabPlayQueue), or take all of them combined (e.g. the feed).
This commit is contained in:
Stypox 2023-04-14 10:19:58 +02:00
parent dfbd39e898
commit c076a0f771
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
19 changed files with 301 additions and 362 deletions

View File

@ -197,7 +197,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test // name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/ // This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.Theta-Dev:NewPipeExtractor:e278a2d6d428dec82a304d271803d35afbd7340c' implementation 'com.github.Theta-Dev:NewPipeExtractor:c3651bef5c622abf0cdfc34c9985ba8c33d1491e'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
/** Checkstyle **/ /** Checkstyle **/

View File

@ -65,6 +65,7 @@ import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException; import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException; import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.ktx.ExceptionUtils;
@ -72,10 +73,11 @@ import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
@ -1022,7 +1024,16 @@ public class RouterActivity extends AppCompatActivity {
} }
playQueue = new SinglePlayQueue((StreamInfo) info); playQueue = new SinglePlayQueue((StreamInfo) info);
} else if (info instanceof ChannelInfo) { } else if (info instanceof ChannelInfo) {
playQueue = new ChannelPlayQueue((ChannelInfo) info); final Optional<ListLinkHandler> playableTab = ((ChannelInfo) info).getTabs()
.stream()
.filter(ChannelTabHelper::isStreamsTab)
.findFirst();
if (playableTab.isPresent()) {
playQueue = new ChannelTabPlayQueue(info.getServiceId(), playableTab.get());
} else {
return; // there is no playable tab
}
} else if (info instanceof PlaylistInfo) { } else if (info instanceof PlaylistInfo) {
playQueue = new PlaylistPlayQueue((PlaylistInfo) info); playQueue = new PlaylistPlayQueue((PlaylistInfo) info);
} else { } else {

View File

@ -16,7 +16,6 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo; import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.views.NewPipeRecyclerView; import org.schabi.newpipe.views.NewPipeRecyclerView;
@ -236,11 +235,9 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
// showEmptyState should be called only if there is no item as // showEmptyState should be called only if there is no item as
// well as no header in infoListAdapter // well as no header in infoListAdapter
if (!(result instanceof ChannelInfo && infoListAdapter.getItemCount() == 1)) {
showEmptyState(); showEmptyState();
} }
} }
}
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
final List<Throwable> errors = new ArrayList<>(result.getErrors()); final List<Throwable> errors = new ArrayList<>(result.getErrors());

View File

@ -277,10 +277,9 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
}, onError)); }, onError));
} }
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription, private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
final ChannelInfo info) {
return (@NonNull Object o) -> { return (@NonNull Object o) -> {
subscriptionManager.insertSubscription(subscription, info); subscriptionManager.insertSubscription(subscription);
return o; return o;
}; };
} }
@ -355,7 +354,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
info.getSubscriberCount()); info.getSubscriberCount());
channelSubscription = null; channelSubscription = null;
updateNotifyButton(null); updateNotifyButton(null);
subscribeButtonMonitor = monitorSubscribeButton(mapOnSubscribe(channel, info)); subscribeButtonMonitor = monitorSubscribeButton(mapOnSubscribe(channel));
} else { } else {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Found subscription to this channel!"); Log.d(TAG, "Found subscription to this channel!");
@ -451,8 +450,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
tabAdapter.clearAllItems(); tabAdapter.clearAllItems();
if (currentInfo != null && !channelContentNotSupported) { if (currentInfo != null && !channelContentNotSupported) {
tabAdapter.addFragment(new ChannelVideosFragment(currentInfo), "Videos");
final Context context = requireContext(); final Context context = requireContext();
final SharedPreferences preferences = PreferenceManager final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(context); .getDefaultSharedPreferences(context);

View File

@ -1,157 +0,0 @@
package org.schabi.newpipe.fragments.list.channel;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.databinding.FragmentChannelVideosBinding;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class ChannelVideosFragment extends BaseListInfoFragment<StreamInfoItem, ChannelInfo> {
private final CompositeDisposable disposables = new CompositeDisposable();
private FragmentChannelVideosBinding channelBinding;
private PlaylistControlBinding playlistControlBinding;
/*//////////////////////////////////////////////////////////////////////////
// Constructors and lifecycle
//////////////////////////////////////////////////////////////////////////*/
// required by the Android framework to restore fragments after saving
public ChannelVideosFragment() {
super(UserAction.REQUESTED_CHANNEL);
}
public ChannelVideosFragment(final int serviceId, final String url, final String name) {
this();
setInitialData(serviceId, url, name);
}
public ChannelVideosFragment(@NonNull final ChannelInfo info) {
this(info.getServiceId(), info.getUrl(), info.getName());
this.currentInfo = info;
this.currentNextPage = info.getNextPage();
}
@Override
public void onResume() {
super.onResume();
if (activity != null && useAsFrontPage) {
setTitle(currentInfo != null ? currentInfo.getName() : name);
}
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(false);
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
channelBinding = FragmentChannelVideosBinding.inflate(inflater, container, false);
return channelBinding.getRoot();
}
@Override
public void onDestroy() {
super.onDestroy();
disposables.clear();
channelBinding = null;
playlistControlBinding = null;
}
@Override
protected Supplier<View> getListHeaderSupplier() {
playlistControlBinding = PlaylistControlBinding
.inflate(activity.getLayoutInflater(), itemsList, false);
return playlistControlBinding::getRoot;
}
/*//////////////////////////////////////////////////////////////////////////
// Loading
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<ListExtractor.InfoItemsPage<StreamInfoItem>> loadMoreItemsLogic() {
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
}
@Override
protected Single<ChannelInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
public void handleResult(@NonNull final ChannelInfo result) {
super.handleResult(result);
// PlaylistControls should be visible only if there is some item in
// infoListAdapter other than header
if (infoListAdapter.getItemCount() != 1) {
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
} else {
playlistControlBinding.getRoot().setVisibility(View.GONE);
}
disposables.clear();
playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP);
return true;
});
playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO);
return true;
});
}
private PlayQueue getPlayQueue() {
final List<StreamInfoItem> streamItems = infoListAdapter.getItemsList().stream()
.filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast)
.collect(Collectors.toList());
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
currentInfo.getNextPage(), streamItems, 0);
}
}

View File

@ -58,7 +58,7 @@ class NotificationHelper(val context: Context) {
.setAutoCancel(true) .setAutoCancel(true)
.setCategory(NotificationCompat.CATEGORY_SOCIAL) .setCategory(NotificationCompat.CATEGORY_SOCIAL)
.setGroupSummary(true) .setGroupSummary(true)
.setGroup(data.listInfo.url) .setGroup(data.originalInfo.url)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
// Build a summary notification for Android versions < 7.0 // Build a summary notification for Android versions < 7.0
@ -73,7 +73,7 @@ class NotificationHelper(val context: Context) {
context, context,
data.pseudoId, data.pseudoId,
NavigationHelper NavigationHelper
.getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url) .getChannelIntent(context, data.originalInfo.serviceId, data.originalInfo.url)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
0, 0,
false false
@ -88,7 +88,7 @@ class NotificationHelper(val context: Context) {
// Show individual stream notifications, set channel icon only if there is actually // Show individual stream notifications, set channel icon only if there is actually
// one // one
showStreamNotifications(newStreams, data.listInfo.serviceId, bitmap) showStreamNotifications(newStreams, data.originalInfo.serviceId, bitmap)
// Show summary notification // Show summary notification
manager.notify(data.pseudoId, summaryBuilder.build()) manager.notify(data.pseudoId, summaryBuilder.build())
@ -97,7 +97,7 @@ class NotificationHelper(val context: Context) {
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
// Show individual stream notifications // Show individual stream notifications
showStreamNotifications(newStreams, data.listInfo.serviceId, null) showStreamNotifications(newStreams, data.originalInfo.serviceId, null)
// Show summary notification // Show summary notification
manager.notify(data.pseudoId, summaryBuilder.build()) manager.notify(data.pseudoId, summaryBuilder.build())
iconLoadingTargets.remove(this) // allow it to be garbage-collected iconLoadingTargets.remove(this) // allow it to be garbage-collected

View File

@ -13,11 +13,16 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.NotificationMode
import org.schabi.newpipe.extractor.ListInfo import org.schabi.newpipe.extractor.Info
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.feed.FeedInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.ChannelTabHelper
import org.schabi.newpipe.util.ExtractorHelper.getChannelInfo
import org.schabi.newpipe.util.ExtractorHelper.getChannelTab
import org.schabi.newpipe.util.ExtractorHelper.getMoreChannelTabItems
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -102,49 +107,88 @@ class FeedLoadManager(private val context: Context) {
.filter { !cancelSignal.get() } .filter { !cancelSignal.get() }
.map { subscriptionEntity -> .map { subscriptionEntity ->
var error: Throwable? = null var error: Throwable? = null
val storeOriginalErrorAndRethrow = { e: Throwable ->
// keep original to prevent blockingGet() from wrapping it into RuntimeException
error = e
throw e
}
try { try {
// check for and load new streams // check for and load new streams
// either by using the dedicated feed method or by getting the channel info // either by using the dedicated feed method or by getting the channel info
val listInfo = if (useFeedExtractor) { var originalInfo: Info? = null
ExtractorHelper var streams: List<StreamInfoItem>? = null
.getFeedInfoFallbackToChannelInfo( val errors = ArrayList<Throwable>()
subscriptionEntity.serviceId,
subscriptionEntity.url if (useFeedExtractor) {
) NewPipe.getService(subscriptionEntity.serviceId)
.onErrorReturn { .getFeedExtractor(subscriptionEntity.url)
error = it // store error, otherwise wrapped into RuntimeException ?.also { feedExtractor ->
throw it // the user wants to use a feed extractor and there is one, use it
val feedInfo = FeedInfo.getInfo(feedExtractor)
errors.addAll(feedInfo.errors)
originalInfo = feedInfo
streams = feedInfo.relatedItems
} }
}
if (originalInfo == null) {
// use the normal channel tabs extractor if either the user wants it, or
// the current service does not have a dedicated feed extractor
val channelInfo = getChannelInfo(
subscriptionEntity.serviceId,
subscriptionEntity.url, true
)
.onErrorReturn(storeOriginalErrorAndRethrow)
.blockingGet() .blockingGet()
errors.addAll(channelInfo.errors)
originalInfo = channelInfo
streams = channelInfo.tabs
.filter(ChannelTabHelper::isStreamsTab)
.map {
Pair(
getChannelTab(subscriptionEntity.serviceId, it, true)
.onErrorReturn(storeOriginalErrorAndRethrow)
.blockingGet(),
it
)
}
.flatMap { (channelTabInfo, linkHandler) ->
errors.addAll(channelTabInfo.errors)
if (channelTabInfo.relatedItems.isEmpty()) {
val infoItemsPage = getMoreChannelTabItems(
subscriptionEntity.serviceId,
linkHandler, channelTabInfo.nextPage
)
.blockingGet()
errors.addAll(infoItemsPage.errors)
return@flatMap infoItemsPage.items
} else { } else {
ExtractorHelper return@flatMap channelTabInfo.relatedItems
.getChannelInfo( }
subscriptionEntity.serviceId, }
subscriptionEntity.url, .filterIsInstance<StreamInfoItem>()
true
)
.onErrorReturn {
error = it // store error, otherwise wrapped into RuntimeException
throw it
} }
.blockingGet()
} as ListInfo<StreamInfoItem>
return@map Notification.createOnNext( return@map Notification.createOnNext(
FeedUpdateInfo( FeedUpdateInfo(
subscriptionEntity, subscriptionEntity,
listInfo originalInfo!!,
streams!!,
errors,
) )
) )
} catch (e: Throwable) { } catch (e: Throwable) {
if (error == null) {
// do this to prevent blockingGet() from wrapping into RuntimeException
error = e
}
val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}" val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
val wrapper = val wrapper = FeedLoadService.RequestException(
FeedLoadService.RequestException(subscriptionEntity.uid, request, error!!) subscriptionEntity.uid,
request,
// do this to prevent blockingGet() from wrapping into RuntimeException
error ?: e
)
return@map Notification.createOnError<FeedUpdateInfo>(wrapper) return@map Notification.createOnError<FeedUpdateInfo>(wrapper)
} }
} }
@ -203,24 +247,24 @@ class FeedLoadManager(private val context: Context) {
for (notification in list) { for (notification in list) {
when { when {
notification.isOnNext -> { notification.isOnNext -> {
val subscriptionId = notification.value!!.uid val info = notification.value!!
val info = notification.value!!.listInfo
notification.value!!.newStreams = filterNewStreams( notification.value!!.newStreams = filterNewStreams(info.streams)
notification.value!!.listInfo.relatedItems
)
feedDatabaseManager.upsertAll(subscriptionId, info.relatedItems) feedDatabaseManager.upsertAll(info.uid, info.streams)
subscriptionManager.updateFromInfo(subscriptionId, info) subscriptionManager.updateFromInfo(info.uid, info.originalInfo)
if (info.errors.isNotEmpty()) { if (info.errors.isNotEmpty()) {
feedResultsHolder.addErrors( feedResultsHolder.addErrors(
FeedLoadService.RequestException.wrapList( info.errors.map {
subscriptionId, FeedLoadService.RequestException(
info info.uid,
"${info.originalInfo.serviceId}:${info.originalInfo.url}",
it
) )
}
) )
feedDatabaseManager.markAsOutdated(subscriptionId) feedDatabaseManager.markAsOutdated(info.uid)
} }
} }
notification.isOnError -> { notification.isOnError -> {

View File

@ -39,8 +39,6 @@ import org.schabi.newpipe.App
import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.ListInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -126,17 +124,7 @@ class FeedLoadService : Service() {
// Loading & Handling // Loading & Handling
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) { class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause)
companion object {
fun wrapList(subscriptionId: Long, info: ListInfo<StreamInfoItem>): List<Throwable> {
val toReturn = ArrayList<Throwable>(info.errors.size)
info.errors.mapTo(toReturn) {
RequestException(subscriptionId, info.serviceId.toString() + ":" + info.url, it)
}
return toReturn
}
}
}
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
// Notification // Notification

View File

@ -2,7 +2,7 @@ package org.schabi.newpipe.local.feed.service
import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.NotificationMode
import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.extractor.ListInfo import org.schabi.newpipe.extractor.Info
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
data class FeedUpdateInfo( data class FeedUpdateInfo(
@ -11,24 +11,30 @@ data class FeedUpdateInfo(
val notificationMode: Int, val notificationMode: Int,
val name: String, val name: String,
val avatarUrl: String, val avatarUrl: String,
val listInfo: ListInfo<StreamInfoItem>, val originalInfo: Info,
val streams: List<StreamInfoItem>,
val errors: List<Throwable>,
) { ) {
constructor( constructor(
subscription: SubscriptionEntity, subscription: SubscriptionEntity,
listInfo: ListInfo<StreamInfoItem>, originalInfo: Info,
streams: List<StreamInfoItem>,
errors: List<Throwable>,
) : this( ) : this(
uid = subscription.uid, uid = subscription.uid,
notificationMode = subscription.notificationMode, notificationMode = subscription.notificationMode,
name = subscription.name, name = subscription.name,
avatarUrl = subscription.avatarUrl, avatarUrl = subscription.avatarUrl,
listInfo = listInfo, originalInfo = originalInfo,
streams = streams,
errors = errors,
) )
/** /**
* Integer id, can be used as notification id, etc. * Integer id, can be used as notification id, etc.
*/ */
val pseudoId: Int val pseudoId: Int
get() = listInfo.url.hashCode() get() = originalInfo.url.hashCode()
lateinit var newStreams: List<StreamInfoItem> lateinit var newStreams: List<StreamInfoItem>
} }

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.subscription package org.schabi.newpipe.local.subscription
import android.content.Context import android.content.Context
import android.util.Pair
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
@ -11,8 +12,9 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.NotificationMode
import org.schabi.newpipe.database.subscription.SubscriptionDAO import org.schabi.newpipe.database.subscription.SubscriptionDAO
import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.extractor.ListInfo import org.schabi.newpipe.extractor.Info
import org.schabi.newpipe.extractor.channel.ChannelInfo import org.schabi.newpipe.extractor.channel.ChannelInfo
import org.schabi.newpipe.extractor.channel.ChannelTabInfo
import org.schabi.newpipe.extractor.feed.FeedInfo import org.schabi.newpipe.extractor.feed.FeedInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
@ -46,26 +48,31 @@ class SubscriptionManager(context: Context) {
} }
} }
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> { fun upsertAll(infoList: List<Pair<ChannelInfo, List<ChannelTabInfo>>>): List<SubscriptionEntity> {
val listEntities = subscriptionTable.upsertAll( val listEntities = subscriptionTable.upsertAll(
infoList.map { SubscriptionEntity.from(it) } infoList.map { SubscriptionEntity.from(it.first) }
) )
database.runInTransaction { database.runInTransaction {
infoList.forEachIndexed { index, info -> infoList.forEachIndexed { index, info ->
feedDatabaseManager.upsertAll(listEntities[index].uid, info.relatedItems) info.second.forEach {
feedDatabaseManager.upsertAll(
listEntities[index].uid,
it.relatedItems.filterIsInstance<StreamInfoItem>()
)
}
} }
} }
return listEntities return listEntities
} }
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url) fun updateChannelInfo(info: ChannelInfo): Completable =
subscriptionTable.getSubscription(info.serviceId, info.url)
.flatMapCompletable { .flatMapCompletable {
Completable.fromRunnable { Completable.fromRunnable {
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount) it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
subscriptionTable.update(it) subscriptionTable.update(it)
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
} }
} }
@ -84,7 +91,7 @@ class SubscriptionManager(context: Context) {
} }
} }
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) { fun updateFromInfo(subscriptionId: Long, info: Info) {
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId) val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
if (info is FeedInfo) { if (info is FeedInfo) {
@ -107,11 +114,8 @@ class SubscriptionManager(context: Context) {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) { fun insertSubscription(subscriptionEntity: SubscriptionEntity) {
database.runInTransaction { subscriptionTable.insert(subscriptionEntity)
val subscriptionId = subscriptionTable.insert(subscriptionEntity)
feedDatabaseManager.upsertAll(subscriptionId, info.relatedItems)
}
} }
fun deleteSubscription(subscriptionEntity: SubscriptionEntity) { fun deleteSubscription(subscriptionEntity: SubscriptionEntity) {
@ -125,7 +129,10 @@ class SubscriptionManager(context: Context) {
*/ */
private fun rememberAllStreams(subscription: SubscriptionEntity): Completable { private fun rememberAllStreams(subscription: SubscriptionEntity): Completable {
return ExtractorHelper.getChannelInfo(subscription.serviceId, subscription.url, false) return ExtractorHelper.getChannelInfo(subscription.serviceId, subscription.url, false)
.map { channel -> channel.relatedItems.map { stream -> StreamEntity(stream) } } .flatMap { info ->
ExtractorHelper.getChannelTab(subscription.serviceId, info.tabs.first(), false)
}
.map { channel -> channel.relatedItems.filterIsInstance<StreamInfoItem>().map { stream -> StreamEntity(stream) } }
.flatMapCompletable { entities -> .flatMapCompletable { entities ->
Completable.fromAction { Completable.fromAction {
database.streamDAO().upsertAll(entities) database.streamDAO().upsertAll(entities)

View File

@ -26,6 +26,7 @@ import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -38,6 +39,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelTabInfo;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem; import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.streams.io.SharpInputStream; import org.schabi.newpipe.streams.io.SharpInputStream;
@ -48,6 +50,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -199,12 +202,19 @@ public class SubscriptionsImportService extends BaseImportExportService {
.parallel(PARALLEL_EXTRACTIONS) .parallel(PARALLEL_EXTRACTIONS)
.runOn(Schedulers.io()) .runOn(Schedulers.io())
.map((Function<SubscriptionItem, Notification<ChannelInfo>>) subscriptionItem -> { .map((Function<SubscriptionItem, Notification<Pair<ChannelInfo,
List<ChannelTabInfo>>>>) subscriptionItem -> {
try { try {
return Notification.createOnNext(ExtractorHelper final ChannelInfo channelInfo = ExtractorHelper
.getChannelInfo(subscriptionItem.getServiceId(), .getChannelInfo(subscriptionItem.getServiceId(),
subscriptionItem.getUrl(), true) subscriptionItem.getUrl(), true)
.blockingGet()); .blockingGet();
return Notification.createOnNext(new Pair<>(channelInfo,
Collections.singletonList(
ExtractorHelper.getChannelTab(
subscriptionItem.getServiceId(),
channelInfo.getTabs().get(0), true).blockingGet()
)));
} catch (final Throwable e) { } catch (final Throwable e) {
return Notification.createOnError(e); return Notification.createOnError(e);
} }
@ -223,7 +233,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
} }
private Subscriber<List<SubscriptionEntity>> getSubscriber() { private Subscriber<List<SubscriptionEntity>> getSubscriber() {
return new Subscriber<List<SubscriptionEntity>>() { return new Subscriber<>() {
@Override @Override
public void onSubscribe(final Subscription s) { public void onSubscribe(final Subscription s) {
subscription = s; subscription = s;
@ -254,10 +264,11 @@ public class SubscriptionsImportService extends BaseImportExportService {
}; };
} }
private Consumer<Notification<ChannelInfo>> getNotificationsConsumer() { private Consumer<Notification<Pair<ChannelInfo,
List<ChannelTabInfo>>>> getNotificationsConsumer() {
return notification -> { return notification -> {
if (notification.isOnNext()) { if (notification.isOnNext()) {
final String name = notification.getValue().getName(); final String name = notification.getValue().first.getName();
eventListener.onItemCompleted(!TextUtils.isEmpty(name) ? name : ""); eventListener.onItemCompleted(!TextUtils.isEmpty(name) ? name : "");
} else if (notification.isOnError()) { } else if (notification.isOnError()) {
final Throwable error = notification.getError(); final Throwable error = notification.getError();
@ -275,10 +286,12 @@ public class SubscriptionsImportService extends BaseImportExportService {
}; };
} }
private Function<List<Notification<ChannelInfo>>, List<SubscriptionEntity>> upsertBatch() { private Function<List<Notification<Pair<ChannelInfo, List<ChannelTabInfo>>>>,
List<SubscriptionEntity>> upsertBatch() {
return notificationList -> { return notificationList -> {
final List<ChannelInfo> infoList = new ArrayList<>(notificationList.size()); final List<Pair<ChannelInfo, List<ChannelTabInfo>>> infoList =
for (final Notification<ChannelInfo> n : notificationList) { new ArrayList<>(notificationList.size());
for (final Notification<Pair<ChannelInfo, List<ChannelTabInfo>>> n : notificationList) {
if (n.isOnNext()) { if (n.isOnNext()) {
infoList.add(n.getValue()); infoList.add(n.getValue());
} }

View File

@ -4,6 +4,7 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo; import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.Page;
@ -15,7 +16,7 @@ import java.util.stream.Collectors;
import io.reactivex.rxjava3.core.SingleObserver; import io.reactivex.rxjava3.core.SingleObserver;
import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
abstract class AbstractInfoPlayQueue<T extends ListInfo<StreamInfoItem>> abstract class AbstractInfoPlayQueue<T extends ListInfo<? extends InfoItem>>
extends PlayQueue { extends PlayQueue {
boolean isInitial; boolean isInitial;
private boolean isComplete; private boolean isComplete;
@ -27,7 +28,10 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo<StreamInfoItem>>
private transient Disposable fetchReactor; private transient Disposable fetchReactor;
protected AbstractInfoPlayQueue(final T info) { protected AbstractInfoPlayQueue(final T info) {
this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems(), 0); this(info.getServiceId(), info.getUrl(), info.getNextPage(),
info.getRelatedItems().stream().filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast).collect(
Collectors.toList()), 0);
} }
protected AbstractInfoPlayQueue(final int serviceId, protected AbstractInfoPlayQueue(final int serviceId,
@ -72,7 +76,10 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo<StreamInfoItem>>
} }
nextPage = result.getNextPage(); nextPage = result.getNextPage();
append(extractListItems(result.getRelatedItems())); append(extractListItems(result.getRelatedItems().stream()
.filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast).collect(
Collectors.toList())));
fetchReactor.dispose(); fetchReactor.dispose();
fetchReactor = null; fetchReactor = null;
@ -87,7 +94,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo<StreamInfoItem>>
}; };
} }
SingleObserver<ListExtractor.InfoItemsPage<StreamInfoItem>> getNextPageObserver() { SingleObserver<ListExtractor.InfoItemsPage<? extends InfoItem>> getNextPageObserver() {
return new SingleObserver<>() { return new SingleObserver<>() {
@Override @Override
public void onSubscribe(@NonNull final Disposable d) { public void onSubscribe(@NonNull final Disposable d) {
@ -101,13 +108,16 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo<StreamInfoItem>>
@Override @Override
public void onSuccess( public void onSuccess(
@NonNull final ListExtractor.InfoItemsPage<StreamInfoItem> result) { @NonNull final ListExtractor.InfoItemsPage<? extends InfoItem> result) {
if (!result.hasNextPage()) { if (!result.hasNextPage()) {
isComplete = true; isComplete = true;
} }
nextPage = result.getNextPage(); nextPage = result.getNextPage();
append(extractListItems(result.getItems())); append(extractListItems(result.getItems().stream()
.filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast).collect(
Collectors.toList())));
fetchReactor.dispose(); fetchReactor.dispose();
fetchReactor = null; fetchReactor = null;

View File

@ -1,47 +0,0 @@
package org.schabi.newpipe.player.playqueue;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.List;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo> {
public ChannelPlayQueue(final ChannelInfo info) {
super(info);
}
public ChannelPlayQueue(final int serviceId,
final String url,
final Page nextPage,
final List<StreamInfoItem> streams,
final int index) {
super(serviceId, url, nextPage, streams, index);
}
@Override
protected String getTag() {
return "ChannelPlayQueue@" + Integer.toHexString(hashCode());
}
@Override
public void fetch() {
if (this.isInitial) {
ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
} else {
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getNextPageObserver());
}
}
}

View File

@ -0,0 +1,53 @@
package org.schabi.newpipe.player.playqueue;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.ChannelTabInfo;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.Collections;
import java.util.List;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class ChannelTabPlayQueue extends AbstractInfoPlayQueue<ChannelTabInfo> {
final ListLinkHandler linkHandler;
public ChannelTabPlayQueue(final int serviceId,
final ListLinkHandler linkHandler,
final Page nextPage,
final List<StreamInfoItem> streams,
final int index) {
super(serviceId, linkHandler.getUrl(), nextPage, streams, index);
this.linkHandler = linkHandler;
}
public ChannelTabPlayQueue(final int serviceId,
final ListLinkHandler linkHandler) {
this(serviceId, linkHandler, null, Collections.emptyList(), 0);
}
@Override
protected String getTag() {
return "ChannelTabPlayQueue@" + Integer.toHexString(hashCode());
}
@Override
public void fetch() {
if (isInitial) {
ExtractorHelper.getChannelTab(this.serviceId, this.linkHandler, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
} else {
ExtractorHelper.getMoreChannelTabItems(this.serviceId, this.linkHandler, this.nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getNextPageObserver());
}
}
}

View File

@ -19,7 +19,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.fragments.BlankFragment; import org.schabi.newpipe.fragments.BlankFragment;
import org.schabi.newpipe.fragments.list.channel.ChannelVideosFragment; import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
import org.schabi.newpipe.fragments.list.kiosk.DefaultKioskFragment; import org.schabi.newpipe.fragments.list.kiosk.DefaultKioskFragment;
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
@ -432,8 +432,8 @@ public abstract class Tab {
} }
@Override @Override
public ChannelVideosFragment getFragment(final Context context) { public ChannelFragment getFragment(final Context context) {
return new ChannelVideosFragment(channelServiceId, channelUrl, channelName); return ChannelFragment.getInstance(channelServiceId, channelUrl, channelName);
} }
@Override @Override

View File

@ -7,24 +7,58 @@ import androidx.annotation.StringRes;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.linkhandler.ChannelTabs; import org.schabi.newpipe.extractor.linkhandler.ChannelTabs;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import java.util.List;
import java.util.Set; import java.util.Set;
public final class ChannelTabHelper { public final class ChannelTabHelper {
private ChannelTabHelper() { private ChannelTabHelper() {
} }
/**
* @param tab the channel tab to check
* @return whether the tab should contain (playable) streams or not
*/
public static boolean isStreamsTab(final String tab) {
switch (tab) {
case ChannelTabs.VIDEOS:
case ChannelTabs.TRACKS:
case ChannelTabs.SHORTS:
case ChannelTabs.LIVESTREAMS:
return true;
}
return false;
}
/**
* @param tab the channel tab link handler to check
* @return whether the tab should contain (playable) streams or not
*/
public static boolean isStreamsTab(final ListLinkHandler tab) {
final List<String> contentFilters = tab.getContentFilters();
if (contentFilters.isEmpty()) {
return false; // this should never happen, but check just to be sure
} else {
return isStreamsTab(contentFilters.get(0));
}
}
@StringRes @StringRes
private static int getShowTabKey(final String tab) { private static int getShowTabKey(final String tab) {
switch (tab) { switch (tab) {
case ChannelTabs.PLAYLISTS: case ChannelTabs.VIDEOS:
return R.string.show_channel_tabs_playlists; return R.string.show_channel_tabs_videos;
case ChannelTabs.LIVESTREAMS: case ChannelTabs.TRACKS:
return R.string.show_channel_tabs_livestreams; return R.string.show_channel_tabs_tracks;
case ChannelTabs.SHORTS: case ChannelTabs.SHORTS:
return R.string.show_channel_tabs_shorts; return R.string.show_channel_tabs_shorts;
case ChannelTabs.LIVESTREAMS:
return R.string.show_channel_tabs_livestreams;
case ChannelTabs.CHANNELS: case ChannelTabs.CHANNELS:
return R.string.show_channel_tabs_channels; return R.string.show_channel_tabs_channels;
case ChannelTabs.PLAYLISTS:
return R.string.show_channel_tabs_playlists;
case ChannelTabs.ALBUMS: case ChannelTabs.ALBUMS:
return R.string.show_channel_tabs_albums; return R.string.show_channel_tabs_albums;
} }
@ -34,14 +68,18 @@ public final class ChannelTabHelper {
@StringRes @StringRes
public static int getTranslationKey(final String tab) { public static int getTranslationKey(final String tab) {
switch (tab) { switch (tab) {
case ChannelTabs.PLAYLISTS: case ChannelTabs.VIDEOS:
return R.string.channel_tab_playlists; return R.string.channel_tab_videos;
case ChannelTabs.LIVESTREAMS: case ChannelTabs.TRACKS:
return R.string.channel_tab_livestreams; return R.string.channel_tab_tracks;
case ChannelTabs.SHORTS: case ChannelTabs.SHORTS:
return R.string.channel_tab_shorts; return R.string.channel_tab_shorts;
case ChannelTabs.LIVESTREAMS:
return R.string.channel_tab_livestreams;
case ChannelTabs.CHANNELS: case ChannelTabs.CHANNELS:
return R.string.channel_tab_channels; return R.string.channel_tab_channels;
case ChannelTabs.PLAYLISTS:
return R.string.channel_tab_playlists;
case ChannelTabs.ALBUMS: case ChannelTabs.ALBUMS:
return R.string.channel_tab_albums; return R.string.channel_tab_albums;
} }

View File

@ -36,17 +36,13 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.MetaInfo; import org.schabi.newpipe.extractor.MetaInfo;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelTabInfo; import org.schabi.newpipe.extractor.channel.ChannelTabInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.feed.FeedExtractor;
import org.schabi.newpipe.extractor.feed.FeedInfo;
import org.schabi.newpipe.extractor.kiosk.KioskInfo; import org.schabi.newpipe.extractor.kiosk.KioskInfo;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
@ -129,30 +125,6 @@ public final class ExtractorHelper {
ChannelInfo.getInfo(NewPipe.getService(serviceId), url))); ChannelInfo.getInfo(NewPipe.getService(serviceId), url)));
} }
public static Single<InfoItemsPage<StreamInfoItem>> getMoreChannelItems(final int serviceId,
final String url,
final Page nextPage) {
checkServiceId(serviceId);
return Single.fromCallable(() ->
ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextPage));
}
public static Single<ListInfo<StreamInfoItem>> getFeedInfoFallbackToChannelInfo(
final int serviceId, final String url) {
final Maybe<ListInfo<StreamInfoItem>> maybeFeedInfo = Maybe.fromCallable(() -> {
final StreamingService service = NewPipe.getService(serviceId);
final FeedExtractor feedExtractor = service.getFeedExtractor(url);
if (feedExtractor == null) {
return null;
}
return FeedInfo.getInfo(feedExtractor);
});
return maybeFeedInfo.switchIfEmpty(getChannelInfo(serviceId, url, true));
}
public static Single<ChannelTabInfo> getChannelTab(final int serviceId, public static Single<ChannelTabInfo> getChannelTab(final int serviceId,
final ListLinkHandler listLinkHandler, final ListLinkHandler listLinkHandler,
final boolean forceLoad) { final boolean forceLoad) {

View File

@ -275,25 +275,31 @@
<!-- Content & History --> <!-- Content & History -->
<string name="show_channel_tabs_key">channel_tabs</string> <string name="show_channel_tabs_key">channel_tabs</string>
<string name="show_channel_tabs_playlists">show_channel_tabs_playlists</string> <string name="show_channel_tabs_videos">show_channel_tabs_videos</string>
<string name="show_channel_tabs_livestreams">show_channel_tabs_live</string> <string name="show_channel_tabs_tracks">show_channel_tabs_tracks</string>
<string name="show_channel_tabs_shorts">show_channel_tabs_shorts</string> <string name="show_channel_tabs_shorts">show_channel_tabs_shorts</string>
<string name="show_channel_tabs_livestreams">show_channel_tabs_live</string>
<string name="show_channel_tabs_channels">show_channel_tabs_channels</string> <string name="show_channel_tabs_channels">show_channel_tabs_channels</string>
<string name="show_channel_tabs_playlists">show_channel_tabs_playlists</string>
<string name="show_channel_tabs_albums">show_channel_tabs_albums</string> <string name="show_channel_tabs_albums">show_channel_tabs_albums</string>
<string name="show_channel_tabs_about">show_channel_tabs_about</string> <string name="show_channel_tabs_about">show_channel_tabs_about</string>
<string-array name="show_channel_tabs_value_list"> <string-array name="show_channel_tabs_value_list">
<item>@string/show_channel_tabs_playlists</item> <item>@string/show_channel_tabs_videos</item>
<item>@string/show_channel_tabs_livestreams</item> <item>@string/show_channel_tabs_tracks</item>
<item>@string/show_channel_tabs_shorts</item> <item>@string/show_channel_tabs_shorts</item>
<item>@string/show_channel_tabs_livestreams</item>
<item>@string/show_channel_tabs_channels</item> <item>@string/show_channel_tabs_channels</item>
<item>@string/show_channel_tabs_playlists</item>
<item>@string/show_channel_tabs_albums</item> <item>@string/show_channel_tabs_albums</item>
<item>@string/show_channel_tabs_about</item> <item>@string/show_channel_tabs_about</item>
</string-array> </string-array>
<string-array name="show_channel_tabs_description_list"> <string-array name="show_channel_tabs_description_list">
<item>@string/channel_tab_playlists</item> <item>@string/channel_tab_videos</item>
<item>@string/channel_tab_livestreams</item> <item>@string/channel_tab_tracks</item>
<item>@string/channel_tab_shorts</item> <item>@string/channel_tab_shorts</item>
<item>@string/channel_tab_livestreams</item>
<item>@string/channel_tab_channels</item> <item>@string/channel_tab_channels</item>
<item>@string/channel_tab_playlists</item>
<item>@string/channel_tab_albums</item> <item>@string/channel_tab_albums</item>
<item>@string/channel_tab_about</item> <item>@string/channel_tab_about</item>
</string-array> </string-array>

View File

@ -798,10 +798,11 @@
<string name="audio_track_type_dubbed">dubbed</string> <string name="audio_track_type_dubbed">dubbed</string>
<string name="audio_track_type_descriptive">descriptive</string> <string name="audio_track_type_descriptive">descriptive</string>
<string name="channel_tab_videos">Videos</string> <string name="channel_tab_videos">Videos</string>
<string name="channel_tab_livestreams">Live</string> <string name="channel_tab_tracks">Tracks</string>
<string name="channel_tab_shorts">Shorts</string> <string name="channel_tab_shorts">Shorts</string>
<string name="channel_tab_playlists">Playlists</string> <string name="channel_tab_livestreams">Live</string>
<string name="channel_tab_channels">Channels</string> <string name="channel_tab_channels">Channels</string>
<string name="channel_tab_playlists">Playlists</string>
<string name="channel_tab_albums">Albums</string> <string name="channel_tab_albums">Albums</string>
<string name="channel_tab_about">About</string> <string name="channel_tab_about">About</string>
<string name="show_channel_tabs">Channel tabs</string> <string name="show_channel_tabs">Channel tabs</string>