@displayxr/inline3d 1.48.0 → 1.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/call.d.ts +114 -7
- package/camera.d.ts +16 -8
- package/dist/call.js +9 -6
- package/js/call/element.js +3 -1
- package/js/call/options.js +2 -0
- package/js/call/share.js +179 -0
- package/js/call/transport.js +150 -9
- package/js/call/ui.js +3 -0
- package/js/call/wire.js +65 -1
- package/js/camera/app-share.js +114 -0
- package/js/camera/capture.js +108 -23
- package/js/inline3d-call.js +361 -36
- package/js/inline3d-camera.js +9 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,82 @@ entry points (`.`, `./three`) are frozen for 1.x, while the **scene subpaths** (
|
|
|
5
5
|
`./splat`, `./model`) are a preview tier whose options may change in any release. Entries below say
|
|
6
6
|
which tier they touch, because that is what tells you whether an upgrade can move your pixels.
|
|
7
7
|
|
|
8
|
+
## 1.49.1 — 2026-11-03
|
|
9
|
+
|
|
10
|
+
### Fixed — app share on browsers without the app-share device patch (`./call`, `./camera`)
|
|
11
|
+
|
|
12
|
+
Preview tier. A DisplayXR Browser that predates the app-share device patch (1.9.x) lists a running
|
|
13
|
+
app through its 3D-camera device, as `3D Camera (DisplayXR) - <app>` with no `source` in
|
|
14
|
+
`displayxrStereo` — and once the runtime offers more than one source it suffixes the REAL camera the
|
|
15
|
+
same way, so the label cannot tell an app from a camera. On such a browser `camera: 'auto'` could
|
|
16
|
+
open the app and send it as your face, and `listShareableApps()` found nothing.
|
|
17
|
+
|
|
18
|
+
- **Detection by the opened track**: an app source advertises `baselineMm: 0` and
|
|
19
|
+
`horizontalFovDeg: 0` (runtime ADR-061 decision 2); a real camera never has baseline 0.
|
|
20
|
+
`isAppShareDevice()` now accepts that hint as well as `source: 'app'`. The legacy label alone is
|
|
21
|
+
never enough.
|
|
22
|
+
- **Camera probe**: every probe path checks the opened track; an app is closed again and reported
|
|
23
|
+
in `skipped` with `reason: 'app-share'`, and probing goes on. Suffixed real cameras open as before.
|
|
24
|
+
- **Discovery**: with no `DisplayXR: <app> (3D)` device listed, `listShareableApps()` returns the
|
|
25
|
+
`3D Camera (DisplayXR) - <name>` entries as candidates (`ShareableApp.legacy: true`, `appName`
|
|
26
|
+
from the label). `openAppShare()` verifies the opened track and rejects a real camera with
|
|
27
|
+
`code: 'not-app-share'` (stopped again); the descriptor's `appName` falls back to the label's name.
|
|
28
|
+
|
|
29
|
+
## 1.49.0 — 2026-11-03
|
|
30
|
+
|
|
31
|
+
### Added — the content tile: share a running 3D app into a call (`./call`, `./camera`)
|
|
32
|
+
|
|
33
|
+
Preview tier. In the DisplayXR Browser a participant can share a running DisplayXR app, and every
|
|
34
|
+
other participant sees it in 3D on their own display (runtime ADR-061, "3D screen share"). What is
|
|
35
|
+
sent is the app's two views before weaving, side by side, on a **second video track** next to the
|
|
36
|
+
camera; each receiver weaves it for its own panel. See [`docs/call.md` § Sharing a 3D app](docs/call.md#sharing-a-3d-app).
|
|
37
|
+
|
|
38
|
+
- **Discovery** (`./call`): `listShareableApps()`, `watchShareableApps(cb)`, `isAppShareDevice()`,
|
|
39
|
+
`openAppShare(deviceId)`. The browser lists each app as a `videoinput` labelled
|
|
40
|
+
`DisplayXR: <app> (3D)` (id `dxr-app:…`); detection takes the label, the id prefix or the track's
|
|
41
|
+
`displayxrStereo.source === 'app'`, so browsers that do not expose the hint fields yet work, and a
|
|
42
|
+
missing `zeroDisparityShiftFrac` reads as 0. `openAppShare` is when the runtime asks the user on
|
|
43
|
+
the sender's machine; a Deny rejects with `code: 'share-denied'`.
|
|
44
|
+
- **Sharing**: `handle.shareContent(stream, descriptor)`, `handle.stopContent({ stopTrack })`,
|
|
45
|
+
`handle.content`; the `content` event (`{ id | 'self', on, appName }`); `CallPeer.content`;
|
|
46
|
+
`tile(id, 'content')` (`data-dxr-kind="content"`); `selfContentView` (`<dxr-call self-content-view>`)
|
|
47
|
+
for a woven preview of your own share.
|
|
48
|
+
- **Display-referred convergence**: the content message carries the disparity of the sender's
|
|
49
|
+
display plane (`convergence: { known: true, frac }`, 0 for every app today); a receiver applies
|
|
50
|
+
it directly, never measures the frames, and its depth slider still adds.
|
|
51
|
+
- **Wire**: `hello.content: true` (capability) and a `content` message, both additive (wire version
|
|
52
|
+
stays 1). The content transceiver is added only when both hellos say `content`, by the pair's
|
|
53
|
+
offerer with a same-generation re-offer, so an older participant never gets a second video
|
|
54
|
+
m-line (nor a second camera sender) — they keep seeing your camera, and you get
|
|
55
|
+
`warning { code: 'content-unsupported' }`. While content is on, the camera sends at 60% of its
|
|
56
|
+
bitrate; the content track gets the side-by-side budget and holds its resolution.
|
|
57
|
+
- `weave`, `speaking` and `display` events now carry `kind: 'camera' | 'content'`.
|
|
58
|
+
- **`./camera`: a running app is never a camera.** Its label says "3D", so `stereoLabelHint` would
|
|
59
|
+
have preferred it: no probe path of `openCamera` (and so `camera: 'auto'`) opens an app-share
|
|
60
|
+
device, `exclude: false` included, and `label:` searches skip them. They are in `skipped` with
|
|
61
|
+
`reason: 'app-share'` (`CameraSkip.reason`, new).
|
|
62
|
+
|
|
63
|
+
### Fixed — the Ultraleap hand tracker is excluded by its bare brand name; the default camera is not opened blind (`./camera`, `./call`)
|
|
64
|
+
|
|
65
|
+
1.48.0's built-in exclusion list is generic words (hand tracking, tracking camera, eye tracking,
|
|
66
|
+
depth camera). On Windows an Ultraleap (Leap 2 / Leap Motion Controller) enumerates as a UVC device
|
|
67
|
+
labelled just **"Ultraleap"**, with modes of 2:1 or squarer, so neither the list nor the pair shape
|
|
68
|
+
caught it; with its tracking service stopped it was free and, as the browser's *default* camera on
|
|
69
|
+
that box, became the call's self view (win box, 2026-11-04). The list now also names `ultraleap`,
|
|
70
|
+
`leap motion`, `infrared` and `ir camera`.
|
|
71
|
+
|
|
72
|
+
While an excluded device is listed and labels are visible, the default camera is no longer opened
|
|
73
|
+
blind and closed again: the first camera that is not excluded is opened by id, so a sensor is never
|
|
74
|
+
started (an eye tracker's camera must not be). With labels hidden the prime stays blind, as before.
|
|
75
|
+
|
|
76
|
+
### Added — `prefer: 'label:<text>'` opens the first camera whose label contains the text (`./camera`, `./call`)
|
|
77
|
+
|
|
78
|
+
For a URL or a config, where a per-origin `deviceId` is useless: `openCamera({ prefer: 'label:hd webcam' })`,
|
|
79
|
+
`addCall(wall, el, { camera: 'label:brio' })`. Case-insensitive; with no permission yet the default
|
|
80
|
+
camera is primed once and let go so the labels become visible; no match rejects with `no-camera`.
|
|
81
|
+
Explicit like a `deviceId`, so it opens an excluded device too. The call sample passes
|
|
82
|
+
`?camera=label:…` (and a bare `?camera=<deviceId>`) straight through.
|
|
83
|
+
|
|
8
84
|
## 1.48.0 — 2026-11-03
|
|
9
85
|
|
|
10
86
|
### Changed — auto-convergence is measured on the sender's raw frames (`./call`)
|
package/call.d.ts
CHANGED
|
@@ -273,6 +273,11 @@ export interface CallOptions {
|
|
|
273
273
|
autoJoin?: boolean;
|
|
274
274
|
/** The small self view (mirrored; a stereo self view is mirrored AND eye-swapped — an `addCameraView`). Default true. */
|
|
275
275
|
selfView?: boolean;
|
|
276
|
+
/**
|
|
277
|
+
* While you share content (`shareContent`), also show it to yourself: a woven preview tile,
|
|
278
|
+
* `tile('self', 'content')`. Default false (you have the app itself on your display).
|
|
279
|
+
*/
|
|
280
|
+
selfContentView?: boolean;
|
|
276
281
|
/** Colours, radius, font, tile aspect and strings — see {@link CallTheme}. */
|
|
277
282
|
theme?: CallTheme;
|
|
278
283
|
/** Invite links: `base` (default this page's URL, query kept, fragment replaced); `updateUrl` writes `#room=` into the page URL on join (default: `ui === true`). */
|
|
@@ -329,8 +334,92 @@ export interface CallPeer {
|
|
|
329
334
|
readonly speaking: boolean;
|
|
330
335
|
/** Their display name (`name` option / `setName()`), sanitised; null when they sent none (or an older SDK). */
|
|
331
336
|
readonly name: string | null;
|
|
337
|
+
/** What they share (3D app share): `on`, its format and the app's name (null while `on` is false). */
|
|
338
|
+
readonly content: { readonly on: boolean; readonly format: CallFormat | null; readonly appName: string | null };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Which of a participant's tiles: their camera (the person) or the content they share. */
|
|
342
|
+
export type CallTileKind = 'camera' | 'content';
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* The convergence of shared content. `known: true`: the frames are DISPLAY-REFERRED — `frac` is the
|
|
346
|
+
* disparity of the sender's display plane as a fraction of the per-eye width (`zeroDisparityShiftFrac`
|
|
347
|
+
* of a DisplayXR app, 0 in v1). Receivers apply it directly and never measure the frames; their
|
|
348
|
+
* depth control adds to it. `known: false`: shown as sent.
|
|
349
|
+
*/
|
|
350
|
+
export interface ContentConvergence {
|
|
351
|
+
known: boolean;
|
|
352
|
+
frac: number | null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** What `shareContent` announces (also `openAppShare()`'s `descriptor`, which fits it as is). */
|
|
356
|
+
export interface ContentDescriptor {
|
|
357
|
+
format?: CallFormat;
|
|
358
|
+
width?: number;
|
|
359
|
+
height?: number;
|
|
360
|
+
convergence?: ContentConvergence;
|
|
361
|
+
appName?: string | null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** `handle.content`: what you share right now. */
|
|
365
|
+
export interface CallContent {
|
|
366
|
+
readonly on: true;
|
|
367
|
+
readonly format: CallFormat;
|
|
368
|
+
readonly width: number;
|
|
369
|
+
readonly height: number;
|
|
370
|
+
readonly appName: string | null;
|
|
371
|
+
readonly convergence: Readonly<ContentConvergence>;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** A running DisplayXR app the browser offers to share (`listShareableApps`). */
|
|
375
|
+
export interface ShareableApp {
|
|
376
|
+
readonly deviceId: string;
|
|
377
|
+
/** `DisplayXR: <app> (3D)` (or `3D Camera (DisplayXR) - <app>` for a `legacy` candidate). */
|
|
378
|
+
readonly label: string;
|
|
379
|
+
readonly appName: string | null;
|
|
380
|
+
/**
|
|
381
|
+
* A browser without the app-share device patch: a CANDIDATE listed by its label shape only — a
|
|
382
|
+
* real camera can carry the same shape. `openAppShare()` verifies it (a camera rejects with
|
|
383
|
+
* `not-app-share`).
|
|
384
|
+
*/
|
|
385
|
+
readonly legacy?: true;
|
|
332
386
|
}
|
|
333
387
|
|
|
388
|
+
/** An opened app share (`openAppShare`): pass `stream` (+ `descriptor`) to `shareContent`. The track is yours to stop. */
|
|
389
|
+
export interface AppShare {
|
|
390
|
+
stream: MediaStream;
|
|
391
|
+
track: MediaStreamTrack;
|
|
392
|
+
descriptor: { kind: 'content'; format: CallFormat; width: number; height: number; convergence: { known: true; frac: number }; appName: string | null };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Why `openAppShare` rejected (`error.code`): the user said no at the runtime's prompt, it failed, or the device is not an app. */
|
|
396
|
+
export type AppShareErrorCode = 'share-denied' | 'share-failed' | 'not-app-share';
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Is this a running DisplayXR app offered for 3D screen share (runtime ADR-061) rather than a
|
|
400
|
+
* camera? A `MediaDeviceInfo`, a label, a track, or its settings: true on the label
|
|
401
|
+
* `DisplayXR: <app> (3D)`, a `dxr-app:` device id, `displayxrStereo.source === 'app'`, or (browsers
|
|
402
|
+
* without the app-share device patch) a hint with `baselineMm` and `horizontalFovDeg` both 0. No
|
|
403
|
+
* camera probe (`camera: 'auto'`, `openCamera`) ever uses one.
|
|
404
|
+
*/
|
|
405
|
+
export function isAppShareDevice(device: MediaDeviceInfo | MediaStreamTrack | MediaTrackSettings | { label?: string; deviceId?: string } | string | null | undefined): boolean;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* The running apps this browser offers to share. Empty before the page holds camera permission
|
|
409
|
+
* (the browser lists none until then), on any other browser, or when none is shareable.
|
|
410
|
+
*/
|
|
411
|
+
export function listShareableApps(opts?: { mediaDevices?: MediaDevices }): Promise<ShareableApp[]>;
|
|
412
|
+
|
|
413
|
+
/** `cb(apps)` now and whenever the list changes (`devicechange`). Returns the unsubscribe. */
|
|
414
|
+
export function watchShareableApps(cb: (apps: ShareableApp[]) => void, opts?: { mediaDevices?: MediaDevices }): () => void;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Open a shareable app as a side-by-side track (default 2560×720 at 30 fps). The runtime asks the
|
|
418
|
+
* user on THIS machine now; Deny rejects with `code: 'share-denied'`. The track ends when the app
|
|
419
|
+
* exits. Stop it yourself when you stop sharing.
|
|
420
|
+
*/
|
|
421
|
+
export function openAppShare(deviceId: string, opts?: { width?: number; height?: number; frameRate?: number; mediaDevices?: MediaDevices }): Promise<AppShare>;
|
|
422
|
+
|
|
334
423
|
/**
|
|
335
424
|
* `error` codes — fatal for a feature, never for the widget. `'camera-busy'` (held by another
|
|
336
425
|
* app: audio-only until `retryCamera()`), `'no-camera'`, `'permission-denied'`, `'unreachable'`
|
|
@@ -367,7 +456,7 @@ export type CallErrorCode =
|
|
|
367
456
|
| (string & {});
|
|
368
457
|
|
|
369
458
|
/** `warning` codes — degraded, not broken. */
|
|
370
|
-
export type CallWarningCode = 'lift-not-bundled' | 'turn-shed' | (string & {});
|
|
459
|
+
export type CallWarningCode = 'lift-not-bundled' | 'turn-shed' | 'content-unsupported' | (string & {});
|
|
371
460
|
|
|
372
461
|
export interface CallEvents {
|
|
373
462
|
joined: { room: string; id: string };
|
|
@@ -376,8 +465,8 @@ export interface CallEvents {
|
|
|
376
465
|
peer: { id: string };
|
|
377
466
|
peerleft: { id: string; reason: string };
|
|
378
467
|
state: { id: string; state: PeerState };
|
|
379
|
-
/** How this side shows a participant changed (a hello arrived, lift went live, the wall came back). */
|
|
380
|
-
display: { id: string; display: CallDisplay };
|
|
468
|
+
/** How this side shows a participant changed (a hello arrived, lift went live, the wall came back). `kind`: which of their tiles. */
|
|
469
|
+
display: { id: string; display: CallDisplay; kind: CallTileKind };
|
|
381
470
|
speaker: { id: string | null };
|
|
382
471
|
/**
|
|
383
472
|
* Per peer (`id`) every 2 s; or, with `id: null` and `lift`, when lifted tiles on the web
|
|
@@ -393,6 +482,8 @@ export interface CallEvents {
|
|
|
393
482
|
* `'turn-shed'`: only THIS session got no TURN from the hosted service (an anonymous session
|
|
394
483
|
* while the monthly relay budget is nearly spent, or too many credential requests from this
|
|
395
484
|
* address this hour); the call runs STUN-only. A publishable key keeps TURN.
|
|
485
|
+
* `'content-unsupported'`: you share content and a participant's SDK predates it (no `content` in
|
|
486
|
+
* their hello) — they keep seeing your camera only. Once per participant per share.
|
|
396
487
|
*/
|
|
397
488
|
warning: { code: CallWarningCode; message: string };
|
|
398
489
|
/** A participant's display name arrived or changed (`name: null` = they cleared it). */
|
|
@@ -412,9 +503,14 @@ export interface CallEvents {
|
|
|
412
503
|
* (`rewoven()`). `id` is a participant's, or `'self'` for your self view. `'flat'`: the wall
|
|
413
504
|
* says the tile will not weave (`reason`, e.g. `'layer-failed'`) — it is shown flat.
|
|
414
505
|
*/
|
|
415
|
-
weave: { id: string; state: 'woven' | 'flat'; reason?: string; ms: number };
|
|
506
|
+
weave: { id: string; state: 'woven' | 'flat'; reason?: string; ms: number; kind: CallTileKind };
|
|
416
507
|
/** A participant (or `'self'`) started / stopped speaking. */
|
|
417
|
-
speaking: { id: string; on: boolean };
|
|
508
|
+
speaking: { id: string; on: boolean; kind: CallTileKind };
|
|
509
|
+
/**
|
|
510
|
+
* A participant (or `'self'`) started or stopped sharing content (3D app share). `reason` on your
|
|
511
|
+
* own stop: `'stopped'` (`stopContent()`), `'ended'` (the track ended — the app exited), `'left'`.
|
|
512
|
+
*/
|
|
513
|
+
content: { id: string; on: boolean; appName: string | null; reason?: 'stopped' | 'ended' | 'left' };
|
|
418
514
|
}
|
|
419
515
|
|
|
420
516
|
export interface CallHandle {
|
|
@@ -456,7 +552,18 @@ export interface CallHandle {
|
|
|
456
552
|
* A participant's tile element (for `layout: 'none'`), or null; `'self'` is your self view
|
|
457
553
|
* (`data-dxr-peer="self"`). Style it in place; never move it.
|
|
458
554
|
*/
|
|
459
|
-
tile(peerId: string): HTMLElement | null;
|
|
555
|
+
tile(peerId: string, kind?: CallTileKind): HTMLElement | null;
|
|
556
|
+
/**
|
|
557
|
+
* Share content on a second video track (3D app share): a running DisplayXR app's views from
|
|
558
|
+
* `openAppShare()` (pass its `descriptor` too), or any side-by-side track. Every participant on a
|
|
559
|
+
* DisplayXR SDK with content support gets a content tile; older ones keep seeing your camera only
|
|
560
|
+
* (`warning` `'content-unsupported'`). Replaces a previous share. Resolves with what is announced.
|
|
561
|
+
*/
|
|
562
|
+
shareContent(src: MediaStream | MediaStreamTrack, opts?: ContentDescriptor): Promise<CallContent>;
|
|
563
|
+
/** Stop sharing. `stopTrack: true` also stops the track (ends the runtime's share). Resolves false when nothing was shared. */
|
|
564
|
+
stopContent(opts?: { stopTrack?: boolean }): Promise<boolean>;
|
|
565
|
+
/** What you share right now, or null. */
|
|
566
|
+
readonly content: CallContent | null;
|
|
460
567
|
/** Your display name, or null. */
|
|
461
568
|
readonly name: string | null;
|
|
462
569
|
/** Change your display name (re-sent to everyone in `hello`). Returns it sanitised (null = none). */
|
|
@@ -511,7 +618,7 @@ export function parseInviteLink(link: string | { hash?: string; href?: string }
|
|
|
511
618
|
// a bubbling, composed `CustomEvent` named `dxr-call:<event>` with the payload in `detail`, and
|
|
512
619
|
// exposes the handle as `el.call`. Attributes (read once, at connect): `room`, `signaling` (a
|
|
513
620
|
// URL), `key`, `camera`, `layout`, `accent`, `max-peers`, `no-ui`, `ui="tiles"`, `auto-join`,
|
|
514
|
-
// `mono3d="off"`, `no-audio`, `no-self-view`, `no-auto-converge`, `no-send-convergence`,
|
|
621
|
+
// `mono3d="off"`, `no-audio`, `no-self-view`, `self-content-view`, `no-auto-converge`, `no-send-convergence`,
|
|
515
622
|
// `tile-aspect`, `invite-base`, `browser-url`, `debug`. Everything else is `el.options`, set before connect; options win.
|
|
516
623
|
|
|
517
624
|
export class DxrCallElement extends HTMLElement {
|
package/camera.d.ts
CHANGED
|
@@ -21,8 +21,11 @@ export type CameraFormat = 'sbs' | 'mono';
|
|
|
21
21
|
* Which camera to open. `'auto'` (default): a stereo device when one is present — the DisplayXR
|
|
22
22
|
* Browser's "3D Camera" (recognised by its `displayxrStereo` track settings, never by its label),
|
|
23
23
|
* or a device delivering wider than 2.5:1 frames — else the default webcam. `'stereo'` prefers
|
|
24
|
-
* the pair and falls back to mono; `'mono'` never probes; a string is a `deviceId
|
|
25
|
-
* `
|
|
24
|
+
* the pair and falls back to mono; `'mono'` never probes; a string is a `deviceId`, or
|
|
25
|
+
* `'label:<text>'` for the first camera whose label contains the text (case-insensitive; for a URL
|
|
26
|
+
* or a config, where a per-origin deviceId is useless); a `MediaStream` is used as given, with
|
|
27
|
+
* {@link CameraOptions.format} declaring what it is. A `deviceId` or `label:` is explicit and
|
|
28
|
+
* opens an {@link CameraOptions.exclude excluded} device too.
|
|
26
29
|
*/
|
|
27
30
|
export type CameraPreference = 'auto' | 'stereo' | 'mono' | (string & {}) | MediaStream;
|
|
28
31
|
|
|
@@ -59,12 +62,15 @@ export interface CameraOptions {
|
|
|
59
62
|
/**
|
|
60
63
|
* Devices no probe may open. Built in, always (unless `false`): video inputs whose label marks a
|
|
61
64
|
* sensor rather than a camera — hand tracking, an eye tracker's or a "tracking camera"'s stream,
|
|
62
|
-
* a depth camera
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* a depth camera, an infrared / IR camera, and the Ultraleap / Leap Motion hand tracker, which
|
|
66
|
+
* enumerates under its bare brand name
|
|
67
|
+
* (`/\b(hand[- ]?track|tracking camera|eye[- ]?track|depth camera|ultraleap|leap motion|infrared|ir camera)/i`).
|
|
68
|
+
* Add your own: a string matches a label (case-insensitive substring) or a deviceId exactly; a
|
|
69
|
+
* RegExp tests the label. Applied on every probe path, the last-resort "try every device" one
|
|
70
|
+
* included; while an excluded device is listed the default camera is not opened blind (the first
|
|
71
|
+
* other camera is opened by id), and a default that turns out to be excluded is closed again.
|
|
72
|
+
* Left-out devices are listed in `skipped` with `excluded: true`. `false` turns the built-in
|
|
73
|
+
* list off. An explicit `deviceId` or `label:` (`prefer`) is your choice and is opened regardless.
|
|
68
74
|
*/
|
|
69
75
|
exclude?: ReadonlyArray<string | RegExp> | false;
|
|
70
76
|
/** Verbose console logging. */
|
|
@@ -85,6 +91,8 @@ export interface CameraSkip {
|
|
|
85
91
|
busy?: boolean;
|
|
86
92
|
denied?: boolean;
|
|
87
93
|
excluded?: boolean;
|
|
94
|
+
/** Why it was left out: `'excluded'` (a sensor, or your `exclude`), or `'app-share'` (a running DisplayXR app offered for 3D screen share — never a camera). */
|
|
95
|
+
reason?: 'excluded' | 'app-share';
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
export interface CameraEvents {
|