ExoPlayer for 360 stereoscopic videoSpatialActivityManager.executeOnVrActivityMediaPlayerSample project in Android Studio. Connect your Meta Quest device via USB, enable developer mode, and run the sample. When the sample launches, you see a custom 3D room environment with a video catalog panel on the left and a large video screen on the wall.| File / Scene | What it demonstrates | Key concepts |
|---|---|---|
Main VR activity managing scene, panels, video playback, and state transitions | AppSystemActivity lifecycle, panel registration, GLXF loading, custom shader override, declarative state management | |
Separate ComponentActivity for video catalog with Jetpack Navigation and ViewModel | ActivityPanelRegistration, Compose navigation (grid/detail views), cross-activity communication via SpatialActivityManager | |
Inline Compose panel for passthrough toggle | ComposeViewPanelRegistration, passthrough and environment depth APIs | |
registerPanels() method | Four different panel registration types in one application | LayoutXMLPanelRegistration, ActivityPanelRegistration, ComposeViewPanelRegistration, VideoSurfacePanelRegistration |
app/scenes/Composition/ | GLXF scene composition with named nodes | Meta Spatial Editor workflow, named node extraction via composition.getNodeByName() |
app/src/shaders/ | Custom fragment and vertex shaders for transitions and 360 rendering | Material shader override, environment projection effect, skybox alpha wipe |
MediaRoom with a large flat video panel mounted on the wall. To the left, a catalog panel displays 10 video thumbnails in a grid (9 YouTube videos and 1 local 360 video). Below the catalog, a small panel shows a passthrough toggle switch. When you select a YouTube video from the catalog, it loads in the flat panel via WebView, and the video content projects onto the room walls as emissive lighting. When you select the “Soloist 3D” video, the room animates out with a transition shader effect and a 360-degree stereoscopic sphere appears around you. ExoPlayer plays the immersive video in top/bottom stereoscopic format. Toggle the passthrough switch to hide the room and skybox, enable camera passthrough with environment depth, and make the video panel grabbable so you can reposition it in your physical space.registerPanels() method. The video panel uses LayoutXMLPanelRegistration with a WebView defined in XML. The catalog panel uses ActivityPanelRegistration, launching a separate ComponentActivity with Jetpack Compose UI. The passthrough toggle uses ComposeViewPanelRegistration, rendering inline Compose content without a separate activity. The 360 video sphere uses VideoSurfacePanelRegistration with Equirect360ShapeOptions and StereoMode.UpDown for top/bottom stereoscopic rendering.MediaPlayerSampleActivity.kt method registerPanels().ListPanel) is a separate ComponentActivity, not embedded in the main VR activity. When a user selects a video in the detail view, the panel communicates with the VR activity using:SpatialActivityManager.executeOnVrActivity<MediaPlayerSampleActivity> { activity ->
activity.playVideo(selectedMovie.url)
}
ListPanel.kt in MovieDetailScreen composable.ExoPlayer and render to a VideoSurfacePanelRegistration configured with Equirect360ShapeOptions for 360-degree playback. The playVideo() method inspects the URL string and sets movieState accordingly.MediaPlayerSampleActivity.kt method playVideo() and the movieState setter.scene.enablePassthrough(state) and scene.enableEnvironmentDepth(state) when the switch changes. Environment depth enables spatial occlusion, so virtual content appears behind real-world objects. The mrState setter hides the room and skybox entities, disables locomotion, and makes the video panel grabbable:activity.scene.enablePassthrough(state) activity.scene.enableEnvironmentDepth(state) activity.mrState = state
MRPanel.ktMRApp() composable.updateTextures() method obtains the panel’s SceneTexture and sets it on the environment materials along with an occlusion mask that controls where the projection appears.MediaPlayerSampleActivity.kt method updateTextures() and app/src/shaders/transition.frag.environment = composition.getNodeByName("Environment").entity
videoPanel = composition.getNodeByName("VideoPanel").entity
MediaPlayerSampleActivity.kt method loadGLXF().MovieViewModel state management in ListPanel.kt.SpatialVideoSample for spatial audio patterns.customParams to control the reveal mask.