Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions deps/cloudxr/webxr_client/helpers/BrowserCapabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@
const MIN_OCULUS_BROWSER_MAJOR = 40;
const MIN_PICO_CHROME_MAJOR = 125;

/** Headset family inferred from the browser user-agent. */
export type HeadsetFamily = 'quest' | 'pico' | 'other';

/**
* Classify the headset from the user-agent. Pico is checked first because Pico UAs
* also include an "OculusBrowser/7.0" compat token that would otherwise match Quest.
* The UA does not expose the specific Quest/Pico model, so this returns the family only.
* `ua` is injectable for testing; defaults to the live navigator user-agent.
*/
export function detectHeadset(ua: string = navigator.userAgent): HeadsetFamily {
if (/PicoBrowser\//.test(ua)) return 'pico';
if (/OculusBrowser\//.test(ua)) return 'quest';
return 'other';
}

// Returns a warning message if the current browser is below the documented minimum
// version, or null if the version is acceptable or cannot be determined.
// Pass emulated=true when running under an XR emulator (e.g. IWER) to skip the check.
Expand All @@ -31,10 +46,9 @@ export function checkBrowserVersion(emulated = false): string | null {
}

const ua = navigator.userAgent;
const headset = detectHeadset(ua);

// Detect Pico first — Pico UAs also include "OculusBrowser/7.0" as a compat token
// which would otherwise match the Quest check below.
if (/PicoBrowser\//.test(ua)) {
if (headset === 'pico') {
const chromeMatch = ua.match(/Chrome\/(\d+)\./);
if (chromeMatch) {
const major = parseInt(chromeMatch[1], 10);
Expand All @@ -49,7 +63,7 @@ export function checkBrowserVersion(emulated = false): string | null {
return null;
}

const questMatch = ua.match(/OculusBrowser\/(\d+)\./);
const questMatch = headset === 'quest' ? ua.match(/OculusBrowser\/(\d+)\./) : null;
if (questMatch) {
const major = parseInt(questMatch[1], 10);
if (major < MIN_OCULUS_BROWSER_MAJOR) {
Expand Down
24 changes: 24 additions & 0 deletions deps/cloudxr/webxr_client/helpers/DeviceProfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* limitations under the License.
*/

import { detectHeadset } from './BrowserCapabilities';

// Device profiles provide per-device defaults used by the example UIs.
// These are not hard requirements; they are applied as suggested values and remain user-editable.
export type DeviceProfileId = 'custom' | 'quest2' | 'quest3' | 'quest3s' | 'pico4ultra';
Expand Down Expand Up @@ -176,3 +178,25 @@ export function resolveDeviceProfileId(value: string | null | undefined): Device
export function getDeviceProfile(id: DeviceProfileId): DeviceProfile {
return DEVICE_PROFILES[id] ?? CUSTOM_PROFILE;
}

/**
* Best-effort default profile from the headset user-agent, reusing the same UA detection
* as the browser capability check ({@link detectHeadset}).
*
* Per Meta's Browser Specs the UA platform token carries the model — "Quest 2", "Quest 3",
* "Quest Pro", "Quest" — but Quest 3, Quest 3S and Quest 3S Xbox Edition all report
* "Quest 3", so 3-vs-3S is not distinguishable (and our quest3/quest3s profiles are
* identical anyway). The only split that matters is hardware AV1 support: Quest 3/3S have
* it (→ quest3, AV1); Quest 2 / Quest 1 / Quest Pro do not (→ quest2, H.265). Pico →
* pico4ultra; non-headset (desktop/emulator) → custom. `ua` is injectable for testing.
*/
export function detectDeviceProfileId(ua: string = navigator.userAgent): DeviceProfileId {
switch (detectHeadset(ua)) {
case 'quest':
return /\bQuest 3\b/.test(ua) ? 'quest3' : 'quest2';
case 'pico':
return 'pico4ultra';
default:
return 'custom';
}
}
1 change: 1 addition & 0 deletions deps/cloudxr/webxr_client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"ts-jest": "^29",
"ts-loader": "^9.5.7",
"typescript": "^5.8.2",
"webpack": "5.105.4",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.3"
}
Expand Down
3 changes: 1 addition & 2 deletions deps/cloudxr/webxr_client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import type { XRDevice } from 'iwer';
import { useState, useMemo, useEffect, useRef } from 'react';

import { v5 } from 'uuid';
import { CloudXR2DUI } from './CloudXR2DUI';
import { CloudXR2DUI, COUNTDOWN_STORAGE_KEY } from './CloudXR2DUI';
import { readUrlParam } from './config/resolve';
import CloudXR3DUI from './CloudXRUI';
import { HeadsetControlChannel } from '@helpers/controlChannel';
Expand Down Expand Up @@ -136,7 +136,6 @@ function buildOobHubWsUrlFromQuery(searchParams: URLSearchParams): string | null

function App() {
const COUNTDOWN_MAX_SECONDS = 9;
const COUNTDOWN_STORAGE_KEY = 'cxr.react.countdownSeconds';
// 2D UI management
const [cloudXR2DUI, setCloudXR2DUI] = useState<CloudXR2DUI | null>(null);
// IWER loading state
Expand Down
Loading
Loading