mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-17 07:50:15 +00:00
Merge branch 'plugin-fixes' into 'master'
V8 Update, V8 interaction locking, Package fixes, ReloadRequiredException support See merge request videostreaming/grayjay!125
This commit is contained in:
commit
0b02ab0e2d
22 changed files with 521 additions and 243 deletions
|
@ -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'
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -56,6 +56,7 @@ class DevJSClient : JSClient {
|
||||||
|
|
||||||
override fun getCopy(privateCopy: Boolean, noSaveState: Boolean): JSClient {
|
override fun getCopy(privateCopy: Boolean, noSaveState: Boolean): JSClient {
|
||||||
val client = DevJSClient(_context, descriptor, _script, if(!privateCopy) _auth else null, _captcha, if (noSaveState) null else saveState(), devID);
|
val client = DevJSClient(_context, descriptor, _script, if(!privateCopy) _auth else null, _captcha, if (noSaveState) null else saveState(), devID);
|
||||||
|
client.setReloadData(getReloadData(true));
|
||||||
if (noSaveState)
|
if (noSaveState)
|
||||||
client.initialize()
|
client.initialize()
|
||||||
return client
|
return client
|
||||||
|
|
|
@ -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
|
||||||
|
@ -83,6 +84,8 @@ open class JSClient : IPlatformClient {
|
||||||
private var _channelCapabilities: ResultCapabilities? = null;
|
private var _channelCapabilities: ResultCapabilities? = null;
|
||||||
private var _peekChannelTypes: List<String>? = null;
|
private var _peekChannelTypes: List<String>? = null;
|
||||||
|
|
||||||
|
private var _usedReloadData: String? = null;
|
||||||
|
|
||||||
protected val _script: String;
|
protected val _script: String;
|
||||||
|
|
||||||
private var _initialized: Boolean = false;
|
private var _initialized: Boolean = false;
|
||||||
|
@ -98,14 +101,14 @@ open class JSClient : IPlatformClient {
|
||||||
override val icon: ImageVariable;
|
override val icon: ImageVariable;
|
||||||
override var capabilities: PlatformClientCapabilities = PlatformClientCapabilities();
|
override var capabilities: PlatformClientCapabilities = PlatformClientCapabilities();
|
||||||
|
|
||||||
private val _busyLock = Object();
|
|
||||||
private var _busyCounter = 0;
|
|
||||||
private var _busyAction = "";
|
private var _busyAction = "";
|
||||||
val isBusy: Boolean get() = _busyCounter > 0;
|
val isBusy: Boolean get() = _plugin.isBusy;
|
||||||
val isBusyAction: String get() {
|
val isBusyAction: String get() {
|
||||||
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>;
|
||||||
|
@ -197,6 +200,7 @@ open class JSClient : IPlatformClient {
|
||||||
|
|
||||||
open fun getCopy(withoutCredentials: Boolean = false, noSaveState: Boolean = false): JSClient {
|
open fun getCopy(withoutCredentials: Boolean = false, noSaveState: Boolean = false): JSClient {
|
||||||
val client = JSClient(_context, descriptor, if (noSaveState) null else saveState(), _script, withoutCredentials);
|
val client = JSClient(_context, descriptor, if (noSaveState) null else saveState(), _script, withoutCredentials);
|
||||||
|
client.setReloadData(getReloadData(true));
|
||||||
if (noSaveState)
|
if (noSaveState)
|
||||||
client.initialize()
|
client.initialize()
|
||||||
return client
|
return client
|
||||||
|
@ -213,14 +217,31 @@ open class JSClient : IPlatformClient {
|
||||||
return plugin.httpClientOthers[id];
|
return plugin.httpClientOthers[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setReloadData(data: String?) {
|
||||||
|
if(data == null) {
|
||||||
|
if(declareOnEnable.containsKey("__reloadData"))
|
||||||
|
declareOnEnable.remove("__reloadData");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
declareOnEnable.put("__reloadData", data ?: "");
|
||||||
|
}
|
||||||
|
fun getReloadData(orLast: Boolean): String? {
|
||||||
|
if(declareOnEnable.containsKey("__reloadData"))
|
||||||
|
return declareOnEnable["__reloadData"];
|
||||||
|
else if(orLast)
|
||||||
|
return _usedReloadData;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
if (_initialized) return
|
if (_initialized) return
|
||||||
|
|
||||||
Logger.i(TAG, "Plugin [${config.name}] initializing");
|
|
||||||
plugin.start();
|
plugin.start();
|
||||||
|
|
||||||
plugin.execute("plugin.config = ${Json.encodeToString(config)}");
|
plugin.execute("plugin.config = ${Json.encodeToString(config)}");
|
||||||
plugin.execute("plugin.settings = parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())})");
|
plugin.execute("plugin.settings = parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())})");
|
||||||
|
|
||||||
|
|
||||||
descriptor.appSettings.loadDefaults(descriptor.config);
|
descriptor.appSettings.loadDefaults(descriptor.config);
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
|
@ -263,7 +284,16 @@ 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")) {
|
||||||
|
Logger.i(TAG, "Plugin [${config.name}] enabled with reload data: ${declareOnEnable["__reloadData"]}");
|
||||||
|
_usedReloadData = declareOnEnable["__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")
|
||||||
|
@ -552,7 +582,7 @@ open class JSClient : IPlatformClient {
|
||||||
Logger.i(TAG, "JSClient.getPlaybackTracker(${url})");
|
Logger.i(TAG, "JSClient.getPlaybackTracker(${url})");
|
||||||
val tracker = plugin.executeTyped<V8Value>("source.getPlaybackTracker(${Json.encodeToString(url)})");
|
val tracker = plugin.executeTyped<V8Value>("source.getPlaybackTracker(${Json.encodeToString(url)})");
|
||||||
if(tracker is V8ValueObject)
|
if(tracker is V8ValueObject)
|
||||||
return@isBusyWith JSPlaybackTracker(config, tracker);
|
return@isBusyWith JSPlaybackTracker(this, tracker);
|
||||||
else
|
else
|
||||||
return@isBusyWith null;
|
return@isBusyWith null;
|
||||||
}
|
}
|
||||||
|
@ -734,19 +764,22 @@ open class JSClient : IPlatformClient {
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> busy(handle: ()->T): T {
|
||||||
private fun <T> isBusyWith(actionName: String, handle: ()->T): T {
|
return _plugin.busy {
|
||||||
try {
|
return@busy handle();
|
||||||
synchronized(_busyLock) {
|
|
||||||
_busyCounter++;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> isBusyWith(actionName: String, handle: ()->T): T {
|
||||||
|
//val busyId = kotlin.random.Random.nextInt(9999);
|
||||||
|
return busy {
|
||||||
|
try {
|
||||||
_busyAction = actionName;
|
_busyAction = actionName;
|
||||||
return handle();
|
return@busy handle();
|
||||||
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
_busyAction = "";
|
_busyAction = "";
|
||||||
synchronized(_busyLock) {
|
|
||||||
_busyCounter--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,25 @@ class JSHttpClient : ManagedHttpClient {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resetAuthCookies() {
|
||||||
|
_currentCookieMap.clear();
|
||||||
|
if(!_auth?.cookieMap.isNullOrEmpty()) {
|
||||||
|
for(domainCookies in _auth!!.cookieMap!!)
|
||||||
|
_currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value));
|
||||||
|
}
|
||||||
|
if(!_captcha?.cookieMap.isNullOrEmpty()) {
|
||||||
|
for(domainCookies in _captcha!!.cookieMap!!) {
|
||||||
|
if(_currentCookieMap.containsKey(domainCookies.key))
|
||||||
|
_currentCookieMap[domainCookies.key]?.putAll(domainCookies.value);
|
||||||
|
else
|
||||||
|
_currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun clearOtherCookies() {
|
||||||
|
_otherCookieMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
override fun clone(): ManagedHttpClient {
|
override fun clone(): ManagedHttpClient {
|
||||||
val newClient = JSHttpClient(_jsClient, _auth);
|
val newClient = JSHttpClient(_jsClient, _auth);
|
||||||
newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) })
|
newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) })
|
||||||
|
|
|
@ -29,7 +29,9 @@ abstract class JSPager<T> : IPager<T> {
|
||||||
this.pager = pager;
|
this.pager = pager;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
|
plugin.busy {
|
||||||
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
||||||
|
}
|
||||||
getResults();
|
getResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +46,14 @@ abstract class JSPager<T> : IPager<T> {
|
||||||
override fun nextPage() {
|
override fun nextPage() {
|
||||||
warnIfMainThread("JSPager.nextPage");
|
warnIfMainThread("JSPager.nextPage");
|
||||||
|
|
||||||
pager = plugin.getUnderlyingPlugin().catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") {
|
val pluginV8 = plugin.getUnderlyingPlugin();
|
||||||
|
pluginV8.busy {
|
||||||
|
pager = pluginV8.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") {
|
||||||
pager.invoke("nextPage", arrayOf<Any>());
|
pager.invoke("nextPage", arrayOf<Any>());
|
||||||
};
|
};
|
||||||
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
||||||
_resultChanged = true;
|
_resultChanged = true;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
try {
|
try {
|
||||||
}
|
}
|
||||||
|
@ -70,15 +75,18 @@ abstract class JSPager<T> : IPager<T> {
|
||||||
return previousResults;
|
return previousResults;
|
||||||
|
|
||||||
warnIfMainThread("JSPager.getResults");
|
warnIfMainThread("JSPager.getResults");
|
||||||
|
|
||||||
|
return plugin.getUnderlyingPlugin().busy {
|
||||||
val items = pager.getOrThrow<V8ValueArray>(config, "results", "JSPager");
|
val items = pager.getOrThrow<V8ValueArray>(config, "results", "JSPager");
|
||||||
if(items.v8Runtime.isDead || items.v8Runtime.isClosed)
|
if (items.v8Runtime.isDead || items.v8Runtime.isClosed)
|
||||||
throw IllegalStateException("Runtime closed");
|
throw IllegalStateException("Runtime closed");
|
||||||
val newResults = items.toArray()
|
val newResults = items.toArray()
|
||||||
.map { convertResult(it as V8ValueObject) }
|
.map { convertResult(it as V8ValueObject) }
|
||||||
.toList();
|
.toList();
|
||||||
_lastResults = newResults;
|
_lastResults = newResults;
|
||||||
_resultChanged = false;
|
_resultChanged = false;
|
||||||
return newResults;
|
return@busy newResults;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun convertResult(obj: V8ValueObject): T;
|
abstract fun convertResult(obj: V8ValueObject): T;
|
||||||
|
|
|
@ -2,44 +2,59 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||||
|
|
||||||
import com.caoccao.javet.values.reference.V8ValueObject
|
import com.caoccao.javet.values.reference.V8ValueObject
|
||||||
import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
|
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.IV8PluginConfig
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
import com.futo.platformplayer.warnIfMainThread
|
import com.futo.platformplayer.warnIfMainThread
|
||||||
|
|
||||||
class JSPlaybackTracker: IPlaybackTracker {
|
class JSPlaybackTracker: IPlaybackTracker {
|
||||||
private val _config: IV8PluginConfig;
|
private lateinit var _client: JSClient;
|
||||||
private val _obj: V8ValueObject;
|
private lateinit var _config: IV8PluginConfig;
|
||||||
|
private lateinit var _obj: V8ValueObject;
|
||||||
|
|
||||||
private var _hasCalledInit: Boolean = false;
|
private var _hasCalledInit: Boolean = false;
|
||||||
private val _hasInit: Boolean;
|
private var _hasInit: Boolean = false;
|
||||||
|
|
||||||
private var _lastRequest: Long = Long.MIN_VALUE;
|
private var _lastRequest: Long = Long.MIN_VALUE;
|
||||||
|
|
||||||
private val _hasOnConcluded: Boolean;
|
private var _hasOnConcluded: Boolean = false;
|
||||||
|
|
||||||
override var nextRequest: Int = 1000
|
override var nextRequest: Int = 1000
|
||||||
private set;
|
private set;
|
||||||
|
|
||||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) {
|
constructor(client: JSClient, obj: V8ValueObject) {
|
||||||
warnIfMainThread("JSPlaybackTracker.constructor");
|
warnIfMainThread("JSPlaybackTracker.constructor");
|
||||||
if(!obj.has("onProgress"))
|
|
||||||
throw ScriptImplementationException(config, "Missing onProgress on PlaybackTracker");
|
client.busy {
|
||||||
if(!obj.has("nextRequest"))
|
if (!obj.has("onProgress"))
|
||||||
throw ScriptImplementationException(config, "Missing nextRequest on PlaybackTracker");
|
throw ScriptImplementationException(
|
||||||
|
client.config,
|
||||||
|
"Missing onProgress on PlaybackTracker"
|
||||||
|
);
|
||||||
|
if (!obj.has("nextRequest"))
|
||||||
|
throw ScriptImplementationException(
|
||||||
|
client.config,
|
||||||
|
"Missing nextRequest on PlaybackTracker"
|
||||||
|
);
|
||||||
_hasOnConcluded = obj.has("onConcluded");
|
_hasOnConcluded = obj.has("onConcluded");
|
||||||
|
|
||||||
this._config = config;
|
this._client = client;
|
||||||
|
this._config = client.config;
|
||||||
this._obj = obj;
|
this._obj = obj;
|
||||||
this._hasInit = obj.has("onInit");
|
this._hasInit = obj.has("onInit");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onInit(seconds: Double) {
|
override fun onInit(seconds: Double) {
|
||||||
warnIfMainThread("JSPlaybackTracker.onInit");
|
warnIfMainThread("JSPlaybackTracker.onInit");
|
||||||
synchronized(_obj) {
|
synchronized(_obj) {
|
||||||
if(_hasCalledInit)
|
if(_hasCalledInit)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_client.busy {
|
||||||
if (_hasInit) {
|
if (_hasInit) {
|
||||||
Logger.i("JSPlaybackTracker", "onInit (${seconds})");
|
Logger.i("JSPlaybackTracker", "onInit (${seconds})");
|
||||||
_obj.invokeVoid("onInit", seconds);
|
_obj.invokeVoid("onInit", seconds);
|
||||||
|
@ -48,6 +63,7 @@ class JSPlaybackTracker: IPlaybackTracker {
|
||||||
_hasCalledInit = true;
|
_hasCalledInit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onProgress(seconds: Double, isPlaying: Boolean) {
|
override fun onProgress(seconds: Double, isPlaying: Boolean) {
|
||||||
warnIfMainThread("JSPlaybackTracker.onProgress");
|
warnIfMainThread("JSPlaybackTracker.onProgress");
|
||||||
|
@ -55,6 +71,7 @@ class JSPlaybackTracker: IPlaybackTracker {
|
||||||
if(!_hasCalledInit && _hasInit)
|
if(!_hasCalledInit && _hasInit)
|
||||||
onInit(seconds);
|
onInit(seconds);
|
||||||
else {
|
else {
|
||||||
|
_client.busy {
|
||||||
Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})");
|
Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})");
|
||||||
_obj.invokeVoid("onProgress", Math.floor(seconds), isPlaying);
|
_obj.invokeVoid("onProgress", Math.floor(seconds), isPlaying);
|
||||||
nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false));
|
nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false));
|
||||||
|
@ -62,15 +79,18 @@ class JSPlaybackTracker: IPlaybackTracker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
override fun onConcluded() {
|
override fun onConcluded() {
|
||||||
warnIfMainThread("JSPlaybackTracker.onConcluded");
|
warnIfMainThread("JSPlaybackTracker.onConcluded");
|
||||||
if(_hasOnConcluded) {
|
if(_hasOnConcluded) {
|
||||||
synchronized(_obj) {
|
synchronized(_obj) {
|
||||||
Logger.i("JSPlaybackTracker", "onConcluded");
|
Logger.i("JSPlaybackTracker", "onConcluded");
|
||||||
|
_client.busy {
|
||||||
_obj.invokeVoid("onConcluded", -1);
|
_obj.invokeVoid("onConcluded", -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun shouldUpdate(): Boolean = (_lastRequest < 0 || (System.currentTimeMillis() - _lastRequest) > nextRequest);
|
override fun shouldUpdate(): Boolean = (_lastRequest < 0 || (System.currentTimeMillis() - _lastRequest) > nextRequest);
|
||||||
|
|
|
@ -46,6 +46,8 @@ class JSRequestExecutor {
|
||||||
if (_executor.isClosed)
|
if (_executor.isClosed)
|
||||||
throw IllegalStateException("Executor object is closed");
|
throw IllegalStateException("Executor object is closed");
|
||||||
|
|
||||||
|
return _plugin.getUnderlyingPlugin().busy {
|
||||||
|
|
||||||
val result = if(_plugin is DevJSClient)
|
val result = if(_plugin is DevJSClient)
|
||||||
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
||||||
V8Plugin.catchScriptErrors<Any>(
|
V8Plugin.catchScriptErrors<Any>(
|
||||||
|
@ -67,7 +69,7 @@ class JSRequestExecutor {
|
||||||
try {
|
try {
|
||||||
if(result is V8ValueString) {
|
if(result is V8ValueString) {
|
||||||
val base64Result = Base64.getDecoder().decode(result.value);
|
val base64Result = Base64.getDecoder().decode(result.value);
|
||||||
return base64Result;
|
return@busy base64Result;
|
||||||
}
|
}
|
||||||
if(result is V8ValueTypedArray) {
|
if(result is V8ValueTypedArray) {
|
||||||
val buffer = result.buffer;
|
val buffer = result.buffer;
|
||||||
|
@ -75,7 +77,7 @@ class JSRequestExecutor {
|
||||||
val bytesResult = ByteArray(result.byteLength);
|
val bytesResult = ByteArray(result.byteLength);
|
||||||
byteBuffer.get(bytesResult, 0, result.byteLength);
|
byteBuffer.get(bytesResult, 0, result.byteLength);
|
||||||
buffer.close();
|
buffer.close();
|
||||||
return bytesResult;
|
return@busy bytesResult;
|
||||||
}
|
}
|
||||||
if(result is V8ValueObject && result.has("type")) {
|
if(result is V8ValueObject && result.has("type")) {
|
||||||
val type = result.getOrThrow<Int>(_config, "type", "JSRequestModifier");
|
val type = result.getOrThrow<Int>(_config, "type", "JSRequestModifier");
|
||||||
|
@ -94,12 +96,13 @@ class JSRequestExecutor {
|
||||||
result.close();
|
result.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun cleanup() {
|
open fun cleanup() {
|
||||||
if (!hasCleanup || _executor.isClosed)
|
if (!hasCleanup || _executor.isClosed)
|
||||||
return;
|
return;
|
||||||
|
_plugin.busy {
|
||||||
if(_plugin is DevJSClient)
|
if(_plugin is DevJSClient)
|
||||||
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
||||||
V8Plugin.catchScriptErrors<Any>(
|
V8Plugin.catchScriptErrors<Any>(
|
||||||
|
@ -118,6 +121,7 @@ class JSRequestExecutor {
|
||||||
_executor.invokeVoid("cleanup", null);
|
_executor.invokeVoid("cleanup", null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected fun finalize() {
|
protected fun finalize() {
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
|
@ -16,7 +16,7 @@ class JSRequestModifier: IRequestModifier {
|
||||||
private val _plugin: JSClient;
|
private val _plugin: JSClient;
|
||||||
private val _config: IV8PluginConfig;
|
private val _config: IV8PluginConfig;
|
||||||
private var _modifier: V8ValueObject;
|
private var _modifier: V8ValueObject;
|
||||||
override var allowByteSkip: Boolean;
|
override var allowByteSkip: Boolean = false;
|
||||||
|
|
||||||
constructor(plugin: JSClient, modifier: V8ValueObject) {
|
constructor(plugin: JSClient, modifier: V8ValueObject) {
|
||||||
this._plugin = plugin;
|
this._plugin = plugin;
|
||||||
|
@ -24,24 +24,29 @@ class JSRequestModifier: IRequestModifier {
|
||||||
this._config = plugin.config;
|
this._config = plugin.config;
|
||||||
val config = plugin.config;
|
val config = plugin.config;
|
||||||
|
|
||||||
|
plugin.busy {
|
||||||
allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true;
|
allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true;
|
||||||
|
|
||||||
if(!modifier.has("modifyRequest"))
|
if(!modifier.has("modifyRequest"))
|
||||||
throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null);
|
throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
override fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
||||||
if (_modifier.isClosed) {
|
if (_modifier.isClosed) {
|
||||||
return Request(url, headers);
|
return Request(url, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _plugin.busy {
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") {
|
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") {
|
||||||
_modifier.invoke("modifyRequest", url, headers);
|
_modifier.invoke("modifyRequest", url, headers);
|
||||||
} as V8ValueObject;
|
} as V8ValueObject;
|
||||||
|
|
||||||
val req = JSRequest(_plugin, result, url, headers);
|
val req = JSRequest(_plugin, result, url, headers);
|
||||||
result.close();
|
result.close();
|
||||||
return req;
|
return@busy req;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.futo.platformplayer.getOrThrowNullable
|
||||||
import com.futo.platformplayer.states.StateDeveloper
|
import com.futo.platformplayer.states.StateDeveloper
|
||||||
|
|
||||||
class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||||
|
private val _plugin: JSClient;
|
||||||
private val _hasGetComments: Boolean;
|
private val _hasGetComments: Boolean;
|
||||||
private val _hasGetContentRecommendations: Boolean;
|
private val _hasGetContentRecommendations: Boolean;
|
||||||
private val _hasGetPlaybackTracker: Boolean;
|
private val _hasGetPlaybackTracker: Boolean;
|
||||||
|
@ -48,6 +49,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||||
|
|
||||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin.config, obj) {
|
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin.config, obj) {
|
||||||
val contextName = "VideoDetails";
|
val contextName = "VideoDetails";
|
||||||
|
_plugin = plugin;
|
||||||
val config = plugin.config;
|
val config = plugin.config;
|
||||||
description = _content.getOrThrow(config, "description", contextName);
|
description = _content.getOrThrow(config, "description", contextName);
|
||||||
video = JSVideoSourceDescriptor.fromV8(plugin, _content.getOrThrow(config, "video", contextName));
|
video = JSVideoSourceDescriptor.fromV8(plugin, _content.getOrThrow(config, "video", contextName));
|
||||||
|
@ -86,7 +88,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||||
val tracker = _content.invoke<V8Value>("getPlaybackTracker", arrayOf<Any>())
|
val tracker = _content.invoke<V8Value>("getPlaybackTracker", arrayOf<Any>())
|
||||||
?: return@catchScriptErrors null;
|
?: return@catchScriptErrors null;
|
||||||
if(tracker is V8ValueObject)
|
if(tracker is V8ValueObject)
|
||||||
return@catchScriptErrors JSPlaybackTracker(_pluginConfig, tracker);
|
return@catchScriptErrors JSPlaybackTracker(_plugin, tracker);
|
||||||
else
|
else
|
||||||
return@catchScriptErrors null;
|
return@catchScriptErrors null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -62,9 +62,11 @@ abstract class JSSource {
|
||||||
if (!hasRequestModifier || _obj.isClosed)
|
if (!hasRequestModifier || _obj.isClosed)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") {
|
val result = _plugin.isBusyWith("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 null;
|
||||||
|
@ -75,9 +77,14 @@ abstract class JSSource {
|
||||||
if (!hasRequestExecutor || _obj.isClosed)
|
if (!hasRequestExecutor || _obj.isClosed)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") {
|
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>());
|
_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;
|
||||||
|
|
|
@ -4,10 +4,9 @@ import android.content.Context
|
||||||
import com.caoccao.javet.exceptions.JavetCompilationException
|
import com.caoccao.javet.exceptions.JavetCompilationException
|
||||||
import com.caoccao.javet.exceptions.JavetException
|
import com.caoccao.javet.exceptions.JavetException
|
||||||
import com.caoccao.javet.exceptions.JavetExecutionException
|
import com.caoccao.javet.exceptions.JavetExecutionException
|
||||||
|
import com.caoccao.javet.interfaces.IJavetEntityError
|
||||||
import com.caoccao.javet.interop.V8Host
|
import com.caoccao.javet.interop.V8Host
|
||||||
import com.caoccao.javet.interop.V8Runtime
|
import com.caoccao.javet.interop.V8Runtime
|
||||||
import com.caoccao.javet.interop.options.V8Flags
|
|
||||||
import com.caoccao.javet.interop.options.V8RuntimeOptions
|
|
||||||
import com.caoccao.javet.values.V8Value
|
import com.caoccao.javet.values.V8Value
|
||||||
import com.caoccao.javet.values.primitive.V8ValueBoolean
|
import com.caoccao.javet.values.primitive.V8ValueBoolean
|
||||||
import com.caoccao.javet.values.primitive.V8ValueInteger
|
import com.caoccao.javet.values.primitive.V8ValueInteger
|
||||||
|
@ -26,6 +25,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
|
||||||
|
@ -40,6 +40,8 @@ import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateAssets
|
import com.futo.platformplayer.states.StateAssets
|
||||||
import com.futo.platformplayer.warnIfMainThread
|
import com.futo.platformplayer.warnIfMainThread
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
class V8Plugin {
|
class V8Plugin {
|
||||||
val config: IV8PluginConfig;
|
val config: IV8PluginConfig;
|
||||||
|
@ -51,6 +53,8 @@ class V8Plugin {
|
||||||
val httpClientAuth: ManagedHttpClient get() = _clientAuth;
|
val httpClientAuth: ManagedHttpClient get() = _clientAuth;
|
||||||
val httpClientOthers: Map<String, JSHttpClient> get() = _clientOthers;
|
val httpClientOthers: Map<String, JSHttpClient> get() = _clientOthers;
|
||||||
|
|
||||||
|
var runtimeId: Int = 0;
|
||||||
|
|
||||||
fun registerHttpClient(client: JSHttpClient) {
|
fun registerHttpClient(client: JSHttpClient) {
|
||||||
synchronized(_clientOthers) {
|
synchronized(_clientOthers) {
|
||||||
_clientOthers.put(client.clientId, client);
|
_clientOthers.put(client.clientId, client);
|
||||||
|
@ -67,10 +71,8 @@ class V8Plugin {
|
||||||
var isStopped = true;
|
var isStopped = true;
|
||||||
val onStopped = Event1<V8Plugin>();
|
val onStopped = Event1<V8Plugin>();
|
||||||
|
|
||||||
//TODO: Implement a more universal isBusy system for plugins + JSClient + pooling? TBD if propagation would be beneficial
|
private val _busyLock = ReentrantLock()
|
||||||
private val _busyCounterLock = Object();
|
val isBusy get() = _busyLock.isLocked;
|
||||||
private var _busyCounter = 0;
|
|
||||||
val isBusy get() = synchronized(_busyCounterLock) { _busyCounter > 0 };
|
|
||||||
|
|
||||||
var allowDevSubmit: Boolean = false
|
var allowDevSubmit: Boolean = false
|
||||||
private set(value) {
|
private set(value) {
|
||||||
|
@ -140,6 +142,7 @@ class V8Plugin {
|
||||||
synchronized(_runtimeLock) {
|
synchronized(_runtimeLock) {
|
||||||
if (_runtime != null)
|
if (_runtime != null)
|
||||||
return;
|
return;
|
||||||
|
runtimeId = runtimeId + 1;
|
||||||
//V8RuntimeOptions.V8_FLAGS.setUseStrict(true);
|
//V8RuntimeOptions.V8_FLAGS.setUseStrict(true);
|
||||||
val host = V8Host.getV8Instance();
|
val host = V8Host.getV8Instance();
|
||||||
val options = host.jsRuntimeType.getRuntimeOptions();
|
val options = host.jsRuntimeType.getRuntimeOptions();
|
||||||
|
@ -184,10 +187,13 @@ class V8Plugin {
|
||||||
}
|
}
|
||||||
fun stop(){
|
fun stop(){
|
||||||
Logger.i(TAG, "Stopping plugin [${config.name}]");
|
Logger.i(TAG, "Stopping plugin [${config.name}]");
|
||||||
isStopped = true;
|
busy {
|
||||||
whenNotBusy {
|
Logger.i(TAG, "Plugin stopping");
|
||||||
synchronized(_runtimeLock) {
|
synchronized(_runtimeLock) {
|
||||||
|
if(isStopped)
|
||||||
|
return@busy;
|
||||||
isStopped = true;
|
isStopped = true;
|
||||||
|
runtimeId = runtimeId + 1;
|
||||||
|
|
||||||
//Cleanup http
|
//Cleanup http
|
||||||
for(pack in _depsPackages) {
|
for(pack in _depsPackages) {
|
||||||
|
@ -211,10 +217,17 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
return handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
fun execute(js: String) : V8Value {
|
fun execute(js: String) : V8Value {
|
||||||
return executeTyped<V8Value>(js);
|
return executeTyped<V8Value>(js);
|
||||||
}
|
}
|
||||||
|
@ -223,49 +236,17 @@ class V8Plugin {
|
||||||
if(isStopped)
|
if(isStopped)
|
||||||
throw PluginEngineStoppedException(config, "Instance is stopped", js);
|
throw PluginEngineStoppedException(config, "Instance is stopped", js);
|
||||||
|
|
||||||
synchronized(_busyCounterLock) {
|
return busy {
|
||||||
_busyCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet");
|
val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet");
|
||||||
try {
|
return@busy catchScriptErrors("Plugin[${config.name}]", js) {
|
||||||
return catchScriptErrors("Plugin[${config.name}]", js) {
|
|
||||||
runtime.getExecutor(js).execute()
|
runtime.getExecutor(js).execute()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
synchronized(_busyCounterLock) {
|
|
||||||
//Free busy *after* afterBusy calls are done to prevent calls on dead runtimes
|
|
||||||
try {
|
|
||||||
afterBusy.emit(_busyCounter - 1);
|
|
||||||
}
|
|
||||||
catch(ex: Throwable) {
|
|
||||||
Logger.e(TAG, "Unhandled V8Plugin.afterBusy", ex);
|
|
||||||
}
|
|
||||||
_busyCounter--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun executeBoolean(js: String) : Boolean? = catchScriptErrors("Plugin[${config.name}]") { executeTyped<V8ValueBoolean>(js).value };
|
|
||||||
fun executeString(js: String) : String? = catchScriptErrors("Plugin[${config.name}]") { executeTyped<V8ValueString>(js).value };
|
|
||||||
fun executeInteger(js: String) : Int? = catchScriptErrors("Plugin[${config.name}]") { executeTyped<V8ValueInteger>(js).value };
|
|
||||||
|
|
||||||
fun whenNotBusy(handler: (V8Plugin)->Unit) {
|
|
||||||
synchronized(_busyCounterLock) {
|
|
||||||
if(_busyCounter == 0)
|
|
||||||
handler(this);
|
|
||||||
else {
|
|
||||||
val tag = Object();
|
|
||||||
afterBusy.subscribe(tag) {
|
|
||||||
if(it == 0) {
|
|
||||||
Logger.w(TAG, "V8Plugin afterBusy handled");
|
|
||||||
afterBusy.remove(tag);
|
|
||||||
handler(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 } }
|
||||||
|
|
||||||
private fun getPackage(packageName: String, allowNull: Boolean = false): V8Package? {
|
private fun getPackage(packageName: String, allowNull: Boolean = false): V8Package? {
|
||||||
//TODO: Auto get all package types?
|
//TODO: Auto get all package types?
|
||||||
|
@ -327,14 +308,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 IJavetEntityError) {
|
||||||
val pluginType = executeEx.scriptingError.context["plugin_type"].toString();
|
val obj = executeEx.scriptingError?.context as IJavetEntityError
|
||||||
|
if(obj.context.containsKey("plugin_type") == true) {
|
||||||
|
val pluginType = obj.context["plugin_type"].toString();
|
||||||
|
|
||||||
//Captcha
|
//Captcha
|
||||||
if (pluginType == "CaptchaRequiredException") {
|
if (pluginType == "CaptchaRequiredException") {
|
||||||
throw ScriptCaptchaRequiredException(config,
|
throw ScriptCaptchaRequiredException(config,
|
||||||
executeEx.scriptingError.context["url"]?.toString(),
|
obj.context["url"]?.toString(),
|
||||||
executeEx.scriptingError.context["body"]?.toString(),
|
obj.context["body"]?.toString(),
|
||||||
|
executeEx, executeEx.scriptingError?.stack, codeStripped);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reload Required
|
||||||
|
if (pluginType == "ReloadRequiredException") {
|
||||||
|
throw ScriptReloadRequiredException(config,
|
||||||
|
obj.context["msg"]?.toString(),
|
||||||
|
obj.context["reloadData"]?.toString(),
|
||||||
executeEx, executeEx.scriptingError?.stack, codeStripped);
|
executeEx, executeEx.scriptingError?.stack, codeStripped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,6 +339,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) {
|
||||||
|
@ -398,9 +391,4 @@ class V8Plugin {
|
||||||
return StateAssets.readAsset(context, path) ?: throw java.lang.IllegalStateException("script ${path} not found");
|
return StateAssets.readAsset(context, path) ?: throw java.lang.IllegalStateException("script ${path} not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Methods available for scripts (bridge object)
|
|
||||||
*/
|
|
||||||
}
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,8 +13,8 @@ open class V8BindObject : IV8Convertable {
|
||||||
|
|
||||||
override fun toV8(runtime: V8Runtime): V8Value? {
|
override fun toV8(runtime: V8Runtime): V8Value? {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if(_runtimeObj != null)
|
//if(_runtimeObj != null)
|
||||||
return _runtimeObj;
|
// return _runtimeObj;
|
||||||
|
|
||||||
val v8Obj = runtime.createV8ValueObject();
|
val v8Obj = runtime.createV8ValueObject();
|
||||||
v8Obj.bind(this);
|
v8Obj.bind(this);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.media.MediaCodec
|
||||||
import android.media.MediaCodecList
|
import android.media.MediaCodecList
|
||||||
import com.caoccao.javet.annotations.V8Function
|
import com.caoccao.javet.annotations.V8Function
|
||||||
import com.caoccao.javet.annotations.V8Property
|
import com.caoccao.javet.annotations.V8Property
|
||||||
|
import com.caoccao.javet.interop.callback.JavetCallbackContext
|
||||||
import com.caoccao.javet.utils.JavetResourceUtils
|
import com.caoccao.javet.utils.JavetResourceUtils
|
||||||
import com.caoccao.javet.values.V8Value
|
import com.caoccao.javet.values.V8Value
|
||||||
import com.caoccao.javet.values.reference.V8ValueFunction
|
import com.caoccao.javet.values.reference.V8ValueFunction
|
||||||
|
@ -78,6 +79,14 @@ class PackageBridge : V8Package {
|
||||||
return "android";
|
return "android";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@V8Property
|
||||||
|
fun supportedFeatures(): Array<String> {
|
||||||
|
return arrayOf(
|
||||||
|
"ReloadRequiredException",
|
||||||
|
"HttpBatchClient"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@V8Property
|
@V8Property
|
||||||
fun supportedContent(): Array<Int> {
|
fun supportedContent(): Array<Int> {
|
||||||
return arrayOf(
|
return arrayOf(
|
||||||
|
@ -105,20 +114,25 @@ class PackageBridge : V8Package {
|
||||||
@V8Function
|
@V8Function
|
||||||
fun setTimeout(func: V8ValueFunction, timeout: Long): Int {
|
fun setTimeout(func: V8ValueFunction, timeout: Long): Int {
|
||||||
val id = timeoutCounter++;
|
val id = timeoutCounter++;
|
||||||
|
|
||||||
val funcClone = func.toClone<V8ValueFunction>()
|
val funcClone = func.toClone<V8ValueFunction>()
|
||||||
|
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
delay(timeout);
|
delay(timeout);
|
||||||
|
if(_plugin.isStopped)
|
||||||
|
return@launch;
|
||||||
synchronized(timeoutMap) {
|
synchronized(timeoutMap) {
|
||||||
if(!timeoutMap.contains(id)) {
|
if(!timeoutMap.contains(id)) {
|
||||||
|
_plugin.busy {
|
||||||
|
if(!_plugin.isStopped)
|
||||||
JavetResourceUtils.safeClose(funcClone);
|
JavetResourceUtils.safeClose(funcClone);
|
||||||
|
}
|
||||||
return@launch;
|
return@launch;
|
||||||
}
|
}
|
||||||
timeoutMap.remove(id);
|
timeoutMap.remove(id);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
_plugin.whenNotBusy {
|
_plugin.busy {
|
||||||
|
if(!_plugin.isStopped)
|
||||||
funcClone.callVoid(null, arrayOf<Any>());
|
funcClone.callVoid(null, arrayOf<Any>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,8 +140,13 @@ class PackageBridge : V8Package {
|
||||||
Logger.e(TAG, "Failed timeout callback", ex);
|
Logger.e(TAG, "Failed timeout callback", ex);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
_plugin.busy {
|
||||||
|
if(!_plugin.isStopped)
|
||||||
JavetResourceUtils.safeClose(funcClone);
|
JavetResourceUtils.safeClose(funcClone);
|
||||||
}
|
}
|
||||||
|
//_plugin.whenNotBusy {
|
||||||
|
//}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
synchronized(timeoutMap) {
|
synchronized(timeoutMap) {
|
||||||
timeoutMap.add(id);
|
timeoutMap.add(id);
|
||||||
|
@ -141,13 +160,17 @@ class PackageBridge : V8Package {
|
||||||
timeoutMap.remove(id);
|
timeoutMap.remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@V8Function
|
||||||
|
fun sleep(length: Int) {
|
||||||
|
Thread.sleep(length.toLong());
|
||||||
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun toast(str: String) {
|
fun toast(str: String) {
|
||||||
Logger.i(TAG, "Plugin toast [${_config.name}]: ${str}");
|
Logger.i(TAG, "Plugin toast [${_config.name}]: ${str}");
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||||
try {
|
try {
|
||||||
UIDialogs.toast(str);
|
UIDialogs.appToast(str);
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.e(TAG, "Failed to show toast.", e);
|
Logger.e(TAG, "Failed to show toast.", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -333,6 +347,17 @@ class PackageHttp: V8Package {
|
||||||
_clientId = if(_client is JSHttpClient) _client.clientId else null;
|
_clientId = if(_client is JSHttpClient) _client.clientId else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@V8Function
|
||||||
|
fun resetAuthCookies(){
|
||||||
|
if(_client is JSHttpClient)
|
||||||
|
_client.resetAuthCookies();
|
||||||
|
}
|
||||||
|
@V8Function
|
||||||
|
fun clearOtherCookies(){
|
||||||
|
if(_client is JSHttpClient)
|
||||||
|
_client.clearOtherCookies();
|
||||||
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun setDefaultHeaders(defaultHeaders: Map<String, String>) {
|
fun setDefaultHeaders(defaultHeaders: Map<String, String>) {
|
||||||
for(pair in defaultHeaders)
|
for(pair in defaultHeaders)
|
||||||
|
@ -429,8 +454,23 @@ class PackageHttp: V8Package {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@V8Function
|
@V8Function
|
||||||
fun POST(url: String, body: String, headers: MutableMap<String, String> = HashMap(), useBytes: Boolean = false) : IBridgeHttpResponse
|
fun POST(url: String, body: Any, headers: MutableMap<String, String> = HashMap(), useBytes: Boolean = false) : IBridgeHttpResponse {
|
||||||
= POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING)
|
if(body is V8ValueString)
|
||||||
|
return POSTInternal(url, body.value, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else if(body is String)
|
||||||
|
return POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else if(body is V8ValueTypedArray)
|
||||||
|
return POSTInternal(url, body.toBytes(), headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else if(body is ByteArray)
|
||||||
|
return POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else if(body is ArrayList<*>) //Avoid this case, used purely for testing
|
||||||
|
return POSTInternal(url, body.map { (it as Double).toInt().toByte() }.toByteArray(), headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else
|
||||||
|
throw NotImplementedError("Body type " + body?.javaClass?.name?.toString() + " not implemented for POST");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// = POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING)
|
||||||
fun POSTInternal(url: String, body: String, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse {
|
fun POSTInternal(url: String, body: String, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse {
|
||||||
applyDefaultHeaders(headers);
|
applyDefaultHeaders(headers);
|
||||||
return logExceptions {
|
return logExceptions {
|
||||||
|
@ -452,9 +492,6 @@ class PackageHttp: V8Package {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@V8Function
|
|
||||||
fun POST(url: String, body: ByteArray, headers: MutableMap<String, String> = HashMap(), useBytes: Boolean = false) : IBridgeHttpResponse
|
|
||||||
= POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING)
|
|
||||||
fun POSTInternal(url: String, body: ByteArray, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse {
|
fun POSTInternal(url: String, body: ByteArray, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse {
|
||||||
applyDefaultHeaders(headers);
|
applyDefaultHeaders(headers);
|
||||||
return logExceptions {
|
return logExceptions {
|
||||||
|
@ -630,8 +667,10 @@ class PackageHttp: V8Package {
|
||||||
_isOpen = true;
|
_isOpen = true;
|
||||||
if(hasOpen && _listeners?.isClosed != true) {
|
if(hasOpen && _listeners?.isClosed != true) {
|
||||||
try {
|
try {
|
||||||
|
_package._plugin.busy {
|
||||||
_listeners?.invokeVoid("open", arrayOf<Any>());
|
_listeners?.invokeVoid("open", arrayOf<Any>());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] open failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] open failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
|
@ -640,8 +679,10 @@ class PackageHttp: V8Package {
|
||||||
override fun message(msg: String) {
|
override fun message(msg: String) {
|
||||||
if(hasMessage && _listeners?.isClosed != true) {
|
if(hasMessage && _listeners?.isClosed != true) {
|
||||||
try {
|
try {
|
||||||
|
_package._plugin.busy {
|
||||||
_listeners?.invokeVoid("message", msg);
|
_listeners?.invokeVoid("message", msg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable) {}
|
catch(ex: Throwable) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -649,8 +690,10 @@ class PackageHttp: V8Package {
|
||||||
if(hasClosing && _listeners?.isClosed != true)
|
if(hasClosing && _listeners?.isClosed != true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
_package._plugin.busy {
|
||||||
_listeners?.invokeVoid("closing", code, reason);
|
_listeners?.invokeVoid("closing", code, reason);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closing failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closing failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
|
@ -660,8 +703,10 @@ class PackageHttp: V8Package {
|
||||||
_isOpen = false;
|
_isOpen = false;
|
||||||
if(hasClosed && _listeners?.isClosed != true) {
|
if(hasClosed && _listeners?.isClosed != true) {
|
||||||
try {
|
try {
|
||||||
|
_package._plugin.busy {
|
||||||
_listeners?.invokeVoid("closed", code, reason);
|
_listeners?.invokeVoid("closed", code, reason);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
|
@ -676,8 +721,10 @@ class PackageHttp: V8Package {
|
||||||
Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception);
|
Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception);
|
||||||
if(hasFailure && _listeners?.isClosed != true) {
|
if(hasFailure && _listeners?.isClosed != true) {
|
||||||
try {
|
try {
|
||||||
|
_package._plugin.busy {
|
||||||
_listeners?.invokeVoid("failure", exception.message);
|
_listeners?.invokeVoid("failure", exception.message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -2492,7 +2497,9 @@ class VideoDetailView : ConstraintLayout {
|
||||||
|
|
||||||
val url = _url;
|
val url = _url;
|
||||||
if (!url.isNullOrBlank()) {
|
if (!url.isNullOrBlank()) {
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
}
|
||||||
_taskLoadVideo.run(url);
|
_taskLoadVideo.run(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3027,6 +3034,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)
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ abstract class SubscriptionsTaskFetchAlgorithm(
|
||||||
|
|
||||||
if (resolve != null) {
|
if (resolve != null) {
|
||||||
resolveCount = resolves.size;
|
resolveCount = resolves.size;
|
||||||
UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size}")
|
UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size})")
|
||||||
for(result in resolve){
|
for(result in resolve){
|
||||||
val task = providedTasks?.find { it.url == result.channelUrl };
|
val task = providedTasks?.find { it.url == result.channelUrl };
|
||||||
if(task != null) {
|
if(task != null) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -560,12 +565,14 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
|
|
||||||
if(videoSource.hasGenerate) {
|
if(videoSource.hasGenerate) {
|
||||||
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
||||||
|
var startId = -1;
|
||||||
try {
|
try {
|
||||||
|
startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1;
|
||||||
val generated = videoSource.generate();
|
val generated = 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))
|
||||||
videoSource.getHttpDataSourceFactory()
|
withContext(Dispatchers.IO) { videoSource.getHttpDataSourceFactory() }
|
||||||
else
|
else
|
||||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||||
|
|
||||||
|
@ -585,6 +592,17 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(reloadRequired: ScriptReloadRequiredException) {
|
||||||
|
Logger.i(TAG, "Reload required detected");
|
||||||
|
val plugin = videoSource.getUnderlyingPlugin();
|
||||||
|
if(plugin == null)
|
||||||
|
return@launch;
|
||||||
|
if(startId != -1 && plugin.getUnderlyingPlugin()?.runtimeId != startId)
|
||||||
|
return@launch;
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@ -671,14 +689,17 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource, play: Boolean, resume: Boolean): Boolean {
|
private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource, play: Boolean, resume: Boolean): Boolean {
|
||||||
Logger.i(TAG, "Loading AudioSource [DashRaw]");
|
Logger.i(TAG, "Loading AudioSource [DashRaw]");
|
||||||
|
if(audioSource.hasGenerate) {
|
||||||
|
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
||||||
|
var startId = -1;
|
||||||
|
try {
|
||||||
|
startId = audioSource.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1;
|
||||||
|
val generated = audioSource.generate();
|
||||||
|
if(generated != null) {
|
||||||
val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource))
|
val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource))
|
||||||
audioSource.getHttpDataSourceFactory()
|
audioSource.getHttpDataSourceFactory()
|
||||||
else
|
else
|
||||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||||
if(audioSource.hasGenerate) {
|
|
||||||
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
|
||||||
val generated = audioSource.generate();
|
|
||||||
if(generated != null) {
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||||
.createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url),
|
.createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url),
|
||||||
|
@ -687,9 +708,28 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(reloadRequired: ScriptReloadRequiredException) {
|
||||||
|
Logger.i(TAG, "Reload required detected");
|
||||||
|
val plugin = audioSource.getUnderlyingPlugin();
|
||||||
|
if(plugin == null)
|
||||||
|
return@launch;
|
||||||
|
if(startId != -1 && plugin.getUnderlyingPlugin()?.runtimeId != startId)
|
||||||
|
return@launch;
|
||||||
|
StatePlatform.instance.reEnableClient(plugin.id, {
|
||||||
|
onReloadRequired.emit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch(ex: Throwable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource))
|
||||||
|
audioSource.getHttpDataSourceFactory()
|
||||||
|
else
|
||||||
|
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||||
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||||
.createMediaSource(
|
.createMediaSource(
|
||||||
DashManifestParser().parse(
|
DashManifestParser().parse(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue