diff --git a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt index a00e19f2d..da2aa9658 100644 --- a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt +++ b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt @@ -1,5 +1,7 @@ package org.schabi.newpipe.util.potoken +import android.os.Handler +import android.os.Looper import android.util.Log import org.schabi.newpipe.App import org.schabi.newpipe.extractor.NewPipe @@ -22,33 +24,68 @@ object PoTokenProviderImpl : PoTokenProvider { return null } - val (poTokenGenerator, visitorData, streamingPot) = synchronized(WebPoTokenGenLock) { - if (webPoTokenGenerator == null || webPoTokenGenerator!!.isExpired()) { - webPoTokenGenerator = PoTokenWebView.newPoTokenGenerator(App.getApp()).blockingGet() - webPoTokenVisitorData = YoutubeParsingHelper - .randomVisitorData(NewPipe.getPreferredContentCountry()) + return getWebClientPoToken(videoId = videoId, forceRecreate = false) + } - // The streaming poToken needs to be generated exactly once before generating any - // other (player) tokens. - webPoTokenStreamingPot = webPoTokenGenerator!! - .generatePoToken(webPoTokenVisitorData!!).blockingGet() + /** + * @param forceRecreate whether to force the recreation of [webPoTokenGenerator], to be used in + * case the current [webPoTokenGenerator] threw an error last time + * [PoTokenGenerator.generatePoToken] was called + */ + private fun getWebClientPoToken(videoId: String, forceRecreate: Boolean): PoTokenResult { + // just a helper class since Kotlin does not have builtin support for 4-tuples + data class Quadruple(val t1: T1, val t2: T2, val t3: T3, val t4: T4) + + val (poTokenGenerator, visitorData, streamingPot, hasBeenRecreated) = + synchronized(WebPoTokenGenLock) { + val shouldRecreate = webPoTokenGenerator == null || forceRecreate || + webPoTokenGenerator!!.isExpired() + + if (shouldRecreate) { + // close the current webPoTokenGenerator on the main thread + webPoTokenGenerator?.let { Handler(Looper.getMainLooper()).post { it.close() } } + + // create a new webPoTokenGenerator + webPoTokenGenerator = PoTokenWebView + .newPoTokenGenerator(App.getApp()).blockingGet() + webPoTokenVisitorData = YoutubeParsingHelper + .randomVisitorData(NewPipe.getPreferredContentCountry()) + + // The streaming poToken needs to be generated exactly once before generating + // any other (player) tokens. + webPoTokenStreamingPot = webPoTokenGenerator!! + .generatePoToken(webPoTokenVisitorData!!).blockingGet() + } + + return@synchronized Quadruple( + webPoTokenGenerator!!, + webPoTokenVisitorData!!, + webPoTokenStreamingPot!!, + shouldRecreate + ) + } + + val playerPot = try { + // Not using synchronized here, since poTokenGenerator would be able to generate + // multiple poTokens in parallel if needed. The only important thing is for exactly one + // visitorData/streaming poToken to be generated before anything else. + poTokenGenerator.generatePoToken(videoId).blockingGet() + } catch (throwable: Throwable) { + if (hasBeenRecreated) { + // the poTokenGenerator has just been recreated (and possibly this is already the + // second time we try), so there is likely nothing we can do + throw throwable + } else { + // retry, this time recreating the [webPoTokenGenerator] from scratch; + // this might happen for example if NewPipe goes in the background and the WebView + // content is lost + Log.e(TAG, "Failed to obtain poToken, retrying", throwable) + return getWebClientPoToken(videoId = videoId, forceRecreate = true) } - return@synchronized Triple( - webPoTokenGenerator!!, webPoTokenVisitorData!!, webPoTokenStreamingPot!! - ) } - // Not using synchronized here, since poTokenGenerator would be able to generate multiple - // poTokens in parallel if needed. The only important thing is for exactly one - // visitorData/streaming poToken to be generated before anything else. - val playerPot = poTokenGenerator.generatePoToken(videoId).blockingGet() Log.e(TAG, "success($videoId) $playerPot,web.gvs+$streamingPot;visitor_data=$visitorData") - - return PoTokenResult( - webPoTokenVisitorData!!, - playerPot, - webPoTokenStreamingPot!!, - ) + return PoTokenResult(visitorData, playerPot, streamingPot) } override fun getWebEmbedClientPoToken(videoId: String): PoTokenResult? = null diff --git a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt index 55cf23d93..30e19df9f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt +++ b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt @@ -7,6 +7,7 @@ import android.os.Looper import android.util.Log import android.webkit.JavascriptInterface import android.webkit.WebView +import androidx.annotation.MainThread import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.SingleEmitter @@ -275,6 +276,7 @@ class PoTokenWebView private constructor( /** * Releases all [webView] and [disposables] resources. */ + @MainThread override fun close() { disposables.dispose()