1
0
Fork 0
mirror of https://github.com/TeamNewPipe/NewPipe.git synced 2025-10-03 17:59:41 +02:00

Merge branch 'dev' into refactor

This commit is contained in:
Stypox 2025-09-05 13:34:53 +02:00
commit 2ee7cc4344
No known key found for this signature in database
GPG key ID: 4BDF1B40A49FDD23
14 changed files with 177 additions and 133 deletions

View file

@ -58,10 +58,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.StreamingService.LinkType; import org.schabi.newpipe.extractor.StreamingService.LinkType;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
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;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
@ -253,7 +250,8 @@ public class RouterActivity extends AppCompatActivity {
showUnsupportedUrlDialog(url); showUnsupportedUrlDialog(url);
} }
}, throwable -> handleError(this, new ErrorInfo(throwable, }, throwable -> handleError(this, new ErrorInfo(throwable,
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url)))); UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url,
null, url))));
} }
/** /**
@ -262,23 +260,19 @@ public class RouterActivity extends AppCompatActivity {
* @param errorInfo the error information * @param errorInfo the error information
*/ */
private static void handleError(final Context context, final ErrorInfo errorInfo) { private static void handleError(final Context context, final ErrorInfo errorInfo) {
if (errorInfo.getThrowable() != null) { if (errorInfo.getRecaptchaUrl() != null) {
errorInfo.getThrowable().printStackTrace();
}
if (errorInfo.getThrowable() instanceof ReCaptchaException) {
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity // Starting ReCaptcha Challenge Activity
final Intent intent = new Intent(context, ReCaptchaActivity.class); final Intent intent = new Intent(context, ReCaptchaActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, errorInfo.getRecaptchaUrl());
context.startActivity(intent); context.startActivity(intent);
} else if (errorInfo.getThrowable() instanceof ContentNotAvailableException } else if (errorInfo.isReportable()) {
|| errorInfo.getThrowable() instanceof ContentNotSupportedException) { ErrorUtil.createNotification(context, errorInfo);
} else {
// this exception does not usually indicate a problem that should be reported, // this exception does not usually indicate a problem that should be reported,
// so just show a toast instead of the notification // so just show a toast instead of the notification
Toast.makeText(context, errorInfo.getMessage(context), Toast.LENGTH_LONG).show(); Toast.makeText(context, errorInfo.getMessage(context), Toast.LENGTH_LONG).show();
} else {
ErrorUtil.createNotification(context, errorInfo);
} }
if (context instanceof RouterActivity) { if (context instanceof RouterActivity) {
@ -641,7 +635,8 @@ public class RouterActivity extends AppCompatActivity {
startActivity(intent); startActivity(intent);
finish(); finish();
}, throwable -> handleError(this, new ErrorInfo(throwable, }, throwable -> handleError(this, new ErrorInfo(throwable,
UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl))) UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl,
null, currentUrl)))
); );
return; return;
} }
@ -828,10 +823,10 @@ public class RouterActivity extends AppCompatActivity {
}) })
)), )),
throwable -> runOnVisible(ctx -> handleError(ctx, new ErrorInfo( throwable -> runOnVisible(ctx -> handleError(ctx, new ErrorInfo(
throwable, throwable, UserAction.REQUESTED_STREAM,
UserAction.REQUESTED_STREAM,
"Tried to add " + currentUrl + " to a playlist", "Tried to add " + currentUrl + " to a playlist",
((RouterActivity) ctx).currentService.getServiceId()) ((RouterActivity) ctx).currentService.getServiceId(),
currentUrl)
)) ))
) )
); );
@ -971,7 +966,7 @@ public class RouterActivity extends AppCompatActivity {
} }
}, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction, }, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction,
choice.url + " opened with " + choice.playerChoice, choice.url + " opened with " + choice.playerChoice,
choice.serviceId))); choice.serviceId, choice.url)));
} }
} }

View file

