VideoSurfacePanelRegistration: Direct-to-surface rendering for maximum performance and DRM supportReadableVideoSurfacePanelRegistration: Surface rendering with post-processing capabilities for custom effectsVideoSurfacePanelRegistration and ReadableVideoSurfacePanelRegistration based on your performance requirements and whether you need post-processing capabilities.VideoSurfacePanelRegistration for maximum performance and DRM support. This renders media content directly to the panel surface, bypassing the Android View system:MediaPanelSettings with MediaPanelRenderOptionsVideoSurfacePanelRegistration(
R.id.video_panel,
surfaceConsumer = { entity, surface ->
// ExoPlayer setup
val exoPlayer = ExoPlayer.Builder(this).build().apply {
setVideoSurface(surface)
setMediaItem(mediaItem)
prepare()
}
},
settingsCreator = { entity ->
MediaPanelSettings(
shape = QuadShapeOptions(width = 1.6f, height = 0.9f),
display = PixelDisplayOptions(width = 1920, height = 1080),
rendering = MediaPanelRenderOptions(
stereoMode = StereoMode.LeftRight,
isDRM = true
)
)
}
)
ReadableVideoSurfacePanelRegistration when you need both media rendering and post-processing capabilities:ReadableMediaPanelSettings with ReadableMediaPanelRenderOptionsReadableMediaPanelRenderOptions with PanelRenderMode.Mesh() incorrectly creates a compositor layer. See Known issues for details and workarounds.ReadableVideoSurfacePanelRegistration(
R.id.readable_video_panel,
surfaceConsumer = { entity, surface ->
// ExoPlayer setup with surface
exoPlayer.setVideoSurface(surface)
},
settingsCreator = { entity ->
ReadableMediaPanelSettings(
shape = QuadShapeOptions(width = 1.6f, height = 0.9f),
display = PixelDisplayOptions(width = 1920, height = 1080),
rendering = ReadableMediaPanelRenderOptions(
mips = 4, // Enable mipmapping for distance viewing
stereoMode = StereoMode.UpDown
)
)
}
)

