fix(android)/aspect-ratio (#4594)

This commit is contained in:
Bartłomiej Piekarz
2025-07-08 15:17:43 +02:00
committed by GitHub
parent 86ea0e3101
commit 481aa7ca65

View File

@@ -4,8 +4,6 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.SurfaceView
import android.view.TextureView
import android.view.View import android.view.View
import android.view.View.MeasureSpec import android.view.View.MeasureSpec
import android.widget.FrameLayout import android.widget.FrameLayout
@@ -19,14 +17,13 @@ import androidx.media3.ui.DefaultTimeBar
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import com.brentvatne.common.api.ResizeMode import com.brentvatne.common.api.ResizeMode
import com.brentvatne.common.api.SubtitleStyle import com.brentvatne.common.api.SubtitleStyle
import com.brentvatne.common.api.ViewType
@UnstableApi @UnstableApi
class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
FrameLayout(context, attrs, defStyleAttr) { FrameLayout(context, attrs, defStyleAttr) {
private var localStyle = SubtitleStyle() private var localStyle = SubtitleStyle()
private var surfaceView: View? = null private var pendingResizeMode: Int? = null
private val liveBadge: TextView = TextView(context).apply { private val liveBadge: TextView = TextView(context).apply {
text = "LIVE" text = "LIVE"
setTextColor(Color.WHITE) setTextColor(Color.WHITE)
@@ -51,15 +48,19 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
// Enable proper surface view handling to prevent rendering issues // Enable proper surface view handling to prevent rendering issues
setUseArtwork(false) setUseArtwork(false)
setDefaultArtwork(null) setDefaultArtwork(null)
// Ensure proper video scaling // Ensure proper video scaling - start with FIT mode
resizeMode = androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT resizeMode = androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT
} }
init { init {
addView(playerView) // Add PlayerView with explicit layout parameters
val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) val playerViewLayoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
lp.setMargins(16, 16, 16, 16) addView(playerView, playerViewLayoutParams)
addView(liveBadge, lp)
// Add live badge with its own layout parameters
val liveBadgeLayoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
liveBadgeLayoutParams.setMargins(16, 16, 16, 16)
addView(liveBadge, liveBadgeLayoutParams)
} }
fun setPlayer(player: ExoPlayer?) { fun setPlayer(player: ExoPlayer?) {
@@ -67,21 +68,16 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
if (currentPlayer != null) { if (currentPlayer != null) {
currentPlayer.removeListener(playerListener) currentPlayer.removeListener(playerListener)
// Clear any existing surface from the player
when (surfaceView) {
is SurfaceView -> currentPlayer.clearVideoSurfaceView(surfaceView as SurfaceView)
is TextureView -> currentPlayer.clearVideoTextureView(surfaceView as TextureView)
}
} }
playerView.player = player playerView.player = player
if (player != null) { if (player != null) {
player.addListener(playerListener) player.addListener(playerListener)
// Set the surface view for the new player
when (surfaceView) { // Apply pending resize mode if we have one
is SurfaceView -> player.setVideoSurfaceView(surfaceView as SurfaceView) pendingResizeMode?.let { resizeMode ->
is TextureView -> player.setVideoTextureView(surfaceView as TextureView) playerView.resizeMode = resizeMode
} }
} }
} }
@@ -89,7 +85,7 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
fun getPlayerView(): PlayerView = playerView fun getPlayerView(): PlayerView = playerView
fun setResizeMode(@ResizeMode.Mode resizeMode: Int) { fun setResizeMode(@ResizeMode.Mode resizeMode: Int) {
playerView.resizeMode = when (resizeMode) { val targetResizeMode = when (resizeMode) {
ResizeMode.RESIZE_MODE_FILL -> AspectRatioFrameLayout.RESIZE_MODE_FILL ResizeMode.RESIZE_MODE_FILL -> AspectRatioFrameLayout.RESIZE_MODE_FILL
ResizeMode.RESIZE_MODE_CENTER_CROP -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM ResizeMode.RESIZE_MODE_CENTER_CROP -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
ResizeMode.RESIZE_MODE_FIT -> AspectRatioFrameLayout.RESIZE_MODE_FIT ResizeMode.RESIZE_MODE_FIT -> AspectRatioFrameLayout.RESIZE_MODE_FIT
@@ -97,6 +93,18 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
ResizeMode.RESIZE_MODE_FIXED_HEIGHT -> AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT ResizeMode.RESIZE_MODE_FIXED_HEIGHT -> AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
} }
// Apply the resize mode to PlayerView immediately
playerView.resizeMode = targetResizeMode
// Store it for reapplication if needed
pendingResizeMode = targetResizeMode
// Force PlayerView to recalculate its layout
playerView.requestLayout()
// Also request layout on the parent to ensure proper sizing
requestLayout()
} }
fun setSubtitleStyle(style: SubtitleStyle) { fun setSubtitleStyle(style: SubtitleStyle) {
@@ -132,49 +140,20 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
} }
fun updateSurfaceView(viewType: Int) { fun updateSurfaceView(viewType: Int) {
val currentSurfaceView = surfaceView // TODO: Implement proper surface type switching if needed
val newSurfaceView: View = when (viewType) {
ViewType.VIEW_TYPE_TEXTURE -> TextureView(context)
ViewType.VIEW_TYPE_SURFACE_SECURE -> SurfaceView(context).apply {
// Requires API 17+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
(this as SurfaceView).setSecure(true)
}
}
else -> SurfaceView(context)
}
if (currentSurfaceView === newSurfaceView) {
return
}
// Remove the old surface view
if (currentSurfaceView != null) {
removeView(currentSurfaceView)
}
// Add the new surface view
surfaceView = newSurfaceView
val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(newSurfaceView, 0, layoutParams)
// Attach player to the new surface
playerView.player?.let {
when (newSurfaceView) {
is SurfaceView -> it.setVideoSurfaceView(newSurfaceView)
is TextureView -> it.setVideoTextureView(newSurfaceView)
}
}
} }
val isPlaying: Boolean val isPlaying: Boolean
get() = playerView.player?.isPlaying ?: false get() = playerView.player?.isPlaying ?: false
fun invalidateAspectRatio() { fun invalidateAspectRatio() {
// PlayerView handles aspect ratio automatically // PlayerView handles aspect ratio automatically through its internal AspectRatioFrameLayout
requestLayout() playerView.requestLayout()
// Reapply the current resize mode to ensure it's properly set
pendingResizeMode?.let { resizeMode ->
playerView.resizeMode = resizeMode
}
} }
fun setUseController(useController: Boolean) { fun setUseController(useController: Boolean) {
@@ -245,7 +224,13 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
private val playerListener = object : Player.Listener { private val playerListener = object : Player.Listener {
override fun onTimelineChanged(timeline: Timeline, reason: Int) { override fun onTimelineChanged(timeline: Timeline, reason: Int) {
playerView.post { playerView.requestLayout() } playerView.post {
playerView.requestLayout()
// Reapply resize mode to ensure it's properly set after timeline changes
pendingResizeMode?.let { resizeMode ->
playerView.resizeMode = resizeMode
}
}
updateLiveUi() updateLiveUi()
} }
@@ -255,6 +240,15 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
) { ) {
updateLiveUi() updateLiveUi()
} }
// Handle video size changes which affect aspect ratio
if (events.contains(Player.EVENT_VIDEO_SIZE_CHANGED)) {
pendingResizeMode?.let { resizeMode ->
playerView.resizeMode = resizeMode
}
playerView.requestLayout()
requestLayout()
}
} }
} }
@@ -282,4 +276,14 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute
// Post a second layout pass so the ExoPlayer internal views get correct bounds. // Post a second layout pass so the ExoPlayer internal views get correct bounds.
post(layoutRunnable) post(layoutRunnable)
} }
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
pendingResizeMode?.let { resizeMode ->
playerView.resizeMode = resizeMode
}
}
}
} }