@ -389,8 +389,7 @@ public class DownloadDialog extends DialogFragment
} }
}, throwable -> ErrorUtil.showSnackbar(context, }, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG, new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading video stream size", "Downloading video stream size", currentInfo))));
currentInfo.getServiceId()))));
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(getWrappedAudioStreams()) disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(getWrappedAudioStreams())
.subscribe(result -> { .subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
@ -399,8 +398,7 @@ public class DownloadDialog extends DialogFragment
} }
}, throwable -> ErrorUtil.showSnackbar(context, }, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG, new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading audio stream size", "Downloading audio stream size", currentInfo))));
currentInfo.getServiceId()))));
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(wrappedSubtitleStreams) disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(wrappedSubtitleStreams)
.subscribe(result -> { .subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
@ -409,8 +407,7 @@ public class DownloadDialog extends DialogFragment
} }
}, throwable -> ErrorUtil.showSnackbar(context, }, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG, new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading subtitle stream size", "Downloading subtitle stream size", currentInfo))));
currentInfo.getServiceId()))));
} }
private void setupAudioTrackSpinner() { private void setupAudioTrackSpinner() {

View file

@ -36,8 +36,8 @@ public class AcraReportSender implements ReportSender {
ErrorUtil.openActivity(context, new ErrorInfo( ErrorUtil.openActivity(context, new ErrorInfo(
new String[]{report.getString(ReportField.STACK_TRACE)}, new String[]{report.getString(ReportField.STACK_TRACE)},
UserAction.UI_ERROR, UserAction.UI_ERROR,
null,
"ACRA report", "ACRA report",
null,
R.string.app_ui_crash)); R.string.app_ui_crash));
} }
} }

View file

