1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-11-13 13:37:12 +00:00

Deduplicate code for fetching stream info when sparse

Fixes #7941
This commit is contained in:
Stypox
2022-03-03 16:24:51 +01:00
parent 05a5e4372a
commit 162a838afc
48 changed files with 138 additions and 249 deletions

View File

@@ -14,7 +14,7 @@ import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.SaveUploaderUrlHelper;
import org.schabi.newpipe.util.SparseItemUtil;
import java.util.Collections;
@@ -62,7 +62,8 @@ public final class QueueItemMenuUtil {
return true;
case R.id.menu_item_channel_details:
SaveUploaderUrlHelper.saveUploaderUrlIfNeeded(context, item,
SparseItemUtil.fetchUploaderUrlIfSparse(context, item.getServiceId(),
item.getUrl(), item.getUploaderUrl(),
// An intent must be used here.
// Opening with FragmentManager transactions is not working,
// as PlayQueueActivity doesn't use fragments.

View File

@@ -1,7 +1,8 @@
package org.schabi.newpipe.info_list.dialog;
import static org.schabi.newpipe.info_list.dialog.StreamDialogEntry.fetchItemInfoIfSparse;
import static org.schabi.newpipe.util.NavigationHelper.openChannelFragment;
import static org.schabi.newpipe.util.SparseItemUtil.fetchItemInfoIfSparse;
import static org.schabi.newpipe.util.SparseItemUtil.fetchUploaderUrlIfSparse;
import android.net.Uri;
@@ -14,7 +15,6 @@ import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.SaveUploaderUrlHelper;
import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils;
@@ -40,8 +40,8 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
*/
public enum StreamDialogDefaultEntry {
SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragment, item) ->
SaveUploaderUrlHelper.saveUploaderUrlIfNeeded(fragment, item,
uploaderUrl -> openChannelFragment(fragment, item, uploaderUrl))
fetchUploaderUrlIfSparse(fragment.requireContext(), item.getServiceId(), item.getUrl(),
item.getUploaderUrl(), url -> openChannelFragment(fragment, item, url))
),
/**

View File

@@ -6,19 +6,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.function.Consumer;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class StreamDialogEntry {
@@ -40,52 +28,4 @@ public class StreamDialogEntry {
public interface StreamDialogEntryAction {
void onClick(Fragment fragment, StreamInfoItem infoItem);
}
/**
* Fetches a {@link StreamInfoItem} if it is incomplete and executes the callback.
* <br />
* This method is required if the info has been fetched
* via a {@link org.schabi.newpipe.extractor.feed.FeedExtractor}.
* FeedExtractors provide a fast and lightweight method to fetch info,
* but the info might be incomplete
* (see {@link org.schabi.newpipe.local.feed.service.FeedLoadService} for more details).
* @param context
* @param item the item which is checked and eventually loaded completely
* @param callback
*/
public static void fetchItemInfoIfSparse(@NonNull final Context context,
@NonNull final StreamInfoItem item,
@NonNull final Consumer<SinglePlayQueue> callback) {
if (!(item.getStreamType() == StreamType.LIVE_STREAM
|| item.getStreamType() == StreamType.AUDIO_LIVE_STREAM)
&& item.getDuration() < 0) {
// Sparse item: fetched by fast fetch
ExtractorHelper.getStreamInfo(
item.getServiceId(),
item.getUrl(),
false
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
final HistoryRecordManager recordManager =
new HistoryRecordManager(context);
recordManager.saveStreamState(result, 0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> ErrorUtil.showSnackbar(
context,
new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
item.getUrl(), item.getServiceId())))
.subscribe();
callback.accept(new SinglePlayQueue(result));
}, throwable -> ErrorUtil.createNotification(context,
new ErrorInfo(throwable, UserAction.REQUESTED_CHANNEL,
"Could not fetch missing stream info")));
} else {
callback.accept(new SinglePlayQueue(item));
}
}
}

View File

@@ -19,6 +19,8 @@
package org.schabi.newpipe.util;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import android.content.Context;
import android.util.Log;
import android.view.View;
@@ -30,8 +32,6 @@ import androidx.preference.PreferenceManager;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.util.external_communication.TextLinkifier;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
@@ -42,6 +42,7 @@ import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
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;
@@ -50,6 +51,7 @@ import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
import org.schabi.newpipe.util.external_communication.TextLinkifier;
import java.util.Collections;
import java.util.List;
@@ -58,8 +60,6 @@ import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
public final class ExtractorHelper {
private static final String TAG = ExtractorHelper.class.getSimpleName();
private static final InfoCache CACHE = InfoCache.getInstance();

View File

@@ -1,94 +0,0 @@
package org.schabi.newpipe.util;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import android.content.Context;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* Utility class for putting the uploader url into the database - when required.
*/
public final class SaveUploaderUrlHelper {
private SaveUploaderUrlHelper() {
}
// Public functions which call the function that does
// the actual work with the correct parameters
public static void saveUploaderUrlIfNeeded(@NonNull final Fragment fragment,
@NonNull final StreamInfoItem infoItem,
@NonNull final SaveUploaderUrlCallback callback) {
saveUploaderUrlIfNeeded(fragment.requireContext(),
infoItem.getServiceId(),
infoItem.getUrl(),
infoItem.getUploaderUrl(),
callback);
}
public static void saveUploaderUrlIfNeeded(@NonNull final Context context,
@NonNull final PlayQueueItem queueItem,
@NonNull final SaveUploaderUrlCallback callback) {
saveUploaderUrlIfNeeded(context,
queueItem.getServiceId(),
queueItem.getUrl(),
queueItem.getUploaderUrl(),
callback);
}
/**
* Fetches and saves the uploaderUrl if it is empty (meaning that it does
* not exist in the video item). The callback is called with either the
* fetched uploaderUrl, or the already saved uploaderUrl, but it is always
* called with a valid uploaderUrl that can be used to show channel details.
*
* @param context Context
* @param serviceId The serviceId of the item
* @param url The item url
* @param uploaderUrl The uploaderUrl of the item, if null or empty, it
* will be fetched using the item url.
* @param callback The callback that returns the fetched or existing
* uploaderUrl
*/
private static void saveUploaderUrlIfNeeded(@NonNull final Context context,
final int serviceId,
@NonNull final String url,
// Only used if not null or empty
@Nullable final String uploaderUrl,
@NonNull final SaveUploaderUrlCallback callback) {
if (isNullOrEmpty(uploaderUrl)) {
Toast.makeText(context, R.string.loading_channel_details,
Toast.LENGTH_SHORT).show();
ExtractorHelper.getStreamInfo(serviceId, url, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
NewPipeDatabase.getInstance(context).streamDAO()
.setUploaderUrl(serviceId, url, result.getUploaderUrl())
.subscribeOn(Schedulers.io()).subscribe();
callback.onCallback(result.getUploaderUrl());
}, throwable -> ErrorUtil.createNotification(context,
new ErrorInfo(throwable, UserAction.REQUESTED_CHANNEL,
"Could not load channel details")
));
} else {
callback.onCallback(uploaderUrl);
}
}
public interface SaveUploaderUrlCallback {
void onCallback(@NonNull String uploaderUrl);
}
}

View File

@@ -0,0 +1,126 @@
package org.schabi.newpipe.util;
import static org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM;
import static org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import android.content.Context;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import java.util.function.Consumer;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* Utility class for fetching additional data for stream items when needed.
*/
public final class SparseItemUtil {
private SparseItemUtil() {
}
/**
* Use this to certainly obtain an single play queue with all of the data filled in when the
* stream info item you are handling might be sparse, e.g. because it was fetched via a {@link
* org.schabi.newpipe.extractor.feed.FeedExtractor}. FeedExtractors provide a fast and
* lightweight method to fetch info, but the info might be incomplete (see
* {@link org.schabi.newpipe.local.feed.service.FeedLoadService} for more details).
*
* @param context the Android context
* @param item the item which is checked and eventually loaded completely
* @param callback the callback to call with the single play queue built from the original item
* if all info was available, otherwise from the fetched {@link
* org.schabi.newpipe.extractor.stream.StreamInfo}
*/
public static void fetchItemInfoIfSparse(@NonNull final Context context,
@NonNull final StreamInfoItem item,
@NonNull final Consumer<SinglePlayQueue> callback) {
if ((!(item.getStreamType() == LIVE_STREAM || item.getStreamType() == AUDIO_LIVE_STREAM)
&& item.getDuration() < 0) || isNullOrEmpty(item.getUploaderUrl())) {
fetchStreamInfoAndSaveToDatabase(context, item.getServiceId(), item.getUrl(),
streamInfo -> callback.accept(new SinglePlayQueue(streamInfo)));
} else {
// all info is already there, no need to fetch
callback.accept(new SinglePlayQueue(item));
}
}
/**
* Use this to certainly obtain an uploader url when the stream info item or play queue item you
* are handling might not have the uploader url (e.g. because it was fetched with {@link
* org.schabi.newpipe.extractor.feed.FeedExtractor}). A toast is shown if loading details is
* required.
*
* @param context the Android context
* @param serviceId the serviceId of the item
* @param url the item url
* @param uploaderUrl the uploaderUrl of the item; if null or empty will be fetched
* @param callback the callback called with either the original uploaderUrl, if it was a valid
* url, otherwise with the uploader url obtained by fetching the {@link
* org.schabi.newpipe.extractor.stream.StreamInfo} corresponding to the item
*/
public static void fetchUploaderUrlIfSparse(@NonNull final Context context,
final int serviceId,
@NonNull final String url,
@Nullable final String uploaderUrl,
@NonNull final Consumer<String> callback) {
if (isNullOrEmpty(uploaderUrl)) {
fetchStreamInfoAndSaveToDatabase(context, serviceId, url,
streamInfo -> callback.accept(streamInfo.getUploaderUrl()));
} else {
callback.accept(uploaderUrl);
}
}
/**
* Loads the stream info corresponding to the given data on an I/O thread, stores the result in
* the database and calls the callback on the main thread with the result. A toast will be shown
* to the user about loading stream details, so this needs to be called on the main thread.
*
* @param context the Android context
* @param serviceId the service id of the stream to load
* @param url the url of the stream to load
* @param callback the callback to call with the result
*/
private static void fetchStreamInfoAndSaveToDatabase(final Context context,
final int serviceId,
@NonNull final String url,
final Consumer<StreamInfo> callback) {
Toast.makeText(context, R.string.loading_stream_details, Toast.LENGTH_SHORT).show();
ExtractorHelper.getStreamInfo(serviceId, url, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
// save to database in the background (not on main thread)
Completable.fromAction(() -> NewPipeDatabase.getInstance(context)
.streamDAO().upsert(new StreamEntity(result)))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.doOnError(throwable ->
ErrorUtil.createNotification(context,
new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
"Saving stream info to database", result)))
.subscribe();
// call callback on main thread with the obtained result
callback.accept(result);
}, throwable -> ErrorUtil.createNotification(context,
new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
"Loading stream info: " + url, serviceId)
));
}
}