QuadShapeOptions:MediaPanelSettings(
shape = QuadShapeOptions(width = 1.6f, height = 0.9f), // 16:9 aspect ratio
display = PixelDisplayOptions(width = 1920, height = 1080),
// ...
)
Video: This video demonstrates a 180° video.
Equirect180ShapeOptions:MediaPanelSettings(
shape = Equirect180ShapeOptions(radius = 50.0f),
display = PixelDisplayOptions(width = 3840, height = 1080),
// ...
)
Equirect360ShapeOptions:MediaPanelSettings(
shape = Equirect360ShapeOptions(radius = 300.0f),
display = PixelDisplayOptions(width = 3840, height = 1920),
rendering = MediaPanelRenderOptions(
stereoMode = StereoMode.UpDown,
zIndex = -1 // Render behind other panels
)
)
stereoMode property in their rendering options:// For VideoSurfacePanelRegistration
MediaPanelSettings(
// ... shape and display options
rendering = MediaPanelRenderOptions(
stereoMode = StereoMode.None // Default monoscopic behavior
)
)
// For ReadableVideoSurfacePanelRegistration
ReadableMediaPanelSettings(
// ... shape and display options
rendering = ReadableMediaPanelRenderOptions(
stereoMode = StereoMode.MonoLeft // Use left half for both eyes
)
)
StereoMode.None: Displays the entire texture to both eyes (default)StereoMode.MonoLeft: Displays the left half of the texture in both eyesStereoMode.MonoUp: Displays the top half of the texture in both eyesMediaPanelSettings(
shape = QuadShapeOptions(width = 1.6f, height = 0.9f),
display = PixelDisplayOptions(width = 3840, height = 1080), // Side-by-side stereo resolution
rendering = MediaPanelRenderOptions(
stereoMode = StereoMode.LeftRight // Left-right stereo content
)
)
StereoMode.LeftRight: Left half to left eye, right half to right eye (common for side-by-side stereo)StereoMode.UpDown: Top half to left eye, bottom half to right eye (common for over/under stereo)// For 1080p video content
MediaPanelSettings(
display = PixelDisplayOptions(width = 1920, height = 1080),
// ...
)
VideoSurfacePanelRegistration for DRM-protected content. It provides the secure rendering pipeline required:VideoSurfacePanelRegistration(
R.id.drm_video_panel,
surfaceConsumer = { entity, surface ->
// DRM-enabled ExoPlayer setup
exoPlayer.setVideoSurface(surface)
},
settingsCreator = {
MediaPanelSettings(
// ... shape and display
rendering = MediaPanelRenderOptions(isDRM = true)
)
}
)
MediaPanelSettings(
shape = Equirect360ShapeOptions(radius = 300.0f),
// ...
rendering = MediaPanelRenderOptions(
zIndex = -1, // Render behind UI panels
stereoMode = StereoMode.UpDown
)
)
PanelConfigOptionsVideoSurfacePanelRegistration or ReadableVideoSurfacePanelRegistration instead.PanelConfigOptions directly. However, this requires manual management of surfaces, performance optimizations, and media integration.mips = 1SceneTexture: forceSceneTexture = falseenableTransparent = falseenableTransparent is false by default. If set to enableTransparent = true, the sceneTexture will be forced on and your app will crash.MediaPanelSettings sets these properties automatically. Here’s an example of manually setting these properties:PanelCreator(
registrationId = R.id.my_media_panel,
panelCreator = { entity ->
val panelConfigOptions = PanelConfigOptions().apply {
// Panel shape and size
layoutWidthInPx = 1920
layoutHeightInPx = 1080
width = 1.6f
height = 0.9f
// Enable direct-to-compositor
mips = 1 // Disable mipmapping
forceSceneTexture = false // Disable scene texture
enableTransparent = false // Disable transparency
}
PanelSceneObject(scene, spatialContext, R.layout.surface_layout, entity, panelConfigOptions)
}
)
VideoSurfacePanelRegistration sets this up automatically. The following code sample shows how to set it up manually if you need more direct control.mips=1, forceSceneTexture=false).PanelSceneObject and attaches it to the entity.setVideoSurface().private fun createDirectToSurfacePanel(
panelConfigBlock: PanelConfigOptions.() -> Unit,
mediaItem: MediaItem,
) {
// Create entity
entity = Entity.create(
Transform(),
Hittable(hittable = MeshCollision.LineTest),
)
val panelConfigOptions = PanelConfigOptions().apply(panelConfigBlock)
// Enable Direct-To-Compositor prerequisites
panelConfigOptions.apply {
mips = 1
forceSceneTexture = false
enableTransparent = false
}
SpatialActivityManager.executeOnVrActivity<AppSystemActivity> { immersiveActivity ->
// Create PanelSceneObject with custom configs
val panelSceneObject = PanelSceneObject(immersiveActivity.scene, entity, panelConfigOptions)
// Assign PanelSceneObject to entity
immersiveActivity.systemManager
.findSystem<SceneObjectSystem>()
.addSceneObject(
entity, CompletableFuture<SceneObject>().apply { complete(panelSceneObject) })
// Set up ExoPlayer
exoPlayer.setMediaItem(mediaItem)
exoPlayer.prepare()
// Connect ExoPlayer directly to panel surface
exoPlayer.setVideoSurface(panelSceneObject.surface)
}
}
PanelConfigOptions for media content, consider migrating to the media-specific panel registrations:// Old approach with PanelConfigOptions
val panelConfigOptions = PanelConfigOptions().apply {
layoutWidthInPx = 1920
layoutHeightInPx = 1080
width = 1.6f
height = 0.9f
mips = 1
forceSceneTexture = false
stereoMode = StereoMode.LEFT_RIGHT
}
// New approach with VideoSurfacePanelRegistration
VideoSurfacePanelRegistration(
R.id.video_panel,
surfaceConsumer = { entity, surface -> exoPlayer.setVideoSurface(surface) },
settingsCreator = { entity ->
MediaPanelSettings(
shape = QuadShapeOptions(width = 1.6f, height = 0.9f),
display = PixelDisplayOptions(width = 1920, height = 1080),
rendering = MediaPanelRenderOptions(stereoMode = StereoMode.LeftRight)
)
}
)