Add CloudXR VR streaming support for PICO 4 Ultra (Early Access)
Replaces manual H264/TCP stereo streaming with NVIDIA CloudXR for
higher-quality stereoscopic rendering and lower latency.
Changes:
- teleop_xr_agent.py: add --cloudxr flag (enables Isaac Sim XR mode,
disables manual StreamingManager)
- deps/cloudxr/: NVIDIA CloudXR.js SDK (Early Access) with Isaac Lab
teleop React web client
- deps/cloudxr/Dockerfile.wss.proxy: HAProxy WSS proxy for PICO 4 Ultra
HTTPS mode (routes wss://48322 → ws://49100)
- deps/cloudxr/isaac/webpack.dev.js: disable file watching to avoid
EMFILE errors with large node_modules
- deps/cloudxr/INSTALL.md: full setup guide
Usage:
# Start CloudXR Runtime + Isaac Lab
cd ~/IsaacLab && ./docker/container.py start \
--files docker-compose.cloudxr-runtime.patch.yaml \
--env-file .env.cloudxr-runtime
# Run teleop with CloudXR
~/IsaacLab/isaaclab.sh -p teleop_xr_agent.py \
--task Isaac-MindRobot-2i-DualArm-IK-Abs-v0 --cloudxr
# Serve web client
cd deps/cloudxr/isaac && npm run dev-server:https
This commit is contained in:
98
deps/cloudxr/helpers/LoadIWER.ts
vendored
Normal file
98
deps/cloudxr/helpers/LoadIWER.ts
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
const IWER_version = '2.1.1';
|
||||
const IWER_DEVUI_version = '1.1.2';
|
||||
|
||||
export interface IWERLoadResult {
|
||||
supportsImmersive: boolean;
|
||||
iwerLoaded: boolean;
|
||||
}
|
||||
|
||||
export async function loadIWERIfNeeded(): Promise<IWERLoadResult> {
|
||||
let supportsImmersive = false;
|
||||
let iwerLoaded = false;
|
||||
|
||||
if ('xr' in navigator) {
|
||||
try {
|
||||
const vr = await (navigator.xr as XRSystem).isSessionSupported?.('immersive-vr');
|
||||
const ar = await (navigator.xr as XRSystem).isSessionSupported?.('immersive-ar');
|
||||
supportsImmersive = Boolean(vr || ar);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (!supportsImmersive) {
|
||||
console.info('Immersive mode not supported, loading IWER as fallback.');
|
||||
|
||||
// Load IWER first
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://unpkg.com/iwer@${IWER_version}/build/iwer.min.js`;
|
||||
script.async = true;
|
||||
script.integrity = 'sha384-ZOdYbNlfA4q9jkBGcdmjy2ZYmjxy2uzncU6it3cPOHi12/WF048bamSU0Z5N+V5u';
|
||||
script.crossOrigin = 'anonymous';
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
script.onload = async () => {
|
||||
console.info('IWER loaded as fallback.');
|
||||
const IWERGlobal = (window as any).IWER || (globalThis as any).IWER;
|
||||
if (!IWERGlobal) {
|
||||
console.warn('IWER global not found after script load.');
|
||||
supportsImmersive = false;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load iwer-devui after IWER
|
||||
const devUIScript = document.createElement('script');
|
||||
devUIScript.src = `https://unpkg.com/@iwer/devui@${IWER_DEVUI_version}/build/iwer-devui.min.js`;
|
||||
devUIScript.async = true;
|
||||
devUIScript.integrity =
|
||||
'sha384-CG/gISX6PadiSzc8i2paU7CYLVsnVJaJ0tgoVnAPq/gyiTX6bddG5rwOgMDGlq74';
|
||||
devUIScript.crossOrigin = 'anonymous';
|
||||
|
||||
await new Promise<void>(devUIResolve => {
|
||||
devUIScript.onload = () => {
|
||||
console.info('IWER DevUI loaded.');
|
||||
devUIResolve();
|
||||
};
|
||||
devUIScript.onerror = error => {
|
||||
console.warn('Failed to load IWER DevUI:', error);
|
||||
devUIResolve();
|
||||
};
|
||||
document.head.appendChild(devUIScript);
|
||||
});
|
||||
|
||||
try {
|
||||
// Create XRDevice with Meta Quest 3 profile
|
||||
const xrDevice = new IWERGlobal.XRDevice(IWERGlobal.metaQuest3);
|
||||
|
||||
// Initialize DevUI with the XR device
|
||||
const IWER_DevUI = (window as any).IWER_DevUI || (globalThis as any).IWER_DevUI;
|
||||
if (IWER_DevUI?.DevUI) {
|
||||
xrDevice.installDevUI(IWER_DevUI.DevUI);
|
||||
console.info('IWER DevUI initialized with XR device.');
|
||||
} else {
|
||||
console.warn('IWER DevUI not found after script load, continuing without DevUI.');
|
||||
}
|
||||
|
||||
// Install the runtime and wait for it to be ready
|
||||
const maybePromise = xrDevice.installRuntime?.();
|
||||
if (maybePromise && typeof maybePromise.then === 'function') {
|
||||
await maybePromise;
|
||||
}
|
||||
supportsImmersive = true;
|
||||
iwerLoaded = true;
|
||||
} catch (e) {
|
||||
console.warn('IWER runtime install failed:', e);
|
||||
supportsImmersive = false;
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
script.onerror = () => {
|
||||
console.warn('Failed to load IWER.');
|
||||
supportsImmersive = false;
|
||||
resolve();
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
return { supportsImmersive, iwerLoaded };
|
||||
}
|
||||
Reference in New Issue
Block a user