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.graphics.Rect
import android.os.Build import android.os.Build
import android.util.Rational import android.util.Rational
import android.view.Gravity
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.View import android.view.View
@@ -11,6 +12,10 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.media3.common.Player 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.bridge.LifecycleEventListener
import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ThemedReactContext
import com.margelo.nitro.omni.HybridOmniPlayerSpec 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 { private val surfaceView = SurfaceView(context).apply {
layoutParams = FrameLayout.LayoutParams( layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
@@ -75,8 +90,19 @@ class OmniView(val context: ThemedReactContext) :
) )
holder.addCallback(this@OmniView) holder.addCallback(this@OmniView)
addOnLayoutChangeListener(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) view.addView(this)
} }
private var surfaceReady = false private var surfaceReady = false
private var boundPlayer: OmniPlayer? = null private var boundPlayer: OmniPlayer? = null
private var rootContent: ViewGroup? = null private var rootContent: ViewGroup? = null
@@ -94,6 +120,20 @@ class OmniView(val context: ThemedReactContext) :
context.addLifecycleEventListener(this) 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( override fun onLayoutChange(
view: View?, view: View?,
left: Int, left: Int,
@@ -307,7 +347,7 @@ class OmniView(val context: ThemedReactContext) :
child.visibility = View.GONE child.visibility = View.GONE
} }
view.removeView(surfaceView) contentFrame.removeView(surfaceView)
root.addView( root.addView(
surfaceView, surfaceView,
FrameLayout.LayoutParams( FrameLayout.LayoutParams(
@@ -331,7 +371,7 @@ class OmniView(val context: ThemedReactContext) :
} }
rootContentViews = emptyList() rootContentViews = emptyList()
view.addView(surfaceView, FrameLayout.LayoutParams( contentFrame.addView(surfaceView, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
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 selectedAudio = player.getSelectedTrack(IMedia.Track.Type.Audio)
val selectedSubtitle = player.getSelectedTrack(IMedia.Track.Type.Text) val selectedSubtitle = player.getSelectedTrack(IMedia.Track.Type.Text)
val videoTracks = player.getTracks(IMedia.Track.Type.Video) player.getTracks(IMedia.Track.Type.Video).orEmpty()
if (!videoTracks.isNullOrEmpty()) { .groupBy { listOf(it.language, it.name, it.description) }
val videoFormats = videoTracks.map { track -> .values.forEach { videoTracks ->
Format.Builder() val videoFormats = videoTracks.map { track ->
.setId(track.id) Format.Builder()
.setLabel(track.name) .setId(track.id)
.setLanguage(track.language) .setLabel(track.name)
.setSampleMimeType("video/x-unknown") .setLanguage(track.language)
.build() .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 -> player.getTracks(IMedia.Track.Type.Audio).orEmpty()
val format = Format.Builder() .groupBy { listOf(it.language, it.name, it.description) }
.setId(track.id) .values.forEach { audioTracks ->
.setLabel(track.name) val audioFormats = audioTracks.map { track ->
.setLanguage(track.language) Format.Builder()
.setSampleMimeType("audio/x-unknown") .setId(track.id)
.build() .setLabel(track.name)
val group = TrackGroup("vlc-audio-${track.id}", format) .setLanguage(track.language)
val selected = booleanArrayOf(selectedAudio != null && selectedAudio.id == track.id) .setSampleMimeType("audio/x-unknown")
val support = intArrayOf(FORMAT_HANDLED) .build()
result.add(Group(group, false, support, selected)) }
} 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 -> player.getTracks(IMedia.Track.Type.Text)?.forEach { track ->
val format = Format.Builder() val format = Format.Builder()