11package org.schabi.newpipe.error
22
3+ import android.content.Context
34import android.os.Parcelable
45import androidx.annotation.StringRes
6+ import androidx.core.content.ContextCompat
57import com.google.android.exoplayer2.ExoPlaybackException
68import com.google.android.exoplayer2.upstream.HttpDataSource
79import com.google.android.exoplayer2.upstream.Loader
810import kotlinx.parcelize.IgnoredOnParcel
911import kotlinx.parcelize.Parcelize
1012import org.schabi.newpipe.R
1113import org.schabi.newpipe.extractor.Info
14+ import org.schabi.newpipe.extractor.ServiceList
15+ import org.schabi.newpipe.extractor.ServiceList.YouTube
1216import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
1317import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException
1418import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
@@ -18,22 +22,21 @@ import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException
1822import org.schabi.newpipe.extractor.exceptions.PaidContentException
1923import org.schabi.newpipe.extractor.exceptions.PrivateContentException
2024import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
25+ import org.schabi.newpipe.extractor.exceptions.SignInConfirmNotBotException
2126import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException
2227import org.schabi.newpipe.extractor.exceptions.UnsupportedContentInCountryException
2328import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException
24- import org.schabi.newpipe.extractor.exceptions.YoutubeSignInConfirmNotBotException
2529import org.schabi.newpipe.ktx.isNetworkRelated
2630import org.schabi.newpipe.player.mediasource.FailedMediaSource
2731import org.schabi.newpipe.player.resolver.PlaybackResolver
28- import org.schabi.newpipe.util.ServiceHelper
2932
3033@Parcelize
31- class ErrorInfo (
34+ class ErrorInfo private constructor (
3235 val stackTraces : Array <String >,
3336 val userAction : UserAction ,
34- val serviceName : String ,
37+ val serviceId : Int? ,
3538 val request : String ,
36- val messageStringId : Int
39+ private val message : ErrorMessage ,
3740) : Parcelable {
3841
3942 // no need to store throwable, all data for report is in other variables
@@ -44,112 +47,191 @@ class ErrorInfo(
4447 private constructor (
4548 throwable: Throwable ,
4649 userAction: UserAction ,
47- serviceName : String ,
50+ serviceId : Int? ,
4851 request: String
4952 ) : this (
5053 throwableToStringList(throwable),
5154 userAction,
52- serviceName ,
55+ serviceId ,
5356 request,
54- getMessageStringId (throwable, userAction)
57+ getMessage (throwable, userAction, serviceId )
5558 ) {
5659 this .throwable = throwable
5760 }
5861
5962 private constructor (
6063 throwable: List <Throwable >,
6164 userAction: UserAction ,
62- serviceName : String ,
65+ serviceId : Int? ,
6366 request: String
6467 ) : this (
6568 throwableListToStringList(throwable),
6669 userAction,
67- serviceName ,
70+ serviceId ,
6871 request,
69- getMessageStringId (throwable.firstOrNull(), userAction)
72+ getMessage (throwable.firstOrNull(), userAction, serviceId )
7073 ) {
7174 this .throwable = throwable.firstOrNull()
7275 }
7376
77+ // constructor to manually build ErrorInfo
78+ constructor (stackTraces: Array <String >, userAction: UserAction , serviceId: Int? , request: String , @StringRes message: Int ) :
79+ this (stackTraces, userAction, serviceId, request, ErrorMessage (message))
80+
7481 // constructors with single throwable
7582 constructor (throwable: Throwable , userAction: UserAction , request: String ) :
76- this (throwable, userAction, SERVICE_NONE , request)
83+ this (throwable, userAction, null , request)
7784 constructor (throwable: Throwable , userAction: UserAction , request: String , serviceId: Int ) :
78- this (throwable, userAction, ServiceHelper .getNameOfServiceById( serviceId) , request)
85+ this (throwable, userAction, serviceId, request)
7986 constructor (throwable: Throwable , userAction: UserAction , request: String , info: Info ? ) :
80- this (throwable, userAction, getInfoServiceName( info) , request)
87+ this (throwable, userAction, info?.serviceId , request)
8188
8289 // constructors with list of throwables
8390 constructor (throwable: List <Throwable >, userAction: UserAction , request: String ) :
84- this (throwable, userAction, SERVICE_NONE , request)
91+ this (throwable, userAction, null , request)
8592 constructor (throwable: List <Throwable >, userAction: UserAction , request: String , serviceId: Int ) :
86- this (throwable, userAction, ServiceHelper .getNameOfServiceById( serviceId) , request)
93+ this (throwable, userAction, serviceId, request)
8794 constructor (throwable: List <Throwable >, userAction: UserAction , request: String , info: Info ? ) :
88- this (throwable, userAction, getInfoServiceName(info), request)
95+ this (throwable, userAction, info?.serviceId, request)
96+
97+ fun getServiceName (): String {
98+ return getServiceName(serviceId)
99+ }
100+
101+ fun getMessage (context : Context ): String {
102+ return message.getString(context)
103+ }
89104
90105 companion object {
91- const val SERVICE_NONE = " none"
106+ @Parcelize
107+ class ErrorMessage (
108+ @StringRes
109+ private val stringRes : Int ,
110+ private vararg val formatArgs : String ,
111+ ) : Parcelable {
112+ fun getString (context : Context ): String {
113+ return if (formatArgs.isEmpty()) {
114+ // use ContextCompat.getString() just in case context is not AppCompatActivity
115+ ContextCompat .getString(context, stringRes)
116+ } else {
117+ // ContextCompat.getString() with formatArgs does not exist, so we just
118+ // replicate its source code but with formatArgs
119+ ContextCompat .getContextForLanguage(context).getString(stringRes, * formatArgs)
120+ }
121+ }
122+ }
123+
124+ const val SERVICE_NONE = " <unknown_service>"
125+
126+ private fun getServiceName (serviceId : Int? ) =
127+ // not using getNameOfServiceById since we want to accept a nullable serviceId and we
128+ // want to default to SERVICE_NONE
129+ ServiceList .all()?.firstOrNull { it.serviceId == serviceId }?.serviceInfo?.name
130+ ? : SERVICE_NONE
92131
93132 fun throwableToStringList (throwable : Throwable ) = arrayOf(throwable.stackTraceToString())
94133
95134 fun throwableListToStringList (throwableList : List <Throwable >) =
96135 throwableList.map { it.stackTraceToString() }.toTypedArray()
97136
98- private fun getInfoServiceName (info : Info ? ) =
99- if (info == null ) SERVICE_NONE else ServiceHelper .getNameOfServiceById(info.serviceId)
100-
101- @StringRes
102- fun getMessageStringId (
137+ fun getMessage (
103138 throwable : Throwable ? ,
104- action : UserAction ?
105- ): Int {
139+ action : UserAction ? ,
140+ serviceId : Int? ,
141+ ): ErrorMessage {
106142 return when {
107143 // player exceptions
108144 // some may be IOException, so do these checks before isNetworkRelated!
109145 throwable is ExoPlaybackException -> {
110146 val cause = throwable.cause
111147 when {
112- cause is HttpDataSource .InvalidResponseCodeException && cause.responseCode == 403 -> R .string.player_error_403
113- cause is Loader .UnexpectedLoaderException && cause.cause is ExtractionException -> getMessageStringId(throwable, action)
114- throwable.type == ExoPlaybackException .TYPE_SOURCE -> R .string.player_stream_failure
115- throwable.type == ExoPlaybackException .TYPE_UNEXPECTED -> R .string.player_recoverable_failure
116- else -> R .string.player_unrecoverable_failure
148+ cause is HttpDataSource .InvalidResponseCodeException -> {
149+ if (cause.responseCode == 403 ) {
150+ if (serviceId == YouTube .serviceId) {
151+ ErrorMessage (R .string.youtube_player_http_403)
152+ } else {
153+ ErrorMessage (R .string.player_http_403)
154+ }
155+ } else {
156+ ErrorMessage (R .string.player_http_invalid_status, cause.responseCode.toString())
157+ }
158+ }
159+ cause is Loader .UnexpectedLoaderException && cause.cause is ExtractionException ->
160+ getMessage(throwable, action, serviceId)
161+ throwable.type == ExoPlaybackException .TYPE_SOURCE ->
162+ ErrorMessage (R .string.player_stream_failure)
163+ throwable.type == ExoPlaybackException .TYPE_UNEXPECTED ->
164+ ErrorMessage (R .string.player_recoverable_failure)
165+ else ->
166+ ErrorMessage (R .string.player_unrecoverable_failure)
117167 }
118168 }
119- throwable is FailedMediaSource .FailedMediaSourceException -> getMessageStringId(throwable.cause, action)
120- throwable is PlaybackResolver .ResolverException -> R .string.player_stream_failure
169+ throwable is FailedMediaSource .FailedMediaSourceException ->
170+ getMessage(throwable.cause, action, serviceId)
171+ throwable is PlaybackResolver .ResolverException ->
172+ ErrorMessage (R .string.player_stream_failure)
121173
122174 // content not available exceptions
123- throwable is AccountTerminatedException -> R .string.account_terminated
124- throwable is AgeRestrictedContentException -> R .string.restricted_video_no_stream
125- throwable is GeographicRestrictionException -> R .string.georestricted_content
126- throwable is PaidContentException -> R .string.paid_content
127- throwable is PrivateContentException -> R .string.private_content
128- throwable is SoundCloudGoPlusContentException -> R .string.soundcloud_go_plus_content
129- throwable is UnsupportedContentInCountryException -> R .string.unsupported_content_in_country
130- throwable is YoutubeMusicPremiumContentException -> R .string.youtube_music_premium_content
131- throwable is YoutubeSignInConfirmNotBotException -> R .string.youtube_sign_in_confirm_not_bot_error
132- throwable is ContentNotAvailableException -> R .string.content_not_available
175+ throwable is AccountTerminatedException ->
176+ throwable.message
177+ ?.takeIf { reason -> ! reason.isEmpty() }
178+ ?.let { reason ->
179+ ErrorMessage (
180+ R .string.account_terminated_service_provides_reason,
181+ getServiceName(serviceId),
182+ reason
183+ )
184+ }
185+ ? : ErrorMessage (R .string.account_terminated)
186+ throwable is AgeRestrictedContentException ->
187+ ErrorMessage (R .string.restricted_video_no_stream)
188+ throwable is GeographicRestrictionException ->
189+ ErrorMessage (R .string.georestricted_content)
190+ throwable is PaidContentException ->
191+ ErrorMessage (R .string.paid_content)
192+ throwable is PrivateContentException ->
193+ ErrorMessage (R .string.private_content)
194+ throwable is SoundCloudGoPlusContentException ->
195+ ErrorMessage (R .string.soundcloud_go_plus_content)
196+ throwable is UnsupportedContentInCountryException ->
197+ ErrorMessage (R .string.unsupported_content_in_country)
198+ throwable is YoutubeMusicPremiumContentException ->
199+ ErrorMessage (R .string.youtube_music_premium_content)
200+ throwable is SignInConfirmNotBotException ->
201+ ErrorMessage (R .string.sign_in_confirm_not_bot_error, getServiceName(serviceId))
202+ throwable is ContentNotAvailableException ->
203+ ErrorMessage (R .string.content_not_available)
133204
134205 // other extractor exceptions
135- throwable is ContentNotSupportedException -> R .string.content_not_supported
206+ throwable is ContentNotSupportedException ->
207+ ErrorMessage (R .string.content_not_supported)
136208 // ReCaptchas should have already been handled elsewhere,
137209 // but return an error message here just in case
138- throwable is ReCaptchaException -> R .string.recaptcha_request_toast
210+ throwable is ReCaptchaException ->
211+ ErrorMessage (R .string.recaptcha_request_toast)
139212 // test this at the end as many exceptions could be a subclass of IOException
140- throwable != null && throwable.isNetworkRelated -> R .string.network_error
213+ throwable != null && throwable.isNetworkRelated ->
214+ ErrorMessage (R .string.network_error)
141215 // an extraction exception unrelated to the network
142216 // is likely an issue with parsing the website
143- throwable is ExtractionException -> R .string.parsing_error
217+ throwable is ExtractionException ->
218+ ErrorMessage (R .string.parsing_error)
144219
145220 // user actions (in case the exception is null or unrecognizable)
146- action == UserAction .UI_ERROR -> R .string.app_ui_crash
147- action == UserAction .REQUESTED_COMMENTS -> R .string.error_unable_to_load_comments
148- action == UserAction .SUBSCRIPTION_CHANGE -> R .string.subscription_change_failed
149- action == UserAction .SUBSCRIPTION_UPDATE -> R .string.subscription_update_failed
150- action == UserAction .LOAD_IMAGE -> R .string.could_not_load_thumbnails
151- action == UserAction .DOWNLOAD_OPEN_DIALOG -> R .string.could_not_setup_download_menu
152- else -> R .string.error_snackbar_message
221+ action == UserAction .UI_ERROR ->
222+ ErrorMessage (R .string.app_ui_crash)
223+ action == UserAction .REQUESTED_COMMENTS ->
224+ ErrorMessage (R .string.error_unable_to_load_comments)
225+ action == UserAction .SUBSCRIPTION_CHANGE ->
226+ ErrorMessage (R .string.subscription_change_failed)
227+ action == UserAction .SUBSCRIPTION_UPDATE ->
228+ ErrorMessage (R .string.subscription_update_failed)
229+ action == UserAction .LOAD_IMAGE ->
230+ ErrorMessage (R .string.could_not_load_thumbnails)
231+ action == UserAction .DOWNLOAD_OPEN_DIALOG ->
232+ ErrorMessage (R .string.could_not_setup_download_menu)
233+ else ->
234+ ErrorMessage (R .string.error_snackbar_message)
153235 }
154236 }
155237 }
0 commit comments