Improved V8 locking, comment section on diff thread than video, global mapping of v8runtimes to plugins

This commit is contained in:
Kelvin K 2025-06-17 11:45:02 +02:00
commit c14378b534
7 changed files with 68 additions and 26 deletions

View file

@ -5,7 +5,9 @@ import com.caoccao.javet.values.primitive.*
import com.caoccao.javet.values.reference.V8ValueArray
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.logging.Logger
//V8
@ -24,6 +26,10 @@ fun <R> V8Value?.orDefault(default: R, handler: (V8Value)->R): R {
return handler(this);
}
inline fun V8Value.getSourcePlugin(): V8Plugin? {
return V8Plugin.getPluginFromRuntime(this.v8Runtime);
}
inline fun <reified T> V8Value.expectOrThrow(config: IV8PluginConfig, contextName: String): T {
if(this !is T)
throw ScriptImplementationException(config, "Expected ${contextName} to be of type ${T::class.simpleName}, but found ${this::class.simpleName}");
@ -90,6 +96,20 @@ inline fun <reified T> V8ValueArray.expectV8Variants(config: IV8PluginConfig, co
}
inline fun <reified T> V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T {
if(false)
{
this?.getSourcePlugin()?.let {
if (!it.isThreadAlreadyBusy()) {
val stacktrace = Thread.currentThread().stackTrace;
Logger.w("Extensions_V8",
"V8 USE OUTSIDE BUSY: " + stacktrace.drop(3)?.firstOrNull().toString() +
", " + stacktrace.drop(4)?.firstOrNull().toString() +
", " + stacktrace.drop(5)?.firstOrNull()?.toString() +
", " + stacktrace.drop(6)?.firstOrNull()?.toString()
)
}
}
}
return when(T::class) {
String::class -> this.expectOrThrow<V8ValueString>(config, contextName).value as T;
Int::class -> {

View file

@ -59,6 +59,9 @@ import com.futo.platformplayer.states.AnnouncementType
import com.futo.platformplayer.states.StateAnnouncement
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.OffsetDateTime
@ -771,6 +774,13 @@ open class JSClient : IPlatformClient {
return@busy handle();
}
}
fun <T> busyBlockingSuspended(handle: suspend ()->T): T {
return _plugin.busy {
return@busy runBlocking {
return@runBlocking handle();
}
}
}
fun <T> isBusyWith(actionName: String, handle: ()->T): T {
//val busyId = kotlin.random.Random.nextInt(9999);

View file

@ -32,7 +32,7 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
override val duration: Long;
override val priority: Boolean;
var url: String?;
val url: String?;
override var manifest: String?;
override val hasGenerate: Boolean;

View file

@ -53,43 +53,39 @@ abstract class JSSource {
hasRequestExecutor = _requestExecutor != null || obj.has("getRequestExecutor");
}
fun getRequestModifier(): IRequestModifier? {
fun getRequestModifier(): IRequestModifier? = _plugin.isBusyWith("getRequestModifier") {
if(_requestModifier != null)
return AdhocRequestModifier { url, headers ->
return@isBusyWith AdhocRequestModifier { url, headers ->
return@AdhocRequestModifier _requestModifier.modify(_plugin, url, headers);
};
if (!hasRequestModifier || _obj.isClosed)
return null;
return@isBusyWith null;
val result = _plugin.isBusyWith("getRequestModifier") {
V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") {
_obj.invoke("getRequestModifier", arrayOf<Any>());
};
}
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") {
_obj.invoke("getRequestModifier", arrayOf<Any>());
};
if (result !is V8ValueObject)
return null;
return@isBusyWith null;
return JSRequestModifier(_plugin, result)
return@isBusyWith JSRequestModifier(_plugin, result)
}
open fun getRequestExecutor(): JSRequestExecutor? {
open fun getRequestExecutor(): JSRequestExecutor? = _plugin.isBusyWith("getRequestExecutor") {
if (!hasRequestExecutor || _obj.isClosed)
return null;
return@isBusyWith null;
Logger.v("JSSource", "Request executor for [${type}] requesting");
val result =_plugin.isBusyWith("getRequestExecutor") {
V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") {
_obj.invoke("getRequestExecutor", arrayOf<Any>());
};
}
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") {
_obj.invoke("getRequestExecutor", arrayOf<Any>());
};
Logger.v("JSSource", "Request executor for [${type}] received");
if (result !is V8ValueObject)
return null;
return@isBusyWith null;
return JSRequestExecutor(_plugin, result)
return@isBusyWith JSRequestExecutor(_plugin, result)
}
fun getUnderlyingPlugin(): JSClient? {

View file

@ -49,6 +49,7 @@ class V8Plugin {
private val _clientAuth: ManagedHttpClient;
private val _clientOthers: ConcurrentHashMap<String, JSHttpClient> = ConcurrentHashMap();
val httpClient: ManagedHttpClient get() = _client;
val httpClientAuth: ManagedHttpClient get() = _clientAuth;
val httpClientOthers: Map<String, JSHttpClient> get() = _clientOthers;
@ -151,6 +152,8 @@ class V8Plugin {
if (!host.isIsolateCreated)
throw IllegalStateException("Isolate not created");
_runtimeMap.put(_runtime!!, this);
//Setup bridge
_runtime?.let {
it.converter = V8Converter();
@ -203,6 +206,7 @@ class V8Plugin {
}
_runtime?.let {
_runtimeMap.remove(it);
_runtime = null;
if(!it.isClosed && !it.isDead) {
try {
@ -222,6 +226,9 @@ class V8Plugin {
}
}
fun isThreadAlreadyBusy(): Boolean {
return _busyLock.isHeldByCurrentThread;
}
fun <T> 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());
@ -273,8 +280,14 @@ class V8Plugin {
private val REGEX_EX_FALLBACK = Regex(".*throw.*?[\"](.*)[\"].*");
private val REGEX_EX_FALLBACK2 = Regex(".*throw.*?['](.*)['].*");
private val _runtimeMap = ConcurrentHashMap<V8Runtime, V8Plugin>();
val TAG = "V8Plugin";
fun getPluginFromRuntime(runtime: V8Runtime): V8Plugin? {
return _runtimeMap.getOrDefault(runtime, null);
}
fun <T: Any?> catchScriptErrors(config: IV8PluginConfig, context: String, code: String? = null, handle: ()->T): T {
var codeStripped = code;
if(codeStripped != null) { //TODO: Improve code stripped

View file

@ -958,7 +958,7 @@ class StatePlatform {
//Comments
fun getComments(content: IPlatformContentDetails): IPager<IPlatformComment> {
val client = getContentClient(content.url);
val pager = content.getComments(client);
val pager = null;//content.getComments(client);
return pager ?: getComments(content.url);
}
@ -969,7 +969,7 @@ class StatePlatform {
return EmptyPager();
if(!StateApp.instance.privateMode)
return client.fromPool(_mainClientPool).getComments(url);
return client.fromPool(_pagerClientPool).getComments(url);
else
return client.fromPool(_privateClientPool).getComments(url);
}

View file

@ -353,8 +353,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
var videoSourceUsed = videoSource;
var audioSourceUsed = audioSource;
if(videoSource is JSDashManifestRawSource && audioSource is JSDashManifestRawAudioSource){
videoSourceUsed = JSDashManifestMergingRawSource(videoSource, audioSource);
audioSourceUsed = null;
videoSource.getUnderlyingPlugin()?.busy {
videoSourceUsed = JSDashManifestMergingRawSource(videoSource, audioSource);
audioSourceUsed = null;
}
}
val didSetVideo = swapSourceInternal(videoSourceUsed, play, resume);
@ -567,8 +569,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
var startId = -1;
try {
startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1;
val generated = videoSource.generate();
val plugin = videoSource.getUnderlyingPlugin() ?: return@launch;
startId = plugin.getUnderlyingPlugin()?.runtimeId ?: -1;
val generated = plugin.busy { videoSource.generate(); };
if (generated != null) {
withContext(Dispatchers.Main) {
val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource))