From 19bfce9c54be9781961338e4b904c24311e0f363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Tue, 16 Jul 2024 11:38:51 +0200 Subject: [PATCH] Integrate Media3 workaround for #515 on API 34 --- .../ui/widget/player/PlayerSurface.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/widget/player/PlayerSurface.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/widget/player/PlayerSurface.kt index b3a658ab1..a9426f3e1 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/widget/player/PlayerSurface.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/widget/player/PlayerSurface.kt @@ -5,7 +5,14 @@ package ch.srgssr.pillarbox.ui.widget.player import android.content.Context +import android.graphics.Canvas +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.view.SurfaceControl import android.view.SurfaceView +import android.window.SurfaceSyncGroup +import androidx.annotation.RequiresApi import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -179,6 +186,12 @@ private fun AndroidPlayerSurfaceView(player: Player, modifier: Modifier = Modifi * Player surface view */ private class PlayerSurfaceView(context: Context) : SurfaceView(context) { + private val playerListener = PlayerListener() + private val surfaceSyncGroupV34 = when { + isInEditMode -> null + needSurfaceSyncWorkaround() -> SurfaceSyncGroupCompatV34() + else -> null + } /** * Player if null is passed just clear surface @@ -187,8 +200,59 @@ private class PlayerSurfaceView(context: Context) : SurfaceView(context) { set(value) { if (field != value) { field?.clearVideoSurfaceView(this) + field?.removeListener(playerListener) value?.setVideoSurfaceView(this) + value?.addListener(playerListener) } field = value } + + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + + if (needSurfaceSyncWorkaround()) { + surfaceSyncGroupV34?.maybeMarkSyncReadyAndClear() + } + } + + // Workaround for a surface sync issue on API 34: https://github.com/androidx/media/issues/1237 + // Imported from https://github.com/androidx/media/commit/30cb76269a67e09f6e1662ea9ead6aac70667028 + private fun needSurfaceSyncWorkaround(): Boolean { + return Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE + } + + private inner class PlayerListener : Player.Listener { + private val mainLooperHandler = Handler(Looper.getMainLooper()) + + override fun onSurfaceSizeChanged(width: Int, height: Int) { + if (needSurfaceSyncWorkaround()) { + surfaceSyncGroupV34?.postRegister(mainLooperHandler, this@PlayerSurfaceView) + } + } + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private class SurfaceSyncGroupCompatV34 { + private var surfaceSyncGroup: SurfaceSyncGroup? = null + + fun postRegister( + mainLooperHandler: Handler, + surfaceView: SurfaceView, + ) { + mainLooperHandler.post { + // The SurfaceView isn't attached to a window, so don't apply the workaround. + val rootSurfaceControl = surfaceView.getRootSurfaceControl() ?: return@post + + surfaceSyncGroup = SurfaceSyncGroup("exo-sync-b-334901521") + surfaceSyncGroup?.add(rootSurfaceControl) {} + surfaceView.invalidate() + rootSurfaceControl.applyTransactionOnDraw(SurfaceControl.Transaction()) + } + } + + fun maybeMarkSyncReadyAndClear() { + surfaceSyncGroup?.markSyncReady() + surfaceSyncGroup = null + } + } }