Spatial Video
From a planar player to an SBS immersive sphere
The player owns decoding and time state, while VideoPlayerComponent chooses the target mesh and material, allowing planar, Portal, and 360° modes to share one playback core.
- Capability
- Planar, Portal, and 360° SBS video
- Environment
- PICO Emulator 0.10
- Result
- Playback and controls verified after asset-path fix
Emulator capture
Captured from the local PICO Emulator 0.10 run; no mockup is used as test evidence.

Issue found: mismatched file extension
VideoViewModel pointed to balenciaga.mp4 while the asset was balenciaga.mov, causing FileNotFoundException. Switching to cave_4096x2048_sbs.mp4 restored playback, seeking, volume, and fullscreen controls.
Core code: the player state machine
PlaybackManager registers callbacks, prepares asynchronously, and exposes READY/PLAYING/PAUSED as Compose state. Reset unregisters callbacks, closes the player, then restores initial state.
context.assets.openFd(videoPath).use {
player.setDataSource(it)
}
player.prepareAsync()
override fun onPrepared() {
state = PlaybackState.READY
duration = player.getDuration().coerceAtLeast(1L)
}Validate asset paths during build or tests instead of deferring failures to runtime.
Core code: one stream, two geometries
Planar mode creates a rounded video panel. Immersive mode uses a sphere, SBS dimension mode, and front culling for inside viewing. Portal adds shader-graph parameters to VideoMaterial.
val material = VideoMaterial(
BlendingMode.OPAQUE,
VideoDimensionMode.SIDE_BY_SIDE,
MaterialCullingMode.FRONT,
Color4.BLACK
)
entity.components.set(VideoPlayerComponent(player, sphereMesh, material))The decoder does not need to know whether the destination is a panel, portal, or sphere.
