Skip to content

Commit 5d49bb7

Browse files
oceanjulescopybara-github
authored andcommitted
[ui-compose] Add API 34 fix for SurfaceView wrapped in AndroidView
A similar fix was done on PlayerView for the cases when it needs to be wrapped in AndroidView: 968f72f This fix ensures that the PlayerSurface composable can also be resized correctly on API 34 (API 33/Android 13 didn't have this problem, API 35/Android 15 had it fixed in the platform). Issue: #2811 PiperOrigin-RevId: 819276595
1 parent e1b0410 commit 5d49bb7

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

RELEASENOTES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@
281281
* Add `ContentFrame` Composable to `media3-ui-compose` which combines
282282
`PlayerSurface` management with aspect ratio resizing and covering with
283283
a shutter.
284+
* Work around a known API 34 platform bug causing stretched/cropped videos
285+
when using `SurfaceView` inside a Compose `AndroidView` and hence
286+
affecting `ContentFrame` and `PlayerSurface` Composables with
287+
`SURFACE_TYPE_SURFACE_VIEW`
288+
([#1237](https://github.com/androidx/media/issues/1237),
289+
[#2811](https://github.com/androidx/media/issues/2811)).
284290
* Create a new `media3-ui-compose-material3` module and add
285291
Material3-themed Composables (PlayPauseButton, NextButton,
286292
PreviousButton, SeekBackButton, SeekForwardButton, RepeatButton,

libraries/ui_compose/src/main/java/androidx/media3/ui/compose/PlayerSurface.kt

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,28 @@
1717
package androidx.media3.ui.compose
1818

1919
import android.content.Context
20+
import android.graphics.Canvas
21+
import android.os.Build.VERSION.SDK_INT
22+
import android.view.SurfaceControl
2023
import android.view.SurfaceView
2124
import android.view.TextureView
2225
import android.view.View
26+
import android.window.SurfaceSyncGroup
2327
import androidx.annotation.IntDef
2428
import androidx.compose.runtime.Composable
29+
import androidx.compose.runtime.DisposableEffect
2530
import androidx.compose.runtime.LaunchedEffect
2631
import androidx.compose.runtime.getValue
2732
import androidx.compose.runtime.mutableStateOf
2833
import androidx.compose.runtime.remember
34+
import androidx.compose.runtime.rememberCoroutineScope
2935
import androidx.compose.runtime.setValue
3036
import androidx.compose.ui.Modifier
3137
import androidx.compose.ui.viewinterop.AndroidView
3238
import androidx.media3.common.Player
3339
import androidx.media3.common.util.UnstableApi
3440
import kotlinx.coroutines.Dispatchers
41+
import kotlinx.coroutines.launch
3542
import 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

Comments
 (0)