From 88226ba127de0d72e6bdb4b6af55a5ff78e2a6e9 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:31:36 +0530 Subject: [PATCH] test: libass overlay test --- .../features/player/PlayerEngine.android.kt | 72 +++++++++++++++++-- .../app/features/player/PlayerLibassCompat.kt | 8 +++ composeApp/src/androidMain/res/values/ids.xml | 4 ++ 3 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 composeApp/src/androidMain/res/values/ids.xml diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt index 09d8901e..d186bf9a 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt @@ -45,6 +45,9 @@ import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView import androidx.media3.ui.SubtitleView import androidx.media3.ui.CaptionStyleCompat +import com.nuvio.app.R +import io.github.peerless2012.ass.media.kt.withAssSupport +import io.github.peerless2012.ass.media.widget.AssSubtitleView import kotlinx.coroutines.delay import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -90,6 +93,10 @@ actual fun PlatformPlayerSurface( val sanitizedSourceResponseHeaders = remember(sourceResponseHeaders) { sanitizePlaybackResponseHeaders(sourceResponseHeaders) } + val useLibass = playerSettings.useLibass + val libassRenderType = runCatching { + LibassRenderType.valueOf(playerSettings.libassRenderType) + }.getOrDefault(LibassRenderType.CUES) val exoPlayer = remember(sourceUrl, sourceAudioUrl, sanitizedSourceHeaders, sanitizedSourceResponseHeaders) { val renderersFactory = DefaultRenderersFactory(context) @@ -126,11 +133,6 @@ actual fun PlatformPlayerSurface( useYoutubeChunkedPlayback = useYoutubeChunkedPlayback, ) - val useLibass = playerSettings.useLibass - val libassRenderType = runCatching { - LibassRenderType.valueOf(playerSettings.libassRenderType) - }.getOrDefault(LibassRenderType.CUES) - val player = if (useLibass) { ExoPlayer.Builder(context) .setTrackSelector(trackSelector) @@ -418,6 +420,11 @@ actual fun PlatformPlayerSurface( this.resizeMode = resizeMode.toExoResizeMode() setShutterBackgroundColor(android.graphics.Color.BLACK) playerViewRef = this + syncLibassOverlay( + player = exoPlayer, + enabled = useLibass, + renderType = libassRenderType, + ) applySubtitleStyle(currentSubtitleStyle) } }, @@ -426,6 +433,11 @@ actual fun PlatformPlayerSurface( playerView.useController = useNativeController playerView.resizeMode = resizeMode.toExoResizeMode() playerViewRef = playerView + playerView.syncLibassOverlay( + player = exoPlayer, + enabled = useLibass, + renderType = libassRenderType, + ) playerView.applySubtitleStyle(currentSubtitleStyle) }, ) @@ -456,6 +468,56 @@ private fun PlayerResizeMode.toExoResizeMode(): Int = PlayerResizeMode.Zoom -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM } +private fun PlayerView.syncLibassOverlay( + player: ExoPlayer, + enabled: Boolean, + renderType: LibassRenderType, +) { + val subtitleView = subtitleView ?: return + val needsOverlay = enabled && renderType.usesOverlaySubtitleView() + val boundPlayer = getTag(R.id.libass_overlay_bound_player) as? ExoPlayer + val hasOverlayChild = subtitleView.hasAssOverlayChild() + + if (!needsOverlay) { + if (hasOverlayChild) { + subtitleView.removeAssOverlayChildren() + } + if (boundPlayer != null) { + setTag(R.id.libass_overlay_bound_player, null) + } + return + } + + val assHandler = player.getAssHandlerCompat() ?: return + if (boundPlayer === player && hasOverlayChild) { + return + } + + subtitleView.removeAssOverlayChildren() + subtitleView.withAssSupport(assHandler) + setTag(R.id.libass_overlay_bound_player, player) +} + +private fun LibassRenderType.usesOverlaySubtitleView(): Boolean = + this == LibassRenderType.OVERLAY_CANVAS || this == LibassRenderType.OVERLAY_OPEN_GL + +private fun SubtitleView.hasAssOverlayChild(): Boolean { + for (index in 0 until childCount) { + if (getChildAt(index) is AssSubtitleView) { + return true + } + } + return false +} + +private fun SubtitleView.removeAssOverlayChildren() { + for (index in childCount - 1 downTo 0) { + if (getChildAt(index) is AssSubtitleView) { + removeViewAt(index) + } + } +} + private fun PlayerView.applySubtitleStyle(style: SubtitleStyleState) { subtitleView?.apply { val baseBottomPaddingFraction = SubtitleView.DEFAULT_BOTTOM_PADDING_FRACTION * 2f / 3f diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt index 798eb450..45f64173 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt @@ -20,6 +20,10 @@ import io.github.peerless2012.ass.media.extractor.AssMatroskaExtractor import io.github.peerless2012.ass.media.kt.withAssSupport import io.github.peerless2012.ass.media.parser.AssSubtitleParserFactory import io.github.peerless2012.ass.media.type.AssRenderType +import java.util.Collections +import java.util.WeakHashMap + +private val assHandlersByPlayer = Collections.synchronizedMap(WeakHashMap()) @OptIn(UnstableApi::class) internal fun ExoPlayer.Builder.buildWithAssSupportCompat( @@ -47,10 +51,14 @@ internal fun ExoPlayer.Builder.buildWithAssSupportCompat( .setRenderersFactory(renderersFactory.withAssSupport(assHandler)) .build() + assHandlersByPlayer[player] = assHandler + assHandler.init(player) return player } +internal fun ExoPlayer.getAssHandlerCompat(): AssHandler? = assHandlersByPlayer[this] + @OptIn(UnstableApi::class) private class CompatAssSubtitleParserFactory( private val assHandler: AssHandler diff --git a/composeApp/src/androidMain/res/values/ids.xml b/composeApp/src/androidMain/res/values/ids.xml new file mode 100644 index 00000000..c5bc55da --- /dev/null +++ b/composeApp/src/androidMain/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file