From 6a2dbc0e37f9b231e79d01b0c94b2a0eb58e811d Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 28 Jul 2025 23:54:47 +0200 Subject: [PATCH 1/3] Add more specific error messages and deduplicate their handling --- app/build.gradle | 2 +- .../org/schabi/newpipe/RouterActivity.java | 34 +++---------------- .../org/schabi/newpipe/error/ErrorInfo.kt | 34 +++++++++++++++++-- .../schabi/newpipe/error/ErrorPanelHelper.kt | 31 +---------------- .../org/schabi/newpipe/error/ErrorUtil.kt | 2 +- .../org/schabi/newpipe/error/UserAction.java | 3 +- .../util/text/InternalUrlsHandler.java | 23 +++++-------- app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 52 insertions(+), 79 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 54bd86a72c9..9baedc56878 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -214,7 +214,7 @@ dependencies { // the corresponding commit hash, since JitPack sometimes deletes artifacts. // If there’s already a git hash, just add more of it to the end (or remove a letter) // to cause jitpack to regenerate the artifact. - implementation 'com.github.TeamNewPipe:NewPipeExtractor:7adbc48a0aa872c016b8ec089e278d5e12772054' + implementation 'com.github.Stypox:NewPipeExtractor:1c04bd88c3f1e6b1e0c912814035745e61bb9aba' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 3294cae0b03..dac2d29a126 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -58,20 +58,13 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService.LinkType; import org.schabi.newpipe.extractor.channel.ChannelInfo; -import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException; 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.GeographicRestrictionException; -import org.schabi.newpipe.extractor.exceptions.PaidContentException; -import org.schabi.newpipe.extractor.exceptions.PrivateContentException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException; -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.stream.StreamInfo; -import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -279,28 +272,11 @@ private static void handleError(final Context context, final ErrorInfo errorInfo final Intent intent = new Intent(context, ReCaptchaActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); - } else if (errorInfo.getThrowable() != null - && ExceptionUtils.isNetworkRelated(errorInfo.getThrowable())) { - Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof AgeRestrictedContentException) { - Toast.makeText(context, R.string.restricted_video_no_stream, - Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof GeographicRestrictionException) { - Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof PaidContentException) { - Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof PrivateContentException) { - Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof SoundCloudGoPlusContentException) { - Toast.makeText(context, R.string.soundcloud_go_plus_content, - Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof YoutubeMusicPremiumContentException) { - Toast.makeText(context, R.string.youtube_music_premium_content, - Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof ContentNotAvailableException) { - Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); - } else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) { - Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof ContentNotAvailableException + || errorInfo.getThrowable() instanceof ContentNotSupportedException) { + // this exception does not usually indicate a problem that should be reported, + // so just show a toast instead of the notification + Toast.makeText(context, errorInfo.getMessageStringId(), Toast.LENGTH_LONG).show(); } else { ErrorUtil.createNotification(context, errorInfo); } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index 6d8c1bd638c..014be540f69 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -3,14 +3,25 @@ package org.schabi.newpipe.error import android.os.Parcelable import androidx.annotation.StringRes import com.google.android.exoplayer2.ExoPlaybackException +import com.google.android.exoplayer2.upstream.HttpDataSource +import com.google.android.exoplayer2.upstream.Loader import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Info import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException +import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException 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.GeographicRestrictionException +import org.schabi.newpipe.extractor.exceptions.PaidContentException +import org.schabi.newpipe.extractor.exceptions.PrivateContentException +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException +import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException +import org.schabi.newpipe.extractor.exceptions.UnsupportedContentInCountryException +import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException +import org.schabi.newpipe.extractor.exceptions.YoutubeSignInConfirmNotBotException import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.util.ServiceHelper @@ -91,11 +102,28 @@ class ErrorInfo( action: UserAction ): Int { return when { + // content not available exceptions throwable is AccountTerminatedException -> R.string.account_terminated + throwable is AgeRestrictedContentException -> R.string.restricted_video_no_stream + throwable is GeographicRestrictionException -> R.string.georestricted_content + throwable is PaidContentException -> R.string.paid_content + throwable is PrivateContentException -> R.string.private_content + throwable is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content + throwable is UnsupportedContentInCountryException -> R.string.unsupported_content_in_country + throwable is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content + throwable is YoutubeSignInConfirmNotBotException -> R.string.youtube_sign_in_confirm_not_bot_error throwable is ContentNotAvailableException -> R.string.content_not_available - throwable != null && throwable.isNetworkRelated -> R.string.network_error + + // ReCaptchas should have already been handled elsewhere, + // but return an error message here just in case + throwable is ReCaptchaException -> R.string.recaptcha_request_toast + + // other extractor exceptions throwable is ContentNotSupportedException -> R.string.content_not_supported + throwable != null && throwable.isNetworkRelated -> R.string.network_error throwable is ExtractionException -> R.string.parsing_error + + // ExoPlayer exceptions throwable is ExoPlaybackException -> { when (throwable.type) { ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure @@ -103,13 +131,15 @@ class ErrorInfo( else -> R.string.player_unrecoverable_failure } } + + // user actions (in case the exception is unrecognizable) action == UserAction.UI_ERROR -> R.string.app_ui_crash action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed action == UserAction.SUBSCRIPTION_UPDATE -> R.string.subscription_update_failed action == UserAction.LOAD_IMAGE -> R.string.could_not_load_thumbnails action == UserAction.DOWNLOAD_OPEN_DIALOG -> R.string.could_not_setup_download_menu - else -> R.string.general_error + else -> R.string.error_snackbar_message } } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt index 14ec4114836..66d4d9fae8b 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -15,19 +15,12 @@ import io.reactivex.rxjava3.disposables.Disposable import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException -import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException -import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException -import org.schabi.newpipe.extractor.exceptions.PaidContentException -import org.schabi.newpipe.extractor.exceptions.PrivateContentException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException -import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException -import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.ktx.isInterruptedCaused -import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.external_communication.ShareUtils import java.util.concurrent.TimeUnit @@ -127,7 +120,7 @@ class ErrorPanelHelper( ErrorUtil.openActivity(context, errorInfo) } - errorTextView.setText(getExceptionDescription(errorInfo.throwable)) + errorTextView.setText(errorInfo.messageStringId) if (errorInfo.throwable !is ContentNotAvailableException && errorInfo.throwable !is ContentNotSupportedException @@ -192,27 +185,5 @@ class ErrorPanelHelper( companion object { val TAG: String = ErrorPanelHelper::class.simpleName!! val DEBUG: Boolean = MainActivity.DEBUG - - @StringRes - fun getExceptionDescription(throwable: Throwable?): Int { - return when (throwable) { - is AgeRestrictedContentException -> R.string.restricted_video_no_stream - is GeographicRestrictionException -> R.string.georestricted_content - is PaidContentException -> R.string.paid_content - is PrivateContentException -> R.string.private_content - is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content - is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content - is ContentNotAvailableException -> R.string.content_not_available - is ContentNotSupportedException -> R.string.content_not_supported - else -> { - // show retry button only for content which is not unavailable or unsupported - if (throwable != null && throwable.isNetworkRelated) { - R.string.network_error - } else { - R.string.error_snackbar_message - } - } - } - } } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index 93dd8e522f3..cf191d12145 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -153,7 +153,7 @@ class ErrorUtil { // fallback to showing a notification if no root view is available createNotification(context, errorInfo) } else { - Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG) + Snackbar.make(rootView, errorInfo.messageStringId, Snackbar.LENGTH_LONG) .setActionTextColor(Color.YELLOW) .setAction(context.getString(R.string.error_snackbar_action).uppercase()) { openActivity(context, errorInfo) diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java index afb880a292a..997bff99629 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -33,7 +33,8 @@ public enum UserAction { SHARE_TO_NEWPIPE("share to newpipe"), CHECK_FOR_NEW_APP_VERSION("check for new app version"), OPEN_INFO_ITEM_DIALOG("open info item dialog"), - GETTING_MAIN_SCREEN_TAB("getting main screen tab"); + GETTING_MAIN_SCREEN_TAB("getting main screen tab"), + PLAY_ON_POPUP("play on popup"); private final String message; diff --git a/app/src/main/java/org/schabi/newpipe/util/text/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/text/InternalUrlsHandler.java index 066515d6b96..a2743141bd3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/text/InternalUrlsHandler.java +++ b/app/src/main/java/org/schabi/newpipe/util/text/InternalUrlsHandler.java @@ -1,14 +1,13 @@ package org.schabi.newpipe.util.text; import android.content.Context; -import android.util.Log; import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorPanelHelper; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; @@ -158,19 +157,13 @@ public static boolean playOnPopup(final Context context, disposables.add(single.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(info -> { - final PlayQueue playQueue = - new SinglePlayQueue(info, seconds * 1000L); + final PlayQueue playQueue = new SinglePlayQueue(info, seconds * 1000L); NavigationHelper.playOnPopupPlayer(context, playQueue, false); }, throwable -> { - if (DEBUG) { - Log.e(TAG, "Could not play on popup: " + url, throwable); - } - new AlertDialog.Builder(context) - .setTitle(R.string.player_stream_failure) - .setMessage( - ErrorPanelHelper.Companion.getExceptionDescription(throwable)) - .setPositiveButton(R.string.ok, null) - .show(); + 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: + // otherwise it will resort to showing a notification, so we are safe here. + ErrorUtil.showSnackbar(context, errorInfo); })); return true; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6ab2fc7a541..e0b09e2c76f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -866,4 +866,6 @@ The settings in the export being imported use a vulnerable format that was deprecated since NewPipe 0.27.0. Make sure the export being imported is from a trusted source, and prefer using only exports obtained from NewPipe 0.27.0 or newer in the future. Support for importing settings in this vulnerable format will soon be removed completely, and then old versions of NewPipe will not be able to import settings of exports from new versions anymore. SoundCloud Top 50 page removed SoundCloud has discontinued the original Top 50 charts. The corresponding tab has been removed from your main page. + YouTube refused to provide data, asking for a login.\n\nYour IP might have been temporarily banned by YouTube, you can wait some time or switch to a different IP (for example by turning on/off a VPN, or by switching from WiFi to mobile data). + This content is not available for the currently selected content country.\n\nChange your selection from \"Settings > Content > Default content country\". From 13d191747157a1a9e2b7f9731369e55fed42f20d Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 28 Jul 2025 23:57:55 +0200 Subject: [PATCH 2/3] Show better information about player errors --- .../org/schabi/newpipe/error/ErrorInfo.kt | 19 +++++++++++++------ .../MediaBrowserPlaybackPreparer.kt | 7 ++++--- app/src/main/res/values/strings.xml | 1 + 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index 014be540f69..f4af65bbcb1 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -23,6 +23,8 @@ import org.schabi.newpipe.extractor.exceptions.UnsupportedContentInCountryExcept import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException import org.schabi.newpipe.extractor.exceptions.YoutubeSignInConfirmNotBotException import org.schabi.newpipe.ktx.isNetworkRelated +import org.schabi.newpipe.player.mediasource.FailedMediaSource +import org.schabi.newpipe.player.resolver.PlaybackResolver import org.schabi.newpipe.util.ServiceHelper @Parcelize @@ -97,9 +99,9 @@ class ErrorInfo( if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId) @StringRes - private fun getMessageStringId( + fun getMessageStringId( throwable: Throwable?, - action: UserAction + action: UserAction? ): Int { return when { // content not available exceptions @@ -123,14 +125,19 @@ class ErrorInfo( throwable != null && throwable.isNetworkRelated -> R.string.network_error throwable is ExtractionException -> R.string.parsing_error - // ExoPlayer exceptions + // player exceptions throwable is ExoPlaybackException -> { - when (throwable.type) { - ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure - ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure + val cause = throwable.cause + when { + cause is HttpDataSource.InvalidResponseCodeException && cause.responseCode == 403 -> R.string.player_error_403 + cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException -> getMessageStringId(throwable, action) + throwable.type == ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure + throwable.type == ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure else -> R.string.player_unrecoverable_failure } } + throwable is FailedMediaSource.FailedMediaSourceException -> getMessageStringId(throwable.cause, action) + throwable is PlaybackResolver.ResolverException -> R.string.player_stream_failure // user actions (in case the exception is unrecognizable) action == UserAction.UI_ERROR -> R.string.app_ui_crash diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt index 2948eeaf8d1..7f0f9f8b6a3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt @@ -17,6 +17,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.MainActivity import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.R +import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.extractor.InfoItem.InfoType import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler @@ -84,7 +85,7 @@ class MediaBrowserPlaybackPreparer( }, { throwable -> Log.e(TAG, "Failed to start playback of media ID [$mediaId]", throwable) - onPrepareError() + onPrepareError(throwable) } ) } @@ -115,9 +116,9 @@ class MediaBrowserPlaybackPreparer( ) } - private fun onPrepareError() { + private fun onPrepareError(throwable: Throwable) { setMediaSessionError.accept( - ContextCompat.getString(context, R.string.error_snackbar_message), + ContextCompat.getString(context, ErrorInfo.getMessageStringId(throwable, null)), PlaybackStateCompat.ERROR_CODE_APP_ERROR ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0b09e2c76f..f4251b4fed6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -866,6 +866,7 @@ The settings in the export being imported use a vulnerable format that was deprecated since NewPipe 0.27.0. Make sure the export being imported is from a trusted source, and prefer using only exports obtained from NewPipe 0.27.0 or newer in the future. Support for importing settings in this vulnerable format will soon be removed completely, and then old versions of NewPipe will not be able to import settings of exports from new versions anymore. SoundCloud Top 50 page removed SoundCloud has discontinued the original Top 50 charts. The corresponding tab has been removed from your main page. + HTTP error 403 occurred while playing, likely caused by an IP ban or streaming URL deobfuscation issues YouTube refused to provide data, asking for a login.\n\nYour IP might have been temporarily banned by YouTube, you can wait some time or switch to a different IP (for example by turning on/off a VPN, or by switching from WiFi to mobile data). This content is not available for the currently selected content country.\n\nChange your selection from \"Settings > Content > Default content country\". From 4cb62bfbeb83a29bdd68e7cd4285189a8bd85c5e Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 29 Jul 2025 00:31:54 +0200 Subject: [PATCH 3/3] Fix ordering of error messages conditions --- .../org/schabi/newpipe/error/ErrorInfo.kt | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index f4af65bbcb1..77f818b0080 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -104,6 +104,21 @@ class ErrorInfo( action: UserAction? ): Int { return when { + // player exceptions + // some may be IOException, so do these checks before isNetworkRelated! + throwable is ExoPlaybackException -> { + val cause = throwable.cause + when { + cause is HttpDataSource.InvalidResponseCodeException && cause.responseCode == 403 -> R.string.player_error_403 + cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException -> getMessageStringId(throwable, action) + throwable.type == ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure + throwable.type == ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure + else -> R.string.player_unrecoverable_failure + } + } + throwable is FailedMediaSource.FailedMediaSourceException -> getMessageStringId(throwable.cause, action) + throwable is PlaybackResolver.ResolverException -> R.string.player_stream_failure + // content not available exceptions throwable is AccountTerminatedException -> R.string.account_terminated throwable is AgeRestrictedContentException -> R.string.restricted_video_no_stream @@ -116,30 +131,18 @@ class ErrorInfo( throwable is YoutubeSignInConfirmNotBotException -> R.string.youtube_sign_in_confirm_not_bot_error throwable is ContentNotAvailableException -> R.string.content_not_available + // other extractor exceptions + throwable is ContentNotSupportedException -> R.string.content_not_supported // ReCaptchas should have already been handled elsewhere, // but return an error message here just in case throwable is ReCaptchaException -> R.string.recaptcha_request_toast - - // other extractor exceptions - throwable is ContentNotSupportedException -> R.string.content_not_supported + // test this at the end as many exceptions could be a subclass of IOException throwable != null && throwable.isNetworkRelated -> R.string.network_error + // an extraction exception unrelated to the network + // is likely an issue with parsing the website throwable is ExtractionException -> R.string.parsing_error - // player exceptions - throwable is ExoPlaybackException -> { - val cause = throwable.cause - when { - cause is HttpDataSource.InvalidResponseCodeException && cause.responseCode == 403 -> R.string.player_error_403 - cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException -> getMessageStringId(throwable, action) - throwable.type == ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure - throwable.type == ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure - else -> R.string.player_unrecoverable_failure - } - } - throwable is FailedMediaSource.FailedMediaSourceException -> getMessageStringId(throwable.cause, action) - throwable is PlaybackResolver.ResolverException -> R.string.player_stream_failure - - // user actions (in case the exception is unrecognizable) + // user actions (in case the exception is null or unrecognizable) action == UserAction.UI_ERROR -> R.string.app_ui_crash action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed