From cce117c58592da9a5579793da723825bc06e26dd Mon Sep 17 00:00:00 2001 From: Kelvin Date: Fri, 30 May 2025 17:20:11 +0200 Subject: [PATCH] Article header support, socket handling improvements --- app/src/main/assets/scripts/source.js | 7 +++ .../platforms/js/models/JSArticleDetails.kt | 13 +++++ .../futo/platformplayer/engine/V8Plugin.kt | 8 +++ .../engine/packages/PackageHttp.kt | 51 ++++++++++++++++--- .../main/ArticleDetailFragment.kt | 29 +++++++++++ .../mainactivity/main/VideoDetailView.kt | 1 + 6 files changed, 102 insertions(+), 7 deletions(-) diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index 0e48d174..0638f079 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -346,6 +346,13 @@ class ArticleImagesSegment extends ArticleSegment { this.caption = caption; } } +class ArticleHeaderSegment extends ArticleSegment { + constructor(content, level) { + super(3); + this.level = level; + this.content = content; + } +} class ArticleNestedSegment extends ArticleSegment { constructor(nested) { super(9); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt index 6ed30cc1..0e2c0e32 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt @@ -101,6 +101,7 @@ open class JSArticleDetails : JSContent, IPlatformArticleDetails, IPluginSourced return when(SegmentType.fromInt(obj.getOrThrow(client.config, "type", "JSArticle.Segment"))) { SegmentType.TEXT -> JSTextSegment(client, obj); SegmentType.IMAGES -> JSImagesSegment(client, obj); + SegmentType.HEADER -> JSHeaderSegment(client, obj); SegmentType.NESTED -> JSNestedSegment(client, obj); else -> null; } @@ -112,6 +113,7 @@ enum class SegmentType(val value: Int) { UNKNOWN(0), TEXT(1), IMAGES(2), + HEADER(3), NESTED(9); @@ -152,6 +154,17 @@ class JSImagesSegment: IJSArticleSegment { caption = obj.getOrDefault(client.config, "caption", contextName, "") ?: ""; } } +class JSHeaderSegment: IJSArticleSegment { + override val type = SegmentType.HEADER; + val content: String; + val level: Int; + + constructor(client: JSClient, obj: V8ValueObject) { + val contextName = "JSHeaderSegment"; + content = obj.getOrDefault(client.config, "content", contextName, "") ?: ""; + level = obj.getOrDefault(client.config, "level", contextName, 1) ?: 1; + } +} class JSNestedSegment: IJSArticleSegment { override val type = SegmentType.NESTED; val nested: IPlatformContent; diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 1a9ba3f0..15412fd9 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -188,6 +188,14 @@ class V8Plugin { whenNotBusy { synchronized(_runtimeLock) { isStopped = true; + + //Cleanup http + for(pack in _depsPackages) { + if(pack is PackageHttp) { + pack.cleanup(); + } + } + _runtime?.let { _runtime = null; if(!it.isClosed && !it.isDead) { diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index 5616f7e9..900eb6f0 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -41,6 +41,9 @@ class PackageHttp: V8Package { private var _batchPoolLock: Any = Any(); private var _batchPool: ForkJoinPool? = null; + private val aliveSockets = mutableListOf(); + private var _cleanedUp = false; + constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) { _config = config; @@ -50,6 +53,27 @@ class PackageHttp: V8Package { _packageClientAuth = PackageHttpClient(this, _clientAuth); } + fun cleanup(){ + Logger.w(TAG, "PackageHttp Cleaning up") + val sockets = synchronized(aliveSockets) { aliveSockets.toList() } + _cleanedUp = true; + for(socket in sockets){ + try { + Logger.w(TAG, "PackageHttp Socket Cleaned Up"); + socket.close(1001, "Cleanup"); + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to close socket", ex); + } + } + if(sockets.size > 0) { + //Thread.sleep(100); //Give sockets a bit + } + synchronized(aliveSockets) { + aliveSockets.clear(); + } + } + /* Automatically adjusting threadpool dedicated per PackageHttp for batch requests. @@ -455,9 +479,16 @@ class PackageHttp: V8Package { @V8Function fun socket(url: String, headers: Map? = null): SocketResult { + if(_package._cleanedUp) + throw IllegalStateException("Plugin shutdown"); val socketHeaders = headers?.toMutableMap() ?: HashMap(); applyDefaultHeaders(socketHeaders); - return SocketResult(this, _client, url, socketHeaders); + val socket = SocketResult(_package, this, _client, url, socketHeaders); + Logger.w(TAG, "PackageHttp Socket opened"); + synchronized(_package.aliveSockets) { + _package.aliveSockets.add(socket); + } + return socket; } private fun applyDefaultHeaders(headerMap: MutableMap) { @@ -563,13 +594,15 @@ class PackageHttp: V8Package { private var _listeners: V8ValueObject? = null; + private val _package: PackageHttp; private val _packageClient: PackageHttpClient; private val _client: ManagedHttpClient; private val _url: String; private val _headers: Map; - constructor(pack: PackageHttpClient, client: ManagedHttpClient, url: String, headers: Map) { + constructor(parent: PackageHttp, pack: PackageHttpClient, client: ManagedHttpClient, url: String, headers: Map) { _packageClient = pack; + _package = parent; _client = client; _url = url; _headers = headers; @@ -595,7 +628,7 @@ class PackageHttp: V8Package { override fun open() { Logger.i(TAG, "Websocket opened: " + _url); _isOpen = true; - if(hasOpen) { + if(hasOpen && _listeners?.isClosed != true) { try { _listeners?.invokeVoid("open", arrayOf()); } @@ -605,7 +638,7 @@ class PackageHttp: V8Package { } } override fun message(msg: String) { - if(hasMessage) { + if(hasMessage && _listeners?.isClosed != true) { try { _listeners?.invokeVoid("message", msg); } @@ -613,7 +646,7 @@ class PackageHttp: V8Package { } } override fun closing(code: Int, reason: String) { - if(hasClosing) + if(hasClosing && _listeners?.isClosed != true) { try { _listeners?.invokeVoid("closing", code, reason); @@ -625,7 +658,7 @@ class PackageHttp: V8Package { } override fun closed(code: Int, reason: String) { _isOpen = false; - if(hasClosed) { + if(hasClosed && _listeners?.isClosed != true) { try { _listeners?.invokeVoid("closed", code, reason); } @@ -633,11 +666,15 @@ class PackageHttp: V8Package { Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex); } } + Logger.w(TAG, "PackageHttp Socket removed"); + synchronized(_package.aliveSockets) { + _package.aliveSockets.remove(this@SocketResult); + } } override fun failure(exception: Throwable) { _isOpen = false; Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception); - if(hasFailure) { + if(hasFailure && _listeners?.isClosed != true) { try { _listeners?.invokeVoid("failure", exception.message); } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt index 0f210d8d..989a19e1 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.fragment.mainactivity.main import android.content.Context import android.content.Intent +import android.graphics.Color import android.graphics.Typeface import android.graphics.drawable.Animatable import android.os.Bundle @@ -44,6 +45,7 @@ import com.futo.platformplayer.api.media.models.ratings.RatingLikes import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo import com.futo.platformplayer.api.media.platforms.js.models.JSArticleDetails +import com.futo.platformplayer.api.media.platforms.js.models.JSHeaderSegment import com.futo.platformplayer.api.media.platforms.js.models.JSImagesSegment import com.futo.platformplayer.api.media.platforms.js.models.JSNestedSegment import com.futo.platformplayer.api.media.platforms.js.models.JSTextSegment @@ -54,6 +56,7 @@ import com.futo.platformplayer.fixHtmlWhitespace import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod +import com.futo.platformplayer.sp import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlayer @@ -480,6 +483,11 @@ class ArticleDetailFragment : MainFragment { for(seg in value.segments) { when(seg.type) { + SegmentType.HEADER -> { + if(seg is JSHeaderSegment) { + _containerSegments.addView(ArticleHeaderBlock(context, seg.content, seg.level)) + } + } SegmentType.TEXT -> { if(seg is JSTextSegment) { _containerSegments.addView(ArticleTextBlock(context, seg.content, seg.textType)) @@ -690,6 +698,27 @@ class ArticleDetailFragment : MainFragment { } } + class ArticleHeaderBlock : LinearLayout { + constructor(context: Context?, content: String, level: Int) : super(context){ + inflate(context, R.layout.view_segment_text, this); + + findViewById(R.id.text_content)?.let { + it.text = content; + + val sp = when(level) { + 1 -> 6.sp(resources); + 2 -> 8.sp(resources); + 3 -> 10.sp(resources); + 4 -> 12.sp(resources); + 5 -> 14.sp(resources); + else -> 6.sp(resources); + } + it.setTextColor(Color.WHITE); + it.setTypeface(Typeface.create(null, 600, false)); + it.textSize = sp.toFloat(); + } + } + } class ArticleTextBlock : LinearLayout { constructor(context: Context?, content: String, textType: TextType) : super(context){ inflate(context, R.layout.view_segment_text, this); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index fd2cb19b..9c34dfac 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -781,6 +781,7 @@ class VideoDetailView : ConstraintLayout { _lastAudioSource = null; _lastSubtitleSource = null; video = null; + _container_content_liveChat?.close(); _player.clear(); cleanupPlaybackTracker(); Logger.i(TAG, "Keep screen on unset onClose")