1717package androidx.media3.ui.compose
1818
1919import android.content.Context
20+ import android.graphics.Canvas
21+ import android.os.Build.VERSION.SDK_INT
22+ import android.view.SurfaceControl
2023import android.view.SurfaceView
2124import android.view.TextureView
2225import android.view.View
26+ import android.window.SurfaceSyncGroup
2327import androidx.annotation.IntDef
2428import androidx.compose.runtime.Composable
29+ import androidx.compose.runtime.DisposableEffect
2530import androidx.compose.runtime.LaunchedEffect
2631import androidx.compose.runtime.getValue
2732import androidx.compose.runtime.mutableStateOf
2833import androidx.compose.runtime.remember
34+ import androidx.compose.runtime.rememberCoroutineScope
2935import androidx.compose.runtime.setValue
3036import androidx.compose.ui.Modifier
3137import androidx.compose.ui.viewinterop.AndroidView
3238import androidx.media3.common.Player
3339import androidx.media3.common.util.UnstableApi
3440import kotlinx.coroutines.Dispatchers
41+ import kotlinx.coroutines.launch
3542import kotlinx.coroutines.withContext
3643
3744/* *
@@ -55,14 +62,51 @@ fun PlayerSurface(
5562 surfaceType : @SurfaceType Int = SURFACE_TYPE_SURFACE_VIEW ,
5663) {
5764 when (surfaceType) {
58- SURFACE_TYPE_SURFACE_VIEW ->
65+ SURFACE_TYPE_SURFACE_VIEW -> {
66+ var surfaceSyncGroup: SurfaceSyncGroup ? by remember { mutableStateOf(null ) }
67+
68+ val createSurfaceView: (Context ) -> SurfaceView = { context ->
69+ object : SurfaceView (context) {
70+ override fun dispatchDraw (canvas : Canvas ) {
71+ super .dispatchDraw(canvas)
72+ if (SDK_INT == 34 ) {
73+ surfaceSyncGroup?.markSyncReady()
74+ surfaceSyncGroup = null
75+ }
76+ }
77+ }
78+ }
79+
80+ val coroutineScope = rememberCoroutineScope()
81+ val onSurfaceSizeChanged: (SurfaceView ) -> Unit = { surfaceView ->
82+ if (SDK_INT == 34 ) {
83+ coroutineScope.launch(Dispatchers .Main ) {
84+ surfaceView.rootSurfaceControl?.let { rootSurfaceControl ->
85+ // Register a SurfaceSyncGroup to work around
86+ // https://github.com/androidx/media/issues/1237
87+ // (only present on API 34, fixed on API 35).
88+ surfaceSyncGroup =
89+ SurfaceSyncGroup (" exo-sync-b-334901521" ).apply {
90+ check(add(rootSurfaceControl) {}) {
91+ " Failed to add rootSurfaceControl to SurfaceSyncGroup"
92+ }
93+ }
94+ surfaceView.invalidate()
95+ rootSurfaceControl.applyTransactionOnDraw(SurfaceControl .Transaction ())
96+ }
97+ }
98+ }
99+ }
100+
59101 PlayerSurfaceInternal (
60102 player,
61103 modifier,
62- createView = :: SurfaceView ,
104+ createView = createSurfaceView ,
63105 setVideoView = Player ::setVideoSurfaceView,
64106 clearVideoView = Player ::clearVideoSurfaceView,
107+ onSurfaceSizeChanged = onSurfaceSizeChanged,
65108 )
109+ }
66110 SURFACE_TYPE_TEXTURE_VIEW ->
67111 PlayerSurfaceInternal (
68112 player,
@@ -82,6 +126,7 @@ private fun <T : View> PlayerSurfaceInternal(
82126 createView : (Context ) -> T ,
83127 setVideoView : Player .(T ) -> Unit ,
84128 clearVideoView : Player .(T ) -> Unit ,
129+ onSurfaceSizeChanged : (T ) -> Unit = {},
85130) {
86131 var view by remember { mutableStateOf<T ?>(null ) }
87132
@@ -93,14 +138,29 @@ private fun <T : View> PlayerSurfaceInternal(
93138 )
94139
95140 view?.let { view ->
141+ DisposableEffect (view, player) {
142+ val listener =
143+ if (player != null ) {
144+ object : Player .Listener {
145+ override fun onSurfaceSizeChanged (width : Int , height : Int ) {
146+ onSurfaceSizeChanged(view)
147+ }
148+ }
149+ .also { player.addListener(it) }
150+ } else null
151+
152+ onDispose { listener?.let { player?.removeListener(it) } }
153+ }
154+
96155 LaunchedEffect (view, player) {
97156 if (player != null ) {
98157 view.attachedPlayer?.let { previousPlayer ->
99158 if (
100159 previousPlayer != player &&
101160 previousPlayer.isCommandAvailable(Player .COMMAND_SET_VIDEO_SURFACE )
102- )
161+ ) {
103162 previousPlayer.clearVideoView(view)
163+ }
104164 }
105165 if (player.isCommandAvailable(Player .COMMAND_SET_VIDEO_SURFACE )) {
106166 player.setVideoView(view)
@@ -113,8 +173,9 @@ private fun <T : View> PlayerSurfaceInternal(
113173 // unnecessarily creating a Surface placeholder.
114174 withContext(Dispatchers .Main ) {
115175 view.attachedPlayer?.let { previousPlayer ->
116- if (previousPlayer.isCommandAvailable(Player .COMMAND_SET_VIDEO_SURFACE ))
176+ if (previousPlayer.isCommandAvailable(Player .COMMAND_SET_VIDEO_SURFACE )) {
117177 previousPlayer.clearVideoView(view)
178+ }
118179 view.attachedPlayer = null
119180 }
120181 }
0 commit comments