This commit is contained in:
darkflame91 2026-07-26 22:48:39 +05:30 committed by GitHub
commit 82ebdf31b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -7,6 +7,7 @@ import android.text.SpannableString
import android.net.Uri
import android.util.Log
import android.util.TypedValue
import android.graphics.RectF
import android.graphics.Typeface
import android.os.Build
import android.os.SystemClock
@ -39,6 +40,7 @@ import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.VideoSize
import androidx.media3.common.text.Cue
import androidx.media3.common.text.CueGroup
import androidx.media3.common.util.UnstableApi
@ -254,6 +256,9 @@ private fun ExoPlayerSurface(
var selectedExternalSubtitleMimeType by remember(playerSourceKey) { mutableStateOf<String?>(null) }
val latestSubtitleDelayMs = rememberUpdatedState(subtitleDelayMs)
val latestExternalSubtitleMimeType = rememberUpdatedState(selectedExternalSubtitleMimeType)
var playerViewRef by remember { mutableStateOf<PlayerView?>(null) }
var videoAspectRatio by remember(playerSourceKey) { mutableStateOf(0f) }
val latestVideoAspectRatio = rememberUpdatedState(videoAspectRatio)
var decoderPriorityOverride by remember(playerSourceKey) { mutableStateOf<Int?>(null) }
var fallbackStartPositionMs by remember(playerSourceKey) { mutableStateOf<Long?>(null) }
val effectiveDecoderPriority = decoderPriorityOverride ?: playerSettings.decoderPriority
@ -342,6 +347,9 @@ private fun ExoPlayerSurface(
shouldNormalizeCuePositionProvider = {
latestExternalSubtitleMimeType.value == MimeTypes.TEXT_VTT
},
videoBoundsFractionProvider = {
playerViewRef?.videoBoundsFraction(latestVideoAspectRatio.value)
},
)
.setExtensionRendererMode(effectiveDecoderPriority)
.setEnableDecoderFallback(true)
@ -449,7 +457,6 @@ private fun ExoPlayerSurface(
val pendingSubtitleTrackIndex = remember { mutableListOf<Int>() }
val pendingAudioTrackSelection = remember { mutableListOf<TrackSelectionSnapshot>() }
var playerViewRef by remember { mutableStateOf<PlayerView?>(null) }
var currentSubtitleStyle by remember { mutableStateOf(SubtitleStyleState.DEFAULT) }
var subtitleSelectionJob by remember { mutableStateOf<Job?>(null) }
val isInPip = rememberIsInPictureInPicture()
@ -580,10 +587,6 @@ private fun ExoPlayerSurface(
dispatchExoPlayerSnapshot()
}
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
latestOnSnapshot.value(exoPlayer.snapshot())
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
Log.i(
PLAYER_DIAGNOSTIC_TAG,
@ -608,6 +611,13 @@ private fun ExoPlayerSurface(
dispatchExoPlayerSnapshot()
}
override fun onVideoSizeChanged(videoSize: VideoSize) {
latestOnSnapshot.value(exoPlayer.snapshot())
if (videoSize.width > 0 && videoSize.height > 0) {
videoAspectRatio = videoSize.width.toFloat() / videoSize.height.toFloat()
}
}
override fun onTracksChanged(tracks: androidx.media3.common.Tracks) {
Log.d(TAG, "onTracksChanged: ${tracks.groups.size} groups total")
exoPlayer.logCurrentTracks("onTracksChanged")
@ -1794,11 +1804,50 @@ private fun ExoPlayer.logCurrentTracks(context: String) {
Log.d(TAG, "--- end logCurrentTracks ---")
}
@androidx.annotation.OptIn(UnstableApi::class)
private fun PlayerView.videoBoundsFraction(aspectRatio: Float): RectF? {
val subtitleView = this.subtitleView ?: return null
val viewWidth = subtitleView.width.toFloat()
val viewHeight = subtitleView.height.toFloat()
if (viewWidth <= 0f || viewHeight <= 0f) return null
if (aspectRatio > 0f) {
val parentRatio = viewWidth / viewHeight
return if (parentRatio > aspectRatio) {
val fitW = viewHeight * aspectRatio
val leftPx = (viewWidth - fitW) / 2f
RectF(leftPx / viewWidth, 0f, (leftPx + fitW) / viewWidth, 1f)
} else {
val fitH = viewWidth / aspectRatio
val topPx = (viewHeight - fitH) / 2f
RectF(0f, topPx / viewHeight, 1f, (topPx + fitH) / viewHeight)
}
}
val contentFrame = getTag(androidx.media3.ui.R.id.exo_content_frame) as? AspectRatioFrameLayout
?: findViewById<AspectRatioFrameLayout>(androidx.media3.ui.R.id.exo_content_frame)
?.also { setTag(androidx.media3.ui.R.id.exo_content_frame, it) }
?: return null
val frameWidth = contentFrame.width.toFloat()
val frameHeight = contentFrame.height.toFloat()
if (frameWidth <= 0f || frameHeight <= 0f) return null
if (frameWidth > viewWidth || frameHeight > viewHeight) return null
val left = contentFrame.x / viewWidth
val top = contentFrame.y / viewHeight
return RectF(
left,
top,
left + frameWidth / viewWidth,
top + frameHeight / viewHeight,
)
}
@androidx.annotation.OptIn(UnstableApi::class)
private class SubtitleOffsetRenderersFactory(
context: Context,
private val subtitleDelayUsProvider: () -> Long,
private val shouldNormalizeCuePositionProvider: () -> Boolean,
private val videoBoundsFractionProvider: () -> RectF?,
) : DefaultRenderersFactory(context) {
override fun buildTextRenderers(
context: Context,
@ -1810,6 +1859,7 @@ private class SubtitleOffsetRenderersFactory(
val normalizingOutput = CueNormalizingTextOutput(
delegate = output,
shouldNormalizeCuePositionProvider = shouldNormalizeCuePositionProvider,
videoBoundsFractionProvider = videoBoundsFractionProvider,
)
val startIndex = out.size
super.buildTextRenderers(context, normalizingOutput, outputLooper, extensionRendererMode, out)
@ -1825,6 +1875,7 @@ private class SubtitleOffsetRenderersFactory(
private class CueNormalizingTextOutput(
private val delegate: TextOutput,
private val shouldNormalizeCuePositionProvider: () -> Boolean,
private val videoBoundsFractionProvider: () -> RectF?,
) : TextOutput {
override fun onCues(cueGroup: CueGroup) {
val processed = cueGroup.cues.map(::processCue)
@ -1841,9 +1892,36 @@ private class CueNormalizingTextOutput(
if (shouldNormalizeCuePositionProvider()) {
processed = normalizeCuePosition(processed)
}
if (processed.bitmap != null) {
val bounds = videoBoundsFractionProvider()
if (bounds != null && bounds.width() > 0f && bounds.height() > 0f) {
val isIdentity = bounds.left == 0f && bounds.top == 0f
&& bounds.width() == 1f && bounds.height() == 1f
if (!isIdentity) {
processed = remapBitmapCueToVideoBounds(processed, bounds)
}
}
}
return processed
}
private fun remapBitmapCueToVideoBounds(cue: Cue, bounds: RectF): Cue {
val builder = cue.buildUpon()
if (cue.position != Cue.DIMEN_UNSET) {
builder.setPosition(bounds.left + cue.position * bounds.width())
}
if (cue.size != Cue.DIMEN_UNSET) {
builder.setSize(cue.size * bounds.width())
}
if (cue.lineType == Cue.LINE_TYPE_FRACTION && cue.line != Cue.DIMEN_UNSET) {
builder.setLine(bounds.top + cue.line * bounds.height(), Cue.LINE_TYPE_FRACTION)
}
if (cue.bitmapHeight != Cue.DIMEN_UNSET) {
builder.setBitmapHeight(cue.bitmapHeight * bounds.height())
}
return builder.build()
}
private fun normalizeCuePosition(cue: Cue): Cue {
if (cue.bitmap != null || cue.verticalType != Cue.TYPE_UNSET || cue.line == Cue.DIMEN_UNSET) {
return cue