diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt index b26abe45..1f29bf2a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt @@ -56,6 +56,7 @@ class DevJSClient : JSClient { override fun getCopy(privateCopy: Boolean, noSaveState: Boolean): JSClient { val client = DevJSClient(_context, descriptor, _script, if(!privateCopy) _auth else null, _captcha, if (noSaveState) null else saveState(), devID); + client.setReloadData(getReloadData(true)); if (noSaveState) client.initialize() return client diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 144faa2c..4422dd28 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -84,6 +84,8 @@ open class JSClient : IPlatformClient { private var _channelCapabilities: ResultCapabilities? = null; private var _peekChannelTypes: List? = null; + private var _usedReloadData: String? = null; + protected val _script: String; private var _initialized: Boolean = false; @@ -198,6 +200,7 @@ open class JSClient : IPlatformClient { open fun getCopy(withoutCredentials: Boolean = false, noSaveState: Boolean = false): JSClient { val client = JSClient(_context, descriptor, if (noSaveState) null else saveState(), _script, withoutCredentials); + client.setReloadData(getReloadData(true)); if (noSaveState) client.initialize() return client @@ -215,19 +218,29 @@ open class JSClient : IPlatformClient { } fun setReloadData(data: String?) { - declareOnEnable.put("__reloadData", data ?: ""); + if(data == null) { + if(declareOnEnable.containsKey("__reloadData")) + declareOnEnable.remove("__reloadData"); + } + else + declareOnEnable.put("__reloadData", data ?: ""); + } + fun getReloadData(orLast: Boolean): String? { + if(declareOnEnable.containsKey("__reloadData")) + return declareOnEnable["__reloadData"]; + else if(orLast) + return _usedReloadData; + return null; } override fun initialize() { if (_initialized) return - Logger.i(TAG, "Plugin [${config.name}] initializing"); plugin.start(); - Logger.i(TAG, "Plugin [${config.name}] started"); + plugin.execute("plugin.config = ${Json.encodeToString(config)}"); plugin.execute("plugin.settings = parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())})"); - Logger.i(TAG, "Plugin [${config.name}] configs set"); descriptor.appSettings.loadDefaults(descriptor.config); @@ -255,7 +268,6 @@ open class JSClient : IPlatformClient { hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false, hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false ); - Logger.i(TAG, "Plugin [${config.name}] capabilities retrieved"); try { if (capabilities.hasGetChannelTemplateByClaimMap) @@ -277,8 +289,11 @@ open class JSClient : IPlatformClient { } plugin.execute("source.enable(${Json.encodeToString(config)}, parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())}), ${Json.encodeToString(_injectedSaveState)})"); - if(declareOnEnable.containsKey("__reloadData")) + if(declareOnEnable.containsKey("__reloadData")) { + Logger.i(TAG, "Plugin [${config.name}] enabled with reload data: ${declareOnEnable["__reloadData"]}"); + _usedReloadData = declareOnEnable["__reloadData"]; declareOnEnable.remove("__reloadData"); + } _enabled = true; } @JSDocs(1, "source.saveState()", "Provide a string that is passed to enable for quicker startup of multiple instances") diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt index eec4414a..03c5c2c6 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt @@ -67,9 +67,28 @@ class JSHttpClient : ManagedHttpClient { } + fun resetAuthCookies() { + _currentCookieMap.clear(); + if(!_auth?.cookieMap.isNullOrEmpty()) { + for(domainCookies in _auth!!.cookieMap!!) + _currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value)); + } + if(!_captcha?.cookieMap.isNullOrEmpty()) { + for(domainCookies in _captcha!!.cookieMap!!) { + if(_currentCookieMap.containsKey(domainCookies.key)) + _currentCookieMap[domainCookies.key]?.putAll(domainCookies.value); + else + _currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value)); + } + } + } + fun clearOtherCookies() { + _otherCookieMap.clear(); + } + override fun clone(): ManagedHttpClient { val newClient = JSHttpClient(_jsClient, _auth); - //newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) + newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) return newClient; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt index 150189e7..f7d169af 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt @@ -16,7 +16,7 @@ class JSRequestModifier: IRequestModifier { private val _plugin: JSClient; private val _config: IV8PluginConfig; private var _modifier: V8ValueObject; - override var allowByteSkip: Boolean; + override var allowByteSkip: Boolean = false; constructor(plugin: JSClient, modifier: V8ValueObject) { this._plugin = plugin; @@ -24,10 +24,13 @@ class JSRequestModifier: IRequestModifier { this._config = plugin.config; val config = plugin.config; - allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true; + plugin.busy { + allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true; + + if(!modifier.has("modifyRequest")) + throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null); + } - if(!modifier.has("modifyRequest")) - throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null); } override fun modifyRequest(url: String, headers: Map): IRequest { @@ -35,13 +38,15 @@ class JSRequestModifier: IRequestModifier { return Request(url, headers); } - val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") { - _modifier.invoke("modifyRequest", url, headers); - } as V8ValueObject; + return _plugin.busy { + val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") { + _modifier.invoke("modifyRequest", url, headers); + } as V8ValueObject; - val req = JSRequest(_plugin, result, url, headers); - result.close(); - return req; + val req = JSRequest(_plugin, result, url, headers); + result.close(); + return@busy req; + } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt index 649c74cf..00906239 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -62,9 +62,11 @@ abstract class JSSource { if (!hasRequestModifier || _obj.isClosed) return null; - val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") { - _obj.invoke("getRequestModifier", arrayOf()); - }; + val result = _plugin.isBusyWith("getRequestModifier") { + V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") { + _obj.invoke("getRequestModifier", arrayOf()); + }; + } if (result !is V8ValueObject) return null; @@ -76,13 +78,12 @@ abstract class JSSource { return null; Logger.v("JSSource", "Request executor for [${type}] requesting"); - val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { - _plugin.isBusyWith("getRequestExecutor") { - _plugin.getUnderlyingPlugin().busy { - _obj.invoke("getRequestExecutor", arrayOf()); - } - } - }; + val result =_plugin.isBusyWith("getRequestExecutor") { + V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { + _obj.invoke("getRequestExecutor", arrayOf()); + }; + } + Logger.v("JSSource", "Request executor for [${type}] received"); if (result !is V8ValueObject) diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 5b6ffd91..323aa5e1 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -1,15 +1,12 @@ package com.futo.platformplayer.engine import android.content.Context -import com.caoccao.javet.entities.JavetEntityError import com.caoccao.javet.exceptions.JavetCompilationException import com.caoccao.javet.exceptions.JavetException import com.caoccao.javet.exceptions.JavetExecutionException import com.caoccao.javet.interfaces.IJavetEntityError import com.caoccao.javet.interop.V8Host import com.caoccao.javet.interop.V8Runtime -import com.caoccao.javet.interop.options.V8Flags -import com.caoccao.javet.interop.options.V8RuntimeOptions import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.primitive.V8ValueBoolean import com.caoccao.javet.values.primitive.V8ValueInteger @@ -17,7 +14,6 @@ import com.caoccao.javet.values.primitive.V8ValueString import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient -import com.futo.platformplayer.assume import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.engine.exceptions.NoInternetException import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException @@ -44,7 +40,6 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateAssets import com.futo.platformplayer.warnIfMainThread import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Semaphore import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -58,7 +53,7 @@ class V8Plugin { val httpClientAuth: ManagedHttpClient get() = _clientAuth; val httpClientOthers: Map get() = _clientOthers; - var startId: Int = 0; + var runtimeId: Int = 0; fun registerHttpClient(client: JSHttpClient) { synchronized(_clientOthers) { @@ -76,11 +71,8 @@ class V8Plugin { var isStopped = true; val onStopped = Event1(); - //TODO: Implement a more universal isBusy system for plugins + JSClient + pooling? TBD if propagation would be beneficial - //private val _busyCounterLock = Object(); - //private var _busyCounter = 0; - private val _busyLock = ReentrantLock()//Semaphore(1); - val isBusy get() = _busyLock.isLocked;//synchronized(_busyCounterLock) { _busyCounter > 0 }; + private val _busyLock = ReentrantLock() + val isBusy get() = _busyLock.isLocked; var allowDevSubmit: Boolean = false private set(value) { @@ -150,24 +142,19 @@ class V8Plugin { synchronized(_runtimeLock) { if (_runtime != null) return; - startId + 1; + runtimeId = runtimeId + 1; //V8RuntimeOptions.V8_FLAGS.setUseStrict(true); val host = V8Host.getV8Instance(); val options = host.jsRuntimeType.getRuntimeOptions(); - Logger.i(TAG, "Plugin [${config.name}] start: Creating runtime") - _runtime = host.createV8Runtime(options); if (!host.isIsolateCreated) throw IllegalStateException("Isolate not created"); - Logger.i(TAG, "Plugin [${config.name}] start: Created runtime") - //Setup bridge _runtime?.let { it.converter = V8Converter(); - Logger.i(TAG, "Plugin [${config.name}] start: Loading packages") for (pack in _depsPackages) { if (pack.variableName != null) it.createV8ValueObject().use { v8valueObject -> @@ -180,8 +167,6 @@ class V8Plugin { } } - Logger.i(TAG, "Plugin [${config.name}] start: Loading deps") - //Load deps for (dep in _deps) catchScriptErrors("Dep[${dep.key}]") { @@ -192,13 +177,11 @@ class V8Plugin { if (config.allowEval) it.allowEval(true); - Logger.i(TAG, "Plugin [${config.name}] start: Loading script") //Load plugin catchScriptErrors("Plugin[${config.name}]") { it.getExecutor(script).executeVoid() }; isStopped = false; - Logger.i(TAG, "Plugin [${config.name}] start: Script loaded") } } } @@ -210,7 +193,7 @@ class V8Plugin { if(isStopped) return@busy; isStopped = true; - startId = -1; + runtimeId = runtimeId + 1; //Cleanup http for(pack in _depsPackages) { @@ -260,79 +243,11 @@ class V8Plugin { runtime.getExecutor(js).execute() }; } - /* - synchronized(_busyCounterLock) { - _busyCounter++; - } - - val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet"); - try { - return catchScriptErrors("Plugin[${config.name}]", js) { - runtime.getExecutor(js).execute() - }; - } - finally { - synchronized(_busyCounterLock) { - //Free busy *after* afterBusy calls are done to prevent calls on dead runtimes - try { - afterBusy.emit(_busyCounter - 1); - } - catch(ex: Throwable) { - Logger.e(TAG, "Unhandled V8Plugin.afterBusy", ex); - } - _busyCounter--; - } - } - */ } fun executeBoolean(js: String) : Boolean? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } fun executeString(js: String) : String? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } fun executeInteger(js: String) : Int? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } - /* - fun whenNotBusyBlocking(handler: (V8Plugin)->T): T { - while(true) { - synchronized(_busyCounterLock) { - if(_busyCounter == 0) - { - return handler(this); - } - } - Thread.sleep(1); - } - } - */ - /* - fun whenNotBusy(handler: (V8Plugin)->Unit) { - synchronized(_busyCounterLock) { - if(_busyCounter == 0) - handler(this); - else { - val tag = Object(); - afterBusy.subscribe(tag) { - if(it == 0) { - Logger.w(TAG, "V8Plugin afterBusy handled"); - afterBusy.remove(tag); - - var failed = false; - synchronized(_busyCounterLock) { - if(_busyCounter > 0) { - failed = true; - return@synchronized - } - handler(this); - } - if(failed) - busy { - handler(this); - } - } - } - } - } - } - */ - private fun getPackage(packageName: String, allowNull: Boolean = false): V8Package? { //TODO: Auto get all package types? return when(packageName) { @@ -397,15 +312,12 @@ class V8Plugin { val obj = executeEx.scriptingError?.context as IJavetEntityError if(obj.context.containsKey("plugin_type") == true) { val pluginType = obj.context["plugin_type"].toString(); - //val pluginType = obj.get("plugin_type").toString(); //Captcha if (pluginType == "CaptchaRequiredException") { throw ScriptCaptchaRequiredException(config, obj.context["url"]?.toString(), obj.context["body"]?.toString(), - //obj.get("url")?.toString(), - //obj.get("body")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } @@ -414,8 +326,6 @@ class V8Plugin { throw ScriptReloadRequiredException(config, obj.context["msg"]?.toString(), obj.context["reloadData"]?.toString(), - //obj.get("message")?.toString(), - //obj.get("reloadData")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } @@ -481,9 +391,4 @@ class V8Plugin { return StateAssets.readAsset(context, path) ?: throw java.lang.IllegalStateException("script ${path} not found"); } } - - - /** - * Methods available for scripts (bridge object) - */ } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt index b4cc821d..858b020b 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -82,7 +82,8 @@ class PackageBridge : V8Package { @V8Property fun supportedFeatures(): Array { return arrayOf( - "ReloadRequiredException" + "ReloadRequiredException", + "HttpBatchClient" ); } @@ -130,14 +131,10 @@ class PackageBridge : V8Package { timeoutMap.remove(id); } try { - Logger.v(TAG, "Timeout started [${id}]"); _plugin.busy { - Logger.v(TAG, "Timeout call started [${id}]"); if(!_plugin.isStopped) funcClone.callVoid(null, arrayOf()); - Logger.v(TAG, "Timeout call ended [${id}]"); } - Logger.v(TAG, "Timeout resolved [${id}]"); } catch(ex: Throwable) { Logger.e(TAG, "Failed timeout callback", ex); @@ -173,7 +170,7 @@ class PackageBridge : V8Package { Logger.i(TAG, "Plugin toast [${_config.name}]: ${str}"); StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { try { - UIDialogs.toast(str); + UIDialogs.appToast(str); } catch (e: Throwable) { Logger.e(TAG, "Failed to show toast.", e); } diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index 2930a476..82edb023 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -347,6 +347,17 @@ class PackageHttp: V8Package { _clientId = if(_client is JSHttpClient) _client.clientId else null; } + @V8Function + fun resetAuthCookies(){ + if(_client is JSHttpClient) + _client.resetAuthCookies(); + } + @V8Function + fun clearOtherCookies(){ + if(_client is JSHttpClient) + _client.clearOtherCookies(); + } + @V8Function fun setDefaultHeaders(defaultHeaders: Map) { for(pair in defaultHeaders) diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index b5e29075..7f361105 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -567,7 +567,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { var startId = -1; try { - startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.startId ?: -1; + startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1; val generated = videoSource.generate(); if (generated != null) { withContext(Dispatchers.Main) { @@ -597,7 +597,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val plugin = videoSource.getUnderlyingPlugin(); if(plugin == null) return@launch; - if(startId != -1 && plugin.getUnderlyingPlugin()?.startId != startId) + if(startId != -1 && plugin.getUnderlyingPlugin()?.runtimeId != startId) return@launch; StatePlatform.instance.handleReloadRequired(reloadRequired, { onReloadRequired.emit(); @@ -689,17 +689,17 @@ abstract class FutoVideoPlayerBase : RelativeLayout { @OptIn(UnstableApi::class) private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource, play: Boolean, resume: Boolean): Boolean { Logger.i(TAG, "Loading AudioSource [DashRaw]"); - val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource)) - audioSource.getHttpDataSourceFactory() - else - DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); if(audioSource.hasGenerate) { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { var startId = -1; try { - startId = audioSource.getUnderlyingPlugin()?.getUnderlyingPlugin()?.startId ?: -1; + startId = audioSource.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1; val generated = audioSource.generate(); if(generated != null) { + val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource)) + audioSource.getHttpDataSourceFactory() + else + DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); withContext(Dispatchers.Main) { _lastVideoMediaSource = DashMediaSource.Factory(dataSource) .createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url), @@ -713,7 +713,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val plugin = audioSource.getUnderlyingPlugin(); if(plugin == null) return@launch; - if(startId != -1 && plugin.getUnderlyingPlugin()?.startId != startId) + if(startId != -1 && plugin.getUnderlyingPlugin()?.runtimeId != startId) return@launch; StatePlatform.instance.reEnableClient(plugin.id, { onReloadRequired.emit(); @@ -726,6 +726,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout { return false; } else { + val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource)) + audioSource.getHttpDataSourceFactory() + else + DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); _lastVideoMediaSource = DashMediaSource.Factory(dataSource) .createMediaSource( DashManifestParser().parse(