Article header support, socket handling improvements

This commit is contained in:
Kelvin 2025-05-30 17:20:11 +02:00
commit cce117c585
6 changed files with 102 additions and 7 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -41,6 +41,9 @@ class PackageHttp: V8Package {
private var _batchPoolLock: Any = Any();
private var _batchPool: ForkJoinPool? = null;
private val aliveSockets = mutableListOf<SocketResult>();
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<String, String>? = 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<String, String>) {
@ -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<String, String>;
constructor(pack: PackageHttpClient, client: ManagedHttpClient, url: String, headers: Map<String,String>) {
constructor(parent: PackageHttp, pack: PackageHttpClient, client: ManagedHttpClient, url: String, headers: Map<String,String>) {
_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<Any>());
}
@ -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);
}

View file

@ -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<TextView>(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);

View file

@ -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")