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..77f818b0080 100644
--- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
@@ -3,15 +3,28 @@ 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.player.mediasource.FailedMediaSource
+import org.schabi.newpipe.player.resolver.PlaybackResolver
import org.schabi.newpipe.util.ServiceHelper
@Parcelize
@@ -86,30 +99,57 @@ 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 {
- throwable is AccountTerminatedException -> R.string.account_terminated
- throwable is ContentNotAvailableException -> R.string.content_not_available
- throwable != null && throwable.isNetworkRelated -> R.string.network_error
- throwable is ContentNotSupportedException -> R.string.content_not_supported
- throwable is ExtractionException -> R.string.parsing_error
+ // player exceptions
+ // some may be IOException, so do these checks before isNetworkRelated!
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
+
+ // 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
+
+ // 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
+ // 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
+
+ // 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
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/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/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..f4251b4fed6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -866,4 +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\".