空间外部表面

适用的 XR 设备
本指南可帮助您为以下类型的 XR 设备打造优质体验。
扩展现实头戴设备
有线扩展现实眼镜

SpatialExternalSurface 是一个子空间可组合项,用于创建和管理 Surface,您的应用可以在其中绘制内容,例如图片或视频。SpatialExternalSurface 支持立体或单视场内容。

添加用于展示图片或视频内容的平台

以下示例演示了如何使用 Media3 Exoplayer 和 SpatialExternalSurface 加载并排立体视频:

@OptIn(ExperimentalComposeApi::class)
@Composable
fun SpatialExternalSurfaceContent() {
    val context = LocalContext.current
    Subspace {
        SpatialExternalSurface(
            modifier = SubspaceModifier
                .width(1200.dp) // Default width is 400.dp if no width modifier is specified
                .height(676.dp), // Default height is 400.dp if no height modifier is specified
            // Use StereoMode.Mono, StereoMode.SideBySide, or StereoMode.TopBottom, depending
            // upon which type of content you are rendering: monoscopic content, side-by-side stereo
            // content, or top-bottom stereo content
            stereoMode = StereoMode.SideBySide,
        ) {
            val exoPlayer = remember { ExoPlayer.Builder(context).build() }
            val videoUri = Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                // Represents a side-by-side stereo video, where each frame contains a pair of
                // video frames arranged side-by-side. The frame on the left represents the left
                // eye view, and the frame on the right represents the right eye view.
                .path("sbs_video.mp4")
                .build()
            val mediaItem = MediaItem.fromUri(videoUri)

            // onSurfaceCreated is invoked only one time, when the Surface is created
            onSurfaceCreated { surface ->
                exoPlayer.setVideoSurface(surface)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
                exoPlayer.play()
            }
            // onSurfaceDestroyed is invoked when the SpatialExternalSurface composable and its
            // associated Surface are destroyed
            onSurfaceDestroyed { exoPlayer.release() }
        }
    }
}

代码要点

  • 将 StereoMode 设置为 Mono、SideBySide 或 TopBottom,具体取决于您要呈现的内容类型:
    • Mono:图片或视频帧由向双眼显示的单个相同图片组成。
    • SideBySide:图片或视频帧包含一对并排排列的图片或视频帧,其中左侧的图片或帧代表左眼视图,右侧的图片或帧代表右眼视图。
    • TopBottom:图片或视频帧包含一对垂直堆叠的图片或视频帧,其中顶部的图片或帧表示左眼视图,底部的图片或帧表示右眼视图。
  • SpatialExternalSurface 仅支持矩形表面。
  • 此 Surface 不会捕获输入事件。
  • 无法将 StereoMode 更改与应用渲染或视频解码同步。
  • 此可组合项无法在其他面板前面呈现,因此如果布局中还有其他面板,您不应使用 MovePolicy。

为受 DRM 保护的视频内容添加 Surface

SpatialExternalSurface 还支持播放受 DRM 保护的视频流。如需实现此目的,您必须创建一个可渲染到受保护图形缓冲区的安全 Surface。这样可以防止内容被录屏或被不安全的系统组件访问。

如需创建安全 Surface,请在 SpatialExternalSurface 可组合项上将 SpatialExternalSurfaceProtection 参数设置为 SpatialExternalSurfaceProtection.Protected。此外,您还必须为 Media3 Exoplayer 配置适当的 DRM 信息,以便处理从许可服务器获取许可的操作。

以下示例演示了如何配置 SpatialExternalSurface 和 ExoPlayer 以播放受 DRM 保护的视频流:

@OptIn(ExperimentalComposeApi::class)
@Composable
fun DrmSpatialVideoPlayer() {
    val context = LocalContext.current
    Subspace {
        SpatialExternalSurface(
            modifier = SubspaceModifier
                .width(1200.dp)
                .height(676.dp),
            stereoMode = StereoMode.SideBySide,
            surfaceProtection = SpatialExternalSurfaceProtection.Protected
        ) {
            val exoPlayer = remember { ExoPlayer.Builder(context).build() }

            // Define the URI for your DRM-protected content and license server.
            val videoUri = "https://your-content-provider.com/video.mpd"
            val drmLicenseUrl = "https://your-license-server.com/license"

            // Build a MediaItem with the necessary DRM configuration.
            val mediaItem = MediaItem.Builder()
                .setUri(videoUri)
                .setDrmConfiguration(
                    MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
                        .setLicenseUri(drmLicenseUrl)
                        .build()
                )
                .build()

            onSurfaceCreated { surface ->
                // The created surface is secure and can be used by the player.
                exoPlayer.setVideoSurface(surface)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
                exoPlayer.play()
            }

            onSurfaceDestroyed { exoPlayer.release() }
        }
    }
}

代码要点

  • 受保护的 Surface:在 SpatialExternalSurface 上设置 surfaceProtection = SpatialExternalSurfaceProtection.Protected 至关重要,这样底层 Surface 才能由适合 DRM 内容的安全缓冲区提供支持。
  • DRM 配置:您必须使用 DRM 方案(例如 C.WIDEVINE_UUID)和许可服务器的 URI 配置 MediaItem。ExoPlayer 会使用这些信息来管理 DRM 会话。
  • 安全内容:在渲染到受保护的 Surface 时,视频内容会在安全路径上进行解码和显示,这有助于满足内容许可要求。这还可以防止内容出现在屏幕截图中。