Develop

Stereo surface composition overview

Updated: May 14, 2026
Stereo surface composition lets an Android app present different image content to the left and right eyes from a single Android Surface buffer. Use it when your app already has stereoscopic content, such as side-by-side video, top-bottom video, stereo photos, or custom rendered stereo content that should appear inside a 2D Android panel on Horizon OS.
This guide explains how stereo surface composition works, how to select the correct composition mode, and how to size your surface so packed stereo buffers display with the intended aspect ratio.

Requirements

  • A Meta Quest device running Horizon OS SDK version 204 or later.
  • An Android app that uses a SurfaceView, VideoView, or an app-managed SurfaceControl.
  • Content that is already laid out in a supported stereo format. The stereo surface composition API does not convert mono content into stereo.
Your application’s AndroidManifest.xml must declare the uses-horizonos-sdk element to access these APIs.

When to use stereo surface composition

Use stereo surface composition when your app wants to show stereo media or stereo rendered content through the normal Android Surface pipeline. Common use cases include:
  • Video players that support stereoscopic side-by-side or top-bottom video files.
  • Media apps for stereo photos or previews.
  • Android UI experiences that combine standard controls with a stereo content region.
  • Apps that present stereoscopic content through a standard Android surface.
Do not use this API as a mono-to-stereo conversion tool. Setting a stereo surface composition mode only tells Horizon OS how to route regions of your app-provided buffer to each eye.

Composition modes

The API supports three composition modes:
ModeConstantBuffer layoutUse case
Mono
SurfaceControlExt.STEREO_COMPOSITION_MONO
One standard single-view image.
Normal non-stereo video or UI content.
Side by side
SurfaceControlExt.STEREO_COMPOSITION_SIDE_BY_SIDE
Left eye in the left half, right eye in the right half.
Stereo content encoded at double width.
Top bottom
SurfaceControlExt.STEREO_COMPOSITION_TOP_BOTTOM
Left eye in the top half, right eye in the bottom half.
Stereo content encoded at double height.

Buffer layouts

For side-by-side content, each frame contains the left eye image and right eye image next to each other in one encoded buffer:
Side-by-side packed buffer

+----------------------+----------------------+
|    Left eye image    |   Right eye image    |
+----------------------+----------------------+
0                      W                     2W

Encoded buffer size: 2W x H
Per-eye content size: W x H
For top-bottom content, each frame contains the left eye image above the right eye image:
Top-bottom packed buffer

+----------------------+
|    Left eye image    |
+----------------------+
|   Right eye image    |
+----------------------+
0                      W

Encoded buffer size: W x 2H
Per-eye content size: W x H

Apply stereo surface composition to a SurfaceView

For most Android apps, use SurfaceViewExt.setStereoComposition(). VideoView extends SurfaceView, so the same API also applies to VideoView.
import horizonos.view.SurfaceControlExt
import horizonos.view.SurfaceViewExt

SurfaceViewExt.setStereoComposition(
    surfaceView,
    SurfaceControlExt.STEREO_COMPOSITION_SIDE_BY_SIDE,
)
Switch back to mono with:
SurfaceViewExt.setStereoComposition(
    surfaceView,
    SurfaceControlExt.STEREO_COMPOSITION_MONO,
)
Call SurfaceViewExt.setStereoComposition() on the UI thread. When switching between stereo modes, update both the content source and the composition mode so the buffer layout matches the selected mode.

Advanced: Apply stereo surface composition to a SurfaceControl

If your app manages a SurfaceControl directly, use SurfaceControlExt.setStereoComposition() with a SurfaceControl.Transaction:
import android.view.SurfaceControl
import horizonos.view.SurfaceControlExt

val transaction = SurfaceControl.Transaction()

SurfaceControlExt.setStereoComposition(
    surfaceControl,
    transaction,
    SurfaceControlExt.STEREO_COMPOSITION_TOP_BOTTOM,
)

transaction.apply()
Do not use this path for a SurfaceControl obtained from a SurfaceView. For SurfaceView and VideoView, use SurfaceViewExt.setStereoComposition().

Buffer size and display size

Packed stereo buffers often have a different encoded size than the image size you want each eye to see. Size the displayed surface based on the per-eye content dimensions, not only the encoded buffer dimensions.
ModeEncoded buffer sizePer-eye display size
Mono
width x height
width x height
Side by side
2 * width x height
width x height
Top bottom
width x 2 * height
width x height
For example, if a side-by-side video is encoded as 2560 x 720, each eye receives a 1280 x 720 image. The visual aspect ratio should be based on 1280 x 720, not 2560 x 720.
If a top-bottom video is encoded as 1280 x 1440, each eye receives a 1280 x 720 image. The visual aspect ratio should be based on 1280 x 720, not 1280 x 1440.
A simple sizing rule is:
val displayWidth = when (mode) {
    SurfaceControlExt.STEREO_COMPOSITION_SIDE_BY_SIDE -> encodedWidth / 2
    else -> encodedWidth
}

val displayHeight = when (mode) {
    SurfaceControlExt.STEREO_COMPOSITION_TOP_BOTTOM -> encodedHeight / 2
    else -> encodedHeight
}
Android View objects may report different measured view sizes as the app panel is resized or as VideoView preserves the aspect ratio of the current video. That is expected. The important part is that the content buffer layout and selected composition mode match.

Switching modes

When your UI lets users switch modes, update the composition mode and the media source together:
private fun selectVideoSource(source: VideoSource) {
    SurfaceViewExt.setStereoComposition(surfaceView, source.stereoComposition)
    videoView.setVideoURI(source.uri)
    videoView.start()
}
Use mono mode for normal video, side-by-side mode for side-by-side packed video, and top-bottom mode for top-bottom packed video. If the mode and content layout do not match, each eye may receive the wrong region of the buffer.

Validation

Validate stereo surface composition in a headset. Screenshots and screen recordings can show the packed source buffer or sample UI, but they do not fully represent the binocular depth effect seen by the user.
When testing:
  • Confirm mono content displays normally in mono mode.
  • Confirm side-by-side content shows the left half to the left eye and right half to the right eye.
  • Confirm top-bottom content shows the top half to the left eye and bottom half to the right eye.
  • Resize the app panel and confirm the content keeps the intended visual aspect ratio.
  • Switch between modes and confirm the content source and composition mode stay in sync.

Caveats

  • Stereo surface composition does not generate stereo content. Your app must provide content in the selected layout.
  • The API controls how Horizon OS composes the surface. It does not change Android media decoding, codec support, or digital rights management behavior.
  • Use SurfaceViewExt for SurfaceView and VideoView. Use SurfaceControlExt only when your app manages a SurfaceControl directly.
  • Set the composition mode before or while presenting content on the surface. Avoid presenting a stereo-packed buffer with mono composition, or mono content with stereo surface composition.
  • Top-bottom and side-by-side are packed-frame layouts. Multi-view video encodings such as MV-HEVC are separate from this API.
If your app supports devices or OS versions where the API is unavailable, check the Horizon OS SDK version before enabling this path.

Sample

For a complete working project, see the Stereo Video Sample.
The sample demonstrates:
  • Using SurfaceViewExt.setStereoComposition() with VideoView.
  • Using SurfaceViewExt.setStereoComposition() with SurfaceView and MediaPlayer.
  • Switching between mono, side-by-side, and top-bottom videos.
  • Matching display size to per-eye content size for packed stereo video.