fix(exoplayer): properly handle aspect ratio & subtitles

This commit is contained in:
2026-07-31 01:02:43 +02:00
parent 77a8b2337d
commit d2ca76b4a9
2 changed files with 77 additions and 30 deletions
@@ -4,6 +4,7 @@ import android.app.PictureInPictureParams
import android.graphics.Rect
import android.os.Build
import android.util.Rational
import android.view.Gravity
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
@@ -11,6 +12,10 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
import androidx.media3.common.text.CueGroup
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.SubtitleView
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.uimanager.ThemedReactContext
import com.margelo.nitro.omni.HybridOmniPlayerSpec
@@ -68,6 +73,16 @@ class OmniView(val context: ThemedReactContext) :
}
}
private val contentFrame = AspectRatioFrameLayout(context).apply {
setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT)
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT,
Gravity.CENTER
)
view.addView(this)
}
private val surfaceView = SurfaceView(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
@@ -75,8 +90,19 @@ class OmniView(val context: ThemedReactContext) :
)
holder.addCallback(this@OmniView)
addOnLayoutChangeListener(this@OmniView)
contentFrame.addView(this)
}
private val subtitleView = SubtitleView(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
setUserDefaultStyle()
setUserDefaultTextSize()
view.addView(this)
}
private var surfaceReady = false
private var boundPlayer: OmniPlayer? = null
private var rootContent: ViewGroup? = null
@@ -94,6 +120,20 @@ class OmniView(val context: ThemedReactContext) :
context.addLifecycleEventListener(this)
}
override fun onVideoSizeChanged(videoSize: VideoSize) {
contentFrame.setAspectRatio(
if (videoSize.width > 0 && videoSize.height > 0) {
videoSize.width * videoSize.pixelWidthHeightRatio / videoSize.height
} else {
0f
}
)
}
override fun onCues(cueGroup: CueGroup) {
subtitleView.setCues(cueGroup.cues)
}
override fun onLayoutChange(
view: View?,
left: Int,
@@ -307,7 +347,7 @@ class OmniView(val context: ThemedReactContext) :
child.visibility = View.GONE
}
view.removeView(surfaceView)
contentFrame.removeView(surfaceView)
root.addView(
surfaceView,
FrameLayout.LayoutParams(
@@ -331,7 +371,7 @@ class OmniView(val context: ThemedReactContext) :
}
rootContentViews = emptyList()
view.addView(surfaceView, FrameLayout.LayoutParams(
contentFrame.addView(surfaceView, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
))
@@ -525,36 +525,43 @@ class VlcPlayer(ctx: Context) :
val selectedAudio = player.getSelectedTrack(IMedia.Track.Type.Audio)
val selectedSubtitle = player.getSelectedTrack(IMedia.Track.Type.Text)
val videoTracks = player.getTracks(IMedia.Track.Type.Video)
if (!videoTracks.isNullOrEmpty()) {
val videoFormats = videoTracks.map { track ->
Format.Builder()
.setId(track.id)
.setLabel(track.name)
.setLanguage(track.language)
.setSampleMimeType("video/x-unknown")
.build()
player.getTracks(IMedia.Track.Type.Video).orEmpty()
.groupBy { listOf(it.language, it.name, it.description) }
.values.forEach { videoTracks ->
val videoFormats = videoTracks.map { track ->
Format.Builder()
.setId(track.id)
.setLabel(track.name)
.setLanguage(track.language)
.setSampleMimeType("video/x-unknown")
.build()
}
val group = TrackGroup("vlc-video-${videoTracks.minOf { it.id }}", *videoFormats.toTypedArray())
val selected = BooleanArray(videoFormats.size) { idx ->
selectedVideo != null && selectedVideo.id == videoTracks[idx].id
}
val support = IntArray(videoFormats.size) { FORMAT_HANDLED }
result.add(Group(group, videoFormats.size > 1, support, selected))
}
val group = TrackGroup("vlc-video", *videoFormats.toTypedArray())
val selected = BooleanArray(videoFormats.size) { idx ->
selectedVideo != null && selectedVideo.id == videoTracks[idx].id
}
val support = IntArray(videoFormats.size) { FORMAT_HANDLED }
result.add(Group(group, videoFormats.size > 1, support, selected))
}
player.getTracks(IMedia.Track.Type.Audio)?.forEach { track ->
val format = Format.Builder()
.setId(track.id)
.setLabel(track.name)
.setLanguage(track.language)
.setSampleMimeType("audio/x-unknown")
.build()
val group = TrackGroup("vlc-audio-${track.id}", format)
val selected = booleanArrayOf(selectedAudio != null && selectedAudio.id == track.id)
val support = intArrayOf(FORMAT_HANDLED)
result.add(Group(group, false, support, selected))
}
player.getTracks(IMedia.Track.Type.Audio).orEmpty()
.groupBy { listOf(it.language, it.name, it.description) }
.values.forEach { audioTracks ->
val audioFormats = audioTracks.map { track ->
Format.Builder()
.setId(track.id)
.setLabel(track.name)
.setLanguage(track.language)
.setSampleMimeType("audio/x-unknown")
.build()
}
val group = TrackGroup("vlc-audio-${audioTracks.minOf { it.id }}", *audioFormats.toTypedArray())
val selected = BooleanArray(audioFormats.size) { idx ->
selectedAudio != null && selectedAudio.id == audioTracks[idx].id
}
val support = IntArray(audioFormats.size) { FORMAT_HANDLED }
result.add(Group(group, audioFormats.size > 1, support, selected))
}
player.getTracks(IMedia.Track.Type.Text)?.forEach { track ->
val format = Format.Builder()