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.V8ValueArray
import com.caoccao.javet.values.reference.V8ValueObject import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.logging.Logger
//V8 //V8
@ -24,6 +26,10 @@ fun <R> V8Value?.orDefault(default: R, handler: (V8Value)->R): R {
return handler(this); return handler(this);
} }
inline fun V8Value.getSourcePlugin(): V8Plugin? {
return V8Plugin.getPluginFromRuntime(this.v8Runtime);
}
inline fun <reified T> V8Value.expectOrThrow(config: IV8PluginConfig, contextName: String): T { inline fun <reified T> V8Value.expectOrThrow(config: IV8PluginConfig, contextName: String): T {
if(this !is T) if(this !is T)
throw ScriptImplementationException(config, "Expected ${contextName} to be of type ${T::class.simpleName}, but found ${this::class.simpleName}"); 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 { 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) { return when(T::class) {
String::class -> this.expectOrThrow<V8ValueString>(config, contextName).value as T; String::class -> this.expectOrThrow<V8ValueString>(config, contextName).value as T;
Int::class -> { 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.StateAnnouncement
import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins 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.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.time.OffsetDateTime import java.time.OffsetDateTime
@ -771,6 +774,13 @@ open class JSClient : IPlatformClient {
return@busy handle(); 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 { fun <T> isBusyWith(actionName: String, handle: ()->T): T {
//val busyId = kotlin.random.Random.nextInt(9999); //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 duration: Long;
override val priority: Boolean; override val priority: Boolean;
var url: String?; val url: String?;
override var manifest: String?; override var manifest: String?;
override val hasGenerate: Boolean; override val hasGenerate: Boolean;

View file

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

View file

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

View file

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

View file

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