From c58d1cb0d7e9781db726691a62401eb38cb3fcee Mon Sep 17 00:00:00 2001 From: KhooLy <73142442+KhooLy@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:55:43 +0300 Subject: [PATCH] Fix libass relay renderer never activating for HTTP streams Pre-extraction is skipped for non-local streams so embeddedNativeAssTracks is always empty, which meant embeddedSubtitle was always null, which meant the relay renderer was never assigned as the active renderer. Relay renderer should win whenever there is no external subtitle selected. --- .../ui/catalog/NativeLibassSubtitleOverlay.kt | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/NativeLibassSubtitleOverlay.kt b/app/src/main/java/com/fluxa/app/ui/catalog/NativeLibassSubtitleOverlay.kt index fbaea01..46e5806 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/NativeLibassSubtitleOverlay.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/NativeLibassSubtitleOverlay.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.net.Uri +import android.os.SystemClock import android.util.Log import android.view.View import androidx.compose.runtime.Composable @@ -19,6 +20,7 @@ import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.fluxa.app.player.ExternalSubtitleTrack +import com.fluxa.app.player.LibassDebugLog import com.fluxa.app.player.MediaPlayerController import com.fluxa.app.player.MediaTrack import com.fluxa.app.player.MkvNativeAssExtractor @@ -72,6 +74,10 @@ internal fun NativeLibassSubtitleOverlay( var localRenderer by remember { mutableStateOf(null) } LaunchedEffect(externalSubtitle?.url, embeddedSubtitle?.id) { + LibassDebugLog.d( + "overlay selection external=${externalSubtitle?.let { "${LibassDebugLog.urlSummary(it.url)} label=${it.label} lang=${it.language}" } ?: ""} " + + "embedded=${embeddedSubtitle?.let { "id=${it.id} label=${it.label} lang=${it.language} bytes=${it.assData.size} fonts=${it.fonts.size}" } ?: ""}" + ) val previous = localRenderer localRenderer = null previous?.close() @@ -83,19 +89,29 @@ internal fun NativeLibassSubtitleOverlay( fonts = embedded.fonts, fontsDir = context.filesDir.resolve("fonts").absolutePath ) - }.onFailure { e -> Log.w("NativeLibassOverlay", "Embedded renderer failed", e) }.getOrNull() + }.onFailure { e -> + Log.w("NativeLibassOverlay", "Embedded renderer failed", e) + LibassDebugLog.w("overlay embedded renderer failed id=${embedded.id}", e) + }.getOrNull() + LibassDebugLog.d("overlay embedded renderer ${if (localRenderer != null) "ready" else "not ready"} id=${embedded.id}") return@LaunchedEffect } val url = externalSubtitle?.url ?: return@LaunchedEffect localRenderer = runCatching { val data = fetchSubtitleBytes(context, url) + LibassDebugLog.d("overlay fetched external ASS bytes=${data.size} url=${LibassDebugLog.urlSummary(url)}") val fonts = MkvNativeAssExtractor.extractExternalFontHints(url) + LibassDebugLog.d("overlay external font hints fonts=${fonts.size} url=${LibassDebugLog.urlSummary(url)}") NativeLibassRenderer.create( assData = data, fonts = fonts, fontsDir = context.filesDir.resolve("fonts").absolutePath ) - }.onFailure { e -> Log.w("NativeLibassOverlay", "External renderer failed", e) }.getOrNull() + }.onFailure { e -> + Log.w("NativeLibassOverlay", "External renderer failed", e) + LibassDebugLog.w("overlay external renderer failed url=${LibassDebugLog.urlSummary(url)}", e) + }.getOrNull() + LibassDebugLog.d("overlay external renderer ${if (localRenderer != null) "ready" else "not ready"} url=${LibassDebugLog.urlSummary(url)}") } DisposableEffect(Unit) { @@ -106,9 +122,14 @@ internal fun NativeLibassSubtitleOverlay( } val activeRenderer = when { - embeddedSubtitle != null -> relayRenderer ?: localRenderer externalSubtitle != null -> localRenderer - else -> null + else -> relayRenderer ?: localRenderer + } + LaunchedEffect(activeRenderer, relayRenderer, localRenderer, externalSubtitle?.url, embeddedSubtitle?.id) { + LibassDebugLog.d( + "overlay activeRenderer=${activeRenderer != null} relayRenderer=${relayRenderer != null} localRenderer=${localRenderer != null} " + + "external=${externalSubtitle != null} embedded=${embeddedSubtitle != null}" + ) } AndroidView( @@ -133,6 +154,9 @@ private class NativeLibassSubtitleView(context: Context) : View(context) { var renderer: NativeLibassRenderer? = null set(value) { + if (field !== value) { + LibassDebugLog.d("overlay view renderer changed active=${value != null} view=${width}x$height attached=$isAttachedToWindow") + } field = value if (isAttachedToWindow) postInvalidateOnAnimation() } @@ -144,6 +168,8 @@ private class NativeLibassSubtitleView(context: Context) : View(context) { } private var bitmap: Bitmap? = null + private var lastDrawLogMs = 0L + private var lastDrawHadPixels: Boolean? = null private val playerListener = object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { @@ -161,12 +187,14 @@ private class NativeLibassSubtitleView(context: Context) : View(context) { override fun onAttachedToWindow() { super.onAttachedToWindow() + LibassDebugLog.d("overlay view attached size=${width}x$height") player?.addListener(playerListener) postInvalidateOnAnimation() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() + LibassDebugLog.d("overlay view detached") player?.removeListener(playerListener) bitmap?.recycle() bitmap = null @@ -186,6 +214,15 @@ private class NativeLibassSubtitleView(context: Context) : View(context) { val result = activeRenderer.render(activePlayer.currentPosition + subtitleDelayMs, targetBitmap) if (result > 0) canvas.drawBitmap(targetBitmap, 0f, 0f, null) + val hasPixels = result > 0 + val now = SystemClock.elapsedRealtime() + if (lastDrawHadPixels != hasPixels || now - lastDrawLogMs > 1_500L) { + LibassDebugLog.d( + "overlay draw positionMs=${activePlayer.currentPosition} delayMs=$subtitleDelayMs size=${width}x$height result=$result hasPixels=$hasPixels playing=${activePlayer.isPlaying} playWhenReady=${activePlayer.playWhenReady}" + ) + lastDrawHadPixels = hasPixels + lastDrawLogMs = now + } if (activePlayer.isPlaying || activePlayer.playWhenReady) { if (result == 0) postInvalidateDelayed(100) else postInvalidateOnAnimation()