空間外部サーフェス

対象となる XR デバイス
このガイダンスは、次のようなタイプの XR デバイス向けのエクスペリエンスを構築する際に役立ちます。
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 で保護された動画コンテンツ用のサーフェスを追加する

SpatialExternalSurface は、DRM で保護された動画ストリームの再生もサポートしています。これを有効にするには、保護されたグラフィック バッファにレンダリングするセキュアなサーフェスを作成する必要があります。これにより、コンテンツが画面録画されたり、安全でないシステム コンポーネントからアクセスされたりするのを防ぐことができます。

セキュアなサーフェスを作成するには、SpatialExternalSurface コンポーザブルで SpatialExternalSurfaceProtection パラメータを SpatialExternalSurfaceProtection.Protected に設定します。また、ライセンス サーバーからライセンスを取得できるように、適切な DRM 情報を使用して Media3 Exoplayer を構成する必要があります。

次の例は、DRM で保護された動画ストリームを再生するように SpatialExternalSurface と ExoPlayer を構成する方法を示しています。

@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() }
        }
    }
}

コードに関する主なポイント

  • 保護されたサーフェス: SpatialExternalSurface で surfaceProtection = SpatialExternalSurfaceProtection.Protected を設定することは、基盤となる Surface が DRM コンテンツに適したセキュアなバッファによってバックアップされるために不可欠です。
  • DRM 構成: DRM スキーム(C.WIDEVINE_UUID など)とライセンス サーバーの URI を使用して MediaItem を構成する必要があります。ExoPlayer はこの情報を使用して DRM セッションを管理します。
  • セキュアなコンテンツ: 保護されたサーフェスにレンダリングする場合、動画コンテンツはデコードされ、セキュアなパスに表示されます。これにより、コンテンツ ライセンスの要件を満たすことができます。これにより、コンテンツがスクリーン キャプチャに表示されることも防ぐことができます。