@ -7,7 +7,6 @@ import androidx.core.content.ContextCompat
import com.google.android.exoplayer2.ExoPlaybackException import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.upstream.HttpDataSource import com.google.android.exoplayer2.upstream.HttpDataSource
import com.google.android.exoplayer2.upstream.Loader import com.google.android.exoplayer2.upstream.Loader
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.Info import org.schabi.newpipe.extractor.Info
@ -29,70 +28,108 @@ import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentExcepti
import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.ktx.isNetworkRelated
import org.schabi.newpipe.player.mediasource.FailedMediaSource import org.schabi.newpipe.player.mediasource.FailedMediaSource
import org.schabi.newpipe.player.resolver.PlaybackResolver import org.schabi.newpipe.player.resolver.PlaybackResolver
import java.net.UnknownHostException
/**
* An error has occurred in the app. This class contains plain old parcelable data that can be used
* to report the error and to show it to the user along with correct action buttons.
*/
@Parcelize @Parcelize
class ErrorInfo private constructor( class ErrorInfo private constructor(
val stackTraces: Array<String>, val stackTraces: Array<String>,
val userAction: UserAction, val userAction: UserAction,
val serviceId: Int?,
val request: String, val request: String,
val serviceId: Int?,
private val message: ErrorMessage, private val message: ErrorMessage,
/**
* If `true`, a report button will be shown for this error. Otherwise the error is not something
* that can really be reported (e.g. a network issue, or content not being available at all).
*/
val isReportable: Boolean,
/**
* If `true`, the process causing this error can be retried, otherwise not.
*/
val isRetryable: Boolean,
/**
* If present, indicates that the exception was a ReCaptchaException, and this is the URL
* provided by the service that can be used to solve the ReCaptcha challenge.
*/
val recaptchaUrl: String?,
/**
* If present, this resource can alternatively be opened in browser (useful if NewPipe is
* badly broken).
*/
val openInBrowserUrl: String?,
) : Parcelable { ) : Parcelable {
// no need to store throwable, all data for report is in other variables @JvmOverloads
// also, the throwable might not be serializable, see TeamNewPipe/NewPipe#7302 constructor(
@IgnoredOnParcel
var throwable: Throwable? = null
private constructor(
throwable: Throwable, throwable: Throwable,
userAction: UserAction, userAction: UserAction,
serviceId: Int?, request: String,
request: String serviceId: Int? = null,
openInBrowserUrl: String? = null,
) : this( ) : this(
throwableToStringList(throwable), throwableToStringList(throwable),
userAction, userAction,
serviceId,
request, request,
getMessage(throwable, userAction, serviceId) serviceId,
) { getMessage(throwable, userAction, serviceId),
this.throwable = throwable isReportable(throwable),
} isRetryable(throwable),
(throwable as? ReCaptchaException)?.url,
openInBrowserUrl,
)
private constructor( @JvmOverloads
throwable: List<Throwable>, constructor(
throwables: List<Throwable>,
userAction: UserAction, userAction: UserAction,
serviceId: Int?, request: String,
request: String serviceId: Int? = null,
openInBrowserUrl: String? = null,
) : this( ) : this(
throwableListToStringList(throwable), throwableListToStringList(throwables),
userAction, userAction,
serviceId,
request, request,
getMessage(throwable.firstOrNull(), userAction, serviceId) serviceId,
) { getMessage(throwables.firstOrNull(), userAction, serviceId),
this.throwable = throwable.firstOrNull() throwables.any(::isReportable),
} throwables.isEmpty() || throwables.any(::isRetryable),
throwables.firstNotNullOfOrNull { it as? ReCaptchaException }?.url,
openInBrowserUrl,
)
// constructor to manually build ErrorInfo // constructor to manually build ErrorInfo when no throwable is available
constructor(stackTraces: Array<String>, userAction: UserAction, serviceId: Int?, request: String, @StringRes message: Int) : constructor(
this(stackTraces, userAction, serviceId, request, ErrorMessage(message)) stackTraces: Array<String>,
userAction: UserAction,
request: String,
serviceId: Int?,
@StringRes message: Int
) :
this(
stackTraces, userAction, request, serviceId, ErrorMessage(message),
true, false, null, null
)
// constructors with single throwable // constructor with only one throwable to extract service id and openInBrowserUrl from an Info
constructor(throwable: Throwable, userAction: UserAction, request: String) : constructor(
this(throwable, userAction, null, request) throwable: Throwable,
constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) : userAction: UserAction,
this(throwable, userAction, serviceId, request) request: String,
constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) : info: Info?,
this(throwable, userAction, info?.serviceId, request) ) :
this(throwable, userAction, request, info?.serviceId, info?.url)
// constructors with list of throwables // constructor with multiple throwables to extract service id and openInBrowserUrl from an Info
constructor(throwable: List<Throwable>, userAction: UserAction, request: String) : constructor(
this(throwable, userAction, null, request) throwables: List<Throwable>,
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, serviceId: Int) : userAction: UserAction,
this(throwable, userAction, serviceId, request) request: String,
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, info: Info?) : info: Info?,
this(throwable, userAction, info?.serviceId, request) ) :
this(throwables, userAction, request, info?.serviceId, info?.url)
fun getServiceName(): String { fun getServiceName(): String {
return getServiceName(serviceId) return getServiceName(serviceId)
@ -205,8 +242,7 @@ class ErrorInfo private constructor(
// other extractor exceptions // other extractor exceptions
throwable is ContentNotSupportedException -> throwable is ContentNotSupportedException ->
ErrorMessage(R.string.content_not_supported) ErrorMessage(R.string.content_not_supported)
// ReCaptchas should have already been handled elsewhere, // ReCaptchas will be handled in a special way anyway
// but return an error message here just in case
throwable is ReCaptchaException -> throwable is ReCaptchaException ->
ErrorMessage(R.string.recaptcha_request_toast) ErrorMessage(R.string.recaptcha_request_toast)
// test this at the end as many exceptions could be a subclass of IOException // test this at the end as many exceptions could be a subclass of IOException
@ -234,5 +270,36 @@ class ErrorInfo private constructor(
ErrorMessage(R.string.error_snackbar_message) ErrorMessage(R.string.error_snackbar_message)
} }
} }
fun isReportable(throwable: Throwable?): Boolean {
return when (throwable) {
// we don't have an exception, so this is a manually built error, which likely
// indicates that it's important and is thus reportable
null -> true
// the service explicitly said that content is not available (e.g. age restrictions,
// video deleted, etc.), there is no use in letting users report it
is ContentNotAvailableException -> false
// we know the content is not supported, no need to let the user report it
is ContentNotSupportedException -> false
// happens often when there is no internet connection; we don't use
// `throwable.isNetworkRelated` since any `IOException` would make that function
// return true, but not all `IOException`s are network related
is UnknownHostException -> false
// by default, this is an unexpected exception, which the user could report
else -> true
}
}
fun isRetryable(throwable: Throwable?): Boolean {
return when (throwable) {
// we know the content is not available, retrying won't help
is ContentNotAvailableException -> false
// we know the content is not supported, retrying won't help
is ContentNotSupportedException -> false
// by default (including if throwable is null), enable retrying (though the retry
// button will be shown only if a way to perform the retry is implemented)
else -> true
}
}
} }
} }

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe.error
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
@ -14,11 +13,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import org.schabi.newpipe.MainActivity import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.ktx.isInterruptedCaused
import org.schabi.newpipe.util.external_communication.ShareUtils import org.schabi.newpipe.util.external_communication.ShareUtils
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -68,50 +63,32 @@ class ErrorPanelHelper(
} }
fun showError(errorInfo: ErrorInfo) { fun showError(errorInfo: ErrorInfo) {
if (errorInfo.throwable != null && errorInfo.throwable!!.isInterruptedCaused) {
if (DEBUG) {
Log.w(TAG, "onError() isInterruptedCaused! = [$errorInfo.throwable]")
}
return
}
ensureDefaultVisibility() ensureDefaultVisibility()
errorTextView.text = errorInfo.getMessage(context)
if (errorInfo.throwable is ReCaptchaException) { if (errorInfo.recaptchaUrl != null) {
errorTextView.setText(R.string.recaptcha_request_toast) showAndSetErrorButtonAction(R.string.recaptcha_solve) {
showAndSetErrorButtonAction(
R.string.recaptcha_solve
) {
// Starting ReCaptcha Challenge Activity // Starting ReCaptcha Challenge Activity
val intent = Intent(context, ReCaptchaActivity::class.java) val intent = Intent(context, ReCaptchaActivity::class.java)
intent.putExtra( intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, errorInfo.recaptchaUrl)
ReCaptchaActivity.RECAPTCHA_URL_EXTRA,
(errorInfo.throwable as ReCaptchaException).url
)
fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST) fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST)
errorActionButton.setOnClickListener(null) errorActionButton.setOnClickListener(null)
} }
} else if (errorInfo.isReportable) {
errorRetryButton.isVisible = retryShouldBeShown showAndSetErrorButtonAction(R.string.error_snackbar_action) {
showAndSetOpenInBrowserButtonAction(errorInfo)
} else {
showAndSetErrorButtonAction(
R.string.error_snackbar_action
) {
ErrorUtil.openActivity(context, errorInfo) ErrorUtil.openActivity(context, errorInfo)
} }
}
errorTextView.text = errorInfo.getMessage(context) if (errorInfo.isRetryable) {
errorRetryButton.isVisible = retryShouldBeShown
}
if (errorInfo.throwable !is ContentNotAvailableException && if (errorInfo.openInBrowserUrl != null) {
errorInfo.throwable !is ContentNotSupportedException errorOpenInBrowserButton.isVisible = true
) { errorOpenInBrowserButton.setOnClickListener {
// show retry button only for content which is not unavailable or unsupported ShareUtils.openUrlInBrowser(context, errorInfo.openInBrowserUrl)
errorRetryButton.isVisible = retryShouldBeShown
} }
showAndSetOpenInBrowserButtonAction(errorInfo)
} }
setRootVisible() setRootVisible()
@ -129,15 +106,6 @@ class ErrorPanelHelper(
errorActionButton.setOnClickListener(listener) errorActionButton.setOnClickListener(listener)
} }
fun showAndSetOpenInBrowserButtonAction(
errorInfo: ErrorInfo
) {
errorOpenInBrowserButton.isVisible = true
errorOpenInBrowserButton.setOnClickListener {
ShareUtils.openUrlInBrowser(context, errorInfo.request)
}
}
fun showTextError(errorString: String) { fun showTextError(errorString: String) {
ensureDefaultVisibility() ensureDefaultVisibility()

View file

@ -771,7 +771,7 @@ class VideoDetailFragment :
}, },
{ throwable -> { throwable ->
showError( showError(
ErrorInfo(throwable, UserAction.REQUESTED_STREAM, url ?: "no url", serviceId) ErrorInfo(throwable, UserAction.REQUESTED_STREAM, url ?: "no url", serviceId, url)
) )
} }
) )
@ -1460,7 +1460,7 @@ class VideoDetailFragment :
if (!info.errors.isEmpty()) { if (!info.errors.isEmpty()) {
showSnackBarError( showSnackBarError(
ErrorInfo(info.errors, UserAction.REQUESTED_STREAM, info.url, info) ErrorInfo(info.errors, UserAction.REQUESTED_STREAM, "Some info not extracted: " + info.url, info)
) )
} }
} }

