Slightly optimize subtitles view & workaround possible crash (#1334)

## Description
Instead of adding/removing the `SubtitleView` from composition
sometimes, instead toggle its visibility. This is technically slightly
more efficient. There's also a couple other minor tweaks that make the
subtitles style a little more efficient.

This should also help prevent crash caused by possible a race condition
in libass-android
(https://github.com/peerless2012/libass-android/issues/75).

### Related issues
I think this fixes #1322

### Testing
Emulator mostly

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-05-06 18:29:26 -04:00 committed by GitHub
parent 45ab0713a9
commit b23d30f94b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,7 @@
package com.github.damontecres.wholphin.ui.playback package com.github.damontecres.wholphin.ui.playback
import android.view.Gravity import android.view.Gravity
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
@ -28,6 +29,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -35,12 +37,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
@ -74,7 +76,6 @@ import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.LocalImageUrlService import com.github.damontecres.wholphin.ui.LocalImageUrlService
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.LoadingPage import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.playback.overlay.PauseIndicator import com.github.damontecres.wholphin.ui.playback.overlay.PauseIndicator
import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackAction import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackAction
@ -435,9 +436,9 @@ fun PlaybackPageContent(
remember(subtitleSettings) { subtitleSettings.imageSubtitleOpacity / 100f } remember(subtitleSettings) { subtitleSettings.imageSubtitleOpacity / 100f }
// Subtitles // Subtitles
if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled && !presentationState.coverSurface) { val subtitleMaxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f)
val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f)
val isImageSubtitles = remember(cues) { cues.firstOrNull()?.bitmap != null } val isImageSubtitles = remember(cues) { cues.firstOrNull()?.bitmap != null }
var cueCount by remember { mutableIntStateOf(0) }
AndroidView( AndroidView(
factory = { context -> factory = { context ->
SubtitleView(context).apply { SubtitleView(context).apply {
@ -463,16 +464,24 @@ fun PlaybackPageContent(
} }
} }
}, },
update = { update = { subtitleView ->
it.setCues(cues) val subtitleVisible =
skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled && !presentationState.coverSurface
subtitleView.visibility = if (subtitleVisible) View.VISIBLE else View.INVISIBLE
subtitleView.setCues(cues)
if (cues.size > cueCount) {
// The output creates a painter for each cue, so need to apply the changes when the number of cues increases
Media3SubtitleOverride(subtitleSettings.calculateEdgeSize(density)) Media3SubtitleOverride(subtitleSettings.calculateEdgeSize(density))
.apply(it) .apply(subtitleView)
it.children.firstOrNull { it is AssSubtitleView }?.let { cueCount = cues.size
}
subtitleView.children.firstOrNull { it is AssSubtitleView }?.let {
(it as? AssSubtitleView)?.apply { (it as? AssSubtitleView)?.apply {
val resized = val resized =
layoutParams.let { it.width != playerSurfaceSize.width || it.height != playerSurfaceSize.height } layoutParams.let { it.width != playerSurfaceSize.width || it.height != playerSurfaceSize.height }
if (resized) { if (resized) {
Timber.v("Resizing AssSubtitleView: $playerSurfaceSize") Timber.v("Resizing AssSubtitleView: %s", playerSurfaceSize)
layoutParams = layoutParams =
FrameLayout FrameLayout
.LayoutParams( .LayoutParams(
@ -488,13 +497,14 @@ fun PlaybackPageContent(
}, },
modifier = modifier =
Modifier Modifier
.fillMaxSize(maxSize) .fillMaxSize(subtitleMaxSize)
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.background(Color.Transparent) .background(Color.Transparent)
.ifElse(isImageSubtitles, Modifier.alpha(subtitleImageOpacity)), .graphicsLayer {
alpha = if (isImageSubtitles) subtitleImageOpacity else 1f
},
) )
} }
}
// Ask to skip intros, etc button // Ask to skip intros, etc button
AnimatedVisibility( AnimatedVisibility(