From 7b1a0376adc4e98bf7dee69de4efadd9345db6cc Mon Sep 17 00:00:00 2001 From: jbibik Date: Fri, 7 Jun 2024 10:13:08 -0700 Subject: [PATCH] Add `PlayerSurface` Compose component Underneath, it delegates to either * `AndroidExternalSurface` (equivalent of SurfaceView), which is generally better for power and latency * `AndroidEmbeddedExternalSurface` (equivalent of TextureView) which is better for interactions with other widgets or applying visual effects through the graphicsLayer Note: the resulting surface is stretched across the whole screen. Aspect ratio handling will be addressed in the follow-up changes. PiperOrigin-RevId: 641285482 --- demos/compose/build.gradle | 6 +- .../media3/demo/compose/MainActivity.kt | 33 ++++---- .../media3/demo/compose/PlayerSurface.kt | 76 +++++++++++++++++++ 3 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt diff --git a/demos/compose/build.gradle b/demos/compose/build.gradle index cadcd86150..9726565c87 100644 --- a/demos/compose/build.gradle +++ b/demos/compose/build.gradle @@ -61,11 +61,11 @@ android { } dependencies { - def composeBom = platform('androidx.compose:compose-bom:2024.02.00') + def composeBom = platform('androidx.compose:compose-bom:2024.05.00') implementation composeBom - implementation 'androidx.activity:activity-compose:1.8.2' - implementation 'androidx.compose.foundation:foundation-android:1.6.4' + implementation 'androidx.activity:activity-compose:1.9.0' + implementation 'androidx.compose.foundation:foundation-android:1.6.7' implementation 'androidx.compose.material3:material3-android:1.2.1' implementation 'com.google.android.material:material:' + androidxMaterialVersion diff --git a/demos/compose/src/main/java/androidx/media3/demo/compose/MainActivity.kt b/demos/compose/src/main/java/androidx/media3/demo/compose/MainActivity.kt index 16d24d4f7c..0f37a2a40d 100644 --- a/demos/compose/src/main/java/androidx/media3/demo/compose/MainActivity.kt +++ b/demos/compose/src/main/java/androidx/media3/demo/compose/MainActivity.kt @@ -21,16 +21,12 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.style.TextAlign import androidx.media3.common.MediaItem +import androidx.media3.common.Player import androidx.media3.demo.compose.data.videos import androidx.media3.exoplayer.ExoPlayer @@ -40,26 +36,25 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - Surface(modifier = Modifier.fillMaxSize()) { + Surface { Column( - modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { - Text(text = "Placeholder for your Player", textAlign = TextAlign.Center) - PlayerScreen(videos[0]) + val exoPlayer = + ExoPlayer.Builder(LocalContext.current).build().apply { + setMediaItem(MediaItem.fromUri(videos[0])) + prepare() + playWhenReady = true + repeatMode = Player.REPEAT_MODE_ONE + } + PlayerSurface( + player = exoPlayer, + surfaceType = SURFACE_TYPE_SURFACE_VIEW, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) } } } } } - -@Composable -fun PlayerScreen(videoUrl: String) { - val exoPlayer = - ExoPlayer.Builder(LocalContext.current).build().apply { - setMediaItem(MediaItem.fromUri(videoUrl)) - prepare() - playWhenReady = true - } -} diff --git a/demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt b/demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt new file mode 100644 index 0000000000..d26cc74733 --- /dev/null +++ b/demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.media3.demo.compose + +import android.view.Surface +import android.view.SurfaceView +import android.view.TextureView +import androidx.annotation.IntDef +import androidx.compose.foundation.AndroidEmbeddedExternalSurface +import androidx.compose.foundation.AndroidExternalSurface +import androidx.compose.foundation.AndroidExternalSurfaceScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.media3.common.Player + +/** + * Provides a dedicated drawing [Surface] for media playbacks using a [Player]. + * + * The player's video output is displayed with either a [SurfaceView]/[AndroidExternalSurface] or a + * [TextureView]/[AndroidEmbeddedExternalSurface]. + * + * [Player] takes care of attaching the rendered output to the [Surface] and clearing it, when it is + * destroyed. + * + * See + * [Choosing a surface type](https://developer.android.com/media/media3/ui/playerview#surfacetype) + * for more information. + */ +@Composable +fun PlayerSurface(player: Player, surfaceType: @SurfaceType Int, modifier: Modifier = Modifier) { + val onSurfaceCreated: (Surface) -> Unit = { surface -> player.setVideoSurface(surface) } + val onSurfaceDestroyed: () -> Unit = { player.setVideoSurface(null) } + val onSurfaceInitialized: AndroidExternalSurfaceScope.() -> Unit = { + onSurface { surface, _, _ -> + onSurfaceCreated(surface) + surface.onDestroyed { onSurfaceDestroyed() } + } + } + + when (surfaceType) { + SURFACE_TYPE_SURFACE_VIEW -> + AndroidExternalSurface(modifier = modifier, onInit = onSurfaceInitialized) + SURFACE_TYPE_TEXTURE_VIEW -> + AndroidEmbeddedExternalSurface(modifier = modifier, onInit = onSurfaceInitialized) + else -> throw IllegalArgumentException("Unrecognized surface type: $surfaceType") + } +} + +/** + * The type of surface view used for media playbacks. One of [SURFACE_TYPE_SURFACE_VIEW] or + * [SURFACE_TYPE_TEXTURE_VIEW]. + */ +@MustBeDocumented +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.TYPE_PARAMETER) +@IntDef(SURFACE_TYPE_SURFACE_VIEW, SURFACE_TYPE_TEXTURE_VIEW) +annotation class SurfaceType + +/** Surface type equivalent to [SurfaceView] . */ +const val SURFACE_TYPE_SURFACE_VIEW = 1 +/** Surface type equivalent to [TextureView]. */ +const val SURFACE_TYPE_TEXTURE_VIEW = 2