WIP: V8 update, package http fixes, ReloadRequiredException support, other fixes. Currently broken in situations where setTimeout is used

This commit is contained in:
Kelvin K 2025-06-14 15:51:31 +02:00
commit 58c9aeb1a2
13 changed files with 205 additions and 44 deletions

View file

@ -179,7 +179,8 @@ dependencies {
implementation 'com.google.code.gson:gson:2.10.1' //Used for complex/anonymous cases like during development conversions (eg. V8RemoteObject) implementation 'com.google.code.gson:gson:2.10.1' //Used for complex/anonymous cases like during development conversions (eg. V8RemoteObject)
//JS //JS
implementation("com.caoccao.javet:javet-android:3.0.2") //implementation("com.caoccao.javet:javet-android:3.0.2")
implementation 'com.caoccao.javet:javet-v8-android:4.1.4'
//Exoplayer //Exoplayer
implementation 'androidx.media3:media3-exoplayer:1.2.1' implementation 'androidx.media3:media3-exoplayer:1.2.1'

View file

@ -103,6 +103,12 @@ class UnavailableException extends ScriptException {
super("UnavailableException", msg); super("UnavailableException", msg);
} }
} }
class ReloadRequiredException extends ScriptException {
constructor(msg, reloadData) {
super("ReloadRequiredException", msg);
this.reloadData = reloadData;
}
}
class AgeException extends ScriptException { class AgeException extends ScriptException {
constructor(msg) { constructor(msg) {
super("AgeException", msg); super("AgeException", msg);

View file

@ -62,6 +62,7 @@ import com.futo.platformplayer.states.StatePlugins
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
import java.util.Random
import kotlin.Exception import kotlin.Exception
import kotlin.reflect.full.findAnnotations import kotlin.reflect.full.findAnnotations
import kotlin.reflect.jvm.kotlinFunction import kotlin.reflect.jvm.kotlinFunction
@ -106,6 +107,8 @@ open class JSClient : IPlatformClient {
return _busyAction; return _busyAction;
} }
val declareOnEnable = HashMap<String, String>();
val settings: HashMap<String, String?> get() = descriptor.settings; val settings: HashMap<String, String?> get() = descriptor.settings;
val flags: Array<String>; val flags: Array<String>;
@ -213,6 +216,10 @@ open class JSClient : IPlatformClient {
return plugin.httpClientOthers[id]; return plugin.httpClientOthers[id];
} }
fun setReloadData(data: String?) {
declareOnEnable.put("__reloadData", data ?: "");
}
override fun initialize() { override fun initialize() {
if (_initialized) return if (_initialized) return
@ -263,7 +270,13 @@ open class JSClient : IPlatformClient {
fun enable() { fun enable() {
if(!_initialized) if(!_initialized)
initialize(); initialize();
for(toDeclare in declareOnEnable) {
plugin.execute("var ${toDeclare.key} = " + Json.encodeToString(toDeclare.value));
}
plugin.execute("source.enable(${Json.encodeToString(config)}, parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())}), ${Json.encodeToString(_injectedSaveState)})"); plugin.execute("source.enable(${Json.encodeToString(config)}, parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())}), ${Json.encodeToString(_injectedSaveState)})");
if(declareOnEnable.containsKey("__reloadData"))
declareOnEnable.remove("__reloadData");
_enabled = true; _enabled = true;
} }
@JSDocs(1, "source.saveState()", "Provide a string that is passed to enable for quicker startup of multiple instances") @JSDocs(1, "source.saveState()", "Provide a string that is passed to enable for quicker startup of multiple instances")
@ -735,8 +748,12 @@ open class JSClient : IPlatformClient {
} }
private fun <T> isBusyWith(actionName: String, handle: ()->T): T {
fun <T> isBusyWith(actionName: String, handle: ()->T): T {
val busyId = kotlin.random.Random.nextInt(9999);
try { try {
Logger.v(TAG, "Busy with [${actionName}] (${busyId})")
synchronized(_busyLock) { synchronized(_busyLock) {
_busyCounter++; _busyCounter++;
} }
@ -748,6 +765,7 @@ open class JSClient : IPlatformClient {
synchronized(_busyLock) { synchronized(_busyLock) {
_busyCounter--; _busyCounter--;
} }
Logger.v(TAG, "Busy done [${actionName}] (${busyId})")
} }
} }
private fun <T> isBusyWith(handle: ()->T): T { private fun <T> isBusyWith(handle: ()->T): T {

View file

@ -62,13 +62,17 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
if(_plugin is DevJSClient) if(_plugin is DevJSClient)
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) { result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) {
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") { _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
_plugin.isBusyWith("dashAudio.generate") {
_obj.invokeString("generate"); _obj.invokeString("generate");
} }
} }
}
else else
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") { result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
_plugin.isBusyWith("dashAudio.generate") {
_obj.invokeString("generate"); _obj.invokeString("generate");
} }
}
if(result != null){ if(result != null){
val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0; val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0;

View file

@ -67,13 +67,17 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
if(_plugin is DevJSClient) { if(_plugin is DevJSClient) {
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") { result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") {
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", { _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
_plugin.isBusyWith("dashVideo.generate") {
_obj.invokeString("generate"); _obj.invokeString("generate");
}
}); });
} }
} }
else else
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", { result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
_plugin.isBusyWith("dashVideo.generate") {
_obj.invokeString("generate"); _obj.invokeString("generate");
}
}); });
if(result != null){ if(result != null){

View file

@ -75,9 +75,11 @@ abstract class JSSource {
if (!hasRequestExecutor || _obj.isClosed) if (!hasRequestExecutor || _obj.isClosed)
return null; return null;
Logger.v("JSSource", "Request executor for [${type}] requesting");
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { val result = 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");
if (result !is V8ValueObject) if (result !is V8ValueObject)
return null; return null;

View file

@ -15,6 +15,7 @@ import com.caoccao.javet.values.primitive.V8ValueString
import com.caoccao.javet.values.reference.V8ValueObject import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient 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.constructs.Event1
import com.futo.platformplayer.engine.exceptions.NoInternetException import com.futo.platformplayer.engine.exceptions.NoInternetException
import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException
@ -26,6 +27,7 @@ import com.futo.platformplayer.engine.exceptions.ScriptException
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptTimeoutException import com.futo.platformplayer.engine.exceptions.ScriptTimeoutException
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
import com.futo.platformplayer.engine.internal.V8Converter import com.futo.platformplayer.engine.internal.V8Converter
@ -186,6 +188,7 @@ class V8Plugin {
Logger.i(TAG, "Stopping plugin [${config.name}]"); Logger.i(TAG, "Stopping plugin [${config.name}]");
isStopped = true; isStopped = true;
whenNotBusy { whenNotBusy {
Logger.i(TAG, "Plugin stopping");
synchronized(_runtimeLock) { synchronized(_runtimeLock) {
isStopped = true; isStopped = true;
@ -200,7 +203,7 @@ class V8Plugin {
_runtime = null; _runtime = null;
if(!it.isClosed && !it.isDead) { if(!it.isClosed && !it.isDead) {
try { try {
it.close(); it.close(true);
} }
catch(ex: JavetException) { catch(ex: JavetException) {
//In case race conditions are going on, already closed runtimes are fine. //In case race conditions are going on, already closed runtimes are fine.
@ -211,6 +214,7 @@ class V8Plugin {
Logger.i(TAG, "Stopped plugin [${config.name}]"); Logger.i(TAG, "Stopped plugin [${config.name}]");
}; };
} }
Logger.i(TAG, "Plugin stopped");
onStopped.emit(this); onStopped.emit(this);
} }
} }
@ -327,14 +331,24 @@ class V8Plugin {
throw ScriptCompilationException(config, "Compilation: [${context}]: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped); 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) { catch(executeEx: JavetExecutionException) {
if(executeEx.scriptingError?.context?.containsKey("plugin_type") == true) { if(executeEx.scriptingError?.context is V8ValueObject) {
val pluginType = executeEx.scriptingError.context["plugin_type"].toString(); val obj = executeEx.scriptingError?.context as V8ValueObject
if(obj.has("plugin_type") == true) {
val pluginType = obj.get<V8ValueString>("plugin_type").toString();
//Captcha //Captcha
if (pluginType == "CaptchaRequiredException") { if (pluginType == "CaptchaRequiredException") {
throw ScriptCaptchaRequiredException(config, throw ScriptCaptchaRequiredException(config,
executeEx.scriptingError.context["url"]?.toString(), obj.get<V8ValueString>("url")?.toString(),
executeEx.scriptingError.context["body"]?.toString(), obj.get<V8ValueString>("body")?.toString(),
executeEx, executeEx.scriptingError?.stack, codeStripped);
}
//Reload Required
if (pluginType == "ReloadRequiredException") {
throw ScriptReloadRequiredException(config,
obj.get<V8ValueString>("message")?.toString(),
obj.get<V8ValueString>("reloadData")?.toString(),
executeEx, executeEx.scriptingError?.stack, codeStripped); executeEx, executeEx.scriptingError?.stack, codeStripped);
} }
@ -348,6 +362,8 @@ class V8Plugin {
codeStripped codeStripped
); );
} }
}
throw ScriptExecutionException(config, extractJSExceptionMessage(executeEx) ?: "", null, executeEx.scriptingError?.stack, codeStripped); throw ScriptExecutionException(config, extractJSExceptionMessage(executeEx) ?: "", null, executeEx.scriptingError?.stack, codeStripped);
} }
catch(ex: Exception) { catch(ex: Exception) {

View file

@ -0,0 +1,20 @@
package com.futo.platformplayer.engine.exceptions
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.engine.V8PluginConfig
import com.futo.platformplayer.getOrDefault
import com.futo.platformplayer.getOrThrow
class ScriptReloadRequiredException(config: IV8PluginConfig, val msg: String?, val reloadData: String?, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, msg ?: "ReloadRequired", ex, stack, code) {
companion object {
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException {
val contextName = "ScriptReloadRequiredException";
return ScriptReloadRequiredException(config,
obj.getOrThrow(config, "message", contextName),
obj.getOrDefault<String>(config, "reloadData", contextName, null));
}
}
}

View file

@ -78,6 +78,13 @@ class PackageBridge : V8Package {
return "android"; return "android";
} }
@V8Property
fun supportedFeatures(): Array<String> {
return arrayOf(
"ReloadRequiredException"
);
}
@V8Property @V8Property
fun supportedContent(): Array<Int> { fun supportedContent(): Array<Int> {
return arrayOf( return arrayOf(

View file

@ -44,6 +44,17 @@ class PackageHttp: V8Package {
private val aliveSockets = mutableListOf<SocketResult>(); private val aliveSockets = mutableListOf<SocketResult>();
private var _cleanedUp = false; private var _cleanedUp = false;
private val _clients = mutableMapOf<String, PackageHttpClient>()
fun getClient(id: String?): PackageHttpClient {
if(id == null)
throw IllegalArgumentException("Http client ${id} doesn't exist");
if(_packageClient.clientId() == id)
return _packageClient;
if(_packageClientAuth.clientId() == id)
return _packageClientAuth;
return _clients.getOrDefault(id, null) ?: throw IllegalArgumentException("Http client ${id} doesn't exist");
}
constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) { constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) {
_config = config; _config = config;
@ -112,6 +123,8 @@ class PackageHttp: V8Package {
_plugin.registerHttpClient(httpClient); _plugin.registerHttpClient(httpClient);
val client = PackageHttpClient(this, httpClient); val client = PackageHttpClient(this, httpClient);
_clients.put(client.clientId() ?: "", client);
return client; return client;
} }
@V8Function @V8Function
@ -246,18 +259,18 @@ class PackageHttp: V8Package {
@V8Function @V8Function
fun request(method: String, url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder { fun request(method: String, url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder {
return clientRequest(_package.getDefaultClient(useAuth), method, url, headers); return clientRequest(_package.getDefaultClient(useAuth).clientId(), method, url, headers);
} }
@V8Function @V8Function
fun requestWithBody(method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder { fun requestWithBody(method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder {
return clientRequestWithBody(_package.getDefaultClient(useAuth), method, url, body, headers); return clientRequestWithBody(_package.getDefaultClient(useAuth).clientId(), method, url, body, headers);
} }
@V8Function @V8Function
fun GET(url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder fun GET(url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder
= clientGET(_package.getDefaultClient(useAuth), url, headers); = clientGET(_package.getDefaultClient(useAuth).clientId(), url, headers);
@V8Function @V8Function
fun POST(url: String, body: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder fun POST(url: String, body: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BatchBuilder
= clientPOST(_package.getDefaultClient(useAuth), url, body, headers); = clientPOST(_package.getDefaultClient(useAuth).clientId(), url, body, headers);
@V8Function @V8Function
fun DUMMY(): BatchBuilder { fun DUMMY(): BatchBuilder {
@ -268,21 +281,21 @@ class PackageHttp: V8Package {
//Client-specific //Client-specific
@V8Function @V8Function
fun clientRequest(client: PackageHttpClient, method: String, url: String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder { fun clientRequest(clientId: String?, method: String, url: String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder {
_reqs.add(Pair(client, RequestDescriptor(method, url, headers))); _reqs.add(Pair(_package.getClient(clientId), RequestDescriptor(method, url, headers)));
return BatchBuilder(_package, _reqs); return BatchBuilder(_package, _reqs);
} }
@V8Function @V8Function
fun clientRequestWithBody(client: PackageHttpClient, method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder { fun clientRequestWithBody(clientId: String?, method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder {
_reqs.add(Pair(client, RequestDescriptor(method, url, headers, body))); _reqs.add(Pair(_package.getClient(clientId), RequestDescriptor(method, url, headers, body)));
return BatchBuilder(_package, _reqs); return BatchBuilder(_package, _reqs);
} }
@V8Function @V8Function
fun clientGET(client: PackageHttpClient, url: String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder fun clientGET(clientId: String?, url: String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder
= clientRequest(client, "GET", url, headers); = clientRequest(clientId, "GET", url, headers);
@V8Function @V8Function
fun clientPOST(client: PackageHttpClient, url: String, body: String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder fun clientPOST(clientId: String?, url: String, body: String, headers: MutableMap<String, String> = HashMap()) : BatchBuilder
= clientRequestWithBody(client, "POST", url, body, headers); = clientRequestWithBody(clientId, "POST", url, body, headers);
//Finalizer //Finalizer
@ -321,6 +334,7 @@ class PackageHttp: V8Package {
@Transient @Transient
private val _clientId: String?; private val _clientId: String?;
@V8Property @V8Property
fun clientId(): String? { fun clientId(): String? {
return _clientId; return _clientId;

View file

@ -93,6 +93,7 @@ import com.futo.platformplayer.engine.exceptions.ScriptAgeException
import com.futo.platformplayer.engine.exceptions.ScriptException import com.futo.platformplayer.engine.exceptions.ScriptException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
import com.futo.platformplayer.exceptions.UnsupportedCastException import com.futo.platformplayer.exceptions.UnsupportedCastException
import com.futo.platformplayer.fixHtmlLinks import com.futo.platformplayer.fixHtmlLinks
@ -608,6 +609,10 @@ class VideoDetailView : ConstraintLayout {
} }
} }
_player.onReloadRequired.subscribe {
fetchVideo();
}
_player.onPlayChanged.subscribe { _player.onPlayChanged.subscribe {
if (StateCasting.instance.activeDevice == null) { if (StateCasting.instance.activeDevice == null) {
handlePlayChanged(it); handlePlayChanged(it);
@ -3025,6 +3030,11 @@ class VideoDetailView : ConstraintLayout {
return@TaskHandler result; return@TaskHandler result;
}) })
.success { setVideoDetails(it, true) } .success { setVideoDetails(it, true) }
.exception<ScriptReloadRequiredException> {
StatePlatform.instance.handleReloadRequired(it, {
fetchVideo();
});
}
.exception<NoPlatformClientException> { .exception<NoPlatformClientException> {
Logger.w(TAG, "exception<NoPlatformClientException>", it) Logger.w(TAG, "exception<NoPlatformClientException>", it)

View file

@ -2,6 +2,7 @@ package com.futo.platformplayer.states
import android.content.Context import android.content.Context
import androidx.collection.LruCache import androidx.collection.LruCache
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
@ -38,6 +39,7 @@ import com.futo.platformplayer.awaitFirstNotNullDeferred
import com.futo.platformplayer.constructs.BatchedTaskHandler import com.futo.platformplayer.constructs.BatchedTaskHandler
import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException
import com.futo.platformplayer.fromPool import com.futo.platformplayer.fromPool
import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.getNowDiffDays
import com.futo.platformplayer.getNowDiffSeconds import com.futo.platformplayer.getNowDiffSeconds
@ -316,7 +318,18 @@ class StatePlatform {
_platformOrderPersistent.save(); _platformOrderPersistent.save();
} }
suspend fun reloadClient(context: Context, id: String) : JSClient? { fun handleReloadRequired(reloadRequiredException: ScriptReloadRequiredException, afterReload: (() -> Unit)? = null) {
val id = if(reloadRequiredException.config is SourcePluginConfig) reloadRequiredException.config.id else "";
UIDialogs.appToast("Reloading [${reloadRequiredException.config.name}] by plugin request");
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
if(!reloadRequiredException.reloadData.isNullOrEmpty())
reEnableClientWithData(id, reloadRequiredException.reloadData, afterReload);
else
reEnableClient(id, afterReload);
}
}
suspend fun reloadClient(context: Context, id: String, afterReload: (()->Unit)? = null) : JSClient? {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val client = getClient(id); val client = getClient(id);
if (client !is JSClient) if (client !is JSClient)
@ -347,10 +360,27 @@ class StatePlatform {
_availableClients.removeIf { it.id == id }; _availableClients.removeIf { it.id == id };
_availableClients.add(newClient); _availableClients.add(newClient);
} }
afterReload?.invoke();
return@withContext newClient; return@withContext newClient;
}; };
} }
suspend fun reEnableClientWithData(id: String, data: String? = null, afterReload: (()->Unit)? = null) {
val enabledBefore = getEnabledClients().map { it.id };
if(data != null) {
val client = getClientOrNull(id);
if(client != null && client is JSClient)
client.setReloadData(data);
}
selectClients({
_scope.launch(Dispatchers.IO) {
selectClients({
afterReload?.invoke();
}, *(enabledBefore).distinct().toTypedArray());
}
}, *(enabledBefore.filter { it != id }).distinct().toTypedArray())
}
suspend fun reEnableClient(id: String, afterReload: (()->Unit)? = null) = reEnableClientWithData(id, null, afterReload);
suspend fun enableClient(ids: List<String>) { suspend fun enableClient(ids: List<String>) {
val currentClients = getEnabledClients().map { it.id }; val currentClients = getEnabledClients().map { it.id };
@ -361,6 +391,9 @@ class StatePlatform {
* If a client is disabled, NO requests are made to said client * If a client is disabled, NO requests are made to said client
*/ */
suspend fun selectClients(vararg ids: String) { suspend fun selectClients(vararg ids: String) {
selectClients(null, *ids);
}
suspend fun selectClients(afterLoad: (() -> Unit)?, vararg ids: String) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
synchronized(_clientsLock) { synchronized(_clientsLock) {
val removed = _enabledClients.toMutableList(); val removed = _enabledClients.toMutableList();
@ -385,6 +418,7 @@ class StatePlatform {
onSourceDisabled.emit(oldClient); onSourceDisabled.emit(oldClient);
} }
} }
afterLoad?.invoke();
}; };
} }

View file

@ -52,10 +52,13 @@ import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManif
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException
import com.futo.platformplayer.helpers.VideoHelper import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.video.datasources.PluginMediaDrmCallback import com.futo.platformplayer.views.video.datasources.PluginMediaDrmCallback
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
@ -108,6 +111,8 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
val onPositionDiscontinuity = Event1<Long>(); val onPositionDiscontinuity = Event1<Long>();
val onDatasourceError = Event1<Throwable>(); val onDatasourceError = Event1<Throwable>();
val onReloadRequired = Event0();
private var _didCallSourceChange = false; private var _didCallSourceChange = false;
private var _lastState: Int = -1; private var _lastState: Int = -1;
@ -585,6 +590,12 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
} }
} }
} }
catch(reloadRequired: ScriptReloadRequiredException) {
Logger.i(TAG, "Reload required detected");
StatePlatform.instance.handleReloadRequired(reloadRequired, {
onReloadRequired.emit();
});
}
catch(ex: Throwable) { catch(ex: Throwable) {
Logger.e(TAG, "DashRaw generator failed", ex); Logger.e(TAG, "DashRaw generator failed", ex);
} }
@ -677,6 +688,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
if(audioSource.hasGenerate) { if(audioSource.hasGenerate) {
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
try {
val generated = audioSource.generate(); val generated = audioSource.generate();
if(generated != null) { if(generated != null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -687,6 +699,19 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
} }
} }
} }
catch(reloadRequired: ScriptReloadRequiredException) {
Logger.i(TAG, "Reload required detected");
val plugin = audioSource.getUnderlyingPlugin();
if(plugin == null)
return@launch;
StatePlatform.instance.reEnableClient(plugin.id, {
onReloadRequired.emit();
});
}
catch(ex: Throwable) {
}
}
return false; return false;
} }
else { else {