View file

@ -153,7 +153,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
handleResult(result); handleResult(result);
}, throwable -> }, throwable ->
showError(new ErrorInfo(throwable, errorUserAction, showError(new ErrorInfo(throwable, errorUserAction,
"Start loading: " + url, serviceId))); "Start loading: " + url, serviceId, url)));
} }
/** /**
@ -184,7 +184,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
handleNextItems(infoItemsPage); handleNextItems(infoItemsPage);
}, (@NonNull Throwable throwable) -> }, (@NonNull Throwable throwable) ->
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(throwable, dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(throwable,
errorUserAction, "Loading more items: " + url, serviceId))); errorUserAction, "Loading more items: " + url, serviceId, url)));
} }
private void forbidDownwardFocusScroll() { private void forbidDownwardFocusScroll() {
@ -210,7 +210,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), errorUserAction, dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), errorUserAction,
"Get next items of: " + url, serviceId)); "Get next items of: " + url, serviceId, url));
} }
} }
@ -250,7 +250,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(),
errorUserAction, "Start loading: " + url, serviceId)); errorUserAction, "Start loading: " + url, serviceId, url));
} }
} }
} }

View file

@ -583,7 +583,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
isLoading.set(false); isLoading.set(false);
handleResult(result); handleResult(result);
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_CHANNEL, }, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_CHANNEL,
url == null ? "No URL" : url, serviceId))); url == null ? "No URL" : url, serviceId, url)));
} }
@Override @Override

View file

@ -54,6 +54,7 @@ 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.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo; import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory; import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
@ -940,7 +941,21 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
showEmptyState(); showEmptyState();
} else { } else {
showError(new ErrorInfo(exception, UserAction.SEARCHED, searchString, serviceId)); showError(new ErrorInfo(exception, UserAction.SEARCHED, searchString, serviceId,
getOpenInBrowserUrlForErrors()));
}
}
@Nullable
private String getOpenInBrowserUrlForErrors() {
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
return service.getSearchQHFactory().getUrl(searchString,
Arrays.asList(contentFilter), sortFilter);
} catch (final NullPointerException | ParsingException ignored) {
return null;
} }
} }
@ -1028,7 +1043,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
&& !(exceptions.size() == 1 && !(exceptions.size() == 1
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) { && exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED, showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
searchString, serviceId)); searchString, serviceId, getOpenInBrowserUrlForErrors()));
} }
searchSuggestion = result.getSearchSuggestion(); searchSuggestion = result.getSearchSuggestion();
@ -1101,13 +1116,14 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// whose results are handled here, but let's check it anyway // whose results are handled here, but let's check it anyway
if (nextPage == null) { if (nextPage == null) {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED, showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
"\"" + searchString + "\" → nextPage == null", serviceId)); "\"" + searchString + "\" → nextPage == null", serviceId,
getOpenInBrowserUrlForErrors()));
} else { } else {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED, showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", " "\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
+ "pageIds: " + nextPage.getIds() + ", " + "pageIds: " + nextPage.getIds() + ", "
+ "pageCookies: " + nextPage.getCookies(), + "pageCookies: " + nextPage.getCookies(),
serviceId)); serviceId, getOpenInBrowserUrlForErrors()));
} }
} }

View file

@ -85,8 +85,8 @@ public class SubscriptionsImportFragment extends BaseFragment {
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) { if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
ErrorUtil.showSnackbar(activity, ErrorUtil.showSnackbar(activity,
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT, new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
currentServiceId,
"Service does not support importing subscriptions", "Service does not support importing subscriptions",
currentServiceId,
R.string.general_error)); R.string.general_error));
activity.finish(); activity.finish();
} }

View file

@ -1283,7 +1283,8 @@ public final class Player implements PlaybackListener, Listener {
UserAction.PLAY_STREAM, UserAction.PLAY_STREAM,
"Loading failed for [" + currentMetadata.getTitle() "Loading failed for [" + currentMetadata.getTitle()
+ "]: " + currentMetadata.getStreamUrl(), + "]: " + currentMetadata.getStreamUrl(),
currentMetadata.getServiceId()); currentMetadata.getServiceId(),
currentMetadata.getStreamUrl());
ErrorUtil.createNotification(context, errorInfo); ErrorUtil.createNotification(context, errorInfo);
} }
@ -1499,7 +1500,7 @@ public final class Player implements PlaybackListener, Listener {
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM, errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
"Player error[type=" + error.getErrorCodeName() "Player error[type=" + error.getErrorCodeName()
+ "] occurred while playing " + currentMetadata.getStreamUrl(), + "] occurred while playing " + currentMetadata.getStreamUrl(),
currentMetadata.getServiceId()); currentMetadata.getServiceId(), currentMetadata.getStreamUrl());
} }
ErrorUtil.createNotification(context, errorInfo); ErrorUtil.createNotification(context, errorInfo);
} }

View file

@ -121,7 +121,7 @@ public final class SparseItemUtil {
callback.accept(result); callback.accept(result);
}, throwable -> ErrorUtil.createNotification(context, }, throwable -> ErrorUtil.createNotification(context,
new ErrorInfo(throwable, UserAction.REQUESTED_STREAM, new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
"Loading stream info: " + url, serviceId) "Loading stream info: " + url, serviceId, url)
)); ));
} }
} }

View file

@ -160,10 +160,10 @@ public final class InternalUrlsHandler {
final PlayQueue playQueue = new SinglePlayQueue(info, seconds * 1000L); final PlayQueue playQueue = new SinglePlayQueue(info, seconds * 1000L);
NavigationHelper.playOnPopupPlayer(context, playQueue, false); NavigationHelper.playOnPopupPlayer(context, playQueue, false);
}, throwable -> { }, throwable -> {
final var errorInfo = new ErrorInfo(throwable, UserAction.PLAY_ON_POPUP, url);
// This will only show a snackbar if the passed context has a root view: // This will only show a snackbar if the passed context has a root view:
// otherwise it will resort to showing a notification, so we are safe here. // otherwise it will resort to showing a notification, so we are safe here.
ErrorUtil.showSnackbar(context, errorInfo); ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.PLAY_ON_POPUP, url, null, url));
})); }));
return true; return true;
} }

View file

@ -572,7 +572,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
ErrorUtil.createNotification(mContext, ErrorUtil.createNotification(mContext,
new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action, new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action,
service, request.toString(), reason)); request.toString(), service, reason));
} }
public void clearFinishedDownloads(boolean delete) { public void clearFinishedDownloads(boolean delete) {