Browse by type

A complete ecosystem for using Apple Vision Pro in robotics research — from real-world teleoperation to simulation teleoperation to egocentric dataset recording. Stream hand/head tracking from Vision Pro, send video/audio/simulation back, and record everything to the cloud.
For a more detailed explanation, check out this short paper.
The recently updated App Store version of Tracking Streamer requires python library
avp_streamover 2.50.0. It will show a warning message on the VisionOS side if the python library is outdated. You can upgrade the library by runningpip install --upgrade avp_stream.
This project provides:
Together, they enable three major workflows for robotics research:
| Use Case | Description | Primary Tools | |
|---|---|---|---|
| Real-World Teleoperation | Control physical robots with hand tracking while viewing robot camera feeds | avp_stream + WebRTC streaming configure_video() (or direct USB connection) of Physical Camera |
|
| Simulation Teleoperation | 2D Renderings | Control simulated robots with hand tracking while viewing 2D renderings from simulation | avp_stream + WebRTC streaming of 2D simulation rendering configure_video() |
| AR | Control simulated robots with MuJoCo/Isaac Lab with scenes directly presented in AR | avp_stream + MuJoCo/Isaac Lab streaming configure_mujoco() configure_isaac() |
|
| Egocentric Human Video Recording | Record first-person manipulation videos with synchronized tracking | UVC camera + Developer Strap |
Installing is easy: install it from the App Store and PyPI.
| Component | Installation |
|-----------|-------------|
| Tracking Streamer (VisionOS) | Install from App Store |
| Tracking Manager (iOS) | Install from App Store |
| avp_stream (Python) | pip install --upgrade avp_stream |
No other network configurations are required. Everything should work out of the box after installation. An easy way to get onboarded is to go through the examples folder. All examples should work out of the box without any extra configurations required.
Note: Some examples demonstrate teleoperating things within IsaacLab world; since IsaacLab is an extremely heavy dependency, I did not include that as a dependency for avp_stream. If you're trying to run examples including IsaacLab as a simulation backend, you should install things according to their official installation guide.
So far, Vision Pro and your Python client had to be on the same local network (e.g., same WiFi) for them to communicate. With External Network Mode with v2.5 release, you can make bilateral connection from anywhere over the internet! It's extremely useful when your robot is in a lab (likely behind school/company network's firewall) and you're working remotely using your home WiFi outside the school network.
| Mode | Connection Method | Use Case |
|---|---|---|
| Local Network | IP address (e.g., "192.168.1.100") |
Same WiFi/LAN |
| External Network | Room code (e.g., "ABC-1234") |
Different networks, over internet |
External Network Mode uses WebRTC with TURN relay servers for NAT traversal:
from avp_stream import VisionProStreamer
# Instead of IP address, use the room code shown on Vision Pro
s = VisionProStreamer(ip="ABC-1234")
# Everything else works exactly the same
s.configure_video(device="/dev/video0", format="v4l2", size="1280x720", fps=30)
s.start_webrtc()
while True:
r = s.get_latest()
# ...
Stream your robot's camera feed to Vision Pro while receiving hand/head tracking data for control. Perfect for teleoperating physical robots with visual feedback.
from avp_stream import VisionProStreamer
avp_ip = "10.31.181.201" # Vision Pro IP (shown in the app)
s = VisionProStreamer(ip=avp_ip)
# Configure video streaming from robot camera
s.configure_video(device="/dev/video0", format="v4l2", size="1280x720", fps=30)
s.start_webrtc()
while True:
r = s.get_latest()
# Use tracking data to control your robot
head_pose = r['head']
right_wrist = r['right_wrist']
right_fingers = r['right_fingers']
Camera with overlay processing:
def add_overlay(frame):
return cv2.putText(frame, "Robot View", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
s = VisionProStreamer(ip=avp_ip)
s.register_frame_callback(add_overlay)
s.configure_video(device="/dev/video0", format="v4l2", size="640x480", fps=30)
s.start_webrtc()
Stereo camera (side-by-side 3D):
s = VisionProStreamer(ip=avp_ip)
s.configure_video(device="/dev/video0", format="v4l2", size="1920x1080", fps=30, stereo=True)
s.start_webrtc()
Synthetic video (generated frames):
s = VisionProStreamer(ip=avp_ip)
s.register_frame_callback(render_visualization) # Your rendering function
s.configure_video(size="1280x720", fps=60) # No device = synthetic mode
s.start_webrtc()
With microphone input:
s = VisionProStreamer(ip=avp_ip)
s.configure_video(device="/dev/video0", format="v4l2", size="1280x720", fps=30)
s.configure_audio(device=":0", stereo=True) # Default mic
s.start_webrtc()
With synthetic audio (feedback sounds):
def beep_on_pinch(audio_frame):
# Generate audio based on hand tracking state
return audio_frame
s = VisionProStreamer(ip=avp_ip)
s.register_audio_callback(beep_on_pinch)
s.configure_video(size="1280x720", fps=60)
s.configure_audio(sample_rate=48000, stereo=True)
s.start_webrtc()
Render MuJoCo/Isaac Lab physics simulations directly in AR on Vision Pro. Consider it as a 3D Lifted version of your simulation renderings; rather than rendering your simulation environments on a 2D flat screen (either mono/stereo), you can view them in a 3D space in AR with super realistic rendering provided by Apple RealityKit.
The simulation environment (both for MuJoCo/Isaac Lab) is automatically converted to USD and rendered natively using RealityKit, with real-time pose updates streamed via WebRTC. Note that pose updates are way more compact than the full rendered frames in terms of network communication, enabling low-latency and reliable teleoperation experience.
Control simulated robots with your hands in a mixed-reality environment.

https://github.com/user-attachments/assets/7e6a3b6a-34f8-472a-ac6f-0f032fc0eae5
import mujoco
from avp_stream import VisionProStreamer
model = mujoco.MjModel.from_xml_path("robot.xml")
data = mujoco.MjData(model)
s = VisionProStreamer(ip=avp_ip)
s.configure_mujoco("robot.xml", model, data, relative_to=[0, 0, 0.8, 90])
s.start_webrtc()
while True:
# Your control logic using hand tracking
r = s.get_latest()
# ... update robot based on hand positions ...
mujoco.mj_step(model, data)
s.update_sim() # Stream updated poses to Vision Pro
from avp_stream import VisionProStreamer
# After creating your Isaac Lab environment...
streamer = VisionProStreamer(ip=avp_ip)
streamer.configure_isaac(
scene=env.scene,
relative_to=[0, 0, 0.8, 90],
include_ground=False,
env_indices=[0], # Stream only first environment
)
streamer.start_webrtc()
while simulation_app.is_running():
env.step(action)
streamer.update_sim() # Stream updated poses to Vision Pro
Since AR blends your simulation with the real world, you need to decide where the simulation's world frame should be placed in your physical space. Use relative_to parameter:
[x, y, z, yaw°] — translation + rotation around z-axis (degrees)[x, y, z, qw, qx, qy, qz] — full quaternion orientation# Place world frame 0.8m above ground, rotated 90° around z-axis
s.configure_mujoco("robot.xml", model, data, relative_to=[0, 0, 0.8, 90])
Default Behavior: VisionOS automatically detects the physical ground and places the origin there (below your feet if standing, below your chair if sitting).
| Examples from MuJoCo Menagerie | Unitree G1 | Google Robot | ALOHA 2 |
|---|---|---|---|
Visualization of world frame |
![]() |
![]() |
![]() |
world frame on ground |
world frame on ground |
world frame on table |
|
Recommended relative_to |
Default | Default | Offset in z-axis |
When using simulation streaming, you often want hand tracking data in the simulation's coordinate frame (not Vision Pro's native fram
$ claude mcp add VisionProTeleop \
-- python -m otcore.mcp_server <graph>