From 2fca7e9a01e6c7e42840463015501032892bd9c2 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 14:13:47 +0200 Subject: [PATCH] Locking of most known v8 interactions, fix returning previously returned jvm objects, Related fixes --- .../api/media/platforms/js/JSClient.kt | 36 +++--- .../api/media/platforms/js/models/JSPager.kt | 38 +++--- .../platforms/js/models/JSPlaybackTracker.kt | 66 +++++++---- .../platforms/js/models/JSRequestExecutor.kt | 110 +++++++++--------- .../platforms/js/models/JSVideoDetails.kt | 4 +- .../platforms/js/models/sources/JSSource.kt | 6 +- .../futo/platformplayer/engine/V8Plugin.kt | 99 +++++++++++++--- .../engine/internal/V8BindObject.kt | 4 +- .../engine/packages/PackageBridge.kt | 25 +++- .../engine/packages/PackageHttp.kt | 20 +++- .../mainactivity/main/VideoDetailView.kt | 4 +- .../views/video/FutoVideoPlayerBase.kt | 5 +- 12 files changed, 274 insertions(+), 143 deletions(-) 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 6ec1eae2..144faa2c 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 @@ -99,10 +99,8 @@ open class JSClient : IPlatformClient { override val icon: ImageVariable; override var capabilities: PlatformClientCapabilities = PlatformClientCapabilities(); - private val _busyLock = Object(); - private var _busyCounter = 0; private var _busyAction = ""; - val isBusy: Boolean get() = _busyCounter > 0; + val isBusy: Boolean get() = _plugin.isBusy; val isBusyAction: String get() { return _busyAction; } @@ -225,9 +223,12 @@ open class JSClient : IPlatformClient { 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); _initialized = true; @@ -254,6 +255,7 @@ 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) @@ -565,7 +567,7 @@ open class JSClient : IPlatformClient { Logger.i(TAG, "JSClient.getPlaybackTracker(${url})"); val tracker = plugin.executeTyped("source.getPlaybackTracker(${Json.encodeToString(url)})"); if(tracker is V8ValueObject) - return@isBusyWith JSPlaybackTracker(config, tracker); + return@isBusyWith JSPlaybackTracker(this, tracker); else return@isBusyWith null; } @@ -747,25 +749,23 @@ open class JSClient : IPlatformClient { return urls; } - + fun busy(handle: ()->T): T { + return _plugin.busy { + return@busy handle(); + } + } fun isBusyWith(actionName: String, handle: ()->T): T { - val busyId = kotlin.random.Random.nextInt(9999); - try { + //val busyId = kotlin.random.Random.nextInt(9999); + return busy { + try { + _busyAction = actionName; + return@busy handle(); - Logger.v(TAG, "Busy with [${actionName}] (${busyId})") - synchronized(_busyLock) { - _busyCounter++; } - _busyAction = actionName; - return handle(); - } - finally { - _busyAction = ""; - synchronized(_busyLock) { - _busyCounter--; + finally { + _busyAction = ""; } - Logger.v(TAG, "Busy done [${actionName}] (${busyId})") } } private fun isBusyWith(handle: ()->T): T { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt index 8782b742..e81a288d 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt @@ -29,7 +29,9 @@ abstract class JSPager : IPager { this.pager = pager; this.config = config; - _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; + plugin.busy { + _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; + } getResults(); } @@ -44,11 +46,14 @@ abstract class JSPager : IPager { override fun nextPage() { warnIfMainThread("JSPager.nextPage"); - pager = plugin.getUnderlyingPlugin().catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") { - pager.invoke("nextPage", arrayOf()); - }; - _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; - _resultChanged = true; + val pluginV8 = plugin.getUnderlyingPlugin(); + pluginV8.busy { + pager = pluginV8.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") { + pager.invoke("nextPage", arrayOf()); + }; + _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; + _resultChanged = true; + } /* try { } @@ -70,15 +75,18 @@ abstract class JSPager : IPager { return previousResults; warnIfMainThread("JSPager.getResults"); - val items = pager.getOrThrow(config, "results", "JSPager"); - if(items.v8Runtime.isDead || items.v8Runtime.isClosed) - throw IllegalStateException("Runtime closed"); - val newResults = items.toArray() - .map { convertResult(it as V8ValueObject) } - .toList(); - _lastResults = newResults; - _resultChanged = false; - return newResults; + + return plugin.getUnderlyingPlugin().busy { + val items = pager.getOrThrow(config, "results", "JSPager"); + if (items.v8Runtime.isDead || items.v8Runtime.isClosed) + throw IllegalStateException("Runtime closed"); + val newResults = items.toArray() + .map { convertResult(it as V8ValueObject) } + .toList(); + _lastResults = newResults; + _resultChanged = false; + return@busy newResults; + } } abstract fun convertResult(obj: V8ValueObject): T; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt index e5ee7b68..15a7d854 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt @@ -2,37 +2,50 @@ package com.futo.platformplayer.api.media.platforms.js.models import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.warnIfMainThread class JSPlaybackTracker: IPlaybackTracker { - private val _config: IV8PluginConfig; - private val _obj: V8ValueObject; + private lateinit var _client: JSClient; + private lateinit var _config: IV8PluginConfig; + private lateinit var _obj: V8ValueObject; private var _hasCalledInit: Boolean = false; - private val _hasInit: Boolean; + private var _hasInit: Boolean = false; private var _lastRequest: Long = Long.MIN_VALUE; - private val _hasOnConcluded: Boolean; + private var _hasOnConcluded: Boolean = false; override var nextRequest: Int = 1000 private set; - constructor(config: IV8PluginConfig, obj: V8ValueObject) { + constructor(client: JSClient, obj: V8ValueObject) { warnIfMainThread("JSPlaybackTracker.constructor"); - if(!obj.has("onProgress")) - throw ScriptImplementationException(config, "Missing onProgress on PlaybackTracker"); - if(!obj.has("nextRequest")) - throw ScriptImplementationException(config, "Missing nextRequest on PlaybackTracker"); - _hasOnConcluded = obj.has("onConcluded"); - this._config = config; - this._obj = obj; - this._hasInit = obj.has("onInit"); + client.busy { + if (!obj.has("onProgress")) + throw ScriptImplementationException( + client.config, + "Missing onProgress on PlaybackTracker" + ); + if (!obj.has("nextRequest")) + throw ScriptImplementationException( + client.config, + "Missing nextRequest on PlaybackTracker" + ); + _hasOnConcluded = obj.has("onConcluded"); + + this._client = client; + this._config = client.config; + this._obj = obj; + this._hasInit = obj.has("onInit"); + } } override fun onInit(seconds: Double) { @@ -40,12 +53,15 @@ class JSPlaybackTracker: IPlaybackTracker { synchronized(_obj) { if(_hasCalledInit) return; - if (_hasInit) { - Logger.i("JSPlaybackTracker", "onInit (${seconds})"); - _obj.invokeVoid("onInit", seconds); + + _client.busy { + if (_hasInit) { + Logger.i("JSPlaybackTracker", "onInit (${seconds})"); + _obj.invokeVoid("onInit", seconds); + } + nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); + _hasCalledInit = true; } - nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); - _hasCalledInit = true; } } @@ -55,10 +71,12 @@ class JSPlaybackTracker: IPlaybackTracker { if(!_hasCalledInit && _hasInit) onInit(seconds); else { - Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})"); - _obj.invokeVoid("onProgress", Math.floor(seconds), isPlaying); - nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); - _lastRequest = System.currentTimeMillis(); + _client.busy { + Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})"); + _obj.invokeVoid("onProgress", Math.floor(seconds), isPlaying); + nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); + _lastRequest = System.currentTimeMillis(); + } } } } @@ -67,7 +85,9 @@ class JSPlaybackTracker: IPlaybackTracker { if(_hasOnConcluded) { synchronized(_obj) { Logger.i("JSPlaybackTracker", "onConcluded"); - _obj.invokeVoid("onConcluded", -1); + _client.busy { + _obj.invokeVoid("onConcluded", -1); + } } } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt index 70dfecfd..36cfc7db 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt @@ -46,16 +46,18 @@ class JSRequestExecutor { if (_executor.isClosed) throw IllegalStateException("Executor object is closed"); - val result = if(_plugin is DevJSClient) - StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { - V8Plugin.catchScriptErrors( - _config, - "[${_config.name}] JSRequestExecutor", - "builder.modifyRequest()" - ) { - _executor.invoke("executeRequest", url, headers, method, body); - } as V8Value; - } + return _plugin.getUnderlyingPlugin().busy { + + val result = if(_plugin is DevJSClient) + StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { + V8Plugin.catchScriptErrors( + _config, + "[${_config.name}] JSRequestExecutor", + "builder.modifyRequest()" + ) { + _executor.invoke("executeRequest", url, headers, method, body); + } as V8Value; + } else V8Plugin.catchScriptErrors( _config, "[${_config.name}] JSRequestExecutor", @@ -64,34 +66,35 @@ class JSRequestExecutor { _executor.invoke("executeRequest", url, headers, method, body); } as V8Value; - try { - if(result is V8ValueString) { - val base64Result = Base64.getDecoder().decode(result.value); - return base64Result; - } - if(result is V8ValueTypedArray) { - val buffer = result.buffer; - val byteBuffer = buffer.byteBuffer; - val bytesResult = ByteArray(result.byteLength); - byteBuffer.get(bytesResult, 0, result.byteLength); - buffer.close(); - return bytesResult; - } - if(result is V8ValueObject && result.has("type")) { - val type = result.getOrThrow(_config, "type", "JSRequestModifier"); - when(type) { - //TODO: Buffer type? + try { + if(result is V8ValueString) { + val base64Result = Base64.getDecoder().decode(result.value); + return@busy base64Result; } + if(result is V8ValueTypedArray) { + val buffer = result.buffer; + val byteBuffer = buffer.byteBuffer; + val bytesResult = ByteArray(result.byteLength); + byteBuffer.get(bytesResult, 0, result.byteLength); + buffer.close(); + return@busy bytesResult; + } + if(result is V8ValueObject && result.has("type")) { + val type = result.getOrThrow(_config, "type", "JSRequestModifier"); + when(type) { + //TODO: Buffer type? + } + } + if(result is V8ValueUndefined) { + if(_plugin is DevJSClient) + StateDeveloper.instance.logDevException(_plugin.devID, "JSRequestExecutor.executeRequest returned illegal undefined"); + throw ScriptImplementationException(_config, "JSRequestExecutor.executeRequest returned illegal undefined", null); + } + throw NotImplementedError("Executor result type not implemented? " + result.javaClass.name); } - if(result is V8ValueUndefined) { - if(_plugin is DevJSClient) - StateDeveloper.instance.logDevException(_plugin.devID, "JSRequestExecutor.executeRequest returned illegal undefined"); - throw ScriptImplementationException(_config, "JSRequestExecutor.executeRequest returned illegal undefined", null); + finally { + result.close(); } - throw NotImplementedError("Executor result type not implemented? " + result.javaClass.name); - } - finally { - result.close(); } } @@ -99,24 +102,25 @@ class JSRequestExecutor { open fun cleanup() { if (!hasCleanup || _executor.isClosed) return; - - if(_plugin is DevJSClient) - StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { - V8Plugin.catchScriptErrors( - _config, - "[${_config.name}] JSRequestExecutor", - "builder.modifyRequest()" - ) { - _executor.invokeVoid("cleanup", null); - }; - } - else V8Plugin.catchScriptErrors( - _config, - "[${_config.name}] JSRequestExecutor", - "builder.modifyRequest()" - ) { - _executor.invokeVoid("cleanup", null); - }; + _plugin.busy { + if(_plugin is DevJSClient) + StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { + V8Plugin.catchScriptErrors( + _config, + "[${_config.name}] JSRequestExecutor", + "builder.modifyRequest()" + ) { + _executor.invokeVoid("cleanup", null); + }; + } + else V8Plugin.catchScriptErrors( + _config, + "[${_config.name}] JSRequestExecutor", + "builder.modifyRequest()" + ) { + _executor.invokeVoid("cleanup", null); + }; + } } protected fun finalize() { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt index da495498..799c737f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt @@ -27,6 +27,7 @@ import com.futo.platformplayer.getOrThrowNullable import com.futo.platformplayer.states.StateDeveloper class JSVideoDetails : JSVideo, IPlatformVideoDetails { + private val _plugin: JSClient; private val _hasGetComments: Boolean; private val _hasGetContentRecommendations: Boolean; private val _hasGetPlaybackTracker: Boolean; @@ -48,6 +49,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails { constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin.config, obj) { val contextName = "VideoDetails"; + _plugin = plugin; val config = plugin.config; description = _content.getOrThrow(config, "description", contextName); video = JSVideoSourceDescriptor.fromV8(plugin, _content.getOrThrow(config, "video", contextName)); @@ -86,7 +88,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails { val tracker = _content.invoke("getPlaybackTracker", arrayOf()) ?: return@catchScriptErrors null; if(tracker is V8ValueObject) - return@catchScriptErrors JSPlaybackTracker(_pluginConfig, tracker); + return@catchScriptErrors JSPlaybackTracker(_plugin, tracker); else return@catchScriptErrors null; }; 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 ee586083..649c74cf 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 @@ -77,7 +77,11 @@ abstract class JSSource { Logger.v("JSSource", "Request executor for [${type}] requesting"); val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { - _obj.invoke("getRequestExecutor", arrayOf()); + _plugin.isBusyWith("getRequestExecutor") { + _plugin.getUnderlyingPlugin().busy { + _obj.invoke("getRequestExecutor", arrayOf()); + } + } }; Logger.v("JSSource", "Request executor for [${type}] received"); 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 96593f48..462fb192 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -1,9 +1,11 @@ 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 @@ -42,6 +44,9 @@ 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 class V8Plugin { val config: IV8PluginConfig; @@ -70,9 +75,10 @@ class V8Plugin { 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; - val isBusy get() = synchronized(_busyCounterLock) { _busyCounter > 0 }; + //private val _busyCounterLock = Object(); + //private var _busyCounter = 0; + private val _busyLock = ReentrantLock()//Semaphore(1); + val isBusy get() = _busyLock.isLocked;//synchronized(_busyCounterLock) { _busyCounter > 0 }; var allowDevSubmit: Boolean = false private set(value) { @@ -146,14 +152,19 @@ class V8Plugin { 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 -> @@ -166,6 +177,8 @@ class V8Plugin { } } + Logger.i(TAG, "Plugin [${config.name}] start: Loading deps") + //Load deps for (dep in _deps) catchScriptErrors("Dep[${dep.key}]") { @@ -176,20 +189,23 @@ 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") } } } fun stop(){ Logger.i(TAG, "Stopping plugin [${config.name}]"); - isStopped = true; - whenNotBusy { + busy { Logger.i(TAG, "Plugin stopping"); synchronized(_runtimeLock) { + if(isStopped) + return@busy; isStopped = true; //Cleanup http @@ -203,7 +219,7 @@ class V8Plugin { _runtime = null; if(!it.isClosed && !it.isDead) { try { - it.close(true); + it.close(); } catch(ex: JavetException) { //In case race conditions are going on, already closed runtimes are fine. @@ -219,6 +235,12 @@ class V8Plugin { } } + fun busy(handle: ()->T): T { + _busyLock.withLock { + //Logger.i(TAG, "Entered busy: " + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()); + return handle(); + } + } fun execute(js: String) : V8Value { return executeTyped(js); } @@ -227,6 +249,14 @@ class V8Plugin { if(isStopped) throw PluginEngineStoppedException(config, "Instance is stopped", js); + return busy { + + val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet"); + return@busy catchScriptErrors("Plugin[${config.name}]", js) { + runtime.getExecutor(js).execute() + }; + } + /* synchronized(_busyCounterLock) { _busyCounter++; } @@ -249,11 +279,26 @@ class V8Plugin { _busyCounter--; } } + */ } - fun executeBoolean(js: String) : Boolean? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; - fun executeString(js: String) : String? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; - fun executeInteger(js: String) : Int? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; + 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) @@ -264,12 +309,25 @@ class V8Plugin { if(it == 0) { Logger.w(TAG, "V8Plugin afterBusy handled"); afterBusy.remove(tag); - handler(this); + + 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? @@ -331,24 +389,29 @@ class V8Plugin { throw ScriptCompilationException(config, "Compilation: [${context}]: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped); } catch(executeEx: JavetExecutionException) { - if(executeEx.scriptingError?.context is V8ValueObject) { - val obj = executeEx.scriptingError?.context as V8ValueObject - if(obj.has("plugin_type") == true) { - val pluginType = obj.get("plugin_type").toString(); + if(executeEx.scriptingError?.context is IJavetEntityError) { + 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.get("url")?.toString(), - obj.get("body")?.toString(), + obj.context["url"]?.toString(), + obj.context["body"]?.toString(), + //obj.get("url")?.toString(), + //obj.get("body")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } //Reload Required if (pluginType == "ReloadRequiredException") { throw ScriptReloadRequiredException(config, - obj.get("message")?.toString(), - obj.get("reloadData")?.toString(), + obj.context["msg"]?.toString(), + obj.context["reloadData"]?.toString(), + //obj.get("message")?.toString(), + //obj.get("reloadData")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } diff --git a/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt b/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt index 4e861b72..fd30af6f 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt @@ -13,8 +13,8 @@ open class V8BindObject : IV8Convertable { override fun toV8(runtime: V8Runtime): V8Value? { synchronized(this) { - if(_runtimeObj != null) - return _runtimeObj; + //if(_runtimeObj != null) + // return _runtimeObj; val v8Obj = runtime.createV8ValueObject(); v8Obj.bind(this); 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 5450c5eb..b4cc821d 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 @@ -4,6 +4,7 @@ import android.media.MediaCodec import android.media.MediaCodecList import com.caoccao.javet.annotations.V8Function import com.caoccao.javet.annotations.V8Property +import com.caoccao.javet.interop.callback.JavetCallbackContext import com.caoccao.javet.utils.JavetResourceUtils import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.reference.V8ValueFunction @@ -112,28 +113,42 @@ class PackageBridge : V8Package { @V8Function fun setTimeout(func: V8ValueFunction, timeout: Long): Int { val id = timeoutCounter++; - val funcClone = func.toClone() StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { delay(timeout); + if(_plugin.isStopped) + return@launch; synchronized(timeoutMap) { if(!timeoutMap.contains(id)) { - JavetResourceUtils.safeClose(funcClone); + _plugin.busy { + if(!_plugin.isStopped) + JavetResourceUtils.safeClose(funcClone); + } return@launch; } timeoutMap.remove(id); } try { - _plugin.whenNotBusy { - funcClone.callVoid(null, arrayOf()); + 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); } finally { - JavetResourceUtils.safeClose(funcClone); + _plugin.busy { + if(!_plugin.isStopped) + JavetResourceUtils.safeClose(funcClone); + } + //_plugin.whenNotBusy { + //} } }; synchronized(timeoutMap) { 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 0b049ecb..2930a476 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 @@ -656,7 +656,9 @@ class PackageHttp: V8Package { _isOpen = true; if(hasOpen && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("open", arrayOf()); + _package._plugin.busy { + _listeners?.invokeVoid("open", arrayOf()); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] open failed: " + ex.message, ex); @@ -666,7 +668,9 @@ class PackageHttp: V8Package { override fun message(msg: String) { if(hasMessage && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("message", msg); + _package._plugin.busy { + _listeners?.invokeVoid("message", msg); + } } catch(ex: Throwable) {} } @@ -675,7 +679,9 @@ class PackageHttp: V8Package { if(hasClosing && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("closing", code, reason); + _package._plugin.busy { + _listeners?.invokeVoid("closing", code, reason); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closing failed: " + ex.message, ex); @@ -686,7 +692,9 @@ class PackageHttp: V8Package { _isOpen = false; if(hasClosed && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("closed", code, reason); + _package._plugin.busy { + _listeners?.invokeVoid("closed", code, reason); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex); @@ -702,7 +710,9 @@ class PackageHttp: V8Package { Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception); if(hasFailure && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("failure", exception.message); + _package._plugin.busy { + _listeners?.invokeVoid("failure", exception.message); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 08e04730..452af019 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2497,7 +2497,9 @@ class VideoDetailView : ConstraintLayout { val url = _url; if (!url.isNullOrBlank()) { - setLoading(true); + fragment.lifecycleScope.launch(Dispatchers.Main) { + setLoading(true); + } _taskLoadVideo.run(url); } } 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 ab0bf383..b0afd83f 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 @@ -570,7 +570,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { if (generated != null) { withContext(Dispatchers.Main) { val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource)) - videoSource.getHttpDataSourceFactory() + withContext(Dispatchers.IO) { videoSource.getHttpDataSourceFactory() } else DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); @@ -593,6 +593,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { catch(reloadRequired: ScriptReloadRequiredException) { Logger.i(TAG, "Reload required detected"); StatePlatform.instance.handleReloadRequired(reloadRequired, { + Logger.i(TAG, "ReloadRequired started reloading video"); onReloadRequired.emit(); }); } @@ -704,9 +705,11 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val plugin = audioSource.getUnderlyingPlugin(); if(plugin == null) return@launch; + /* StatePlatform.instance.reEnableClient(plugin.id, { onReloadRequired.emit(); }); + */ } catch(ex: Throwable) {