Develop

Virtual Camera Publisher sample overview

Updated: Oct 30, 2026

Overview

This sample demonstrates how to publish custom virtual cameras from a Unity app to Horizon OS, enabling users to record, cast, and livestream from your app’s cameras through the system camera interface. The sample shows how to register multiple cameras, manage capture state, provide thumbnails, and optimize rendering for different resolutions and frame rates. This feature is experimental (package version 0.0.1) and is provided on an as-is basis.

Learning objectives

Complete this guide to learn how to:
  • register and configure virtual cameras with the Horizon OS camera system
  • implement the observer pattern to respond to capture state changes
  • use a single physical Unity Camera to support multiple virtual cameras at different resolutions and frame rates
  • manage camera rendering with frame-rate throttling for performance optimization
  • configure Android manifest permissions and Meta VR OS SDK version requirements

Requirements

  • Meta Quest device running Horizon OS v81 or later
  • Unity 6
For complete build and configuration requirements, see the sample README⁠.

Get started

Clone the sample repository:
git clone https://github.com/oculus-samples/Unity-VirtualCameraPublisher.git
Open the VirtualCameraPublisherSample folder as a project in Unity 6, load Assets/Scenes/SampleScene.unity, and build the APK for your Meta Quest device. This sample does not support Link or Editor testing. You must deploy a full APK to test virtual camera functionality.
To add the package to an existing project instead of running the sample, use the Unity Package Manager Add package from git URL⁠ option with this URL:
https://github.com/oculus-samples/Unity-VirtualCameraPublisher.git?path=com.meta.xr.virtualcamerapublisher

Explore the sample

File / DirectoryWhat it demonstratesKey concepts
Public static API for camera registration and lifecycle management
Centralized manager pattern, frame-by-frame update model
Observer interface for capture state callbacks
Decoupled event notification, capture lifecycle
Internal per-camera state management
Double-buffered RenderTextures, frame-rate throttling
Sample MonoBehaviour showing registration, updates, and cleanup
Multiple cameras from one physical Camera, observer implementation

Runtime behavior

When you run this scene, you see a static environment with floating objects. The sample registers three virtual cameras: two at different resolutions and frame rates sharing a far-view Camera, and one close-up Camera at 1080p. To access these cameras, press the Meta button, open the camera app, navigate to Camera Settings → Camera View, and select one of the registered cameras.

Key concepts

Registering multiple cameras from a single Unity Camera

The sample shows how one physical Unity Camera can back multiple virtual cameras at different resolutions and frame rates:
VirtualCameraManager.RegisterVirtualCamera(
    "1440p Far @ 45fps", cameraId, 2560, 1440, 45, droneCamera, far);
VirtualCameraManager.RegisterVirtualCamera(
    "4k Far @ 60fps", cameraId + 1, 3840, 2160, 60, droneCamera, far);
This pattern allows developers to offer quality/performance trade-offs without duplicating camera GameObjects.

Frame-based update model

Virtual camera rendering happens inside VirtualCameraManager.Update(), which must be called every frame from a MonoBehaviour. The manager handles frame-rate throttling internally, rendering each camera only when its target frame interval has elapsed. This decouples your app’s frame rate from the virtual camera feed rates, allowing 30fps, 45fps, and 60fps cameras to coexist.

Observer pattern for capture state

The sample implements IVirtualCameraObserver on the same MonoBehaviour that registers the cameras. The interface declares both OnCaptureStarted(int id) and OnCaptureStopped(int id), so implement both. Hand the instance to VirtualCameraManager.AddObserver() after registration, and call VirtualCameraManager.RemoveObserver() during teardown:
using Meta.VirtualCameraSystem;
using UnityEngine;

public class UnityVideoCapture : MonoBehaviour, IVirtualCameraObserver
{
    private int cameraId = 1;

    [SerializeField]
    private Camera droneCamera;

    void IVirtualCameraObserver.OnCaptureStarted(int id)
    {
        Debug.Log($"Capture started for camera {id}");
    }

    void IVirtualCameraObserver.OnCaptureStopped(int id)
    {
        Debug.Log($"Capture stopped for camera {id}");
    }

    void Start()
    {
        Texture2D far = Resources.Load<Texture2D>("thumbnails/far");
        VirtualCameraManager.RegisterVirtualCamera(
            "1440p Far @ 45fps", cameraId, 2560, 1440, 45, droneCamera, far);
        VirtualCameraManager.AddObserver(this);
    }

    void Update()
    {
        VirtualCameraManager.Update();
    }

    void OnApplicationQuit()
    {
        VirtualCameraManager.RemoveObserver(this);
        VirtualCameraManager.UnregisterVirtualCamera(cameraId);
    }
}
VirtualCameraManager.Update() detects each capture-state transition and calls OnCaptureStarted or OnCaptureStopped on every registered observer. Use these callbacks to pause expensive app logic, reposition cameras, or adjust quality settings when a camera feed is actively consumed.

Android manifest configuration

The sample requires three manifest additions: the SDK namespace on the root element, the horizonos.permission.CREATE_VIRTUAL_CAMERA permission, and an element declaring the minimum and target SDK versions your app supports.
Add the uses-metavr-sdk element as a child of the manifest element, and set metavr:minSdkVersion and metavr:targetSdkVersion to the versions your app supports. See Meta VR OS SDK versioning for configuration information. For example:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:metavr="http://schemas.meta.com/metavr-sdk">
  <uses-permission android:name="horizonos.permission.CREATE_VIRTUAL_CAMERA" />
  <metavr:uses-metavr-sdk
      metavr:minSdkVersion="81"
      metavr:targetSdkVersion="81" />
  ...
</manifest>
This sample is a publishing app: it creates virtual cameras and renders their image content, so it declares horizonos.permission.CREATE_VIRTUAL_CAMERA. It does not record, stream, or cast the feed itself. A consuming app, such as Meta Camera, handles that. For what each permission covers, see Permissions.

Extend the sample

  • Add interactive camera controls to let users switch between cameras or adjust field of view during capture.
  • Implement dynamic thumbnail generation by capturing a frame and encoding it with Texture2D.EncodeToJPG().
  • Use the capture state observer to toggle expensive rendering features when a camera feed is active.