@displayxr/inline3d 1.47.3 → 1.47.5
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 +42 -0
- package/dist/call.js +5 -5
- package/js/camera/capture.js +73 -19
- package/package.json +1 -1
package/js/camera/capture.js
CHANGED
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
// - On some laptops the stereo camera is held EXCLUSIVELY by the eye tracker, so opening it
|
|
10
10
|
// fails with NotReadableError ("Device in use"). That device is skipped silently — a call must
|
|
11
11
|
// never fail because an optional camera is busy.
|
|
12
|
-
// - Labels are empty until one getUserMedia has succeeded
|
|
13
|
-
//
|
|
12
|
+
// - Labels are empty until one getUserMedia has succeeded. Without them, permission is primed on
|
|
13
|
+
// the DEFAULT camera with no constraints: in the DisplayXR Browser the runtime's 3D camera is
|
|
14
|
+
// listed first, so the prime opens the pair itself and no phone lens is opened at all (a plain
|
|
15
|
+
// open of one lens of the pair, or its teardown, crashes some phones' camera HAL; any facing or
|
|
16
|
+
// size constraint lets the engine pick a lens instead). Then the list is read again and a
|
|
17
|
+
// stereo device is opened by id BEFORE falling back. The primed default is the fallback.
|
|
14
18
|
//
|
|
15
19
|
// RECTIFICATION is not done here (P2): it needs the camera's calibration (intrinsics/extrinsics
|
|
16
20
|
// for the ACTIVE device), which comes from a plug-in or the runtime. P1 sends the raw pair with
|
|
@@ -101,6 +105,20 @@ export async function openCamera(want = 'auto', o = {}) {
|
|
|
101
105
|
log('camera-skip', { label, error: err.name });
|
|
102
106
|
};
|
|
103
107
|
const stop = (r) => r && r.stream.getTracks().forEach((t) => t.stop());
|
|
108
|
+
// Ask an open camera for another mode without a second start; keep it as it is if it refuses.
|
|
109
|
+
const withMode = async (r, constraints) => {
|
|
110
|
+
try {
|
|
111
|
+
await r.track.applyConstraints(constraints);
|
|
112
|
+
const st = r.track.getSettings();
|
|
113
|
+
if (st.width > 0 && st.height > 0) return { ...r, width: st.width, height: st.height };
|
|
114
|
+
} catch {
|
|
115
|
+
/* keep the mode it opened in */
|
|
116
|
+
}
|
|
117
|
+
return r;
|
|
118
|
+
};
|
|
119
|
+
// A track that declares itself a side-by-side pair IS one, whatever its aspect (a phone's portrait
|
|
120
|
+
// eyes make a 1440x1280 pair). Shape is only the guess for an unknown device, e.g. a USB pair.
|
|
121
|
+
const isPair = (r) => !!r && (!!r.stereo || looksSbs(r.width, r.height));
|
|
104
122
|
const result = (r, format) => ({
|
|
105
123
|
stream: r.stream, format, width: r.width, height: r.height, deviceId: r.deviceId, label: r.label, owned: true, skipped,
|
|
106
124
|
calibration: {
|
|
@@ -126,7 +144,7 @@ export async function openCamera(want = 'auto', o = {}) {
|
|
|
126
144
|
skip(want, err);
|
|
127
145
|
throw Object.assign(new Error(`camera unavailable: ${err.message}`), { code: noCameraCode(skipped), skipped });
|
|
128
146
|
}
|
|
129
|
-
if (
|
|
147
|
+
if (isPair(r)) return result(r, 'sbs');
|
|
130
148
|
stop(r);
|
|
131
149
|
return result(await open({ deviceId: { exact: want }, ...MONO_CONSTRAINTS }), 'mono');
|
|
132
150
|
}
|
|
@@ -136,14 +154,16 @@ export async function openCamera(want = 'auto', o = {}) {
|
|
|
136
154
|
// camera start can take seconds (a busy capture stack took 7-20 s per start on a field box), and
|
|
137
155
|
// the default-first probe below costs two or three of them before it reaches the pair.
|
|
138
156
|
const tried = new Set();
|
|
139
|
-
|
|
140
|
-
let early = [];
|
|
157
|
+
const listCameras = async () => {
|
|
141
158
|
try {
|
|
142
|
-
|
|
159
|
+
return (await md.enumerateDevices()).filter((d) => d.kind === 'videoinput');
|
|
143
160
|
} catch {
|
|
144
|
-
|
|
161
|
+
return []; // no enumeration: the paths below still work
|
|
145
162
|
}
|
|
146
|
-
|
|
163
|
+
};
|
|
164
|
+
const tryNamedPairs = async (list) => {
|
|
165
|
+
for (const d of list) {
|
|
166
|
+
if (!d.deviceId || !stereoLabelHint(d.label) || tried.has(d.deviceId)) continue;
|
|
147
167
|
tried.add(d.deviceId);
|
|
148
168
|
let r = null;
|
|
149
169
|
try {
|
|
@@ -152,23 +172,57 @@ export async function openCamera(want = 'auto', o = {}) {
|
|
|
152
172
|
skip(d.label, err);
|
|
153
173
|
continue;
|
|
154
174
|
}
|
|
155
|
-
if (
|
|
175
|
+
if (isPair(r)) {
|
|
156
176
|
log('camera-sbs', { label: r.label, width: r.width, height: r.height, direct: true });
|
|
157
|
-
return
|
|
177
|
+
return r;
|
|
158
178
|
}
|
|
159
179
|
stop(r);
|
|
160
180
|
}
|
|
181
|
+
return null;
|
|
182
|
+
};
|
|
183
|
+
let mono = null;
|
|
184
|
+
if (want !== 'mono') {
|
|
185
|
+
let listed = await listCameras();
|
|
186
|
+
let pair = await tryNamedPairs(listed);
|
|
187
|
+
if (pair) return result(pair, 'sbs');
|
|
188
|
+
// Labels visible but no stereo-named device: a runtime 3D camera can join the list late (after
|
|
189
|
+
// a runtime restart), so read it once more before falling back.
|
|
190
|
+
if (listed.some((d) => d.label) && !listed.some((d) => stereoLabelHint(d.label))) {
|
|
191
|
+
listed = await listCameras();
|
|
192
|
+
pair = await tryNamedPairs(listed);
|
|
193
|
+
if (pair) return result(pair, 'sbs');
|
|
194
|
+
}
|
|
195
|
+
// No labels yet (no permission). Prime it on the default camera with NO constraints: in the
|
|
196
|
+
// DisplayXR Browser that is the 3D camera (listed first), so no phone lens is opened; a facing
|
|
197
|
+
// or size constraint would let the engine pick a lens instead. Then open a pair by id.
|
|
198
|
+
if (listed.length && listed.every((d) => !d.label)) {
|
|
199
|
+
let primed = null;
|
|
200
|
+
try {
|
|
201
|
+
primed = await open(true);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
skip('default', err);
|
|
204
|
+
}
|
|
205
|
+
if (primed && isPair(primed)) return result(primed, 'sbs');
|
|
206
|
+
const named = await tryNamedPairs(await listCameras());
|
|
207
|
+
if (named) {
|
|
208
|
+
stop(primed);
|
|
209
|
+
return result(named, 'sbs');
|
|
210
|
+
}
|
|
211
|
+
// The primed default is the fallback as is (one camera start); ask it for the usual mode.
|
|
212
|
+
if (primed) mono = await withMode(primed, MONO_CONSTRAINTS);
|
|
213
|
+
}
|
|
161
214
|
}
|
|
162
215
|
|
|
163
|
-
// The default camera
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
216
|
+
// The default camera: the fallback (and, with labels already visible, also the probe base).
|
|
217
|
+
if (!mono) {
|
|
218
|
+
try {
|
|
219
|
+
mono = await open(MONO_CONSTRAINTS);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
skip('default', err);
|
|
222
|
+
}
|
|
169
223
|
}
|
|
170
224
|
if (want === 'mono' && mono) return result(mono, 'mono');
|
|
171
|
-
if (mono &&
|
|
225
|
+
if (mono && isPair(mono)) return result(mono, 'sbs'); // the default IS the pair
|
|
172
226
|
|
|
173
227
|
let devices = [];
|
|
174
228
|
try {
|
|
@@ -189,7 +243,7 @@ export async function openCamera(want = 'auto', o = {}) {
|
|
|
189
243
|
skip(d.label, err);
|
|
190
244
|
continue;
|
|
191
245
|
}
|
|
192
|
-
if (
|
|
246
|
+
if (isPair(r)) {
|
|
193
247
|
stop(mono);
|
|
194
248
|
log('camera-sbs', { label: r.label, width: r.width, height: r.height });
|
|
195
249
|
return result(r, 'sbs');
|
|
@@ -202,7 +256,7 @@ export async function openCamera(want = 'auto', o = {}) {
|
|
|
202
256
|
}
|
|
203
257
|
const code = noCameraCode(skipped);
|
|
204
258
|
throw Object.assign(
|
|
205
|
-
new Error(code === 'camera-busy' ? '
|
|
259
|
+
new Error(code === 'camera-busy' ? 'camera unavailable: in use by another app (e.g. eye tracking) or unable to deliver a picture' : code === 'permission-denied' ? 'camera access was denied' : 'no camera could be opened'),
|
|
206
260
|
{ code, skipped }
|
|
207
261
|
);
|
|
208
262
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@displayxr/inline3d",
|
|
3
|
-
"version": "1.47.
|
|
3
|
+
"version": "1.47.5",
|
|
4
4
|
"description": "Turn any HTML <canvas> into a 3D window on a spatial display, inside an ordinary web page, for the DisplayXR Browser. Dependency-free; progressive enhancement (falls back to plain 2D everywhere else).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.d.ts",
|