mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-09-27 19:59:07 +00:00
Merge branch 'wip-async' into 'master'
Basic Async support See merge request videostreaming/grayjay!132
This commit is contained in:
commit
2c463dd5a1
7 changed files with 388 additions and 11 deletions
|
@ -2,12 +2,30 @@ package com.futo.platformplayer
|
|||
|
||||
import com.caoccao.javet.values.V8Value
|
||||
import com.caoccao.javet.values.primitive.*
|
||||
import com.caoccao.javet.values.reference.IV8ValuePromise
|
||||
import com.caoccao.javet.values.reference.V8ValueArray
|
||||
import com.caoccao.javet.values.reference.V8ValueError
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.caoccao.javet.values.reference.V8ValuePromise
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.selects.SelectClause0
|
||||
import kotlinx.coroutines.selects.SelectClause1
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.AbstractCoroutineContextElement
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.reflect.jvm.internal.impl.load.kotlin.JvmType
|
||||
|
||||
|
||||
//V8
|
||||
|
@ -174,4 +192,119 @@ fun V8ObjectToHashMap(obj: V8ValueObject?): HashMap<String, String> {
|
|||
for(prop in obj.ownPropertyNames.keys.map { obj.ownPropertyNames.get<V8Value>(it).toString() })
|
||||
map.put(prop, obj.getString(prop));
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
||||
val latch = CountDownLatch(1);
|
||||
var promiseResult: T? = null;
|
||||
var promiseException: Throwable? = null;
|
||||
plugin.busy {
|
||||
this.register(object: IV8ValuePromise.IListener {
|
||||
override fun onFulfilled(p0: V8Value?) {
|
||||
if(p0 is V8ValueError)
|
||||
promiseException = ScriptExecutionException(plugin.config, p0.message);
|
||||
else
|
||||
promiseResult = p0 as T;
|
||||
latch.countDown();
|
||||
}
|
||||
override fun onRejected(p0: V8Value?) {
|
||||
promiseException = (NotImplementedError("onRejected promise not implemented.."));
|
||||
latch.countDown();
|
||||
}
|
||||
override fun onCatch(p0: V8Value?) {
|
||||
promiseException = (NotImplementedError("onCatch promise not implemented.."));
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
plugin.registerPromise(this) {
|
||||
promiseException = CancellationException("Cancelled by system");
|
||||
latch.countDown();
|
||||
}
|
||||
plugin.unbusy {
|
||||
latch.await();
|
||||
}
|
||||
if(promiseException != null)
|
||||
throw promiseException!!;
|
||||
return promiseResult!!;
|
||||
}
|
||||
fun <T: V8Value> V8ValuePromise.toV8ValueAsync(plugin: V8Plugin): V8Deferred<T> {
|
||||
val underlyingDef = CompletableDeferred<T>();
|
||||
val def = if(this.has("estDuration"))
|
||||
V8Deferred(underlyingDef,
|
||||
this.getOrDefault(plugin.config, "estDuration", "toV8ValueAsync", -1) ?: -1);
|
||||
else
|
||||
V8Deferred<T>(underlyingDef);
|
||||
|
||||
val promise = this;
|
||||
plugin.busy {
|
||||
this.register(object: IV8ValuePromise.IListener {
|
||||
override fun onFulfilled(p0: V8Value?) {
|
||||
plugin.resolvePromise(promise);
|
||||
underlyingDef.complete(p0 as T);
|
||||
}
|
||||
override fun onRejected(p0: V8Value?) {
|
||||
plugin.resolvePromise(promise);
|
||||
underlyingDef.completeExceptionally(NotImplementedError("onRejected promise not implemented.."));
|
||||
}
|
||||
override fun onCatch(p0: V8Value?) {
|
||||
plugin.resolvePromise(promise);
|
||||
underlyingDef.completeExceptionally(NotImplementedError("onCatch promise not implemented.."));
|
||||
}
|
||||
});
|
||||
}
|
||||
plugin.registerPromise(promise) {
|
||||
if(def.isActive)
|
||||
def.cancel("Cancelled by system");
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
class V8Deferred<T>(val deferred: Deferred<T>, val estDuration: Int = -1): Deferred<T> by deferred {
|
||||
|
||||
fun <R> convert(conversion: (result: T)->R): V8Deferred<R>{
|
||||
val newDef = CompletableDeferred<R>()
|
||||
this.invokeOnCompletion {
|
||||
if(it != null)
|
||||
newDef.completeExceptionally(it);
|
||||
else
|
||||
newDef.complete(conversion(this@V8Deferred.getCompleted()));
|
||||
}
|
||||
|
||||
return V8Deferred<R>(newDef, estDuration);
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun <T, R> merge(scope: CoroutineScope, defs: List<V8Deferred<T>>, conversion: (result: List<T>)->R): V8Deferred<R> {
|
||||
|
||||
var amount = -1;
|
||||
for(def in defs)
|
||||
amount = Math.max(amount, def.estDuration);
|
||||
|
||||
val def = scope.async {
|
||||
val results = defs.map { it.await() };
|
||||
return@async conversion(results);
|
||||
}
|
||||
return V8Deferred(def, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun <T: V8Value> V8ValueObject.invokeV8(method: String, vararg obj: Any): T {
|
||||
var result = this.invoke<V8Value>(method, *obj);
|
||||
if(result is V8ValuePromise) {
|
||||
return result.toV8ValueBlocking(this.getSourcePlugin()!!);
|
||||
}
|
||||
return result as T;
|
||||
}
|
||||
fun <T: V8Value> V8ValueObject.invokeV8Async(method: String, vararg obj: Any): V8Deferred<T> {
|
||||
var result = this.invoke<V8Value>(method, *obj);
|
||||
if(result is V8ValuePromise) {
|
||||
return result.toV8ValueAsync(this.getSourcePlugin()!!);
|
||||
}
|
||||
return V8Deferred(CompletableDeferred(result as T));
|
||||
}
|
|
@ -632,7 +632,6 @@ open class JSClient : IPlatformClient {
|
|||
plugin.executeTyped("source.getLiveEvents(${Json.encodeToString(url)})"));
|
||||
}
|
||||
|
||||
|
||||
@JSDocs(19, "source.getContentRecommendations(url)", "Gets recommendations of a content page")
|
||||
@JSDocsParameter("url", "Url of content")
|
||||
override fun getContentRecommendations(url: String): IPager<IPlatformContent>? = isBusyWith("getContentRecommendations") {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.primitive.V8ValueString
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.V8Deferred
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
|
@ -13,8 +15,13 @@ import com.futo.platformplayer.engine.V8Plugin
|
|||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Async
|
||||
import com.futo.platformplayer.others.Language
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
|
||||
class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawSource, IStreamMetaDataSource {
|
||||
override val container : String;
|
||||
|
@ -50,6 +57,44 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
|||
hasGenerate = _obj.has("generate");
|
||||
}
|
||||
|
||||
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
||||
if(!hasGenerate)
|
||||
return V8Deferred(CompletableDeferred(manifest));
|
||||
if(_obj.isClosed)
|
||||
throw IllegalStateException("Source object already closed");
|
||||
|
||||
val plugin = _plugin.getUnderlyingPlugin();
|
||||
|
||||
var result: V8Deferred<V8ValueString>? = null;
|
||||
if(_plugin is DevJSClient)
|
||||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
}
|
||||
|
||||
return plugin.busy {
|
||||
val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val initEnd = _obj.getOrDefault<Int>(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexStart = _obj.getOrDefault<Int>(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexEnd = _obj.getOrDefault<Int>(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
if(initEnd > 0 && indexStart > 0 && indexEnd > 0) {
|
||||
streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd);
|
||||
}
|
||||
|
||||
return@busy result.convert {
|
||||
it.value
|
||||
};
|
||||
}
|
||||
}
|
||||
override fun generate(): String? {
|
||||
if(!hasGenerate)
|
||||
return manifest;
|
||||
|
@ -63,14 +108,14 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
|||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
_obj.invokeString("generate");
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
_obj.invokeString("generate");
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
|||
import com.caoccao.javet.values.V8Value
|
||||
import com.caoccao.javet.values.primitive.V8ValueString
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.V8Deferred
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
|
@ -15,11 +16,18 @@ import com.futo.platformplayer.engine.V8Plugin
|
|||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Async
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
interface IJSDashManifestRawSource {
|
||||
val hasGenerate: Boolean;
|
||||
var manifest: String?;
|
||||
fun generateAsync(scope: CoroutineScope): Deferred<String?>;
|
||||
fun generate(): String?;
|
||||
}
|
||||
open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSource, IStreamMetaDataSource {
|
||||
|
@ -57,6 +65,45 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
|
|||
hasGenerate = _obj.has("generate");
|
||||
}
|
||||
|
||||
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
||||
if(!hasGenerate)
|
||||
return V8Deferred(CompletableDeferred(manifest));
|
||||
if(_obj.isClosed)
|
||||
throw IllegalStateException("Source object already closed");
|
||||
|
||||
val plugin = _plugin.getUnderlyingPlugin();
|
||||
|
||||
var result: V8Deferred<V8ValueString>? = null;
|
||||
if(_plugin is DevJSClient) {
|
||||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
});
|
||||
|
||||
return plugin.busy {
|
||||
val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val initEnd = _obj.getOrDefault<Int>(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexStart = _obj.getOrDefault<Int>(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexEnd = _obj.getOrDefault<Int>(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
if(initEnd > 0 && indexStart > 0 && indexEnd > 0) {
|
||||
streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd);
|
||||
}
|
||||
|
||||
return@busy result.convert {
|
||||
it.value
|
||||
};
|
||||
}
|
||||
}
|
||||
override open fun generate(): String? {
|
||||
if(!hasGenerate)
|
||||
return manifest;
|
||||
|
@ -68,7 +115,7 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
|
|||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
_obj.invokeString("generate");
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -76,7 +123,7 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
|
|||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
_obj.invokeString("generate");
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -116,6 +163,32 @@ class JSDashManifestMergingRawSource(
|
|||
override val priority: Boolean
|
||||
get() = video.priority;
|
||||
|
||||
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
||||
val videoDashDef = video.generateAsync(scope);
|
||||
val audioDashDef = audio.generateAsync(scope);
|
||||
|
||||
return V8Deferred.merge(scope, listOf(videoDashDef, audioDashDef)) {
|
||||
val (videoDash: String?, audioDash: String?) = it;
|
||||
|
||||
if (videoDash != null && audioDash == null) return@merge videoDash;
|
||||
if (audioDash != null && videoDash == null) return@merge audioDash;
|
||||
if (videoDash == null) return@merge null;
|
||||
|
||||
//TODO: Temporary simple solution..make more reliable version
|
||||
|
||||
var result: String? = null;
|
||||
val audioAdaptationSet = adaptationSetRegex.find(audioDash!!);
|
||||
if (audioAdaptationSet != null) {
|
||||
result = videoDash.replace(
|
||||
"</AdaptationSet>",
|
||||
"</AdaptationSet>\n" + audioAdaptationSet.value
|
||||
)
|
||||
} else
|
||||
result = videoDash;
|
||||
|
||||
return@merge result;
|
||||
};
|
||||
}
|
||||
override fun generate(): String? {
|
||||
val videoDash = video.generate();
|
||||
val audioDash = audio.generate();
|
||||
|
|
|
@ -10,7 +10,9 @@ import com.caoccao.javet.values.V8Value
|
|||
import com.caoccao.javet.values.primitive.V8ValueBoolean
|
||||
import com.caoccao.javet.values.primitive.V8ValueInteger
|
||||
import com.caoccao.javet.values.primitive.V8ValueString
|
||||
import com.caoccao.javet.values.reference.IV8ValuePromise
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.caoccao.javet.values.reference.V8ValuePromise
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
|
@ -37,7 +39,15 @@ import com.futo.platformplayer.engine.packages.V8Package
|
|||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StateAssets
|
||||
import com.futo.platformplayer.toList
|
||||
import com.futo.platformplayer.toV8ValueBlocking
|
||||
import com.futo.platformplayer.toV8ValueAsync
|
||||
import com.futo.platformplayer.warnIfMainThread
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
@ -48,6 +58,7 @@ class V8Plugin {
|
|||
private val _clientAuth: ManagedHttpClient;
|
||||
private val _clientOthers: ConcurrentHashMap<String, JSHttpClient> = ConcurrentHashMap();
|
||||
|
||||
private val _promises = ConcurrentHashMap<V8ValuePromise, ((V8ValuePromise)->Unit)?>();
|
||||
|
||||
val httpClient: ManagedHttpClient get() = _client;
|
||||
val httpClientAuth: ManagedHttpClient get() = _clientAuth;
|
||||
|
@ -223,37 +234,144 @@ class V8Plugin {
|
|||
Logger.i(TAG, "Plugin stopped");
|
||||
onStopped.emit(this);
|
||||
}
|
||||
cancelAllPromises();
|
||||
}
|
||||
|
||||
fun isThreadAlreadyBusy(): Boolean {
|
||||
return _busyLock.isHeldByCurrentThread;
|
||||
}
|
||||
fun <T> busy(handle: ()->T): T {
|
||||
_busyLock.lock();
|
||||
try {
|
||||
return handle();
|
||||
}
|
||||
finally {
|
||||
_busyLock.unlock();
|
||||
}
|
||||
/*
|
||||
_busyLock.withLock {
|
||||
//Logger.i(TAG, "Entered busy: " + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString());
|
||||
return handle();
|
||||
}*/
|
||||
}
|
||||
fun <T> unbusy(handle: ()->T): T {
|
||||
val wasLocked = isThreadAlreadyBusy();
|
||||
if(!wasLocked)
|
||||
return handle();
|
||||
val lockCount = _busyLock.holdCount;
|
||||
for(i in 1..lockCount)
|
||||
_busyLock.unlock();
|
||||
try {
|
||||
Logger.w(TAG, "Unlocking V8 thread for [${config.name}] for a blocking resolve of a promise")
|
||||
return handle();
|
||||
}
|
||||
finally {
|
||||
Logger.w(TAG, "Relocking V8 thread for [${config.name}] for a blocking resolve of a promise")
|
||||
|
||||
for(i in 1..lockCount)
|
||||
_busyLock.lock();
|
||||
}
|
||||
}
|
||||
fun execute(js: String) : V8Value {
|
||||
return executeTyped<V8Value>(js);
|
||||
}
|
||||
|
||||
suspend fun <T : V8Value> executeTypedAsync(js: String) : Deferred<T> {
|
||||
warnIfMainThread("V8Plugin.executeTypedAsync");
|
||||
if(isStopped)
|
||||
throw PluginEngineStoppedException(config, "Instance is stopped", js);
|
||||
|
||||
return withContext(IO) {
|
||||
return@withContext busy {
|
||||
try {
|
||||
val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet");
|
||||
val result = catchScriptErrors<V8Value>("Plugin[${config.name}]", js) {
|
||||
runtime.getExecutor(js).execute()
|
||||
};
|
||||
|
||||
if (result is V8ValuePromise) {
|
||||
return@busy result.toV8ValueAsync<T>(this@V8Plugin);
|
||||
} else
|
||||
return@busy CompletableDeferred(result as T);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
val def = CompletableDeferred<T>();
|
||||
def.completeExceptionally(ex);
|
||||
return@busy def;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun <T : V8Value> executeTyped(js: String) : T {
|
||||
warnIfMainThread("V8Plugin.executeTyped");
|
||||
if(isStopped)
|
||||
throw PluginEngineStoppedException(config, "Instance is stopped", js);
|
||||
|
||||
return busy {
|
||||
|
||||
val result = busy {
|
||||
val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet");
|
||||
return@busy catchScriptErrors("Plugin[${config.name}]", js) {
|
||||
return@busy catchScriptErrors<V8Value>("Plugin[${config.name}]", js) {
|
||||
runtime.getExecutor(js).execute()
|
||||
};
|
||||
};
|
||||
if(result is V8ValuePromise) {
|
||||
return result.toV8ValueBlocking(this@V8Plugin);
|
||||
}
|
||||
return result as T;
|
||||
}
|
||||
fun executeBoolean(js: String) : Boolean? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped<V8ValueBoolean>(js).value } }
|
||||
fun executeString(js: String) : String? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped<V8ValueString>(js).value } }
|
||||
fun executeInteger(js: String) : Int? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped<V8ValueInteger>(js).value } }
|
||||
|
||||
|
||||
fun <T: V8Value> handlePromise(result: V8ValuePromise): CompletableDeferred<T> {
|
||||
val def = CompletableDeferred<T>();
|
||||
result.register(object: IV8ValuePromise.IListener {
|
||||
override fun onFulfilled(p0: V8Value?) {
|
||||
resolvePromise(result);
|
||||
def.complete(p0 as T);
|
||||
}
|
||||
override fun onRejected(p0: V8Value?) {
|
||||
resolvePromise(result);
|
||||
def.completeExceptionally(NotImplementedError("onRejected promise not implemented.."));
|
||||
}
|
||||
override fun onCatch(p0: V8Value?) {
|
||||
resolvePromise(result);
|
||||
def.completeExceptionally(NotImplementedError("onCatch promise not implemented.."));
|
||||
}
|
||||
});
|
||||
registerPromise(result) {
|
||||
if(def.isActive)
|
||||
def.cancel("Cancelled by system");
|
||||
}
|
||||
return def;
|
||||
}
|
||||
fun registerPromise(promise: V8ValuePromise, onCancelled: ((V8ValuePromise)->Unit)? = null) {
|
||||
Logger.v(TAG, "Promise registered for plugin [${config.name}]: ${promise.hashCode()}");
|
||||
if (onCancelled != null) {
|
||||
_promises.put(promise, onCancelled)
|
||||
};
|
||||
}
|
||||
fun resolvePromise(promise: V8ValuePromise, cancelled: Boolean = false) {
|
||||
Logger.v(TAG, "Promise resolved for plugin [${config.name}]: ${promise.hashCode()}");
|
||||
val found = synchronized(_promises) {
|
||||
val found = _promises.getOrDefault(promise, null);
|
||||
_promises.remove(promise);
|
||||
return@synchronized found;
|
||||
};
|
||||
if(found != null && cancelled)
|
||||
found(promise);
|
||||
}
|
||||
fun cancelAllPromises(){
|
||||
val promises = _promises.keys().toList();
|
||||
for(key in promises) {
|
||||
try {
|
||||
resolvePromise(key, true);
|
||||
}
|
||||
catch(ex: Throwable) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getPackage(packageName: String, allowNull: Boolean = false): V8Package? {
|
||||
//TODO: Auto get all package types?
|
||||
return when(packageName) {
|
||||
|
|
|
@ -84,7 +84,8 @@ class PackageBridge : V8Package {
|
|||
fun supportedFeatures(): Array<String> {
|
||||
return arrayOf(
|
||||
"ReloadRequiredException",
|
||||
"HttpBatchClient"
|
||||
"HttpBatchClient",
|
||||
"Async"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -130,9 +131,12 @@ class PackageBridge : V8Package {
|
|||
}
|
||||
timeoutMap.remove(id);
|
||||
try {
|
||||
Logger.w(TAG, "setTimeout before busy (${timeout}): ${_plugin.isBusy}");
|
||||
_plugin.busy {
|
||||
Logger.w(TAG, "setTimeout in busy");
|
||||
if (!_plugin.isStopped)
|
||||
funcClone.callVoid(null, arrayOf<Any>());
|
||||
Logger.w(TAG, "setTimeout after");
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Failed timeout callback", ex);
|
||||
|
|
|
@ -567,11 +567,13 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
|
||||
if(videoSource.hasGenerate) {
|
||||
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
||||
val scope = this;
|
||||
var startId = -1;
|
||||
try {
|
||||
val plugin = videoSource.getUnderlyingPlugin() ?: return@launch;
|
||||
startId = plugin.getUnderlyingPlugin()?.runtimeId ?: -1;
|
||||
val generated = plugin.busy { videoSource.generate(); };
|
||||
val generatedDef = plugin.busy { videoSource.generateAsync(scope); };
|
||||
val generated = generatedDef.await();
|
||||
if (generated != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource))
|
||||
|
@ -694,10 +696,13 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
Logger.i(TAG, "Loading AudioSource [DashRaw]");
|
||||
if(audioSource.hasGenerate) {
|
||||
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
||||
val scope = this;
|
||||
var startId = -1;
|
||||
try {
|
||||
val plugin = audioSource.getUnderlyingPlugin() ?: return@launch;
|
||||
startId = audioSource.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1;
|
||||
val generated = audioSource.generate();
|
||||
val generatedDef = plugin.busy { audioSource.generateAsync(scope); }
|
||||
val generated = generatedDef.await();
|
||||
if(generated != null) {
|
||||
val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource))
|
||||
audioSource.getHttpDataSourceFactory()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue