mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-08 01:00:13 +00:00
DashManifestRaw support, RequestExecutor support, http binary body and response support, spec version support, ignore unsupported sources, webm container preference in settings
This commit is contained in:
parent
55781e2b34
commit
2941546ae4
17 changed files with 657 additions and 107 deletions
|
@ -406,6 +406,39 @@ class DashSource {
|
||||||
this.requestModifier = obj.requestModifier;
|
this.requestModifier = obj.requestModifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class DashManifestRawSource {
|
||||||
|
constructor(obj) {
|
||||||
|
obj = obj ?? {};
|
||||||
|
this.plugin_type = "DashRawSource";
|
||||||
|
this.name = obj.name ?? "";
|
||||||
|
this.bitrate = obj.bitrate ?? 0;
|
||||||
|
this.container = obj.container ?? "";
|
||||||
|
this.codec = obj.codec ?? "";
|
||||||
|
this.duration = obj.duration ?? 0;
|
||||||
|
this.url = obj.url;
|
||||||
|
this.language = obj.language ?? Language.UNKNOWN;
|
||||||
|
if(obj.requestModifier)
|
||||||
|
this.requestModifier = obj.requestModifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DashManifestRawAudioSource {
|
||||||
|
constructor(obj) {
|
||||||
|
obj = obj ?? {};
|
||||||
|
this.plugin_type = "DashRawAudioSource";
|
||||||
|
this.name = obj.name ?? "";
|
||||||
|
this.bitrate = obj.bitrate ?? 0;
|
||||||
|
this.container = obj.container ?? "";
|
||||||
|
this.codec = obj.codec ?? "";
|
||||||
|
this.duration = obj.duration ?? 0;
|
||||||
|
this.url = obj.url;
|
||||||
|
this.language = obj.language ?? Language.UNKNOWN;
|
||||||
|
this.manifest = obj.manifest ?? null;
|
||||||
|
if(obj.requestModifier)
|
||||||
|
this.requestModifier = obj.requestModifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RequestModifier {
|
class RequestModifier {
|
||||||
constructor(obj) {
|
constructor(obj) {
|
||||||
|
|
|
@ -454,6 +454,12 @@ class Settings : FragmentedStorageFileJson() {
|
||||||
|
|
||||||
@FormField(R.string.full_screen_portrait, FieldForm.TOGGLE, R.string.allow_full_screen_portrait, 13)
|
@FormField(R.string.full_screen_portrait, FieldForm.TOGGLE, R.string.allow_full_screen_portrait, 13)
|
||||||
var fullscreenPortrait: Boolean = false;
|
var fullscreenPortrait: Boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
@FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 14)
|
||||||
|
var preferWebmVideo: Boolean = false;
|
||||||
|
@FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 15)
|
||||||
|
var preferWebmAudio: Boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FormField(R.string.comments, "group", R.string.comments_description, 6)
|
@FormField(R.string.comments, "group", R.string.comments_description, 6)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.futo.platformplayer.api.media.platforms.js
|
||||||
|
|
||||||
|
class JSClientConstants {
|
||||||
|
companion object {
|
||||||
|
val PLUGIN_SPEC_VERSION = 2;
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,4 +54,8 @@ open class JSContent : IPlatformContent, IPluginSourced {
|
||||||
|
|
||||||
_hasGetDetails = _content.has("getDetails");
|
_hasGetDetails = _content.has("getDetails");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUnderlyingObject(): V8ValueObject? {
|
||||||
|
return _content;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,13 +2,20 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||||
|
|
||||||
import com.caoccao.javet.values.V8Value
|
import com.caoccao.javet.values.V8Value
|
||||||
import com.caoccao.javet.values.primitive.V8ValueString
|
import com.caoccao.javet.values.primitive.V8ValueString
|
||||||
|
import com.caoccao.javet.values.primitive.V8ValueUndefined
|
||||||
import com.caoccao.javet.values.reference.V8ValueObject
|
import com.caoccao.javet.values.reference.V8ValueObject
|
||||||
import com.caoccao.javet.values.reference.V8ValueTypedArray
|
import com.caoccao.javet.values.reference.V8ValueTypedArray
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
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.V8Plugin
|
import com.futo.platformplayer.engine.V8Plugin
|
||||||
|
import com.futo.platformplayer.engine.exceptions.PluginException
|
||||||
|
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.getOrDefault
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.states.StateDeveloper
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
|
|
||||||
|
@ -16,6 +23,9 @@ class JSRequestExecutor {
|
||||||
private val _plugin: JSClient;
|
private val _plugin: JSClient;
|
||||||
private val _config: IV8PluginConfig;
|
private val _config: IV8PluginConfig;
|
||||||
private var _executor: V8ValueObject;
|
private var _executor: V8ValueObject;
|
||||||
|
val urlPrefix: String?;
|
||||||
|
|
||||||
|
private val hasCleanup: Boolean;
|
||||||
|
|
||||||
constructor(plugin: JSClient, executor: V8ValueObject) {
|
constructor(plugin: JSClient, executor: V8ValueObject) {
|
||||||
this._plugin = plugin;
|
this._plugin = plugin;
|
||||||
|
@ -23,16 +33,30 @@ class JSRequestExecutor {
|
||||||
this._config = plugin.config;
|
this._config = plugin.config;
|
||||||
val config = plugin.config;
|
val config = plugin.config;
|
||||||
|
|
||||||
|
urlPrefix = executor.getOrDefault(config, "urlPrefix", "RequestExecutor", null);
|
||||||
|
|
||||||
if(!executor.has("executeRequest"))
|
if(!executor.has("executeRequest"))
|
||||||
throw ScriptImplementationException(config, "RequestExecutor is missing executeRequest", null);
|
throw ScriptImplementationException(config, "RequestExecutor is missing executeRequest", null);
|
||||||
|
hasCleanup = executor.has("cleanup");
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Executor properties?
|
//TODO: Executor properties?
|
||||||
fun executeRequest(url: String, headers: Map<String, String>): ByteArray {
|
@Throws(ScriptException::class)
|
||||||
|
open fun executeRequest(url: String, headers: Map<String, String>): ByteArray {
|
||||||
if (_executor.isClosed)
|
if (_executor.isClosed)
|
||||||
throw IllegalStateException("Executor object is closed");
|
throw IllegalStateException("Executor object is closed");
|
||||||
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(
|
val result = if(_plugin is DevJSClient)
|
||||||
|
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
||||||
|
V8Plugin.catchScriptErrors<Any>(
|
||||||
|
_config,
|
||||||
|
"[${_config.name}] JSRequestExecutor",
|
||||||
|
"builder.modifyRequest()"
|
||||||
|
) {
|
||||||
|
_executor.invoke("executeRequest", url, headers);
|
||||||
|
} as V8Value;
|
||||||
|
}
|
||||||
|
else V8Plugin.catchScriptErrors<Any>(
|
||||||
_config,
|
_config,
|
||||||
"[${_config.name}] JSRequestExecutor",
|
"[${_config.name}] JSRequestExecutor",
|
||||||
"builder.modifyRequest()"
|
"builder.modifyRequest()"
|
||||||
|
@ -41,22 +65,63 @@ class JSRequestExecutor {
|
||||||
} as V8Value;
|
} as V8Value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(result is V8ValueString)
|
if(result is V8ValueString) {
|
||||||
return Base64.getDecoder().decode(result.value);
|
val base64Result = Base64.getDecoder().decode(result.value);
|
||||||
if(result is V8ValueTypedArray)
|
return base64Result;
|
||||||
return result.toBytes();
|
}
|
||||||
|
if(result is V8ValueTypedArray) {
|
||||||
|
val buffer = result.buffer;
|
||||||
|
val byteBuffer = buffer.byteBuffer;
|
||||||
|
val bytesResult = ByteArray(result.byteLength);
|
||||||
|
byteBuffer.get(bytesResult, 0, result.byteLength);
|
||||||
|
buffer.close();
|
||||||
|
return 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");
|
||||||
when(type) {
|
when(type) {
|
||||||
//TODO: Buffer type?
|
//TODO: Buffer type?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw NotImplementedError("Executor result type not implemented?");
|
if(result is V8ValueUndefined) {
|
||||||
|
if(_plugin is DevJSClient)
|
||||||
|
StateDeveloper.instance.logDevException(_plugin.devID, "JSRequestExecutor.executeRequest returned illegal undefined");
|
||||||
|
throw ScriptImplementationException(_config, "JSRequestExecutor.executeRequest returned illegal undefined", null);
|
||||||
|
}
|
||||||
|
throw NotImplementedError("Executor result type not implemented? " + result.javaClass.name);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
result.close();
|
result.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun cleanup() {
|
||||||
|
if (!hasCleanup || _executor.isClosed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(_plugin is DevJSClient)
|
||||||
|
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
||||||
|
V8Plugin.catchScriptErrors<Any>(
|
||||||
|
_config,
|
||||||
|
"[${_config.name}] JSRequestExecutor",
|
||||||
|
"builder.modifyRequest()"
|
||||||
|
) {
|
||||||
|
_executor.invokeVoid("cleanup", null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else V8Plugin.catchScriptErrors<Any>(
|
||||||
|
_config,
|
||||||
|
"[${_config.name}] JSRequestExecutor",
|
||||||
|
"builder.modifyRequest()"
|
||||||
|
) {
|
||||||
|
_executor.invokeVoid("cleanup", null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun finalize() {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: are these available..?
|
//TODO: are these available..?
|
||||||
|
|
|
@ -1,23 +1,64 @@
|
||||||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||||
|
|
||||||
import com.caoccao.javet.values.reference.V8ValueObject
|
import com.caoccao.javet.values.reference.V8ValueObject
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
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.V8Plugin
|
import com.futo.platformplayer.engine.V8Plugin
|
||||||
|
import com.futo.platformplayer.getOrDefault
|
||||||
import com.futo.platformplayer.getOrNull
|
import com.futo.platformplayer.getOrNull
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
|
import com.futo.platformplayer.others.Language
|
||||||
|
import com.futo.platformplayer.states.StateDeveloper
|
||||||
|
|
||||||
class JSDashManifestRawAudioSource : JSSource {
|
class JSDashManifestRawAudioSource : JSSource, IAudioSource {
|
||||||
val container : String = "application/dash+xml";
|
override val container : String = "application/dash+xml";
|
||||||
val name : String;
|
override val name : String;
|
||||||
val manifest: String;
|
override val codec: String;
|
||||||
|
override val bitrate: Int;
|
||||||
|
override val duration: Long;
|
||||||
|
override val priority: Boolean;
|
||||||
|
|
||||||
|
override val language: String;
|
||||||
|
|
||||||
|
val url: String;
|
||||||
|
var manifest: String?;
|
||||||
|
|
||||||
|
val hasGenerate: Boolean;
|
||||||
|
|
||||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) {
|
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) {
|
||||||
val contextName = "DashSource";
|
val contextName = "DashRawSource";
|
||||||
val config = plugin.config;
|
val config = plugin.config;
|
||||||
name = _obj.getOrThrow(config, "name", contextName);
|
name = _obj.getOrThrow(config, "name", contextName);
|
||||||
|
url = _obj.getOrThrow(config, "url", contextName);
|
||||||
manifest = _obj.getOrThrow(config, "manifest", contextName);
|
manifest = _obj.getOrThrow(config, "manifest", contextName);
|
||||||
|
codec = _obj.getOrDefault(config, "codec", contextName, "") ?: "";
|
||||||
|
bitrate = _obj.getOrDefault(config, "bitrate", contextName, 0) ?: 0;
|
||||||
|
duration = _obj.getOrDefault(config, "duration", contextName, 0) ?: 0;
|
||||||
|
priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false;
|
||||||
|
language = _obj.getOrDefault(config, "language", contextName, Language.UNKNOWN) ?: Language.UNKNOWN;
|
||||||
|
hasGenerate = _obj.has("generate");
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generate(): String? {
|
||||||
|
if(!hasGenerate)
|
||||||
|
return manifest;
|
||||||
|
if(_obj.isClosed)
|
||||||
|
throw IllegalStateException("Source object already closed");
|
||||||
|
|
||||||
|
val plugin = _plugin.getUnderlyingPlugin();
|
||||||
|
if(_plugin is DevJSClient)
|
||||||
|
return StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) {
|
||||||
|
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||||
|
_obj.invokeString("generate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||||
|
_obj.invokeString("generate");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,23 +1,109 @@
|
||||||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||||
|
|
||||||
|
import com.caoccao.javet.values.V8Value
|
||||||
|
import com.caoccao.javet.values.primitive.V8ValueString
|
||||||
import com.caoccao.javet.values.reference.V8ValueObject
|
import com.caoccao.javet.values.reference.V8ValueObject
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
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.V8Plugin
|
import com.futo.platformplayer.engine.V8Plugin
|
||||||
|
import com.futo.platformplayer.getOrDefault
|
||||||
import com.futo.platformplayer.getOrNull
|
import com.futo.platformplayer.getOrNull
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
|
import com.futo.platformplayer.states.StateDeveloper
|
||||||
|
|
||||||
class JSDashManifestRawSource : JSSource {
|
open class JSDashManifestRawSource: JSSource, IVideoSource {
|
||||||
val container : String = "application/dash+xml";
|
override val container : String = "application/dash+xml";
|
||||||
val name : String;
|
override val name : String;
|
||||||
val manifest: String;
|
override val width: Int;
|
||||||
|
override val height: Int;
|
||||||
|
override val codec: String;
|
||||||
|
override val bitrate: Int?;
|
||||||
|
override val duration: Long;
|
||||||
|
override val priority: Boolean;
|
||||||
|
|
||||||
|
var url: String?;
|
||||||
|
var manifest: String?;
|
||||||
|
|
||||||
|
val hasGenerate: Boolean;
|
||||||
|
val canMerge: Boolean;
|
||||||
|
|
||||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) {
|
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) {
|
||||||
val contextName = "DashSource";
|
val contextName = "DashRawSource";
|
||||||
val config = plugin.config;
|
val config = plugin.config;
|
||||||
name = _obj.getOrThrow(config, "name", contextName);
|
name = _obj.getOrThrow(config, "name", contextName);
|
||||||
manifest = _obj.getOrThrow(config, "manifest", contextName);
|
url = _obj.getOrThrow(config, "url", contextName);
|
||||||
|
manifest = _obj.getOrDefault<String>(config, "manifest", contextName, null);
|
||||||
|
width = _obj.getOrDefault(config, "width", contextName, 0) ?: 0;
|
||||||
|
height = _obj.getOrDefault(config, "height", contextName, 0) ?: 0;
|
||||||
|
codec = _obj.getOrDefault(config, "codec", contextName, "") ?: "";
|
||||||
|
bitrate = _obj.getOrDefault(config, "bitrate", contextName, 0) ?: 0;
|
||||||
|
duration = _obj.getOrDefault(config, "duration", contextName, 0) ?: 0;
|
||||||
|
priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false;
|
||||||
|
canMerge = _obj.getOrDefault(config, "canMerge", contextName, false) ?: false;
|
||||||
|
hasGenerate = _obj.has("generate");
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun generate(): String? {
|
||||||
|
if(!hasGenerate)
|
||||||
|
return manifest;
|
||||||
|
if(_obj.isClosed)
|
||||||
|
throw IllegalStateException("Source object already closed");
|
||||||
|
if(_plugin is DevJSClient) {
|
||||||
|
return StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") {
|
||||||
|
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||||
|
_obj.invokeString("generate");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||||
|
_obj.invokeString("generate");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JSDashManifestMergingRawSource(
|
||||||
|
val video: JSDashManifestRawSource,
|
||||||
|
val audio: JSDashManifestRawAudioSource): JSDashManifestRawSource(video.getUnderlyingPlugin()!!, video.getUnderlyingObject()!!), IVideoSource {
|
||||||
|
|
||||||
|
override val name: String
|
||||||
|
get() = video.name;
|
||||||
|
override val bitrate: Int
|
||||||
|
get() = (video.bitrate ?: 0) + audio.bitrate;
|
||||||
|
override val codec: String
|
||||||
|
get() = video.codec
|
||||||
|
override val container: String
|
||||||
|
get() = video.container
|
||||||
|
override val duration: Long
|
||||||
|
get() = video.duration;
|
||||||
|
override val height: Int
|
||||||
|
get() = video.height;
|
||||||
|
override val width: Int
|
||||||
|
get() = video.width;
|
||||||
|
override val priority: Boolean
|
||||||
|
get() = video.priority;
|
||||||
|
|
||||||
|
override fun generate(): String? {
|
||||||
|
val videoDash = video.generate();
|
||||||
|
val audioDash = audio.generate();
|
||||||
|
if(videoDash != null && audioDash == null) return videoDash;
|
||||||
|
if(audioDash != null && videoDash == null) return audioDash;
|
||||||
|
if(videoDash == null) return null;
|
||||||
|
|
||||||
|
//TODO: Temporary simple solution..make more reliable version
|
||||||
|
val audioAdaptationSet = adaptationSetRegex.find(audioDash!!);
|
||||||
|
if(audioAdaptationSet != null) {
|
||||||
|
return videoDash.replace("</AdaptationSet>", "</AdaptationSet>\n" + audioAdaptationSet.value)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return videoDash;
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val adaptationSetRegex = Regex("<AdaptationSet.*?>.*?<\\/AdaptationSet>", RegexOption.DOT_MATCHES_ALL);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,6 +15,7 @@ import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier
|
||||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||||
import com.futo.platformplayer.engine.V8Plugin
|
import com.futo.platformplayer.engine.V8Plugin
|
||||||
import com.futo.platformplayer.getOrDefault
|
import com.futo.platformplayer.getOrDefault
|
||||||
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.orNull
|
import com.futo.platformplayer.orNull
|
||||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||||
|
|
||||||
|
@ -70,12 +71,12 @@ abstract class JSSource {
|
||||||
|
|
||||||
return JSRequestModifier(_plugin, result)
|
return JSRequestModifier(_plugin, result)
|
||||||
}
|
}
|
||||||
fun getRequestExecutor(): JSRequestExecutor? {
|
open fun getRequestExecutor(): JSRequestExecutor? {
|
||||||
if (!hasRequestExecutor || _obj.isClosed)
|
if (!hasRequestExecutor || _obj.isClosed)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") {
|
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") {
|
||||||
_obj.invoke("getRequestModifier", arrayOf<Any>());
|
_obj.invoke("getRequestExecutor", arrayOf<Any>());
|
||||||
};
|
};
|
||||||
|
|
||||||
if (result !is V8ValueObject)
|
if (result !is V8ValueObject)
|
||||||
|
@ -84,40 +85,58 @@ abstract class JSSource {
|
||||||
return JSRequestExecutor(_plugin, result)
|
return JSRequestExecutor(_plugin, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUnderlyingPlugin(): JSClient? {
|
||||||
|
return _plugin;
|
||||||
|
}
|
||||||
|
fun getUnderlyingObject(): V8ValueObject? {
|
||||||
|
return _obj;
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE_AUDIOURL = "AudioUrlSource";
|
const val TYPE_AUDIOURL = "AudioUrlSource";
|
||||||
const val TYPE_VIDEOURL = "VideoUrlSource";
|
const val TYPE_VIDEOURL = "VideoUrlSource";
|
||||||
const val TYPE_AUDIO_WITH_METADATA = "AudioUrlRangeSource";
|
const val TYPE_AUDIO_WITH_METADATA = "AudioUrlRangeSource";
|
||||||
const val TYPE_VIDEO_WITH_METADATA = "VideoUrlRangeSource";
|
const val TYPE_VIDEO_WITH_METADATA = "VideoUrlRangeSource";
|
||||||
const val TYPE_DASH = "DashSource";
|
const val TYPE_DASH = "DashSource";
|
||||||
const val TYPE_DASH_RAW = "DashSourceRaw";
|
const val TYPE_DASH_RAW = "DashRawSource";
|
||||||
|
const val TYPE_DASH_RAW_AUDIO = "DashRawAudioSource";
|
||||||
const val TYPE_HLS = "HLSSource";
|
const val TYPE_HLS = "HLSSource";
|
||||||
const val TYPE_AUDIOURL_WIDEVINE = "AudioUrlWidevineSource"
|
const val TYPE_AUDIOURL_WIDEVINE = "AudioUrlWidevineSource"
|
||||||
|
|
||||||
fun fromV8VideoNullable(plugin: JSClient, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(plugin, it as V8ValueObject) };
|
fun fromV8VideoNullable(plugin: JSClient, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(plugin, it as V8ValueObject) };
|
||||||
fun fromV8Video(plugin: JSClient, obj: V8ValueObject) : IVideoSource {
|
fun fromV8Video(plugin: JSClient, obj: V8ValueObject) : IVideoSource? {
|
||||||
val type = obj.getString("plugin_type");
|
val type = obj.getString("plugin_type");
|
||||||
return when(type) {
|
return when(type) {
|
||||||
TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj);
|
TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj);
|
||||||
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(plugin, obj);
|
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(plugin, obj);
|
||||||
TYPE_HLS -> fromV8HLS(plugin, obj);
|
TYPE_HLS -> fromV8HLS(plugin, obj);
|
||||||
TYPE_DASH -> fromV8Dash(plugin, obj);
|
TYPE_DASH -> fromV8Dash(plugin, obj);
|
||||||
else -> throw NotImplementedError("Unknown type ${type}");
|
TYPE_DASH_RAW -> fromV8DashRaw(plugin, obj);
|
||||||
|
else -> {
|
||||||
|
Logger.w("JSSource", "Unknown video type ${type}");
|
||||||
|
null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun fromV8DashNullable(plugin: JSClient, obj: V8Value?) : JSDashManifestSource? = obj.orNull { fromV8Dash(plugin, it as V8ValueObject) };
|
fun fromV8DashNullable(plugin: JSClient, obj: V8Value?) : JSDashManifestSource? = obj.orNull { fromV8Dash(plugin, it as V8ValueObject) };
|
||||||
fun fromV8Dash(plugin: JSClient, obj: V8ValueObject) : JSDashManifestSource = JSDashManifestSource(plugin, obj);
|
fun fromV8Dash(plugin: JSClient, obj: V8ValueObject) : JSDashManifestSource = JSDashManifestSource(plugin, obj);
|
||||||
|
fun fromV8DashRaw(plugin: JSClient, obj: V8ValueObject) : JSDashManifestRawSource = JSDashManifestRawSource(plugin, obj);
|
||||||
|
fun fromV8DashRawAudio(plugin: JSClient, obj: V8ValueObject) : JSDashManifestRawAudioSource = JSDashManifestRawAudioSource(plugin, obj);
|
||||||
fun fromV8HLSNullable(plugin: JSClient, obj: V8Value?) : JSHLSManifestSource? = obj.orNull { fromV8HLS(plugin, it as V8ValueObject) };
|
fun fromV8HLSNullable(plugin: JSClient, obj: V8Value?) : JSHLSManifestSource? = obj.orNull { fromV8HLS(plugin, it as V8ValueObject) };
|
||||||
fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestSource = JSHLSManifestSource(plugin, obj);
|
fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestSource = JSHLSManifestSource(plugin, obj);
|
||||||
|
|
||||||
fun fromV8Audio(plugin: JSClient, obj: V8ValueObject) : IAudioSource {
|
fun fromV8Audio(plugin: JSClient, obj: V8ValueObject) : IAudioSource? {
|
||||||
val type = obj.getString("plugin_type");
|
val type = obj.getString("plugin_type");
|
||||||
return when(type) {
|
return when(type) {
|
||||||
TYPE_HLS -> JSHLSManifestAudioSource.fromV8HLS(plugin, obj);
|
TYPE_HLS -> JSHLSManifestAudioSource.fromV8HLS(plugin, obj);
|
||||||
TYPE_AUDIOURL -> JSAudioUrlSource(plugin, obj);
|
TYPE_AUDIOURL -> JSAudioUrlSource(plugin, obj);
|
||||||
|
TYPE_DASH_RAW_AUDIO -> fromV8DashRawAudio(plugin, obj);
|
||||||
TYPE_AUDIOURL_WIDEVINE -> JSAudioUrlWidevineSource(plugin, obj);
|
TYPE_AUDIOURL_WIDEVINE -> JSAudioUrlWidevineSource(plugin, obj);
|
||||||
TYPE_AUDIO_WITH_METADATA -> JSAudioUrlRangeSource(plugin, obj);
|
TYPE_AUDIO_WITH_METADATA -> JSAudioUrlRangeSource(plugin, obj);
|
||||||
else -> throw NotImplementedError("Unknown type ${type}");
|
else -> {
|
||||||
|
Logger.w("JSSource", "Unknown audio type ${type}");
|
||||||
|
null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,11 @@ class JSUnMuxVideoSourceDescriptor: VideoUnMuxedSourceDescriptor {
|
||||||
this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName);
|
this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName);
|
||||||
this.videoSources = obj.getOrThrow<V8ValueArray>(config, "videoSources", contextName).toArray()
|
this.videoSources = obj.getOrThrow<V8ValueArray>(config, "videoSources", contextName).toArray()
|
||||||
.map { JSSource.fromV8Video(plugin, it as V8ValueObject) }
|
.map { JSSource.fromV8Video(plugin, it as V8ValueObject) }
|
||||||
|
.filterNotNull()
|
||||||
.toTypedArray();
|
.toTypedArray();
|
||||||
this.audioSources = obj.getOrThrow<V8ValueArray>(config, "audioSources", contextName).toArray()
|
this.audioSources = obj.getOrThrow<V8ValueArray>(config, "audioSources", contextName).toArray()
|
||||||
.map { JSSource.fromV8Audio(plugin, it as V8ValueObject) }
|
.map { JSSource.fromV8Audio(plugin, it as V8ValueObject) }
|
||||||
|
.filterNotNull()
|
||||||
.toTypedArray();
|
.toTypedArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ class JSVideoSourceDescriptor : VideoMuxedSourceDescriptor {
|
||||||
this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName);
|
this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName);
|
||||||
this.videoSources = obj.getOrThrow<V8ValueArray>(config, "videoSources", contextName).toArray()
|
this.videoSources = obj.getOrThrow<V8ValueArray>(config, "videoSources", contextName).toArray()
|
||||||
.map { JSSource.fromV8Video(plugin, it as V8ValueObject) }
|
.map { JSSource.fromV8Video(plugin, it as V8ValueObject) }
|
||||||
|
.filterNotNull()
|
||||||
.toTypedArray();
|
.toTypedArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,8 @@ import com.futo.platformplayer.states.StateDeveloper
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
import com.google.gson.ExclusionStrategy
|
import com.google.gson.ExclusionStrategy
|
||||||
import com.google.gson.FieldAttributes
|
import com.google.gson.FieldAttributes
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
@ -573,7 +571,7 @@ class DeveloperEndpoints(private val context: Context) {
|
||||||
val resp = _client.get(body.url!!, body.headers);
|
val resp = _client.get(body.url!!, body.headers);
|
||||||
|
|
||||||
context.respondCode(200,
|
context.respondCode(200,
|
||||||
Json.encodeToString(PackageHttp.BridgeHttpResponse(resp.url, resp.code, resp.body?.string())),
|
Json.encodeToString(PackageHttp.BridgeHttpStringResponse(resp.url, resp.code, resp.body?.string())),
|
||||||
context.query.getOrDefault("CT", "text/plain"));
|
context.query.getOrDefault("CT", "text/plain"));
|
||||||
}
|
}
|
||||||
catch(ex: Exception) {
|
catch(ex: Exception) {
|
||||||
|
|
|
@ -2,12 +2,14 @@ package com.futo.platformplayer.engine.packages
|
||||||
|
|
||||||
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.values.V8Value
|
||||||
import com.futo.platformplayer.BuildConfig
|
import com.futo.platformplayer.BuildConfig
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateDeveloper
|
import com.futo.platformplayer.states.StateDeveloper
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.JSClientConstants
|
||||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||||
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
|
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
|
||||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||||
|
@ -49,6 +51,16 @@ class PackageBridge : V8Package {
|
||||||
fun buildFlavor(): String {
|
fun buildFlavor(): String {
|
||||||
return BuildConfig.FLAVOR;
|
return BuildConfig.FLAVOR;
|
||||||
}
|
}
|
||||||
|
@V8Property
|
||||||
|
fun buildSpecVersion(): Int {
|
||||||
|
return JSClientConstants.PLUGIN_SPEC_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@V8Function
|
||||||
|
fun dispose(value: V8Value) {
|
||||||
|
Logger.e(TAG, "Manual dispose: " + value.javaClass.name);
|
||||||
|
value.close();
|
||||||
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun toast(str: String) {
|
fun toast(str: String) {
|
||||||
|
|
|
@ -7,7 +7,11 @@ import com.caoccao.javet.enums.V8ConversionMode
|
||||||
import com.caoccao.javet.enums.V8ProxyMode
|
import com.caoccao.javet.enums.V8ProxyMode
|
||||||
import com.caoccao.javet.interop.V8Runtime
|
import com.caoccao.javet.interop.V8Runtime
|
||||||
import com.caoccao.javet.values.V8Value
|
import com.caoccao.javet.values.V8Value
|
||||||
|
import com.caoccao.javet.values.primitive.V8ValueString
|
||||||
|
import com.caoccao.javet.values.reference.V8ValueArrayBuffer
|
||||||
import com.caoccao.javet.values.reference.V8ValueObject
|
import com.caoccao.javet.values.reference.V8ValueObject
|
||||||
|
import com.caoccao.javet.values.reference.V8ValueSharedArrayBuffer
|
||||||
|
import com.caoccao.javet.values.reference.V8ValueTypedArray
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||||
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
|
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
|
||||||
|
@ -16,6 +20,9 @@ import com.futo.platformplayer.engine.V8Plugin
|
||||||
import com.futo.platformplayer.engine.internal.IV8Convertable
|
import com.futo.platformplayer.engine.internal.IV8Convertable
|
||||||
import com.futo.platformplayer.engine.internal.V8BindObject
|
import com.futo.platformplayer.engine.internal.V8BindObject
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.states.StateApp
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import kotlin.streams.asSequence
|
import kotlin.streams.asSequence
|
||||||
|
|
||||||
|
@ -64,33 +71,42 @@ class PackageHttp: V8Package {
|
||||||
}
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun request(method: String, url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse {
|
fun request(method: String, url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false, bytesResult: Boolean = false) : IBridgeHttpResponse {
|
||||||
return if(useAuth)
|
return if(useAuth)
|
||||||
_packageClientAuth.request(method, url, headers)
|
_packageClientAuth.request(method, url, headers, if(bytesResult) ReturnType.BYTES else ReturnType.STRING)
|
||||||
else
|
else
|
||||||
_packageClient.request(method, url, headers);
|
_packageClient.request(method, url, headers, if(bytesResult) ReturnType.BYTES else ReturnType.STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun requestWithBody(method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse {
|
fun requestWithBody(method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false, bytesResult: Boolean = false) : IBridgeHttpResponse {
|
||||||
return if(useAuth)
|
return if(useAuth)
|
||||||
_packageClientAuth.requestWithBody(method, url, body, headers)
|
_packageClientAuth.requestWithBody(method, url, body, headers, if(bytesResult) ReturnType.BYTES else ReturnType.STRING)
|
||||||
else
|
else
|
||||||
_packageClient.requestWithBody(method, url, body, headers);
|
_packageClient.requestWithBody(method, url, body, headers, if(bytesResult) ReturnType.BYTES else ReturnType.STRING);
|
||||||
}
|
}
|
||||||
@V8Function
|
@V8Function
|
||||||
fun GET(url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse {
|
fun GET(url: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false, useByteResponse: Boolean = false) : IBridgeHttpResponse {
|
||||||
return if(useAuth)
|
return if(useAuth)
|
||||||
_packageClientAuth.GET(url, headers)
|
_packageClientAuth.GET(url, headers, if(useByteResponse) ReturnType.BYTES else ReturnType.STRING)
|
||||||
else
|
else
|
||||||
_packageClient.GET(url, headers);
|
_packageClient.GET(url, headers, if(useByteResponse) ReturnType.BYTES else ReturnType.STRING);
|
||||||
}
|
}
|
||||||
@V8Function
|
@V8Function
|
||||||
fun POST(url: String, body: String, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse {
|
fun POST(url: String, body: Any, headers: MutableMap<String, String> = HashMap(), useAuth: Boolean = false, useByteResponse: Boolean = false) : IBridgeHttpResponse {
|
||||||
return if(useAuth)
|
|
||||||
_packageClientAuth.POST(url, body, headers)
|
val client = if(useAuth) _packageClientAuth else _packageClient;
|
||||||
|
|
||||||
|
if(body is V8ValueString)
|
||||||
|
return client.POST(url, body.value, headers, if(useByteResponse) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else if(body is V8ValueTypedArray)
|
||||||
|
return client.POST(url, body.toBytes(), headers, if(useByteResponse) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else if(body is ByteArray)
|
||||||
|
return client.POST(url, body, headers, if(useByteResponse) ReturnType.BYTES else ReturnType.STRING);
|
||||||
|
else if(body is ArrayList<*>) //Avoid this case, used purely for testing
|
||||||
|
return client.POST(url, body.map { (it as Double).toInt().toByte() }.toByteArray(), headers, if(useByteResponse) ReturnType.BYTES else ReturnType.STRING);
|
||||||
else
|
else
|
||||||
_packageClient.POST(url, body, headers);
|
throw NotImplementedError("Body type " + body?.javaClass?.name?.toString() + " not implemented for POST");
|
||||||
}
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
|
@ -111,8 +127,19 @@ class PackageHttp: V8Package {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IBridgeHttpResponse {
|
||||||
|
val url: String;
|
||||||
|
val code: Int;
|
||||||
|
val headers: Map<String, List<String>>?;
|
||||||
|
}
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
@kotlinx.serialization.Serializable
|
||||||
class BridgeHttpResponse(val url: String, val code: Int, val body: String?, val headers: Map<String, List<String>>? = null) : IV8Convertable {
|
class BridgeHttpStringResponse(
|
||||||
|
override val url: String,
|
||||||
|
override val code: Int, val
|
||||||
|
body: String?,
|
||||||
|
override val headers: Map<String, List<String>>? = null) : IV8Convertable, IBridgeHttpResponse {
|
||||||
|
|
||||||
val isOk = code >= 200 && code < 300;
|
val isOk = code >= 200 && code < 300;
|
||||||
|
|
||||||
override fun toV8(runtime: V8Runtime): V8Value? {
|
override fun toV8(runtime: V8Runtime): V8Value? {
|
||||||
|
@ -125,6 +152,37 @@ class PackageHttp: V8Package {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@kotlinx.serialization.Serializable
|
||||||
|
class BridgeHttpBytesResponse: IV8Convertable, IBridgeHttpResponse {
|
||||||
|
override val url: String;
|
||||||
|
override val code: Int;
|
||||||
|
val body: ByteArray?;
|
||||||
|
override val headers: Map<String, List<String>>?;
|
||||||
|
|
||||||
|
val isOk: Boolean;
|
||||||
|
|
||||||
|
constructor(url: String, code: Int, body: ByteArray? = null, headers: Map<String, List<String>>? = null) {
|
||||||
|
this.url = url;
|
||||||
|
this.code = code;
|
||||||
|
this.body = body;
|
||||||
|
this.headers = headers;
|
||||||
|
this.isOk = code >= 200 && code < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toV8(runtime: V8Runtime): V8Value? {
|
||||||
|
val obj = runtime.createV8ValueObject();
|
||||||
|
obj.set("url", url);
|
||||||
|
obj.set("code", code);
|
||||||
|
if(body != null) {
|
||||||
|
val buffer = runtime.createV8ValueArrayBuffer(body.size);
|
||||||
|
buffer.fromBytes(body);
|
||||||
|
obj.set("body", body);
|
||||||
|
}
|
||||||
|
obj.set("headers", headers);
|
||||||
|
obj.set("isOk", isOk);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: This object is currently re-wrapped each modification, this is due to an issue passing the same object back and forth, should be fixed in future.
|
//TODO: This object is currently re-wrapped each modification, this is due to an issue passing the same object back and forth, should be fixed in future.
|
||||||
@V8Convert(mode = V8ConversionMode.AllowOnly, proxyMode = V8ProxyMode.Class)
|
@V8Convert(mode = V8ConversionMode.AllowOnly, proxyMode = V8ProxyMode.Class)
|
||||||
|
@ -147,6 +205,12 @@ class PackageHttp: V8Package {
|
||||||
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), url, body, headers);
|
||||||
|
|
||||||
|
@V8Function
|
||||||
|
fun DUMMY(): BatchBuilder {
|
||||||
|
_reqs.add(Pair(_package.getDefaultClient(false), RequestDescriptor("DUMMY", "", mutableMapOf())));
|
||||||
|
return BatchBuilder(_package, _reqs);
|
||||||
|
}
|
||||||
|
|
||||||
//Client-specific
|
//Client-specific
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
|
@ -169,12 +233,14 @@ class PackageHttp: V8Package {
|
||||||
|
|
||||||
//Finalizer
|
//Finalizer
|
||||||
@V8Function
|
@V8Function
|
||||||
fun execute(): List<BridgeHttpResponse> {
|
fun execute(): List<IBridgeHttpResponse?> {
|
||||||
return _reqs.parallelStream().map {
|
return _reqs.parallelStream().map {
|
||||||
|
if(it.second.method == "DUMMY")
|
||||||
|
return@map null;
|
||||||
if(it.second.body != null)
|
if(it.second.body != null)
|
||||||
return@map it.first.requestWithBody(it.second.method, it.second.url, it.second.body!!, it.second.headers);
|
return@map it.first.requestWithBody(it.second.method, it.second.url, it.second.body!!, it.second.headers, it.second.respType);
|
||||||
else
|
else
|
||||||
return@map it.first.request(it.second.method, it.second.url, it.second.headers);
|
return@map it.first.request(it.second.method, it.second.url, it.second.headers, it.second.respType);
|
||||||
}
|
}
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.toList();
|
.toList();
|
||||||
|
@ -232,63 +298,108 @@ class PackageHttp: V8Package {
|
||||||
}
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun request(method: String, url: String, headers: MutableMap<String, String> = HashMap()) : BridgeHttpResponse {
|
fun request(method: String, url: String, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType) : IBridgeHttpResponse {
|
||||||
applyDefaultHeaders(headers);
|
applyDefaultHeaders(headers);
|
||||||
return logExceptions {
|
return logExceptions {
|
||||||
return@logExceptions catchHttp {
|
return@logExceptions catchHttp {
|
||||||
val client = _client;
|
val client = _client;
|
||||||
//logRequest(method, url, headers, null);
|
//logRequest(method, url, headers, null);
|
||||||
val resp = client.requestMethod(method, url, headers);
|
val resp = client.requestMethod(method, url, headers);
|
||||||
val responseBody = resp.body?.string();
|
|
||||||
//logResponse(method, url, resp.code, resp.headers, responseBody);
|
//logResponse(method, url, resp.code, resp.headers, responseBody);
|
||||||
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers,
|
return@catchHttp when(returnType) {
|
||||||
|
ReturnType.STRING -> BridgeHttpStringResponse(resp.url, resp.code, resp.body?.string(), sanitizeResponseHeaders(resp.headers,
|
||||||
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
ReturnType.BYTES -> BridgeHttpBytesResponse(resp.url, resp.code, resp.body?.bytes(), sanitizeResponseHeaders(resp.headers,
|
||||||
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
else -> throw NotImplementedError("Return type " + returnType.toString() + " not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@V8Function
|
@V8Function
|
||||||
fun requestWithBody(method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap()) : BridgeHttpResponse {
|
fun requestWithBody(method: String, url: String, body:String, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType) : IBridgeHttpResponse {
|
||||||
applyDefaultHeaders(headers);
|
applyDefaultHeaders(headers);
|
||||||
return logExceptions {
|
return logExceptions {
|
||||||
catchHttp {
|
catchHttp {
|
||||||
val client = _client;
|
val client = _client;
|
||||||
//logRequest(method, url, headers, body);
|
//logRequest(method, url, headers, body);
|
||||||
val resp = client.requestMethod(method, url, body, headers);
|
val resp = client.requestMethod(method, url, body, headers);
|
||||||
val responseBody = resp.body?.string();
|
|
||||||
//logResponse(method, url, resp.code, resp.headers, responseBody);
|
//logResponse(method, url, resp.code, resp.headers, responseBody);
|
||||||
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers,
|
|
||||||
|
return@catchHttp when(returnType) {
|
||||||
|
ReturnType.STRING -> BridgeHttpStringResponse(resp.url, resp.code, resp.body?.string(), sanitizeResponseHeaders(resp.headers,
|
||||||
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
ReturnType.BYTES -> BridgeHttpBytesResponse(resp.url, resp.code, resp.body?.bytes(), sanitizeResponseHeaders(resp.headers,
|
||||||
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
else -> throw NotImplementedError("Return type " + returnType.toString() + " not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun GET(url: String, headers: MutableMap<String, String> = HashMap()) : BridgeHttpResponse {
|
fun GET(url: String, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse {
|
||||||
applyDefaultHeaders(headers);
|
applyDefaultHeaders(headers);
|
||||||
return logExceptions {
|
return logExceptions {
|
||||||
catchHttp {
|
catchHttp {
|
||||||
val client = _client;
|
val client = _client;
|
||||||
//logRequest("GET", url, headers, null);
|
//logRequest("GET", url, headers, null);
|
||||||
val resp = client.get(url, headers);
|
val resp = client.get(url, headers);
|
||||||
val responseBody = resp.body?.string();
|
//val responseBody = resp.body?.string();
|
||||||
//logResponse("GET", url, resp.code, resp.headers, responseBody);
|
//logResponse("GET", url, resp.code, resp.headers, responseBody);
|
||||||
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers,
|
|
||||||
|
|
||||||
|
return@catchHttp when(returnType) {
|
||||||
|
ReturnType.STRING -> BridgeHttpStringResponse(resp.url, resp.code, resp.body?.string(), sanitizeResponseHeaders(resp.headers,
|
||||||
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
ReturnType.BYTES -> BridgeHttpBytesResponse(resp.url, resp.code, resp.body?.bytes(), sanitizeResponseHeaders(resp.headers,
|
||||||
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
else -> throw NotImplementedError("Return type " + returnType.toString() + " not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@V8Function
|
@V8Function
|
||||||
fun POST(url: String, body: String, headers: MutableMap<String, String> = HashMap()) : BridgeHttpResponse {
|
fun POST(url: String, body: String, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse {
|
||||||
applyDefaultHeaders(headers);
|
applyDefaultHeaders(headers);
|
||||||
return logExceptions {
|
return logExceptions {
|
||||||
catchHttp {
|
catchHttp {
|
||||||
val client = _client;
|
val client = _client;
|
||||||
//logRequest("POST", url, headers, body);
|
//logRequest("POST", url, headers, body);
|
||||||
val resp = client.post(url, body, headers);
|
val resp = client.post(url, body, headers);
|
||||||
val responseBody = resp.body?.string();
|
//val responseBody = resp.body?.string();
|
||||||
//logResponse("POST", url, resp.code, resp.headers, responseBody);
|
//logResponse("POST", url, resp.code, resp.headers, responseBody);
|
||||||
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers,
|
|
||||||
|
|
||||||
|
return@catchHttp when(returnType) {
|
||||||
|
ReturnType.STRING -> BridgeHttpStringResponse(resp.url, resp.code, resp.body?.string(), sanitizeResponseHeaders(resp.headers,
|
||||||
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
ReturnType.BYTES -> BridgeHttpBytesResponse(resp.url, resp.code, resp.body?.bytes(), sanitizeResponseHeaders(resp.headers,
|
||||||
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
else -> throw NotImplementedError("Return type " + returnType.toString() + " not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@V8Function
|
||||||
|
fun POST(url: String, body: ByteArray, headers: MutableMap<String, String> = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse {
|
||||||
|
applyDefaultHeaders(headers);
|
||||||
|
return logExceptions {
|
||||||
|
catchHttp {
|
||||||
|
val client = _client;
|
||||||
|
//logRequest("POST", url, headers, body);
|
||||||
|
val resp = client.post(url, body, headers);
|
||||||
|
//val responseBody = resp.body?.string();
|
||||||
|
//logResponse("POST", url, resp.code, resp.headers, responseBody);
|
||||||
|
|
||||||
|
|
||||||
|
return@catchHttp when(returnType) {
|
||||||
|
ReturnType.STRING -> BridgeHttpStringResponse(resp.url, resp.code, resp.body?.string(), sanitizeResponseHeaders(resp.headers,
|
||||||
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
ReturnType.BYTES -> BridgeHttpBytesResponse(resp.url, resp.code, resp.body?.bytes(), sanitizeResponseHeaders(resp.headers,
|
||||||
|
_client !is JSHttpClient || _client.isLoggedIn || _package._config !is SourcePluginConfig || !_package._config.allowAllHttpHeaderAccess));
|
||||||
|
else -> throw NotImplementedError("Return type " + returnType.toString() + " not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -388,13 +499,13 @@ class PackageHttp: V8Package {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun catchHttp(handle: ()->BridgeHttpResponse): BridgeHttpResponse {
|
private fun catchHttp(handle: ()->IBridgeHttpResponse): IBridgeHttpResponse {
|
||||||
try{
|
try{
|
||||||
return handle();
|
return handle();
|
||||||
}
|
}
|
||||||
//Forward timeouts
|
//Forward timeouts
|
||||||
catch(ex: SocketTimeoutException) {
|
catch(ex: SocketTimeoutException) {
|
||||||
return BridgeHttpResponse("", 408, null);
|
return BridgeHttpStringResponse("", 408, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -514,20 +625,25 @@ class PackageHttp: V8Package {
|
||||||
val url: String,
|
val url: String,
|
||||||
val headers: MutableMap<String, String>,
|
val headers: MutableMap<String, String>,
|
||||||
val body: String? = null,
|
val body: String? = null,
|
||||||
val contentType: String? = null
|
val contentType: String? = null,
|
||||||
|
val respType: ReturnType = ReturnType.STRING
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun catchHttp(handle: ()->BridgeHttpResponse): BridgeHttpResponse {
|
private fun catchHttp(handle: ()->BridgeHttpStringResponse): BridgeHttpStringResponse {
|
||||||
try{
|
try{
|
||||||
return handle();
|
return handle();
|
||||||
}
|
}
|
||||||
//Forward timeouts
|
//Forward timeouts
|
||||||
catch(ex: SocketTimeoutException) {
|
catch(ex: SocketTimeoutException) {
|
||||||
return BridgeHttpResponse("", 408, null);
|
return BridgeHttpStringResponse("", 408, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class ReturnType(val value: Int) {
|
||||||
|
STRING(0),
|
||||||
|
BYTES(1);
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "PackageHttp";
|
private const val TAG = "PackageHttp";
|
||||||
|
|
|
@ -72,6 +72,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails
|
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.casting.CastConnectionState
|
import com.futo.platformplayer.casting.CastConnectionState
|
||||||
import com.futo.platformplayer.casting.StateCasting
|
import com.futo.platformplayer.casting.StateCasting
|
||||||
|
@ -693,6 +694,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
_lastAudioSource = null;
|
_lastAudioSource = null;
|
||||||
_lastSubtitleSource = null;
|
_lastSubtitleSource = null;
|
||||||
video = null;
|
video = null;
|
||||||
|
_player.clear();
|
||||||
cleanupPlaybackTracker();
|
cleanupPlaybackTracker();
|
||||||
Logger.i(TAG, "Keep screen on unset onClose")
|
Logger.i(TAG, "Keep screen on unset onClose")
|
||||||
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
|
@ -3,9 +3,14 @@ package com.futo.platformplayer.views.video
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Xml
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.fragment.app.findFragment
|
||||||
|
import androidx.lifecycle.coroutineScope
|
||||||
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.C.Encoding
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
@ -17,6 +22,8 @@ import androidx.media3.datasource.DefaultHttpDataSource
|
||||||
import androidx.media3.datasource.HttpDataSource
|
import androidx.media3.datasource.HttpDataSource
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.dash.DashMediaSource
|
import androidx.media3.exoplayer.dash.DashMediaSource
|
||||||
|
import androidx.media3.exoplayer.dash.manifest.DashManifest
|
||||||
|
import androidx.media3.exoplayer.dash.manifest.DashManifestParser
|
||||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider
|
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider
|
||||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
import androidx.media3.exoplayer.source.MediaSource
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
@ -42,6 +49,9 @@ import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
|
||||||
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestMergingRawSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestRawAudioSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestRawSource
|
||||||
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
|
||||||
|
@ -52,12 +62,14 @@ 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.video.PlayerManager
|
import com.futo.platformplayer.video.PlayerManager
|
||||||
|
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import getHttpDataSourceFactory
|
import getHttpDataSourceFactory
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@ -319,17 +331,30 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
swapSources(videoSource, audioSource,false, play, keepSubtitles);
|
swapSources(videoSource, audioSource,false, play, keepSubtitles);
|
||||||
}
|
}
|
||||||
fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean {
|
fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean {
|
||||||
swapSourceInternal(videoSource);
|
var videoSourceUsed = videoSource;
|
||||||
swapSourceInternal(audioSource);
|
var audioSourceUsed = audioSource;
|
||||||
|
if(videoSource is JSDashManifestRawSource && audioSource is JSDashManifestRawAudioSource){
|
||||||
|
videoSourceUsed = JSDashManifestMergingRawSource(videoSource, audioSource);
|
||||||
|
audioSourceUsed = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapSourceInternal(videoSourceUsed);
|
||||||
|
swapSourceInternal(audioSourceUsed);
|
||||||
if(!keepSubtitles)
|
if(!keepSubtitles)
|
||||||
_lastSubtitleMediaSource = null;
|
_lastSubtitleMediaSource = null;
|
||||||
return loadSelectedSources(play, resume);
|
return loadSelectedSources(play, resume);
|
||||||
}
|
}
|
||||||
fun swapSource(videoSource: IVideoSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
fun swapSource(videoSource: IVideoSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
||||||
swapSourceInternal(videoSource);
|
var videoSourceUsed = videoSource;
|
||||||
|
if(videoSource is JSDashManifestRawSource && lastVideoSource is JSDashManifestMergingRawSource)
|
||||||
|
videoSourceUsed = JSDashManifestMergingRawSource(videoSource, (lastVideoSource as JSDashManifestMergingRawSource).audio);
|
||||||
|
swapSourceInternal(videoSourceUsed);
|
||||||
return loadSelectedSources(play, resume);
|
return loadSelectedSources(play, resume);
|
||||||
}
|
}
|
||||||
fun swapSource(audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
fun swapSource(audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
||||||
|
if(audioSource is JSDashManifestRawAudioSource && lastVideoSource is JSDashManifestMergingRawSource)
|
||||||
|
swapSourceInternal(JSDashManifestMergingRawSource((lastVideoSource as JSDashManifestMergingRawSource).video, audioSource));
|
||||||
|
else
|
||||||
swapSourceInternal(audioSource);
|
swapSourceInternal(audioSource);
|
||||||
return loadSelectedSources(play, resume);
|
return loadSelectedSources(play, resume);
|
||||||
}
|
}
|
||||||
|
@ -387,6 +412,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
is LocalVideoSource -> swapVideoSourceLocal(videoSource);
|
is LocalVideoSource -> swapVideoSourceLocal(videoSource);
|
||||||
is JSVideoUrlRangeSource -> swapVideoSourceUrlRange(videoSource);
|
is JSVideoUrlRangeSource -> swapVideoSourceUrlRange(videoSource);
|
||||||
is IDashManifestSource -> swapVideoSourceDash(videoSource);
|
is IDashManifestSource -> swapVideoSourceDash(videoSource);
|
||||||
|
is JSDashManifestRawSource -> swapVideoSourceDashRaw(videoSource);
|
||||||
is IHLSManifestSource -> swapVideoSourceHLS(videoSource);
|
is IHLSManifestSource -> swapVideoSourceHLS(videoSource);
|
||||||
is IVideoUrlSource -> swapVideoSourceUrl(videoSource);
|
is IVideoUrlSource -> swapVideoSourceUrl(videoSource);
|
||||||
null -> _lastVideoMediaSource = null;
|
null -> _lastVideoMediaSource = null;
|
||||||
|
@ -399,6 +425,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
is LocalAudioSource -> swapAudioSourceLocal(audioSource);
|
is LocalAudioSource -> swapAudioSourceLocal(audioSource);
|
||||||
is JSAudioUrlRangeSource -> swapAudioSourceUrlRange(audioSource);
|
is JSAudioUrlRangeSource -> swapAudioSourceUrlRange(audioSource);
|
||||||
is JSHLSManifestAudioSource -> swapAudioSourceHLS(audioSource);
|
is JSHLSManifestAudioSource -> swapAudioSourceHLS(audioSource);
|
||||||
|
is JSDashManifestRawAudioSource -> swapAudioSourceDashRaw(audioSource);
|
||||||
is IAudioUrlWidevineSource -> swapAudioSourceUrlWidevine(audioSource)
|
is IAudioUrlWidevineSource -> swapAudioSourceUrlWidevine(audioSource)
|
||||||
is IAudioUrlSource -> swapAudioSourceUrl(audioSource);
|
is IAudioUrlSource -> swapAudioSourceUrl(audioSource);
|
||||||
null -> _lastAudioMediaSource = null;
|
null -> _lastAudioMediaSource = null;
|
||||||
|
@ -459,6 +486,54 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
.createMediaSource(MediaItem.fromUri(videoSource.url))
|
.createMediaSource(MediaItem.fromUri(videoSource.url))
|
||||||
}
|
}
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun swapVideoSourceDashRaw(videoSource: JSDashManifestRawSource) {
|
||||||
|
Logger.i(TAG, "Loading VideoSource [Dash]");
|
||||||
|
|
||||||
|
if(videoSource.hasGenerate) {
|
||||||
|
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val generated = videoSource.generate();
|
||||||
|
if (generated != null) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource))
|
||||||
|
videoSource.getHttpDataSourceFactory()
|
||||||
|
else
|
||||||
|
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||||
|
|
||||||
|
if(dataSource is JSHttpDataSource.Factory && videoSource is JSDashManifestMergingRawSource)
|
||||||
|
dataSource.setRequestExecutor2(videoSource.audio.getRequestExecutor());
|
||||||
|
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||||
|
.createMediaSource(
|
||||||
|
DashManifestParser().parse(
|
||||||
|
Uri.parse(videoSource.url),
|
||||||
|
ByteArrayInputStream(
|
||||||
|
generated?.toByteArray() ?: ByteArray(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
loadSelectedSources(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(ex: Throwable) {
|
||||||
|
Logger.e(TAG, "DashRaw generator failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource))
|
||||||
|
videoSource.getHttpDataSourceFactory()
|
||||||
|
else
|
||||||
|
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||||
|
|
||||||
|
if(dataSource is JSHttpDataSource.Factory && videoSource is JSDashManifestMergingRawSource)
|
||||||
|
dataSource.setRequestExecutor2(videoSource.audio.getRequestExecutor());
|
||||||
|
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||||
|
.createMediaSource(DashManifestParser().parse(Uri.parse(videoSource.url),
|
||||||
|
ByteArrayInputStream(videoSource.manifest?.toByteArray() ?: ByteArray(0))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceHLS(videoSource: IHLSManifestSource) {
|
private fun swapVideoSourceHLS(videoSource: IHLSManifestSource) {
|
||||||
Logger.i(TAG, "Loading VideoSource [HLS]");
|
Logger.i(TAG, "Loading VideoSource [HLS]");
|
||||||
val dataSource = if(videoSource is JSSource && videoSource.requiresCustomDatasource)
|
val dataSource = if(videoSource is JSSource && videoSource.requiresCustomDatasource)
|
||||||
|
@ -521,6 +596,31 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
.createMediaSource(MediaItem.fromUri(audioSource.url));
|
.createMediaSource(MediaItem.fromUri(audioSource.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource) {
|
||||||
|
Logger.i(TAG, "Loading AudioSource [DashRaw]");
|
||||||
|
val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource))
|
||||||
|
audioSource.getHttpDataSourceFactory()
|
||||||
|
else
|
||||||
|
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) {
|
||||||
|
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||||
|
.createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url),
|
||||||
|
ByteArrayInputStream(generated?.toByteArray() ?: ByteArray(0))));
|
||||||
|
loadSelectedSources(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||||
|
.createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url),
|
||||||
|
ByteArrayInputStream(audioSource.manifest?.toByteArray() ?: ByteArray(0))));
|
||||||
|
}
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapAudioSourceUrlWidevine(audioSource: IAudioUrlWidevineSource) {
|
private fun swapAudioSourceUrlWidevine(audioSource: IAudioUrlWidevineSource) {
|
||||||
Logger.i(TAG, "Loading AudioSource [UrlWidevine]")
|
Logger.i(TAG, "Loading AudioSource [UrlWidevine]")
|
||||||
|
@ -574,28 +674,37 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
val sourceAudio = _lastAudioMediaSource;
|
val sourceAudio = _lastAudioMediaSource;
|
||||||
val sourceSubs = _lastSubtitleMediaSource;
|
val sourceSubs = _lastSubtitleMediaSource;
|
||||||
|
|
||||||
val sources = listOf(sourceVideo, sourceAudio, sourceSubs).filter { it != null }.map { it!! }.toTypedArray()
|
|
||||||
|
|
||||||
beforeSourceChanged();
|
beforeSourceChanged();
|
||||||
|
|
||||||
_mediaSource = if(sources.size == 1) {
|
val source = mergeMediaSources(sourceVideo, sourceAudio, sourceSubs);
|
||||||
Logger.i(TAG, "Using single source mode")
|
if(source == null)
|
||||||
(sourceVideo ?: sourceAudio);
|
|
||||||
}
|
|
||||||
else if(sources.size > 1) {
|
|
||||||
Logger.i(TAG, "Using multi source mode ${sources.size}")
|
|
||||||
MergingMediaSource(true, *sources);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Logger.i(TAG, "Using no sources loaded");
|
|
||||||
stop();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
_mediaSource = source;
|
||||||
|
|
||||||
reloadMediaSource(play, resume);
|
reloadMediaSource(play, resume);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
fun mergeMediaSources(sourceVideo: MediaSource?, sourceAudio: MediaSource?, sourceSubs: MediaSource?): MediaSource? {
|
||||||
|
val sources = listOf(sourceVideo, sourceAudio, sourceSubs).filter { it != null }.map { it!! }.toTypedArray()
|
||||||
|
if(sources.size == 1) {
|
||||||
|
Logger.i(TAG, "Using single source mode")
|
||||||
|
return (sourceVideo ?: sourceAudio);
|
||||||
|
}
|
||||||
|
else if(sources.size > 1) {
|
||||||
|
Logger.i(TAG, "Using multi source mode ${sources.size}")
|
||||||
|
return MergingMediaSource(true, *sources);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Logger.i(TAG, "Using no sources loaded");
|
||||||
|
stop();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
private fun reloadMediaSource(play: Boolean = false, resume: Boolean = true) {
|
private fun reloadMediaSource(play: Boolean = false, resume: Boolean = true) {
|
||||||
val player = exoPlayer ?: return
|
val player = exoPlayer ?: return
|
||||||
|
@ -619,6 +728,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
fun clear() {
|
fun clear() {
|
||||||
exoPlayer?.player?.stop();
|
exoPlayer?.player?.stop();
|
||||||
exoPlayer?.player?.clearMediaItems();
|
exoPlayer?.player?.clearMediaItems();
|
||||||
|
_lastVideoMediaSource = null;
|
||||||
|
_lastAudioMediaSource = null;
|
||||||
|
_lastSubtitleMediaSource = null;
|
||||||
|
_mediaSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop(){
|
fun stop(){
|
||||||
|
@ -697,8 +810,15 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0";
|
val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0";
|
||||||
|
|
||||||
val PREFERED_VIDEO_CONTAINERS = arrayOf("video/mp4", "video/webm", "video/3gpp");
|
val PREFERED_VIDEO_CONTAINERS_MP4Pref = arrayOf("video/mp4", "video/webm", "video/3gpp");
|
||||||
val PREFERED_AUDIO_CONTAINERS = arrayOf("audio/mp3", "audio/mp4", "audio/webm", "audio/opus");
|
val PREFERED_VIDEO_CONTAINERS_WEBMPref = arrayOf("video/webm", "video/mp4", "video/3gpp");
|
||||||
|
val PREFERED_VIDEO_CONTAINERS: Array<String> get() { return if(Settings.instance.playback.preferWebmVideo)
|
||||||
|
PREFERED_VIDEO_CONTAINERS_WEBMPref else PREFERED_VIDEO_CONTAINERS_MP4Pref }
|
||||||
|
|
||||||
|
val PREFERED_AUDIO_CONTAINERS_MP4Pref = arrayOf("audio/mp3", "audio/mp4", "audio/webm", "audio/opus");
|
||||||
|
val PREFERED_AUDIO_CONTAINERS_WEBMPref = arrayOf("audio/webm", "audio/opus", "audio/mp3", "audio/mp4");
|
||||||
|
val PREFERED_AUDIO_CONTAINERS: Array<String> get() { return if(Settings.instance.playback.preferWebmAudio)
|
||||||
|
PREFERED_AUDIO_CONTAINERS_WEBMPref else PREFERED_AUDIO_CONTAINERS_MP4Pref }
|
||||||
|
|
||||||
val SUPPORTED_SUBTITLES = hashSetOf("text/vtt", "application/x-subrip");
|
val SUPPORTED_SUBTITLES = hashSetOf("text/vtt", "application/x-subrip");
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.futo.platformplayer.api.media.models.modifier.IRequest;
|
import com.futo.platformplayer.api.media.models.modifier.IRequest;
|
||||||
import com.futo.platformplayer.api.media.models.modifier.IRequestModifier;
|
import com.futo.platformplayer.api.media.models.modifier.IRequestModifier;
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.models.JSRequest;
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor;
|
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor;
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier;
|
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
@ -27,6 +28,8 @@ import androidx.media3.datasource.HttpUtil;
|
||||||
import androidx.media3.datasource.TransferListener;
|
import androidx.media3.datasource.TransferListener;
|
||||||
|
|
||||||
import com.futo.platformplayer.engine.dev.V8RemoteObject;
|
import com.futo.platformplayer.engine.dev.V8RemoteObject;
|
||||||
|
import com.futo.platformplayer.engine.exceptions.PluginException;
|
||||||
|
import com.futo.platformplayer.engine.exceptions.ScriptException;
|
||||||
import com.futo.platformplayer.logging.Logger;
|
import com.futo.platformplayer.logging.Logger;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ForwardingMap;
|
import com.google.common.collect.ForwardingMap;
|
||||||
|
@ -70,7 +73,9 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
private boolean allowCrossProtocolRedirects;
|
private boolean allowCrossProtocolRedirects;
|
||||||
private boolean keepPostFor302Redirects;
|
private boolean keepPostFor302Redirects;
|
||||||
@Nullable private IRequestModifier requestModifier = null;
|
@Nullable private IRequestModifier requestModifier = null;
|
||||||
@Nullable private JSRequestExecutor requestExecutor = null;
|
@Nullable public JSRequestExecutor requestExecutor = null;
|
||||||
|
@Nullable public JSRequestExecutor requestExecutor2 = null;
|
||||||
|
|
||||||
|
|
||||||
/** Creates an instance. */
|
/** Creates an instance. */
|
||||||
public Factory() {
|
public Factory() {
|
||||||
|
@ -109,6 +114,18 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
this.requestExecutor = requestExecutor;
|
this.requestExecutor = requestExecutor;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Sets the secondary request executor that will be used.
|
||||||
|
*
|
||||||
|
* <p>The default is {@code null}, which results in no request modification
|
||||||
|
*
|
||||||
|
* @param requestExecutor The request modifier that will be used, or {@code null} to use no request modifier
|
||||||
|
* @return This factory.
|
||||||
|
*/
|
||||||
|
public Factory setRequestExecutor2(@Nullable JSRequestExecutor requestExecutor) {
|
||||||
|
this.requestExecutor2 = requestExecutor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the user agent that will be used.
|
* Sets the user agent that will be used.
|
||||||
|
@ -216,7 +233,8 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
contentTypePredicate,
|
contentTypePredicate,
|
||||||
keepPostFor302Redirects,
|
keepPostFor302Redirects,
|
||||||
requestModifier,
|
requestModifier,
|
||||||
requestExecutor);
|
requestExecutor,
|
||||||
|
requestExecutor2);
|
||||||
if (transferListener != null) {
|
if (transferListener != null) {
|
||||||
dataSource.addTransferListener(transferListener);
|
dataSource.addTransferListener(transferListener);
|
||||||
}
|
}
|
||||||
|
@ -252,7 +270,10 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
private long bytesToRead;
|
private long bytesToRead;
|
||||||
private long bytesRead;
|
private long bytesRead;
|
||||||
@Nullable private IRequestModifier requestModifier;
|
@Nullable private IRequestModifier requestModifier;
|
||||||
@Nullable private JSRequestExecutor requestExecutor;
|
@Nullable public JSRequestExecutor requestExecutor;
|
||||||
|
@Nullable public JSRequestExecutor requestExecutor2; //Not ideal, but required for now to have 2 executors under 1 datasource
|
||||||
|
|
||||||
|
private Uri fallbackUri = null;
|
||||||
|
|
||||||
private JSHttpDataSource(
|
private JSHttpDataSource(
|
||||||
@Nullable String userAgent,
|
@Nullable String userAgent,
|
||||||
|
@ -263,7 +284,8 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
@Nullable Predicate<String> contentTypePredicate,
|
@Nullable Predicate<String> contentTypePredicate,
|
||||||
boolean keepPostFor302Redirects,
|
boolean keepPostFor302Redirects,
|
||||||
@Nullable IRequestModifier requestModifier,
|
@Nullable IRequestModifier requestModifier,
|
||||||
@Nullable JSRequestExecutor requestExecutor) {
|
@Nullable JSRequestExecutor requestExecutor,
|
||||||
|
@Nullable JSRequestExecutor requestExecutor2) {
|
||||||
super(/* isNetwork= */ true);
|
super(/* isNetwork= */ true);
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
this.connectTimeoutMillis = connectTimeoutMillis;
|
this.connectTimeoutMillis = connectTimeoutMillis;
|
||||||
|
@ -275,12 +297,13 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
this.keepPostFor302Redirects = keepPostFor302Redirects;
|
this.keepPostFor302Redirects = keepPostFor302Redirects;
|
||||||
this.requestModifier = requestModifier;
|
this.requestModifier = requestModifier;
|
||||||
this.requestExecutor = requestExecutor;
|
this.requestExecutor = requestExecutor;
|
||||||
|
this.requestExecutor2 = requestExecutor2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Uri getUri() {
|
public Uri getUri() {
|
||||||
return connection == null ? null : Uri.parse(connection.getURL().toString());
|
return connection == null ? fallbackUri : Uri.parse(connection.getURL().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -330,19 +353,30 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
bytesToRead = 0;
|
bytesToRead = 0;
|
||||||
transferInitializing(dataSpec);
|
transferInitializing(dataSpec);
|
||||||
|
|
||||||
if(requestExecutor != null) {
|
//Use executor 2 if it matches the urlPrefix
|
||||||
byte[] data = requestExecutor.executeRequest(dataSpec.uri.toString(), dataSpec.httpRequestHeaders);
|
JSRequestExecutor executor = (requestExecutor2 != null && requestExecutor2.getUrlPrefix() != null && dataSpec.uri.toString().startsWith(requestExecutor2.getUrlPrefix())) ?
|
||||||
if(data == null)
|
requestExecutor2 : requestExecutor;
|
||||||
|
|
||||||
|
if(executor != null) {
|
||||||
|
try {
|
||||||
|
byte[] data = executor.executeRequest(dataSpec.uri.toString(), dataSpec.httpRequestHeaders);
|
||||||
|
if (data == null)
|
||||||
throw new HttpDataSourceException(
|
throw new HttpDataSourceException(
|
||||||
"No response",
|
"No response",
|
||||||
dataSpec,
|
dataSpec,
|
||||||
PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
|
PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
|
||||||
HttpDataSourceException.TYPE_OPEN);
|
HttpDataSourceException.TYPE_OPEN);
|
||||||
inputStream = new ByteArrayInputStream(data);
|
inputStream = new ByteArrayInputStream(data);
|
||||||
|
fallbackUri = dataSpec.uri;
|
||||||
|
bytesToRead = data.length;
|
||||||
|
|
||||||
transferStarted(dataSpec);
|
transferStarted(dataSpec);
|
||||||
return data.length;
|
return data.length;
|
||||||
}
|
}
|
||||||
|
catch(PluginException ex) {
|
||||||
|
throw HttpDataSourceException.createForIOException(new IOException("Executor failed: " + ex.getMessage(), ex), dataSpec, HttpDataSourceException.TYPE_OPEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
String responseMessage;
|
String responseMessage;
|
||||||
HttpURLConnection connection;
|
HttpURLConnection connection;
|
||||||
|
|
|
@ -373,6 +373,10 @@
|
||||||
<string name="system_volume_descr">Gesture controls adjust system volume</string>
|
<string name="system_volume_descr">Gesture controls adjust system volume</string>
|
||||||
<string name="live_chat_webview">Live Chat Webview</string>
|
<string name="live_chat_webview">Live Chat Webview</string>
|
||||||
<string name="full_screen_portrait">Fullscreen portrait</string>
|
<string name="full_screen_portrait">Fullscreen portrait</string>
|
||||||
|
<string name="prefer_webm">Prefer Webm Video Codecs</string>
|
||||||
|
<string name="prefer_webm_description">If player should prefer Webm codecs (vp9/opus) over mp4 codecs (h264/AAC), may result in worse compatibility.</string>
|
||||||
|
<string name="prefer_webm_audio">Prefer Webm Audio Codecs</string>
|
||||||
|
<string name="prefer_webm_audio_description">If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility.</string>
|
||||||
<string name="allow_full_screen_portrait">Allow fullscreen portrait</string>
|
<string name="allow_full_screen_portrait">Allow fullscreen portrait</string>
|
||||||
<string name="background_switch_audio">Switch to Audio in Background</string>
|
<string name="background_switch_audio">Switch to Audio in Background</string>
|
||||||
<string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string>
|
<string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue