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.
This commit is contained in:
KhooLy 2026-07-03 19:55:43 +03:00
parent 5254b5152f
commit c58d1cb0d7

View file

@ -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<NativeLibassRenderer?>(null) }
LaunchedEffect(externalSubtitle?.url, embeddedSubtitle?.id) {
LibassDebugLog.d(
"overlay selection external=${externalSubtitle?.let { "${LibassDebugLog.urlSummary(it.url)} label=${it.label} lang=${it.language}" } ?: "<none>"} " +
"embedded=${embeddedSubtitle?.let { "id=${it.id} label=${it.label} lang=${it.language} bytes=${it.assData.size} fonts=${it.fonts.size}" } ?: "<none>"}"
)
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()