@octane-xplat/camera 0.14.0 → 0.16.0
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/README.md +26 -0
- package/package.json +22 -16
- package/platforms/android/java/com/octanexplat/camera/CameraPreviewView.java +9 -0
- package/platforms/android/java/com/octanexplat/camera/CameraSessionBridge.java +31 -0
- package/platforms/macos/src/CameraOwnership.swift +22 -0
- package/platforms/macos/src/PhotoCaptureLifecycle.swift +34 -0
- package/platforms/macos/src/XplatCameraHost.swift +139 -13
- package/platforms/macos/src/XplatPhotoCaptureSheet.swift +192 -0
- package/src/CameraView.android.tsrx +8 -18
- package/src/CameraView.ios.tsrx +4 -17
- package/src/CameraView.macos.tsrx +4 -17
- package/src/CameraView.web.tsrx +11 -0
- package/src/CameraView.windows.tsrx +6 -18
- package/src/camera-permission.mobile.ts +18 -4
- package/src/camera-session.windows.ts +4 -1
- package/src/host-movie.web.ts +141 -26
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- package/src/layout-child.ts +39 -18
- package/src/layout-child.windows.ts +5 -2
- package/src/preview-tap.ts +34 -0
- package/src/props.ts +8 -1
- package/src/session-backend.android.ts +42 -0
- package/src/session-backend.ios.ts +183 -17
- package/src/session-backend.linux.ts +22 -4
- package/src/session-backend.macos.ts +100 -17
- package/src/session-backend.ts +2 -0
- package/src/session-backend.web.ts +17 -0
- package/src/session-backend.windows.ts +88 -6
- package/src/session-core.ts +73 -9
- package/src/synthetic-session.ts +10 -0
- package/src/types.ts +28 -0
- package/types/props.d.ts +8 -1
- package/types/session-core.d.ts +11 -1
- package/types/synthetic-session.d.ts +4 -1
- package/types/types.d.ts +27 -0
- package/dist/linux/CameraView.web.tsrx.js +0 -337
- package/dist/linux/host-movie.web.js +0 -100
- package/dist/linux/index.js +0 -5
- package/dist/linux/layout-child.web.js +0 -29
- package/dist/linux/movie-metadata.web.js +0 -65
- package/dist/linux/octane-client-build.json +0 -8
- package/dist/linux/session-backend.linux.js +0 -479
- package/dist/linux/session-core.js +0 -368
- package/dist/linux/session.js +0 -29
- package/dist/linux/synthetic-session.js +0 -280
- package/dist/linux/types.js +0 -19
- package/dist/native/CameraView.android.js +0 -190
- package/dist/native/CameraView.ios.js +0 -154
- package/dist/native/CameraView.windows.js +0 -173
- package/dist/native/camera-permission.mobile.js +0 -45
- package/dist/native/camera-permission.windows.js +0 -43
- package/dist/native/camera-session.ios.js +0 -45
- package/dist/native/camera-session.windows.js +0 -78
- package/dist/native/cx.mobile.js +0 -9
- package/dist/native/cx.windows.js +0 -9
- package/dist/native/index.js +0 -5
- package/dist/native/layout-child.js +0 -53
- package/dist/native/layout-child.windows.js +0 -53
- package/dist/native/octane-client-build.json +0 -8
- package/dist/native/session-backend.android.js +0 -234
- package/dist/native/session-backend.ios.js +0 -682
- package/dist/native/session-backend.js +0 -55
- package/dist/native/session-backend.windows.js +0 -905
- package/dist/native/session-core.js +0 -368
- package/dist/native/session.js +0 -29
- package/dist/native/synthetic-session.js +0 -280
- package/dist/native/types.js +0 -19
- package/dist/web/CameraView.web.tsrx.js +0 -337
- package/dist/web/host-movie.web.js +0 -100
- package/dist/web/index.js +0 -5
- package/dist/web/layout-child.web.js +0 -29
- package/dist/web/movie-metadata.web.js +0 -65
- package/dist/web/movie-storage.web.js +0 -119
- package/dist/web/octane-client-build.json +0 -8
- package/dist/web/session-backend.web.js +0 -490
- package/dist/web/session-core.js +0 -368
- package/dist/web/session.js +0 -29
- package/dist/web/synthetic-session.js +0 -280
- package/dist/web/types.js +0 -19
- package/src/cx.macos.ts +0 -5
- package/src/cx.mobile.ts +0 -6
- package/src/cx.windows.ts +0 -6
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import { attachSessionPreview } from "./session.js";
|
|
2
|
-
import { applyEscapeProps, layoutChildProps } from "./layout-child.web.js";
|
|
3
|
-
import { __s, bag15, bag5, child, clone, delegateEvents, evt2e, evt2u, hookSlots, ifBlock, markDangerouslySetInnerHTMLChildren, queueRefAttach, queueRefDetach, setStyleProperty, snapshotSpread, template, useEffect, useImperativeHandle, useRef } from "octane";
|
|
4
|
-
import { bindSignalHostPropSources, enableSignalBindings, replaceRef, setAttributeIfChanged } from "octane/internal/client";
|
|
5
|
-
//#region src/CameraView.web.tsrx
|
|
6
|
-
enableSignalBindings(1, true);
|
|
7
|
-
delegateEvents(["playing"]);
|
|
8
|
-
var _t$0 = /* @__PURE__ */ template("<video autoplay=\"\" playsinline=\"\" style=\"display: block; width: 100%; height: 100%; object-fit: cover;\"></video>");
|
|
9
|
-
var _t$1 = /* @__PURE__ */ template("<div><!></div>");
|
|
10
|
-
var _hs$ = /* @__PURE__ */ hookSlots(5);
|
|
11
|
-
var __event = (__eventArg, props, readyFired) => {
|
|
12
|
-
if (props.session || readyFired.current) return;
|
|
13
|
-
readyFired.current = true;
|
|
14
|
-
props.onReady?.();
|
|
15
|
-
};
|
|
16
|
-
var _rm$1 = [
|
|
17
|
-
"r",
|
|
18
|
-
"b",
|
|
19
|
-
"a"
|
|
20
|
-
];
|
|
21
|
-
function __then$0(__props, __s, __extra) {
|
|
22
|
-
const facing = __extra[0];
|
|
23
|
-
const hostRef = __extra[1];
|
|
24
|
-
const props = __extra[2];
|
|
25
|
-
const readyFired = __extra[3];
|
|
26
|
-
const video = __extra[4];
|
|
27
|
-
let _b = __s.slots[0];
|
|
28
|
-
if (_b === void 0) {
|
|
29
|
-
let _m1, _m4;
|
|
30
|
-
const _root = clone(_t$0, void 0, "||");
|
|
31
|
-
queueRefAttach(__s, _m1 = (element) => {
|
|
32
|
-
hostRef.current = element;
|
|
33
|
-
video.current = element;
|
|
34
|
-
if (!element) return;
|
|
35
|
-
applyEscapeProps(element, props);
|
|
36
|
-
}, _root);
|
|
37
|
-
(__s.cleanups ??= []).push(() => _b && queueRefDetach(_b.b, _b.a));
|
|
38
|
-
_m4 = evt2e(_root, "$oplaying", __event, props, readyFired);
|
|
39
|
-
_b = bag5(__s, _root, _root, _m1, void 0, __s, _m4);
|
|
40
|
-
__s.refFields = _rm$1;
|
|
41
|
-
} else {
|
|
42
|
-
{
|
|
43
|
-
const _r = (element) => {
|
|
44
|
-
hostRef.current = element;
|
|
45
|
-
video.current = element;
|
|
46
|
-
if (!element) return;
|
|
47
|
-
applyEscapeProps(element, props);
|
|
48
|
-
};
|
|
49
|
-
if (_r !== _b.b) _b.b = replaceRef(__s, _b.b, _r, _b.a);
|
|
50
|
-
}
|
|
51
|
-
evt2u(_b.e, __event, props, readyFired);
|
|
52
|
-
}
|
|
53
|
-
_b.c = setAttributeIfChanged(true, _b.c, _b.a, "muted");
|
|
54
|
-
{
|
|
55
|
-
const _v = facing === "front" ? "scaleX(-1)" : void 0;
|
|
56
|
-
if (_b.d !== _v) {
|
|
57
|
-
setStyleProperty(_b.a, "transform", _v, "display: block; width: 100%; height: 100%; object-fit: cover;", _b.d);
|
|
58
|
-
_b.d = _v;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
var _rm$3 = [
|
|
63
|
-
"s",
|
|
64
|
-
"n",
|
|
65
|
-
"m"
|
|
66
|
-
];
|
|
67
|
-
var CameraView = /* @__PURE__ */ __s(function CameraView(props, __s, __extra) {
|
|
68
|
-
const hostRef = useRef(null, _hs$ + 0);
|
|
69
|
-
const video = useRef(null, _hs$ + 1);
|
|
70
|
-
const facing = props.facing ?? "back";
|
|
71
|
-
const active = props.active !== false;
|
|
72
|
-
const readyFired = useRef(false, _hs$ + 2);
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
const element = video.current;
|
|
75
|
-
if (!element || !active) return;
|
|
76
|
-
if (props.session) {
|
|
77
|
-
if (props.facing !== void 0) {
|
|
78
|
-
props.onError?.({ message: "camera session conflicts with facing" });
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
return attachSessionPreview(props.session, element, {
|
|
83
|
-
onReady: props.onReady,
|
|
84
|
-
onError: (issue) => props.onError?.({ message: issue.message })
|
|
85
|
-
});
|
|
86
|
-
} catch (error) {
|
|
87
|
-
props.onError?.({ message: error?.message ?? "camera session error" });
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const mediaDevices = navigator.mediaDevices;
|
|
92
|
-
if (!mediaDevices?.getUserMedia) {
|
|
93
|
-
props.onError?.({ message: "camera unsupported" });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
let stream = null;
|
|
97
|
-
let disposed = false;
|
|
98
|
-
readyFired.current = false;
|
|
99
|
-
mediaDevices.getUserMedia({
|
|
100
|
-
video: { facingMode: facing === "front" ? "user" : "environment" },
|
|
101
|
-
audio: false
|
|
102
|
-
}).then((nextStream) => {
|
|
103
|
-
if (disposed) {
|
|
104
|
-
for (const track of nextStream.getTracks()) track.stop();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
stream = nextStream;
|
|
108
|
-
element.srcObject = nextStream;
|
|
109
|
-
element.play()?.catch(() => {});
|
|
110
|
-
}).catch((error) => {
|
|
111
|
-
props.onError?.({ message: error?.name === "NotAllowedError" ? "camera permission denied" : String(error?.name ?? error ?? "camera error") });
|
|
112
|
-
});
|
|
113
|
-
return () => {
|
|
114
|
-
disposed = true;
|
|
115
|
-
element.srcObject = null;
|
|
116
|
-
if (stream) for (const track of stream.getTracks()) track.stop();
|
|
117
|
-
};
|
|
118
|
-
}, [
|
|
119
|
-
active,
|
|
120
|
-
facing,
|
|
121
|
-
props.session
|
|
122
|
-
], _hs$ + 3);
|
|
123
|
-
useImperativeHandle(props.ref, () => ({ native: hostRef.current }), void 0, _hs$ + 4);
|
|
124
|
-
let _b = __s.slots[0];
|
|
125
|
-
if (_b === void 0) {
|
|
126
|
-
let _m0, _m1, _m2, _m3, _m4, _m5, _m6, _m7, _m8, _m9, _m10, _m11, _m13, _m14;
|
|
127
|
-
const _root = clone(_t$1);
|
|
128
|
-
_m0 = props.id;
|
|
129
|
-
_m1 = [
|
|
130
|
-
"vx-layers",
|
|
131
|
-
"vx-cameraview",
|
|
132
|
-
props.className
|
|
133
|
-
];
|
|
134
|
-
_m2 = snapshotSpread(layoutChildProps(props, {
|
|
135
|
-
position: "relative",
|
|
136
|
-
overflow: "hidden",
|
|
137
|
-
backgroundColor: "#000",
|
|
138
|
-
...props.style
|
|
139
|
-
}));
|
|
140
|
-
_m3 = props.accessibilityRole;
|
|
141
|
-
_m4 = props.accessibilityLabel;
|
|
142
|
-
_m5 = props.accessible === false ? true : void 0;
|
|
143
|
-
_m6 = props.accessibilityHint;
|
|
144
|
-
_m7 = props.accessibilityValue;
|
|
145
|
-
_m8 = props.accessibilityState?.disabled;
|
|
146
|
-
_m9 = props.accessibilityState?.selected;
|
|
147
|
-
_m10 = props.accessibilityState?.checked;
|
|
148
|
-
_m11 = props.accessibilityLiveRegion === "none" ? "off" : props.accessibilityLiveRegion;
|
|
149
|
-
_m13 = bindSignalHostPropSources(__s, void 0, _root, [
|
|
150
|
-
[
|
|
151
|
-
false,
|
|
152
|
-
"id",
|
|
153
|
-
_m0
|
|
154
|
-
],
|
|
155
|
-
[
|
|
156
|
-
false,
|
|
157
|
-
"className",
|
|
158
|
-
_m1
|
|
159
|
-
],
|
|
160
|
-
[true, _m2],
|
|
161
|
-
[
|
|
162
|
-
false,
|
|
163
|
-
"role",
|
|
164
|
-
_m3
|
|
165
|
-
],
|
|
166
|
-
[
|
|
167
|
-
false,
|
|
168
|
-
"aria-label",
|
|
169
|
-
_m4
|
|
170
|
-
],
|
|
171
|
-
[
|
|
172
|
-
false,
|
|
173
|
-
"aria-hidden",
|
|
174
|
-
_m5
|
|
175
|
-
],
|
|
176
|
-
[
|
|
177
|
-
false,
|
|
178
|
-
"aria-description",
|
|
179
|
-
_m6
|
|
180
|
-
],
|
|
181
|
-
[
|
|
182
|
-
false,
|
|
183
|
-
"aria-valuetext",
|
|
184
|
-
_m7
|
|
185
|
-
],
|
|
186
|
-
[
|
|
187
|
-
false,
|
|
188
|
-
"aria-disabled",
|
|
189
|
-
_m8
|
|
190
|
-
],
|
|
191
|
-
[
|
|
192
|
-
false,
|
|
193
|
-
"aria-selected",
|
|
194
|
-
_m9
|
|
195
|
-
],
|
|
196
|
-
[
|
|
197
|
-
false,
|
|
198
|
-
"aria-checked",
|
|
199
|
-
_m10
|
|
200
|
-
],
|
|
201
|
-
[
|
|
202
|
-
false,
|
|
203
|
-
"aria-live",
|
|
204
|
-
_m11
|
|
205
|
-
]
|
|
206
|
-
], "b:d3c40afd", true);
|
|
207
|
-
markDangerouslySetInnerHTMLChildren(_root);
|
|
208
|
-
_m14 = child(_root);
|
|
209
|
-
_b = bag15(__s, _root, _m0, _m1, _m2, _m3, _m4, _m5, _m6, _m7, _m8, _m9, _m10, _m11, _root, _m13, _m14);
|
|
210
|
-
__s.refFields = _rm$3;
|
|
211
|
-
} else {
|
|
212
|
-
{
|
|
213
|
-
const _v = props.id;
|
|
214
|
-
if (_b.a !== _v) _b.a = _v;
|
|
215
|
-
}
|
|
216
|
-
{
|
|
217
|
-
const _v = [
|
|
218
|
-
"vx-layers",
|
|
219
|
-
"vx-cameraview",
|
|
220
|
-
props.className
|
|
221
|
-
];
|
|
222
|
-
if (_b.b !== _v) _b.b = _v;
|
|
223
|
-
}
|
|
224
|
-
_b.c = snapshotSpread(layoutChildProps(props, {
|
|
225
|
-
position: "relative",
|
|
226
|
-
overflow: "hidden",
|
|
227
|
-
backgroundColor: "#000",
|
|
228
|
-
...props.style
|
|
229
|
-
}));
|
|
230
|
-
{
|
|
231
|
-
const _v = props.accessibilityRole;
|
|
232
|
-
if (_b.d !== _v) _b.d = _v;
|
|
233
|
-
}
|
|
234
|
-
{
|
|
235
|
-
const _v = props.accessibilityLabel;
|
|
236
|
-
if (_b.e !== _v) _b.e = _v;
|
|
237
|
-
}
|
|
238
|
-
{
|
|
239
|
-
const _v = props.accessible === false ? true : void 0;
|
|
240
|
-
if (_b.f !== _v) _b.f = _v;
|
|
241
|
-
}
|
|
242
|
-
{
|
|
243
|
-
const _v = props.accessibilityHint;
|
|
244
|
-
if (_b.g !== _v) _b.g = _v;
|
|
245
|
-
}
|
|
246
|
-
{
|
|
247
|
-
const _v = props.accessibilityValue;
|
|
248
|
-
if (_b.h !== _v) _b.h = _v;
|
|
249
|
-
}
|
|
250
|
-
{
|
|
251
|
-
const _v = props.accessibilityState?.disabled;
|
|
252
|
-
if (_b.i !== _v) _b.i = _v;
|
|
253
|
-
}
|
|
254
|
-
{
|
|
255
|
-
const _v = props.accessibilityState?.selected;
|
|
256
|
-
if (_b.j !== _v) _b.j = _v;
|
|
257
|
-
}
|
|
258
|
-
{
|
|
259
|
-
const _v = props.accessibilityState?.checked;
|
|
260
|
-
if (_b.k !== _v) _b.k = _v;
|
|
261
|
-
}
|
|
262
|
-
{
|
|
263
|
-
const _v = props.accessibilityLiveRegion === "none" ? "off" : props.accessibilityLiveRegion;
|
|
264
|
-
if (_b.l !== _v) _b.l = _v;
|
|
265
|
-
}
|
|
266
|
-
_b.n = bindSignalHostPropSources(__s, _b.n, _b.m, [
|
|
267
|
-
[
|
|
268
|
-
false,
|
|
269
|
-
"id",
|
|
270
|
-
_b.a
|
|
271
|
-
],
|
|
272
|
-
[
|
|
273
|
-
false,
|
|
274
|
-
"className",
|
|
275
|
-
_b.b
|
|
276
|
-
],
|
|
277
|
-
[true, _b.c],
|
|
278
|
-
[
|
|
279
|
-
false,
|
|
280
|
-
"role",
|
|
281
|
-
_b.d
|
|
282
|
-
],
|
|
283
|
-
[
|
|
284
|
-
false,
|
|
285
|
-
"aria-label",
|
|
286
|
-
_b.e
|
|
287
|
-
],
|
|
288
|
-
[
|
|
289
|
-
false,
|
|
290
|
-
"aria-hidden",
|
|
291
|
-
_b.f
|
|
292
|
-
],
|
|
293
|
-
[
|
|
294
|
-
false,
|
|
295
|
-
"aria-description",
|
|
296
|
-
_b.g
|
|
297
|
-
],
|
|
298
|
-
[
|
|
299
|
-
false,
|
|
300
|
-
"aria-valuetext",
|
|
301
|
-
_b.h
|
|
302
|
-
],
|
|
303
|
-
[
|
|
304
|
-
false,
|
|
305
|
-
"aria-disabled",
|
|
306
|
-
_b.i
|
|
307
|
-
],
|
|
308
|
-
[
|
|
309
|
-
false,
|
|
310
|
-
"aria-selected",
|
|
311
|
-
_b.j
|
|
312
|
-
],
|
|
313
|
-
[
|
|
314
|
-
false,
|
|
315
|
-
"aria-checked",
|
|
316
|
-
_b.k
|
|
317
|
-
],
|
|
318
|
-
[
|
|
319
|
-
false,
|
|
320
|
-
"aria-live",
|
|
321
|
-
_b.l
|
|
322
|
-
]
|
|
323
|
-
], "b:d3c40afd", true);
|
|
324
|
-
}
|
|
325
|
-
{
|
|
326
|
-
const __ifCondition = active;
|
|
327
|
-
ifBlock(__s, 1, _b.m, __ifCondition, __then$0, null, _b.o, __ifCondition ? [
|
|
328
|
-
facing,
|
|
329
|
-
hostRef,
|
|
330
|
-
props,
|
|
331
|
-
readyFired,
|
|
332
|
-
video
|
|
333
|
-
] : void 0, 1);
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
//#endregion
|
|
337
|
-
export { CameraView };
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { CameraCaptureError } from "./types.js";
|
|
2
|
-
import { createHostClient } from "@octane-xplat/platform/host";
|
|
3
|
-
import { createWebKitTransport } from "@octane-xplat/platform/host/web";
|
|
4
|
-
//#region src/host-movie.web.ts
|
|
5
|
-
var requiredMethods = [
|
|
6
|
-
"permissionStatus",
|
|
7
|
-
"requestPermission",
|
|
8
|
-
"movieDirectory",
|
|
9
|
-
"reserveMoviePath",
|
|
10
|
-
"writeMovieFile",
|
|
11
|
-
"movieFileInfo",
|
|
12
|
-
"readMovieFile",
|
|
13
|
-
"deleteMovieFile"
|
|
14
|
-
];
|
|
15
|
-
var hostProbe;
|
|
16
|
-
/** The camera-capable desktop host client, or null outside a qualified host. */
|
|
17
|
-
function cameraHost() {
|
|
18
|
-
hostProbe ??= (async () => {
|
|
19
|
-
try {
|
|
20
|
-
const transport = createWebKitTransport();
|
|
21
|
-
if (!transport) return null;
|
|
22
|
-
const client = createHostClient(transport);
|
|
23
|
-
const methods = (await client.capabilities().catch(() => null))?.camera;
|
|
24
|
-
if (Array.isArray(methods) && requiredMethods.every((name) => methods.includes(name))) return client;
|
|
25
|
-
client.dispose();
|
|
26
|
-
return null;
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
})();
|
|
31
|
-
return hostProbe;
|
|
32
|
-
}
|
|
33
|
-
var error = (kind, message) => new CameraCaptureError({
|
|
34
|
-
kind,
|
|
35
|
-
operation: "storage",
|
|
36
|
-
message
|
|
37
|
-
});
|
|
38
|
-
var blobToBase64 = async (bytes) => {
|
|
39
|
-
const data = new Uint8Array(await bytes.arrayBuffer());
|
|
40
|
-
let binary = "";
|
|
41
|
-
const chunk = 32768;
|
|
42
|
-
for (let offset = 0; offset < data.length; offset += chunk) binary += String.fromCharCode(...data.subarray(offset, offset + chunk));
|
|
43
|
-
return btoa(binary);
|
|
44
|
-
};
|
|
45
|
-
var base64ToBlob = (base64, type) => {
|
|
46
|
-
const binary = atob(base64);
|
|
47
|
-
const data = new Uint8Array(binary.length);
|
|
48
|
-
for (let index = 0; index < binary.length; index++) data[index] = binary.charCodeAt(index);
|
|
49
|
-
return new Blob([data], { type });
|
|
50
|
-
};
|
|
51
|
-
var mimeForFileUrl = (fileUrl) => fileUrl.endsWith(".webm") ? "video/webm" : fileUrl.endsWith(".mov") ? "video/quicktime" : "video/mp4";
|
|
52
|
-
/** Reserve a host-side movie file. `destinationFileUrl` is validated
|
|
53
|
-
* host-side: it must be a new file inside the app-private storage root. */
|
|
54
|
-
var reserveHostMovie = async (host, options) => {
|
|
55
|
-
const { reservation } = await host.call("camera", "reserveMoviePath", {
|
|
56
|
-
fileUrl: options.destinationFileUrl,
|
|
57
|
-
container: options.container
|
|
58
|
-
});
|
|
59
|
-
if (!reservation?.fileUrl) throw error("destinationUnavailable", options.destinationFileUrl ? "destinationFileUrl must be a new file inside the app-private recordings directory" : "The host could not allocate a movie recording file");
|
|
60
|
-
return reservation;
|
|
61
|
-
};
|
|
62
|
-
/** Commit finalized movie bytes to host-side app-private storage. */
|
|
63
|
-
var commitHostMovie = async (host, fileUrl, bytes) => {
|
|
64
|
-
const base64 = await blobToBase64(bytes);
|
|
65
|
-
const { file } = await host.call("camera", "writeMovieFile", {
|
|
66
|
-
fileUrl,
|
|
67
|
-
base64
|
|
68
|
-
});
|
|
69
|
-
if (!file?.uri) throw error("storageFailed", "The host could not write the recorded movie");
|
|
70
|
-
return {
|
|
71
|
-
kind: "nativeFile",
|
|
72
|
-
resourceId: file.uri,
|
|
73
|
-
fileUrl: file.uri
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
/** Drop an uncommitted host reservation after a failed attempt. */
|
|
77
|
-
var removeHostReservation = async (host, fileUrl) => {
|
|
78
|
-
await host.call("camera", "deleteMovieFile", { fileUrl }).catch(() => {});
|
|
79
|
-
};
|
|
80
|
-
/** Reopen a host-stored movie as a temporary playback URL. The durable
|
|
81
|
-
* reference remains `fileUrl`; the returned url lives until `release()`. */
|
|
82
|
-
var openHostMovie = async (host, output) => {
|
|
83
|
-
if (output.kind !== "nativeFile" || !output.fileUrl) throw new CameraCaptureError({
|
|
84
|
-
kind: "invalidArgument",
|
|
85
|
-
operation: "openOutput",
|
|
86
|
-
message: "Only file-backed output can be opened through the desktop host"
|
|
87
|
-
});
|
|
88
|
-
const { info } = await host.call("camera", "movieFileInfo", { fileUrl: output.fileUrl });
|
|
89
|
-
if (!info?.exists) throw error("unavailable", `Movie file is missing at ${output.fileUrl}`);
|
|
90
|
-
const { base64 } = await host.call("camera", "readMovieFile", { fileUrl: output.fileUrl });
|
|
91
|
-
if (!base64) throw error("unavailable", `Movie file could not be read at ${output.fileUrl}`);
|
|
92
|
-
const url = URL.createObjectURL(base64ToBlob(base64, mimeForFileUrl(output.fileUrl)));
|
|
93
|
-
return {
|
|
94
|
-
url,
|
|
95
|
-
fileUrl: output.fileUrl,
|
|
96
|
-
release: () => URL.revokeObjectURL(url)
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
//#endregion
|
|
100
|
-
export { cameraHost, commitHostMovie, openHostMovie, removeHostReservation, reserveHostMovie };
|
package/dist/web/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { CameraCaptureError } from "./types.js";
|
|
2
|
-
import { createCameraSession } from "./session.js";
|
|
3
|
-
import { CameraView } from "./CameraView.web.tsrx.js";
|
|
4
|
-
import { createMemoryCameraStorage, createSyntheticCameraSession } from "./synthetic-session.js";
|
|
5
|
-
export { CameraCaptureError, CameraView, createCameraSession, createMemoryCameraStorage, createSyntheticCameraSession };
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
//#region src/layout-child.web.ts
|
|
2
|
-
function layoutChildProps(props, baseStyle = props.style) {
|
|
3
|
-
const style = { ...baseStyle };
|
|
4
|
-
if (props.row !== void 0) style.gridRow = `${props.row + 1} / span ${props.rowSpan ?? 1}`;
|
|
5
|
-
else if (props.rowSpan !== void 0) style.gridRow = `span ${props.rowSpan}`;
|
|
6
|
-
if (props.col !== void 0) style.gridColumn = `${props.col + 1} / span ${props.colSpan ?? 1}`;
|
|
7
|
-
else if (props.colSpan !== void 0) style.gridColumn = `span ${props.colSpan}`;
|
|
8
|
-
if (props.left !== void 0) style.left = props.left;
|
|
9
|
-
if (props.top !== void 0) style.top = props.top;
|
|
10
|
-
if (props.flexGrow !== void 0) style.flexGrow = props.flexGrow;
|
|
11
|
-
if (props.flexShrink !== void 0) style.flexShrink = props.flexShrink;
|
|
12
|
-
if (props.alignSelf !== void 0) style.alignSelf = props.alignSelf;
|
|
13
|
-
if (props.order !== void 0) style.order = props.order;
|
|
14
|
-
return { style };
|
|
15
|
-
}
|
|
16
|
-
function applyEscapeProps(element, props) {
|
|
17
|
-
if (!element || !props.web) return;
|
|
18
|
-
for (const [key, value] of Object.entries(props.web)) if (key.includes("-")) {
|
|
19
|
-
if (value == null || value === false) element.removeAttribute(key);
|
|
20
|
-
else element.setAttribute(key, value === true ? "" : String(value));
|
|
21
|
-
} else if (key in element) try {
|
|
22
|
-
element[key] = value;
|
|
23
|
-
} catch {
|
|
24
|
-
element.setAttribute(key, String(value));
|
|
25
|
-
}
|
|
26
|
-
else if (value != null) element.setAttribute(key, String(value));
|
|
27
|
-
}
|
|
28
|
-
//#endregion
|
|
29
|
-
export { applyEscapeProps, layoutChildProps };
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { CameraCaptureError } from "./types.js";
|
|
2
|
-
import { BlobSource, Input, MP4, WEBM } from "mediabunny";
|
|
3
|
-
//#region src/movie-metadata.web.ts
|
|
4
|
-
/** Read packet end timestamps and track metadata, never the wall-clock stop time. */
|
|
5
|
-
async function inspectMovie(bytes, audioRequested) {
|
|
6
|
-
if (!bytes.size) throw new CameraCaptureError({
|
|
7
|
-
kind: "noMedia",
|
|
8
|
-
operation: "finalize",
|
|
9
|
-
message: "The recorder produced no movie bytes"
|
|
10
|
-
});
|
|
11
|
-
const input = new Input({
|
|
12
|
-
source: new BlobSource(bytes),
|
|
13
|
-
formats: [WEBM, MP4]
|
|
14
|
-
});
|
|
15
|
-
const read = async () => {
|
|
16
|
-
const video = await input.getPrimaryVideoTrack();
|
|
17
|
-
const audio = await input.getPrimaryAudioTrack();
|
|
18
|
-
if (!video) throw new CameraCaptureError({
|
|
19
|
-
kind: "noMedia",
|
|
20
|
-
operation: "finalize",
|
|
21
|
-
message: "The movie contains no video track"
|
|
22
|
-
});
|
|
23
|
-
const videoDuration = await video.computeDuration();
|
|
24
|
-
const hasAudio = audio !== null && await audio.computeDuration() > 0;
|
|
25
|
-
const durationMs = await input.computeDuration() * 1e3;
|
|
26
|
-
const width = await video.getDisplayWidth();
|
|
27
|
-
const height = await video.getDisplayHeight();
|
|
28
|
-
if (!Number.isFinite(durationMs) || durationMs <= 0 || videoDuration <= 0 || width <= 0 || height <= 0) throw new CameraCaptureError({
|
|
29
|
-
kind: "noMedia",
|
|
30
|
-
operation: "finalize",
|
|
31
|
-
message: "The movie has no usable video media"
|
|
32
|
-
});
|
|
33
|
-
if (audioRequested && !hasAudio) throw new CameraCaptureError({
|
|
34
|
-
kind: "captureFailed",
|
|
35
|
-
operation: "finalize",
|
|
36
|
-
message: "Requested audio is absent from the finalized movie"
|
|
37
|
-
});
|
|
38
|
-
return {
|
|
39
|
-
durationMs,
|
|
40
|
-
width,
|
|
41
|
-
height,
|
|
42
|
-
hasAudio,
|
|
43
|
-
mimeType: await input.getMimeType(),
|
|
44
|
-
orientation: "unspecified"
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
let timer;
|
|
48
|
-
try {
|
|
49
|
-
return await Promise.race([read(), new Promise((_resolve, reject) => {
|
|
50
|
-
timer = setTimeout(() => {
|
|
51
|
-
input.dispose();
|
|
52
|
-
reject(new CameraCaptureError({
|
|
53
|
-
kind: "finalizationFailed",
|
|
54
|
-
operation: "metadata",
|
|
55
|
-
message: "Movie metadata verification timed out"
|
|
56
|
-
}));
|
|
57
|
-
}, 3e4);
|
|
58
|
-
})]);
|
|
59
|
-
} finally {
|
|
60
|
-
clearTimeout(timer);
|
|
61
|
-
input.dispose();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
//#endregion
|
|
65
|
-
export { inspectMovie };
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { CameraCaptureError } from "./types.js";
|
|
2
|
-
//#region src/movie-storage.web.ts
|
|
3
|
-
var databaseName = "octane-xplat-camera";
|
|
4
|
-
var storeName = "movies";
|
|
5
|
-
var storageError = (cause) => new CameraCaptureError({
|
|
6
|
-
kind: cause instanceof DOMException && cause.name === "QuotaExceededError" ? "insufficientStorage" : "storageFailed",
|
|
7
|
-
operation: "storage",
|
|
8
|
-
message: "The movie could not be committed to origin-local storage",
|
|
9
|
-
cause
|
|
10
|
-
});
|
|
11
|
-
/** Each operation closes its database; playback survives session and page lifetimes. */
|
|
12
|
-
async function transact(mode, operation) {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
let database;
|
|
15
|
-
let transaction;
|
|
16
|
-
let result;
|
|
17
|
-
let done = false;
|
|
18
|
-
const finish = (cause) => {
|
|
19
|
-
if (done) return;
|
|
20
|
-
done = true;
|
|
21
|
-
clearTimeout(timer);
|
|
22
|
-
database?.close();
|
|
23
|
-
if (cause) reject(storageError(cause));
|
|
24
|
-
else resolve(result);
|
|
25
|
-
};
|
|
26
|
-
const timer = setTimeout(() => {
|
|
27
|
-
transaction?.abort();
|
|
28
|
-
finish(/* @__PURE__ */ new Error("Storage transaction timed out"));
|
|
29
|
-
}, 15e3);
|
|
30
|
-
const request = indexedDB.open(databaseName, 1);
|
|
31
|
-
request.onupgradeneeded = () => request.result.createObjectStore(storeName, { keyPath: "id" });
|
|
32
|
-
request.onerror = () => finish(request.error);
|
|
33
|
-
request.onblocked = () => finish(/* @__PURE__ */ new Error("Storage upgrade is blocked by another page"));
|
|
34
|
-
request.onsuccess = () => {
|
|
35
|
-
database = request.result;
|
|
36
|
-
if (done) {
|
|
37
|
-
database.close();
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
transaction = database.transaction(storeName, mode, { durability: "strict" });
|
|
42
|
-
transaction.oncomplete = () => finish();
|
|
43
|
-
transaction.onabort = () => finish(transaction?.error ?? /* @__PURE__ */ new Error("Storage transaction aborted"));
|
|
44
|
-
transaction.onerror = () => finish(transaction?.error ?? /* @__PURE__ */ new Error("Storage transaction failed"));
|
|
45
|
-
operation(transaction.objectStore(storeName), (value) => {
|
|
46
|
-
result = value;
|
|
47
|
-
});
|
|
48
|
-
} catch (cause) {
|
|
49
|
-
finish(cause);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
async function reserveMovie() {
|
|
55
|
-
const estimate = await navigator.storage.estimate();
|
|
56
|
-
if (estimate.quota !== void 0 && estimate.usage !== void 0 && estimate.quota - estimate.usage < 1048576) throw new CameraCaptureError({
|
|
57
|
-
kind: "insufficientStorage",
|
|
58
|
-
operation: "reserve",
|
|
59
|
-
message: "Origin storage has less than 1 MiB free"
|
|
60
|
-
});
|
|
61
|
-
const id = crypto.randomUUID();
|
|
62
|
-
await transact("readwrite", (store) => {
|
|
63
|
-
store.add({ id });
|
|
64
|
-
});
|
|
65
|
-
return id;
|
|
66
|
-
}
|
|
67
|
-
async function commitMovie(id, bytes, clip) {
|
|
68
|
-
if (!await navigator.storage.persisted()) throw new CameraCaptureError({
|
|
69
|
-
kind: "destinationUnavailable",
|
|
70
|
-
operation: "storage",
|
|
71
|
-
message: "Persistent origin storage is no longer granted"
|
|
72
|
-
});
|
|
73
|
-
await transact("readwrite", (store) => {
|
|
74
|
-
store.put({
|
|
75
|
-
id,
|
|
76
|
-
bytes,
|
|
77
|
-
clip
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
const stored = await readMovie(id);
|
|
81
|
-
if (!stored.bytes || stored.bytes.size !== bytes.size || !stored.clip) throw storageError(/* @__PURE__ */ new Error("Committed movie cannot be reopened"));
|
|
82
|
-
}
|
|
83
|
-
async function removeReservation(id) {
|
|
84
|
-
await transact("readwrite", (store) => {
|
|
85
|
-
store.delete(id);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
async function readMovie(id) {
|
|
89
|
-
return transact("readonly", (store, result) => {
|
|
90
|
-
const request = store.get(id);
|
|
91
|
-
request.onsuccess = () => result(request.result ?? { id });
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
async function openStoredMovie(output) {
|
|
95
|
-
if (output.kind !== "browserStorage" || output.synthetic) throw new CameraCaptureError({
|
|
96
|
-
kind: "invalidArgument",
|
|
97
|
-
operation: "openOutput",
|
|
98
|
-
message: "Expected an origin-local movie reference"
|
|
99
|
-
});
|
|
100
|
-
const stored = await readMovie(output.resourceId);
|
|
101
|
-
if (!stored.bytes || !stored.clip) throw new CameraCaptureError({
|
|
102
|
-
kind: "destinationUnavailable",
|
|
103
|
-
operation: "openOutput",
|
|
104
|
-
message: "Movie is missing or site data was cleared"
|
|
105
|
-
});
|
|
106
|
-
const url = URL.createObjectURL(stored.bytes);
|
|
107
|
-
let released = false;
|
|
108
|
-
return {
|
|
109
|
-
url,
|
|
110
|
-
release: () => {
|
|
111
|
-
if (!released) {
|
|
112
|
-
released = true;
|
|
113
|
-
URL.revokeObjectURL(url);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
export { commitMovie, openStoredMovie, removeReservation, reserveMovie };
|