mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-28 02:33:03 +02:00
Compare commits
14 Commits
livestream
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1579deaec2 | |||
| b0020a3fbb | |||
| 620d4f8fb7 | |||
| 9c0c8fe927 | |||
| 1263070fc9 | |||
| 336f30a631 | |||
| 9ec4b76d5e | |||
| 91bea8faf2 | |||
| 1d6f2d2ff7 | |||
| 32686215c4 | |||
| f226669b77 | |||
| a792dea4c5 | |||
| a7fc549afb | |||
| b345ba5ca3 |
@@ -124,3 +124,9 @@
|
||||
[submodule "app/src/stable/assets/sources/fosdem"]
|
||||
path = app/src/stable/assets/sources/fosdem
|
||||
url = ../plugins/fosdem.git
|
||||
[submodule "app/src/unstable/assets/sources/nasa-plus"]
|
||||
path = app/src/unstable/assets/sources/nasa-plus
|
||||
url = ../plugins/nasa-plus.git
|
||||
[submodule "app/src/stable/assets/sources/nasa-plus"]
|
||||
path = app/src/stable/assets/sources/nasa-plus
|
||||
url = ../plugins/nasa-plus.git
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|smallestScreenSize|screenLayout|uiMode|density|fontScale"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
|
||||
@@ -134,6 +134,11 @@ inline fun V8Value.ensureIsBusy() {
|
||||
}
|
||||
}
|
||||
|
||||
fun V8Value.requireSourcePlugin(context: String): V8Plugin {
|
||||
return getSourcePlugin()
|
||||
?: throw IllegalStateException("$context: V8 object's plugin runtime is no longer available");
|
||||
}
|
||||
|
||||
inline fun <reified T> V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T {
|
||||
ensureIsBusy();
|
||||
return when(T::class) {
|
||||
|
||||
@@ -698,6 +698,11 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@FormField(R.string.enable_video_cache, FieldForm.TOGGLE, R.string.cache_to_quickly_load_previously_fetched_videos, 0)
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var videoCache: Boolean = false; //Temporary default disabled to prevent ui freeze?
|
||||
|
||||
@AdvancedField
|
||||
@FormField(R.string.use_downloaded_ca_bundle, FieldForm.TOGGLE, R.string.use_downloaded_ca_bundle_description, 1)
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var useDownloadedCABundle: Boolean = false;
|
||||
}
|
||||
|
||||
@FormField(R.string.casting, "group", R.string.configure_casting, 9)
|
||||
|
||||
@@ -290,6 +290,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
exIntent.putExtra(ExceptionActivity.EXTRA_STACK, message);
|
||||
startActivity(exIntent);
|
||||
|
||||
Logger.flushBlocking();
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.futo.platformplayer.api.http
|
||||
|
||||
import androidx.collection.arrayMapOf
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.SettingsDev
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.ensureNotMainThread
|
||||
@@ -17,11 +18,17 @@ import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
import java.io.File
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import com.futo.platformplayer.api.media.models.modifier.IRequestModifier
|
||||
import kotlin.system.measureTimeMillis
|
||||
@@ -64,10 +71,17 @@ open class ManagedHttpClient {
|
||||
Logger.w(TAG, "Creating INSECURE client (TrustAll)");
|
||||
}
|
||||
|
||||
private fun applyMergedTrustStore(builder: OkHttpClient.Builder) {
|
||||
val pair = getOrBuildCompositeTrustStore() ?: return;
|
||||
builder.sslSocketFactory(pair.first, pair.second);
|
||||
}
|
||||
|
||||
constructor(builder: OkHttpClient.Builder = OkHttpClient.Builder()) {
|
||||
_builderTemplate = builder;
|
||||
if(FragmentedStorage.isInitialized && StateApp.instance.isMainActive && SettingsDev.instance.developerMode && SettingsDev.instance.networking.allowAllCertificates)
|
||||
trustAllCertificates(builder);
|
||||
else if(FragmentedStorage.isInitialized && Settings.instance.browsing.useDownloadedCABundle)
|
||||
applyMergedTrustStore(builder);
|
||||
client = builder.addNetworkInterceptor { chain ->
|
||||
val request = beforeRequest(chain.request());
|
||||
val response = afterRequest(chain.proceed(request));
|
||||
@@ -328,5 +342,76 @@ open class ManagedHttpClient {
|
||||
|
||||
companion object {
|
||||
val TAG = "ManagedHttpClient";
|
||||
|
||||
@Volatile private var _cachedCompositePair: Pair<SSLSocketFactory, X509TrustManager>? = null;
|
||||
@Volatile private var _cachedCompositeMtime: Long = -1L;
|
||||
private val _compositeBuildLock = Any();
|
||||
|
||||
private fun getOrBuildCompositeTrustStore(): Pair<SSLSocketFactory, X509TrustManager>? {
|
||||
val context = StateApp.instance.contextOrNull ?: return null;
|
||||
val bundleFile = File(context.noBackupFilesDir, "curl-ca-bundle.pem");
|
||||
if (!bundleFile.exists()) {
|
||||
Logger.w(TAG, "useDownloadedCABundle requested but bundle file not present yet");
|
||||
return null;
|
||||
}
|
||||
val modTime = bundleFile.lastModified();
|
||||
|
||||
val cached = _cachedCompositePair;
|
||||
if (cached != null && _cachedCompositeMtime == modTime) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
synchronized(_compositeBuildLock) {
|
||||
val recheck = _cachedCompositePair;
|
||||
if (recheck != null && _cachedCompositeMtime == modTime) {
|
||||
return recheck;
|
||||
}
|
||||
|
||||
try {
|
||||
val defaultAlgo = TrustManagerFactory.getDefaultAlgorithm();
|
||||
|
||||
val platformTmf = TrustManagerFactory.getInstance(defaultAlgo);
|
||||
platformTmf.init(null as KeyStore?);
|
||||
val platformTm = platformTmf.trustManagers.firstOrNull { it is X509TrustManager } as? X509TrustManager
|
||||
?: return null;
|
||||
|
||||
val bundleKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply { load(null, null) };
|
||||
val cf = CertificateFactory.getInstance("X.509");
|
||||
val certs = bundleFile.inputStream().use { cf.generateCertificates(it) };
|
||||
certs.forEachIndexed { i, cert -> bundleKeyStore.setCertificateEntry("bundle-$i", cert) };
|
||||
val bundleTmf = TrustManagerFactory.getInstance(defaultAlgo);
|
||||
bundleTmf.init(bundleKeyStore);
|
||||
val bundleTm = bundleTmf.trustManagers.firstOrNull { it is X509TrustManager } as? X509TrustManager
|
||||
?: return null;
|
||||
|
||||
val composite = object : X509TrustManager {
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
||||
platformTm.checkClientTrusted(chain, authType);
|
||||
}
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
||||
try {
|
||||
platformTm.checkServerTrusted(chain, authType);
|
||||
} catch (primary: CertificateException) {
|
||||
bundleTm.checkServerTrusted(chain, authType);
|
||||
}
|
||||
}
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
||||
return platformTm.acceptedIssuers + bundleTm.acceptedIssuers;
|
||||
}
|
||||
};
|
||||
|
||||
val sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, arrayOf<TrustManager>(composite), SecureRandom());
|
||||
val pair = Pair(sslContext.socketFactory, composite as X509TrustManager);
|
||||
_cachedCompositePair = pair;
|
||||
_cachedCompositeMtime = modTime;
|
||||
Logger.i(TAG, "Built platform+bundle composite trust manager (${certs.size} extra certs from cacert.pem); cached until bundle mtime changes");
|
||||
return pair;
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to build downloaded CA bundle; will use platform default", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -100,7 +100,7 @@ class SourcePluginDescriptor {
|
||||
@FormField(R.string.check_for_updates_setting, FieldForm.TOGGLE, R.string.check_for_updates_setting_description, -1)
|
||||
var checkForUpdates: Boolean = true;
|
||||
@FormField(R.string.automatic_update_setting, FieldForm.TOGGLE, R.string.automatic_update_setting_description, 0)
|
||||
var automaticUpdate: Boolean = false;
|
||||
var automaticUpdate: Boolean = true;
|
||||
|
||||
@FormField(R.string.visibility, "group", R.string.enable_where_this_plugins_content_are_visible, 2)
|
||||
var tabEnabled = TabEnabled();
|
||||
|
||||
+3
-2
@@ -22,6 +22,7 @@ import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.getOrThrowNullableList
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
|
||||
open class JSArticleDetails(
|
||||
@@ -97,14 +98,14 @@ open class JSArticleDetails(
|
||||
}
|
||||
|
||||
private fun getContentRecommendationsJS(client: JSClient): JSContentPager {
|
||||
return client.busy {
|
||||
return _content.requireSourcePlugin("ArticleDetails.getContentRecommendations").busy {
|
||||
val contentPager = _content.invokeV8<V8ValueObject>("getContentRecommendations", arrayOf<Any>());
|
||||
return@busy JSContentPager(_pluginConfig, client, contentPager);
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
||||
return client.busy {
|
||||
return _content.requireSourcePlugin("ArticleDetails.getComments").busy {
|
||||
val commentPager = _content.invokeV8<V8ValueObject>("getComments", arrayOf<Any>());
|
||||
return@busy JSCommentPager(_pluginConfig, client, commentPager);
|
||||
}
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.getOrThrowNullable
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||
import java.time.LocalDateTime
|
||||
import java.time.OffsetDateTime
|
||||
@@ -82,7 +83,7 @@ class JSComment : IPlatformComment {
|
||||
return null;
|
||||
|
||||
val plugin = if(client is JSClient) client else throw NotImplementedError("Only implemented for JSClient");
|
||||
return plugin.busy {
|
||||
return _comment!!.requireSourcePlugin("Comment.getReplies").busy {
|
||||
val obj = _comment!!.invokeV8<V8ValueObject>("getReplies", arrayOf<Any>());
|
||||
return@busy JSCommentPager(_config!!, plugin, obj);
|
||||
}
|
||||
|
||||
+2
-1
@@ -6,12 +6,13 @@ import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
|
||||
class JSCommentPager : JSPager<IPlatformComment>, IPager<IPlatformComment> {
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) : super(config, plugin, pager) { }
|
||||
|
||||
override fun convertResult(obj: V8ValueObject): IPlatformComment {
|
||||
return JSComment(config, plugin.getUnderlyingPlugin(), obj);
|
||||
return JSComment(config, obj.requireSourcePlugin("JSCommentPager.convertResult"), obj);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.getSourcePlugin
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.warnIfMainThread
|
||||
|
||||
@@ -23,14 +24,14 @@ abstract class JSPager<T> : IPager<T> {
|
||||
protected var _hasMorePages: Boolean = false;
|
||||
//private var _morePagesWasFalse: Boolean = false;
|
||||
|
||||
val isAvailable get() = plugin.getUnderlyingPlugin()._runtime?.let { !it.isClosed && !it.isDead } ?: false;
|
||||
val isAvailable get() = !pager.v8Runtime.isClosed && !pager.v8Runtime.isDead;
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) {
|
||||
this.plugin = plugin;
|
||||
this.pager = pager;
|
||||
this.config = config;
|
||||
|
||||
plugin.busy {
|
||||
requirePagerPluginV8("init").busy {
|
||||
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
||||
}
|
||||
getResults();
|
||||
@@ -40,8 +41,13 @@ abstract class JSPager<T> : IPager<T> {
|
||||
return config;
|
||||
}
|
||||
|
||||
protected fun requirePagerPluginV8(context: String): V8Plugin {
|
||||
return pager.getSourcePlugin()
|
||||
?: throw IllegalStateException("[${plugin.config.name}] JSPager.$context: pager runtime is no longer available");
|
||||
}
|
||||
|
||||
override fun hasMorePages(): Boolean {
|
||||
return plugin.getUnderlyingPlugin().busy {
|
||||
return requirePagerPluginV8("hasMorePages").busy {
|
||||
_hasMorePages && !pager.isClosed;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +55,7 @@ abstract class JSPager<T> : IPager<T> {
|
||||
override fun nextPage() {
|
||||
warnIfMainThread("JSPager.nextPage");
|
||||
|
||||
val pluginV8 = plugin.getUnderlyingPlugin();
|
||||
val pluginV8 = requirePagerPluginV8("nextPage");
|
||||
pluginV8.busy {
|
||||
pager = pluginV8.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") {
|
||||
pager.invokeV8("nextPage", arrayOf<Any>());
|
||||
@@ -79,7 +85,7 @@ abstract class JSPager<T> : IPager<T> {
|
||||
|
||||
warnIfMainThread("JSPager.getResults");
|
||||
|
||||
return plugin.getUnderlyingPlugin().busy {
|
||||
return requirePagerPluginV8("getResults").busy {
|
||||
val items = pager.getOrThrow<V8ValueArray>(config, "results", "JSPager");
|
||||
if (items.v8Runtime.isDead || items.v8Runtime.isClosed)
|
||||
throw IllegalStateException("Runtime closed");
|
||||
|
||||
+4
-3
@@ -8,6 +8,7 @@ import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8Void
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.states.StatePlatform
|
||||
import com.futo.platformplayer.warnIfMainThread
|
||||
|
||||
@@ -55,7 +56,7 @@ class JSPlaybackTracker: IPlaybackTracker {
|
||||
if(_hasCalledInit)
|
||||
return;
|
||||
|
||||
_client.busy {
|
||||
_obj.requireSourcePlugin("PlaybackTracker.onInit").busy {
|
||||
if (_hasInit) {
|
||||
Logger.i("JSPlaybackTracker", "onInit (${seconds})");
|
||||
_obj.invokeV8Void("onInit", seconds);
|
||||
@@ -72,7 +73,7 @@ class JSPlaybackTracker: IPlaybackTracker {
|
||||
if(!_hasCalledInit && _hasInit)
|
||||
onInit(seconds);
|
||||
else {
|
||||
_client.busy {
|
||||
_obj.requireSourcePlugin("PlaybackTracker.onProgress").busy {
|
||||
Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})");
|
||||
_obj.invokeV8Void("onProgress", Math.floor(seconds), isPlaying);
|
||||
nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false));
|
||||
@@ -86,7 +87,7 @@ class JSPlaybackTracker: IPlaybackTracker {
|
||||
if(_hasOnConcluded) {
|
||||
synchronized(_obj) {
|
||||
Logger.i("JSPlaybackTracker", "onConcluded");
|
||||
_client.busy {
|
||||
_obj.requireSourcePlugin("PlaybackTracker.onConcluded").busy {
|
||||
_obj.invokeV8Void("onConcluded", -1);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -17,6 +17,7 @@ import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getSourcePlugin
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
|
||||
class JSPostDetails : JSPost, IPlatformPost, IPlatformPostDetails {
|
||||
@@ -93,14 +94,14 @@ class JSPostDetails : JSPost, IPlatformPost, IPlatformPostDetails {
|
||||
return getContentRecommendationsJS(jsClient);
|
||||
}
|
||||
private fun getContentRecommendationsJS(client: JSClient): JSContentPager {
|
||||
return client.busy {
|
||||
return _content.requireSourcePlugin("PostDetails.getContentRecommendations").busy {
|
||||
val contentPager = _content.invokeV8<V8ValueObject>("getContentRecommendations", arrayOf<Any>());
|
||||
return@busy JSContentPager(_pluginConfig, client, contentPager);
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
||||
return client.busy {
|
||||
return _content.requireSourcePlugin("PostDetails.getComments").busy {
|
||||
val commentPager = _content.invokeV8<V8ValueObject>("getComments", arrayOf<Any>());
|
||||
return@busy JSCommentPager(_pluginConfig, client, commentPager);
|
||||
}
|
||||
|
||||
+6
-3
@@ -14,9 +14,11 @@ import com.futo.platformplayer.engine.exceptions.ScriptException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.getSourcePlugin
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Void
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -58,7 +60,7 @@ class JSRequestExecutor: AutoCloseable {
|
||||
//TODO: Executor properties?
|
||||
@Throws(ScriptException::class)
|
||||
open fun executeRequest(method: String, url: String, body: ByteArray?, headers: Map<String, String>): ByteArray {
|
||||
return _plugin.getUnderlyingPlugin().busy {
|
||||
return _executor.requireSourcePlugin("JSRequestExecutor.executeRequest").busy {
|
||||
if (_executor.isClosed)
|
||||
throw IllegalStateException("Executor object is closed");
|
||||
|
||||
@@ -114,7 +116,8 @@ class JSRequestExecutor: AutoCloseable {
|
||||
|
||||
|
||||
open fun cleanup() {
|
||||
_plugin.busy {
|
||||
val pluginV8 = _executor.getSourcePlugin() ?: return;
|
||||
pluginV8.busy {
|
||||
synchronized(_cleanLock) {
|
||||
if (!hasCleanup || _executor.isClosed || _cleaned)
|
||||
return@busy;
|
||||
@@ -122,7 +125,7 @@ class JSRequestExecutor: AutoCloseable {
|
||||
}
|
||||
}
|
||||
Logger.i("JSRequestExecutor", "JSRequestExecutor cleanup requested");
|
||||
_plugin.busy {
|
||||
pluginV8.busy {
|
||||
if(_plugin is DevJSClient)
|
||||
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
||||
V8Plugin.catchScriptErrors<Any>(
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Void
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
|
||||
class JSRequestModifier: IRequestModifier {
|
||||
private val _plugin: JSClient;
|
||||
@@ -36,7 +37,7 @@ class JSRequestModifier: IRequestModifier {
|
||||
}
|
||||
|
||||
override fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
||||
return _plugin.busy {
|
||||
return _modifier.requireSourcePlugin("JSRequestModifier.modifyRequest").busy {
|
||||
if (_modifier.isClosed) {
|
||||
return@busy Request(url, headers);
|
||||
}
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ class JSVODEventPager : JSPager<IPlatformLiveEvent>, IPlatformLiveEventPager {
|
||||
fun nextPage(ms: Int) = plugin.isBusyWith("JSLiveEventPager.nextPage") {
|
||||
warnIfMainThread("VODEventPager.nextPage");
|
||||
|
||||
val pluginV8 = plugin.getUnderlyingPlugin();
|
||||
val pluginV8 = requirePagerPluginV8("nextPage");
|
||||
pluginV8.busy {
|
||||
val newPager: V8Value = pluginV8.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage(...)") {
|
||||
pager.invokeV8<V8Value>("nextPage", ms);
|
||||
@@ -32,8 +32,8 @@ class JSVODEventPager : JSPager<IPlatformLiveEvent>, IPlatformLiveEventPager {
|
||||
pager = newPager;
|
||||
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
||||
_resultChanged = true;
|
||||
nextRequest = pager.getOrThrow(config, "nextRequest", "LiveEventPager");
|
||||
}
|
||||
nextRequest = pager.getOrThrow(config, "nextRequest", "LiveEventPager");
|
||||
}
|
||||
|
||||
override fun nextPage() = nextPage(0);
|
||||
|
||||
+5
-4
@@ -26,6 +26,7 @@ import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.getOrThrowNullable
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -118,7 +119,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||
return getPlaybackTrackerJS();
|
||||
}
|
||||
private fun getPlaybackTrackerJS(): IPlaybackTracker? {
|
||||
return _plugin.busy {
|
||||
return _content.requireSourcePlugin("VideoDetails.getPlaybackTracker").busy {
|
||||
V8Plugin.catchScriptErrors(_pluginConfig, "VideoDetails", "videoDetails.getPlaybackTracker()") {
|
||||
val tracker = _content.invokeV8<V8Value>("getPlaybackTracker", arrayOf<Any>())
|
||||
?: return@catchScriptErrors null;
|
||||
@@ -147,7 +148,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||
return null;
|
||||
}
|
||||
private fun getContentRecommendationsJS(client: JSClient): JSContentPager {
|
||||
return _plugin.busy {
|
||||
return _content.requireSourcePlugin("VideoDetails.getContentRecommendations").busy {
|
||||
val contentPager = _content.invokeV8<V8ValueObject>("getContentRecommendations", arrayOf<Any>());
|
||||
return@busy JSContentPager(_pluginConfig, client, contentPager);
|
||||
}
|
||||
@@ -171,7 +172,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||
}
|
||||
|
||||
private fun getCommentsJS(client: JSClient): IPager<IPlatformComment>? {
|
||||
return _plugin.busy {
|
||||
return _content.requireSourcePlugin("VideoDetails.getComments").busy {
|
||||
val commentPager = _content.invokeV8<V8Value>("getComments", arrayOf<Any>());
|
||||
if (commentPager !is V8ValueObject) //TODO: Maybe handle this better?
|
||||
return@busy null;
|
||||
@@ -183,7 +184,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||
fun hasVODEvents(): Boolean{
|
||||
return _hasGetVODEvents;
|
||||
}
|
||||
fun getVODEvents(url: String): IPager<IPlatformLiveEvent>? = _plugin.busy {
|
||||
fun getVODEvents(url: String): IPager<IPlatformLiveEvent>? = _content.requireSourcePlugin("VideoDetails.getVODEvents").busy {
|
||||
if(!_hasGetVODEvents)
|
||||
return@busy null;
|
||||
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Void
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
|
||||
class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
|
||||
override val licenseUri: String
|
||||
@@ -23,7 +24,7 @@ class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
return _plugin.busy {
|
||||
return _obj.requireSourcePlugin("JSAudioUrlWidevineSource.getLicenseRequestExecutor").busy {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return@busy null
|
||||
|
||||
|
||||
+15
-16
@@ -19,6 +19,7 @@ import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Async
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.others.Language
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -67,7 +68,8 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
||||
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
||||
if(!hasGenerate)
|
||||
return V8Deferred(CompletableDeferred(manifest));
|
||||
if(_plugin.busy { _obj.isClosed })
|
||||
val pluginV8 = _obj.requireSourcePlugin("DashManifestRawAudioSource.generateAsync");
|
||||
if(pluginV8.busy { _obj.isClosed })
|
||||
throw IllegalStateException("Source object already closed");
|
||||
|
||||
val pregenerated = _pregenerate;
|
||||
@@ -76,25 +78,23 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
||||
return pregenerated;
|
||||
}
|
||||
|
||||
val plugin = _plugin.getUnderlyingPlugin();
|
||||
|
||||
var result: V8Deferred<V8ValueString>? = null;
|
||||
if(_plugin is DevJSClient)
|
||||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
pluginV8.catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
result = pluginV8.catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
}
|
||||
|
||||
return plugin.busy {
|
||||
return pluginV8.busy {
|
||||
val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val initEnd = _obj.getOrDefault<Int>(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexStart = _obj.getOrDefault<Int>(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
@@ -111,29 +111,28 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
||||
override fun generate(): String? {
|
||||
if(!hasGenerate)
|
||||
return manifest;
|
||||
if(_plugin.busy { _obj.isClosed })
|
||||
val pluginV8 = _obj.requireSourcePlugin("DashManifestRawAudioSource.generate");
|
||||
if(pluginV8.busy { _obj.isClosed })
|
||||
throw IllegalStateException("Source object already closed");
|
||||
|
||||
val plugin = _plugin.getUnderlyingPlugin();
|
||||
|
||||
var result: String? = null;
|
||||
if(_plugin is DevJSClient)
|
||||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
pluginV8.catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
_plugin.isBusyWith("dashAudio.generate") {
|
||||
result = pluginV8.catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
}
|
||||
|
||||
if(result != null){
|
||||
plugin.busy {
|
||||
pluginV8.busy {
|
||||
val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val initEnd = _obj.getOrDefault<Int>(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexStart = _obj.getOrDefault<Int>(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
|
||||
+15
-14
@@ -19,6 +19,7 @@ import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Async
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -90,7 +91,8 @@ open class JSDashManifestRawSource(
|
||||
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
||||
if(!hasGenerate)
|
||||
return V8Deferred(CompletableDeferred(manifest));
|
||||
if(_plugin.busy { _obj.isClosed })
|
||||
val pluginV8 = _obj.requireSourcePlugin("DashManifestRawSource.generateAsync");
|
||||
if(pluginV8.busy { _obj.isClosed })
|
||||
throw IllegalStateException("Source object already closed");
|
||||
val pregenerated = _pregenerate;
|
||||
if(pregenerated != null) {
|
||||
@@ -98,26 +100,24 @@ open class JSDashManifestRawSource(
|
||||
return pregenerated;
|
||||
}
|
||||
|
||||
val plugin = _plugin.getUnderlyingPlugin();
|
||||
|
||||
var result: V8Deferred<V8ValueString>? = null;
|
||||
if(_plugin is DevJSClient) {
|
||||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
pluginV8.catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
result = pluginV8.catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8Async<V8ValueString>("generate");
|
||||
}
|
||||
});
|
||||
|
||||
return plugin.busy {
|
||||
return pluginV8.busy {
|
||||
val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val initEnd = _obj.getOrDefault<Int>(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexStart = _obj.getOrDefault<Int>(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
@@ -142,28 +142,29 @@ open class JSDashManifestRawSource(
|
||||
override open fun generate(): String? {
|
||||
if(!hasGenerate)
|
||||
return manifest;
|
||||
if(_plugin.busy { _obj.isClosed })
|
||||
val pluginV8 = _obj.requireSourcePlugin("DashManifestRawSource.generate");
|
||||
if(pluginV8.busy { _obj.isClosed })
|
||||
throw IllegalStateException("Source object already closed");
|
||||
|
||||
var result: String? = null;
|
||||
if(_plugin is DevJSClient) {
|
||||
result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") {
|
||||
_plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
pluginV8.catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
_plugin.isBusyWith("dashVideo.generate") {
|
||||
result = pluginV8.catchScriptErrors("DashManifestRaw.generate", "generate()", {
|
||||
pluginV8.busy {
|
||||
_obj.invokeV8<V8ValueString>("generate").value;
|
||||
}
|
||||
});
|
||||
|
||||
if(result != null){
|
||||
_plugin.busy {
|
||||
pluginV8.busy {
|
||||
val initStart = _obj.getOrDefault<Int>(_config, "initStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
val initEnd = _obj.getOrDefault<Int>(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0;
|
||||
val indexStart = _obj.getOrDefault<Int>(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0;
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.invokeV8Void
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
|
||||
class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
|
||||
IDashManifestWidevineSource, JSSource {
|
||||
@@ -49,7 +50,7 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
return _plugin.busy {
|
||||
return _obj.requireSourcePlugin("JSDashManifestWidevineSource.getLicenseRequestExecutor").busy {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return@busy null
|
||||
|
||||
|
||||
+10
-9
@@ -19,6 +19,7 @@ import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.orNull
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||
|
||||
abstract class JSSource {
|
||||
@@ -66,27 +67,27 @@ abstract class JSSource {
|
||||
hasRequestExecutor = parsedHasRequestExecutor;
|
||||
}
|
||||
|
||||
fun getRequestModifier(): IRequestModifier? = _plugin.isBusyWith("getRequestModifier") {
|
||||
fun getRequestModifier(): IRequestModifier? = _obj.requireSourcePlugin("JSSource.getRequestModifier").busy {
|
||||
if(_requestModifier != null)
|
||||
return@isBusyWith AdhocRequestModifier { url, headers ->
|
||||
return@busy AdhocRequestModifier { url, headers ->
|
||||
return@AdhocRequestModifier _requestModifier.modify(_plugin, url, headers);
|
||||
};
|
||||
|
||||
if (!hasRequestModifier || _obj.isClosed)
|
||||
return@isBusyWith null;
|
||||
return@busy null;
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") {
|
||||
_obj.invokeV8("getRequestModifier", arrayOf<Any>());
|
||||
};
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return@isBusyWith null;
|
||||
return@busy null;
|
||||
|
||||
return@isBusyWith JSRequestModifier(_plugin, result)
|
||||
return@busy JSRequestModifier(_plugin, result)
|
||||
}
|
||||
open fun getRequestExecutor(): JSRequestExecutor? = _plugin.isBusyWith("getRequestExecutor") {
|
||||
open fun getRequestExecutor(): JSRequestExecutor? = _obj.requireSourcePlugin("JSSource.getRequestExecutor").busy {
|
||||
if (!hasRequestExecutor || _obj.isClosed)
|
||||
return@isBusyWith null;
|
||||
return@busy null;
|
||||
|
||||
Logger.v("JSSource", "Request executor for [${type}] requesting");
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") {
|
||||
@@ -96,9 +97,9 @@ abstract class JSSource {
|
||||
Logger.v("JSSource", "Request executor for [${type}] received");
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return@isBusyWith null;
|
||||
return@busy null;
|
||||
|
||||
return@isBusyWith JSRequestExecutor(_plugin, result)
|
||||
return@busy JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
fun getUnderlyingPlugin(): JSClient? {
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.invokeV8
|
||||
import com.futo.platformplayer.requireSourcePlugin
|
||||
|
||||
class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
|
||||
override val licenseUri: String
|
||||
@@ -22,7 +23,7 @@ class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
return _plugin.busy {
|
||||
return _obj.requireSourcePlugin("JSVideoUrlWidevineSource.getLicenseRequestExecutor").busy {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return@busy null
|
||||
|
||||
|
||||
@@ -1024,6 +1024,31 @@ class VideoDownload {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val tailUrl = foundTemplateUrl.replace("\$Number\$", indexCounter.toString());
|
||||
Logger.i(TAG, "Downloading tail segment (segIndex=$indexCounter)");
|
||||
val tailData = executeOrGet(client, executor, modifier, tailUrl);
|
||||
fileStream.write(tailData, 0, tailData.size);
|
||||
speedTracker.addWork(tailData.size.toLong());
|
||||
written += tailData.size;
|
||||
} catch (ex: Throwable) {
|
||||
Logger.w(TAG, "$name tail segment fetch (segIndex=$indexCounter) failed; continuing without it", ex);
|
||||
}
|
||||
|
||||
if (foundCues2 != null && foundTemplateUrl2 != null && fileStream2 != null) {
|
||||
try {
|
||||
val tailUrl2 = foundTemplateUrl2!!.replace("\$Number\$", foundCues2.size.toString());
|
||||
Logger.i(TAG, "Downloading audio tail segment (segIndex=${foundCues2.size})");
|
||||
val tailData2 = executeOrGet(client, executor, modifier, tailUrl2);
|
||||
fileStream2.write(tailData2, 0, tailData2.size);
|
||||
speedTracker.addWork(tailData2.size.toLong());
|
||||
written2 += tailData2.size;
|
||||
} catch (ex: Throwable) {
|
||||
Logger.w(TAG, "$name(audio) tail segment fetch (segIndex=${foundCues2.size}) failed; continuing without it", ex);
|
||||
}
|
||||
}
|
||||
|
||||
sourceLength = written;
|
||||
sourceLengthAudio = written2;
|
||||
|
||||
|
||||
+5
-1
@@ -126,6 +126,7 @@ import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringArrayStorage
|
||||
import com.futo.platformplayer.stores.StringStorage
|
||||
import com.futo.platformplayer.stores.db.types.DBHistory
|
||||
import com.futo.platformplayer.sync.internal.GJSyncOpcodes
|
||||
import com.futo.platformplayer.sync.models.SendToDevicePackage
|
||||
@@ -364,7 +365,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
Pair(-5 * 60, 30), //around 5 minutes, try every 30 seconds
|
||||
Pair(0, 10) //around live, try every 10 seconds
|
||||
);
|
||||
private var _subtitleLanguage: String? = null
|
||||
private var _subtitleLanguage: String? = _subtitleLanguageStore.value;
|
||||
|
||||
@androidx.annotation.OptIn(UnstableApi::class)
|
||||
constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) {
|
||||
@@ -1289,6 +1290,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_taskLoadVideo.cancel();
|
||||
_commentsList.cancel();
|
||||
_player.clear();
|
||||
_player.changePlayer(null);
|
||||
_cast.cleanup();
|
||||
_container_content_replies.cleanup();
|
||||
_container_content_queue.cleanup();
|
||||
@@ -2807,6 +2809,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
}
|
||||
_lastSubtitleSource = toSet;
|
||||
_subtitleLanguage = toSet?.language
|
||||
_subtitleLanguageStore.setAndSave(_subtitleLanguage ?: "")
|
||||
}
|
||||
|
||||
private fun handleUnavailableVideo(msg: String? = null) {
|
||||
@@ -3641,6 +3644,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
const val TAG_MORE = "MORE";
|
||||
|
||||
private val _buttonPinStore = FragmentedStorage.get<StringArrayStorage>("videoPinnedButtons");
|
||||
private val _subtitleLanguageStore = FragmentedStorage.get<StringStorage>("subtitleLanguage");
|
||||
private var _lastOfflinePlaybackToastTime: Long = 0
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ class FileLogConsumer : ILogConsumer, Closeable {
|
||||
private var _shouldSubmitLogs = false;
|
||||
private val _linesToWrite = ConcurrentLinkedQueue<String>();
|
||||
private var _writer: BufferedWriter? = null;
|
||||
private val _writerLock = Any();
|
||||
private var _running: Boolean = false;
|
||||
private var _file: File;
|
||||
private val _level: LogLevel;
|
||||
@@ -42,12 +43,7 @@ class FileLogConsumer : ILogConsumer, Closeable {
|
||||
submitLogs();
|
||||
}
|
||||
|
||||
while (_linesToWrite.isNotEmpty()) {
|
||||
val todo = _linesToWrite.remove()
|
||||
_writer?.appendLine(todo);
|
||||
}
|
||||
|
||||
_writer?.flush();
|
||||
drainAndFlush();
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Failed to process logs.", e);
|
||||
}
|
||||
@@ -68,6 +64,25 @@ class FileLogConsumer : ILogConsumer, Closeable {
|
||||
|
||||
override fun consume(level: LogLevel, tag: String, text: String?, e: Throwable?) {
|
||||
_linesToWrite.add(Logging.buildLogString(level, tag, text, e));
|
||||
if (level == LogLevel.ERROR) {
|
||||
try { drainAndFlush() } catch (_: Throwable) { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
fun flushBlocking() {
|
||||
try { drainAndFlush() } catch (e: Throwable) {
|
||||
Log.e(TAG, "flushBlocking failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private fun drainAndFlush() {
|
||||
synchronized(_writerLock) {
|
||||
val w = _writer ?: return
|
||||
while (_linesToWrite.isNotEmpty()) {
|
||||
w.appendLine(_linesToWrite.remove());
|
||||
}
|
||||
w.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fun submitLogs() {
|
||||
@@ -84,8 +99,10 @@ class FileLogConsumer : ILogConsumer, Closeable {
|
||||
Log.i(TAG, "Requesting log writer exit.");
|
||||
|
||||
_running = false;
|
||||
_writer?.close();
|
||||
_writer = null;
|
||||
synchronized(_writerLock) {
|
||||
_writer?.close();
|
||||
_writer = null;
|
||||
}
|
||||
//_logThread?.join();
|
||||
_logThread = null;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,14 @@ class Logger {
|
||||
return loggingEnabled;
|
||||
}
|
||||
|
||||
fun flushBlocking() {
|
||||
for (logConsumer in _logConsumers) {
|
||||
if (logConsumer is FileLogConsumer) {
|
||||
logConsumer.flushBlocking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun log(level: LogLevel, tag: String, e: Throwable? = null, textBuilder: () -> String?) {
|
||||
if (!_logConsumers.any { c -> c.willConsume(level, tag) }) {
|
||||
return;
|
||||
|
||||
@@ -31,47 +31,37 @@ class StateUpdate {
|
||||
private set
|
||||
@Volatile var uiError: String? = null
|
||||
private set
|
||||
@Volatile var uiDismissed: Boolean = false
|
||||
private set
|
||||
|
||||
val onUiChanged = Event0()
|
||||
|
||||
fun setUiAvailable(version: Int) {
|
||||
val transitioned = uiState != UpdateUiState.AVAILABLE
|
||||
uiState = UpdateUiState.AVAILABLE
|
||||
uiVersion = version
|
||||
uiError = null
|
||||
if (transitioned) uiDismissed = false
|
||||
onUiChanged.emit()
|
||||
}
|
||||
|
||||
fun setUiDownloading(version: Int, progress: Int, indeterminate: Boolean) {
|
||||
val transitioned = uiState != UpdateUiState.DOWNLOADING
|
||||
uiState = UpdateUiState.DOWNLOADING
|
||||
uiVersion = version
|
||||
uiProgress = progress
|
||||
uiIndeterminate = indeterminate
|
||||
uiError = null
|
||||
if (transitioned) uiDismissed = false
|
||||
onUiChanged.emit()
|
||||
}
|
||||
|
||||
fun setUiReady(version: Int, apkFile: File) {
|
||||
val transitioned = uiState != UpdateUiState.READY
|
||||
uiState = UpdateUiState.READY
|
||||
uiVersion = version
|
||||
uiApkFile = apkFile
|
||||
uiError = null
|
||||
if (transitioned) uiDismissed = false
|
||||
onUiChanged.emit()
|
||||
}
|
||||
|
||||
fun setUiFailed(version: Int, error: String?) {
|
||||
val transitioned = uiState != UpdateUiState.FAILED
|
||||
uiState = UpdateUiState.FAILED
|
||||
uiVersion = version
|
||||
uiError = error
|
||||
if (transitioned) uiDismissed = false
|
||||
onUiChanged.emit()
|
||||
}
|
||||
|
||||
@@ -82,12 +72,6 @@ class StateUpdate {
|
||||
uiIndeterminate = true
|
||||
uiApkFile = null
|
||||
uiError = null
|
||||
uiDismissed = false
|
||||
onUiChanged.emit()
|
||||
}
|
||||
|
||||
fun dismissUi() {
|
||||
uiDismissed = true
|
||||
onUiChanged.emit()
|
||||
}
|
||||
|
||||
@@ -243,4 +227,4 @@ class StateUpdate {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,18 @@ class PlayerManager {
|
||||
}
|
||||
fun modifyState(name: String, cb: (PlayerState) -> Unit) {
|
||||
val state = getState(name);
|
||||
val previousListener = state.listener;
|
||||
cb(state);
|
||||
if(_currentState == state)
|
||||
if(_currentState == state) {
|
||||
applyState(state);
|
||||
val newListener = state.listener;
|
||||
if(previousListener !== newListener) {
|
||||
if(previousListener != null)
|
||||
player.removeListener(previousListener);
|
||||
if(newListener != null)
|
||||
player.addListener(newListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
fun switchState(name: String) {
|
||||
val newState = getState(name);
|
||||
|
||||
@@ -29,11 +29,9 @@ class UpdateBannerView : LinearLayout {
|
||||
private val _root: FrameLayout
|
||||
private val _iconUpdate: ImageView
|
||||
private val _textTitle: TextView
|
||||
private val _textBody: TextView
|
||||
private val _progressBar: ProgressBar
|
||||
private val _buttonAction: FrameLayout
|
||||
private val _textAction: TextView
|
||||
private val _buttonClose: ImageView
|
||||
|
||||
private val _scope: CoroutineScope?
|
||||
|
||||
@@ -45,15 +43,9 @@ class UpdateBannerView : LinearLayout {
|
||||
_root = findViewById(R.id.root)
|
||||
_iconUpdate = findViewById(R.id.icon_update)
|
||||
_textTitle = findViewById(R.id.text_title)
|
||||
_textBody = findViewById(R.id.text_body)
|
||||
_progressBar = findViewById(R.id.update_banner_progress)
|
||||
_buttonAction = findViewById(R.id.button_action)
|
||||
_textAction = findViewById(R.id.text_action)
|
||||
_buttonClose = findViewById(R.id.button_close)
|
||||
|
||||
_buttonClose.setOnClickListener {
|
||||
StateUpdate.instance.dismissUi()
|
||||
}
|
||||
|
||||
_buttonAction.setOnClickListener {
|
||||
onActionClicked()
|
||||
@@ -96,17 +88,6 @@ class UpdateBannerView : LinearLayout {
|
||||
Logger.w(TAG, "Retry start service failed", t)
|
||||
}
|
||||
}
|
||||
UpdateUiState.DOWNLOADING -> {
|
||||
val intent = Intent(context, UpdateDownloadService::class.java).apply {
|
||||
putExtra(UpdateDownloadService.EXTRA_VERSION, st.uiVersion)
|
||||
putExtra(UpdateDownloadService.EXTRA_CANCEL, true)
|
||||
}
|
||||
try {
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
} catch (t: Throwable) {
|
||||
Logger.w(TAG, "Cancel start service failed", t)
|
||||
}
|
||||
}
|
||||
UpdateUiState.AVAILABLE -> {
|
||||
if (st.uiVersion == 0) return
|
||||
val intent = Intent(context, UpdateDownloadService::class.java).apply {
|
||||
@@ -118,6 +99,7 @@ class UpdateBannerView : LinearLayout {
|
||||
Logger.w(TAG, "Download start service failed", t)
|
||||
}
|
||||
}
|
||||
UpdateUiState.DOWNLOADING -> {}
|
||||
UpdateUiState.NONE -> {}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +107,7 @@ class UpdateBannerView : LinearLayout {
|
||||
private fun refresh() {
|
||||
val st = StateUpdate.instance
|
||||
val gateOpen = Settings.instance.autoUpdate.shouldBackgroundDownload
|
||||
val visible = gateOpen && !st.uiDismissed && st.uiState != UpdateUiState.NONE
|
||||
val visible = gateOpen && st.uiState != UpdateUiState.NONE
|
||||
|
||||
if (!visible) {
|
||||
_root.visibility = View.GONE
|
||||
@@ -135,41 +117,31 @@ class UpdateBannerView : LinearLayout {
|
||||
|
||||
when (st.uiState) {
|
||||
UpdateUiState.AVAILABLE -> {
|
||||
_textTitle.text = "Update available (v${st.uiVersion})"
|
||||
_textBody.text = "A new Grayjay version is available."
|
||||
_textBody.visibility = View.VISIBLE
|
||||
_textTitle.text = "Update v${st.uiVersion}"
|
||||
_progressBar.visibility = View.GONE
|
||||
_textAction.text = "Download"
|
||||
_buttonAction.visibility = View.VISIBLE
|
||||
}
|
||||
UpdateUiState.DOWNLOADING -> {
|
||||
_textTitle.text = "Downloading update (v${st.uiVersion})"
|
||||
if (st.uiIndeterminate) {
|
||||
_textBody.text = "Starting download…"
|
||||
_textTitle.text = "Downloading v${st.uiVersion}"
|
||||
_progressBar.isIndeterminate = true
|
||||
} else {
|
||||
_textBody.text = "${st.uiProgress}% downloaded"
|
||||
_textTitle.text = "Downloading v${st.uiVersion} - ${st.uiProgress}%"
|
||||
_progressBar.isIndeterminate = false
|
||||
_progressBar.progress = st.uiProgress
|
||||
}
|
||||
_textBody.visibility = View.VISIBLE
|
||||
_progressBar.visibility = View.VISIBLE
|
||||
_textAction.text = "Cancel"
|
||||
_buttonAction.visibility = View.VISIBLE
|
||||
_buttonAction.visibility = View.GONE
|
||||
}
|
||||
UpdateUiState.READY -> {
|
||||
_textTitle.text = "Update v${st.uiVersion} ready"
|
||||
_textBody.text = "Tap install to apply the update."
|
||||
_textBody.visibility = View.VISIBLE
|
||||
_textTitle.text = "Ready v${st.uiVersion}"
|
||||
_progressBar.visibility = View.GONE
|
||||
_textAction.text = "Install"
|
||||
_buttonAction.visibility = View.VISIBLE
|
||||
}
|
||||
UpdateUiState.FAILED -> {
|
||||
_textTitle.text = "Update failed"
|
||||
val err = st.uiError
|
||||
_textBody.text = if (err.isNullOrBlank()) "Could not download v${st.uiVersion}." else err
|
||||
_textBody.visibility = View.VISIBLE
|
||||
_progressBar.visibility = View.GONE
|
||||
_textAction.text = "Retry"
|
||||
_buttonAction.visibility = View.VISIBLE
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
android:paddingEnd="12dp"
|
||||
android:background="@drawable/background_pill"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginTop="17dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:id="@+id/root">
|
||||
<LinearLayout
|
||||
@@ -36,4 +36,4 @@
|
||||
tools:text="Tag text" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
@@ -1,94 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/root">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/background_16_round_4dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_margin="10dp">
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:minHeight="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="6dp">
|
||||
|
||||
<ImageView android:id="@+id/icon_update"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:src="@drawable/ic_update"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/text_title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/text_title" />
|
||||
android:layout_marginRight="10dp"
|
||||
android:alpha="0.9"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView android:id="@+id/text_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Downloading update v123"
|
||||
android:layout_weight="1"
|
||||
tools:text="Downloading v123 - 42%"
|
||||
android:fontFamily="@font/inter_semibold"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintLeft_toRightOf="@id/icon_update"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@id/button_close" />
|
||||
|
||||
<ImageView android:id="@+id/button_close"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_close"
|
||||
android:contentDescription="@string/dismiss"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<TextView android:id="@+id/text_body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="42% downloaded"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#9D9D9D"
|
||||
android:layout_marginTop="2dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@id/button_close"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_title" />
|
||||
android:textColor="@color/white"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1" />
|
||||
|
||||
<ProgressBar android:id="@+id/update_banner_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_width="78dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:max="100"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_body" />
|
||||
tools:visibility="visible" />
|
||||
|
||||
<FrameLayout android:id="@+id/button_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/background_button_primary_round_4dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/update_banner_progress"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:background="@drawable/background_button_primary_round_4dp">
|
||||
|
||||
<TextView android:id="@+id/text_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
tools:text="Install"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:textSize="14sp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/white"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp" />
|
||||
android:paddingLeft="13dp"
|
||||
android:paddingRight="13dp" />
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
@@ -372,6 +372,8 @@
|
||||
<string name="deletes_license_keys_from_app">Deletes license keys from app</string>
|
||||
<string name="download_when">Download when</string>
|
||||
<string name="enable_video_cache">Enable Video Cache</string>
|
||||
<string name="use_downloaded_ca_bundle">Use downloaded CA bundle</string>
|
||||
<string name="use_downloaded_ca_bundle_description">May help on devices with stale root certificates.</string>
|
||||
<string name="enable_casting">Enable casting</string>
|
||||
<string name="experimental_background_update_for_subscriptions_cache">Experimental background update for subscriptions cache</string>
|
||||
<string name="export_data">Export Data</string>
|
||||
|
||||
Submodule app/src/stable/assets/sources/apple-podcasts updated: 9c65475be1...8d9dee8a49
Submodule app/src/stable/assets/sources/bilibili updated: 9186672f0f...c63c69beec
Submodule app/src/stable/assets/sources/bitchute updated: b213f91c0b...deed10c077
Submodule app/src/stable/assets/sources/crunchyroll updated: a1714790c5...499ab8b438
Submodule app/src/stable/assets/sources/curiositystream updated: 1ebf5da236...68f85a0d62
Submodule app/src/stable/assets/sources/dailymotion updated: 70f625a3bd...256b8433e0
Submodule app/src/stable/assets/sources/fosdem updated: 2231fbec11...e8fe3b4bb5
Submodule app/src/stable/assets/sources/mixcloud updated: 1b801553b3...c107d15296
Submodule
+1
Submodule app/src/stable/assets/sources/nasa-plus added at 56068c37dd
Submodule app/src/stable/assets/sources/nebula updated: 090cd76dfa...84e920f378
Submodule app/src/stable/assets/sources/odysee updated: 1c7a8a4974...c6e462db9b
Submodule app/src/stable/assets/sources/patreon updated: 52154f36c2...87b168a7cb
Submodule app/src/stable/assets/sources/peertube updated: 7b52405ad0...c955d8ed56
Submodule app/src/stable/assets/sources/redbull-tv updated: 179b7a6e22...7f4317f5c7
Submodule app/src/stable/assets/sources/soundcloud updated: e785c5d8c9...8ed7c19c45
Submodule app/src/stable/assets/sources/tedtalks updated: 292e459eef...f7f31a4f9a
Submodule app/src/stable/assets/sources/twitch updated: cebdad37a3...3a46d407de
Submodule app/src/stable/assets/sources/youtube updated: fb90a44f83...de50576849
@@ -19,7 +19,8 @@
|
||||
"84331338-b045-419c-88e4-c86036f4cbf5": "sources/mixcloud/MixcloudConfig.json",
|
||||
"009775f8-9173-48a2-8df3-d730d08d198d": "sources/radiobrowser/RadioBrowserConfig.json",
|
||||
"5f6658bb-96cc-4965-ba04-c81f8686ab67": "sources/redbull-tv/RedBullTvConfig.json",
|
||||
"d890ff43-7d9f-4f0e-a52d-239014fd512d": "sources/fosdem/FOSDEMConfig.json"
|
||||
"d890ff43-7d9f-4f0e-a52d-239014fd512d": "sources/fosdem/FOSDEMConfig.json",
|
||||
"a1b2c3d4-5e6f-7890-abcd-ef1234567890": "sources/nasa-plus/NASA-PlusConfig.json"
|
||||
},
|
||||
"SOURCES_EMBEDDED_DEFAULT": [
|
||||
"35ae969a-a7db-11ed-afa1-0242ac120002"
|
||||
|
||||
Submodule app/src/unstable/assets/sources/apple-podcasts updated: 9c65475be1...8d9dee8a49
Submodule app/src/unstable/assets/sources/bilibili updated: 9186672f0f...c63c69beec
Submodule app/src/unstable/assets/sources/bitchute updated: b213f91c0b...deed10c077
Submodule app/src/unstable/assets/sources/crunchyroll updated: a1714790c5...499ab8b438
Submodule app/src/unstable/assets/sources/curiositystream updated: 1ebf5da236...68f85a0d62
Submodule app/src/unstable/assets/sources/dailymotion updated: 70f625a3bd...256b8433e0
Submodule app/src/unstable/assets/sources/fosdem updated: 2231fbec11...e8fe3b4bb5
Submodule app/src/unstable/assets/sources/mixcloud updated: 1b801553b3...c107d15296
+1
Submodule app/src/unstable/assets/sources/nasa-plus added at 56068c37dd
Submodule app/src/unstable/assets/sources/nebula updated: 090cd76dfa...84e920f378
Submodule app/src/unstable/assets/sources/odysee updated: 1c7a8a4974...c6e462db9b
Submodule app/src/unstable/assets/sources/patreon updated: 52154f36c2...87b168a7cb
Submodule app/src/unstable/assets/sources/peertube updated: 7b52405ad0...c955d8ed56
Submodule app/src/unstable/assets/sources/redbull-tv updated: 179b7a6e22...7f4317f5c7
Submodule app/src/unstable/assets/sources/soundcloud updated: e785c5d8c9...8ed7c19c45
Submodule app/src/unstable/assets/sources/tedtalks updated: 292e459eef...f7f31a4f9a
Submodule app/src/unstable/assets/sources/twitch updated: cebdad37a3...3a46d407de
Submodule app/src/unstable/assets/sources/youtube updated: fb90a44f83...de50576849
@@ -19,7 +19,8 @@
|
||||
"84331338-b045-419c-88e4-c86036f4cbf5": "sources/mixcloud/MixcloudConfig.json",
|
||||
"009775f8-9173-48a2-8df3-d730d08d198d": "sources/radiobrowser/RadioBrowserConfig.json",
|
||||
"5f6658bb-96cc-4965-ba04-c81f8686ab67": "sources/redbull-tv/RedBullTvConfig.json",
|
||||
"d890ff43-7d9f-4f0e-a52d-239014fd512d": "sources/fosdem/FOSDEMConfig.json"
|
||||
"d890ff43-7d9f-4f0e-a52d-239014fd512d": "sources/fosdem/FOSDEMConfig.json",
|
||||
"a1b2c3d4-5e6f-7890-abcd-ef1234567890": "sources/nasa-plus/NASA-PlusConfig.json"
|
||||
},
|
||||
"SOURCES_EMBEDDED_DEFAULT": [
|
||||
"35ae969a-a7db-11ed-afa1-0242ac120002"
|
||||
|
||||
Reference in New Issue
Block a user