@flighthq/webcam 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e
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 +2 -2
- package/dist/contract.d.ts +1 -2
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -2
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mediaFileCapture.d.ts +13 -0
- package/dist/mediaFileCapture.d.ts.map +1 -0
- package/dist/{webcam.js → mediaFileCapture.js} +38 -60
- package/dist/mediaFileCapture.js.map +1 -0
- package/package.json +3 -3
- package/src/mediaFileCapture.test.ts +207 -0
- package/dist/webcam.d.ts +0 -14
- package/dist/webcam.d.ts.map +0 -1
- package/dist/webcam.js.map +0 -1
- package/dist/webcamStream.d.ts +0 -12
- package/dist/webcamStream.d.ts.map +0 -1
- package/dist/webcamStream.js +0 -34
- package/dist/webcamStream.js.map +0 -1
- package/src/webcam.test.ts +0 -184
- package/src/webcamStream.test.ts +0 -86
package/README.md
CHANGED
|
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/webcam`. The `@fligh
|
|
|
13
13
|
This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
|
|
14
14
|
|
|
15
15
|
- [Flight project](https://github.com/flighthq/flight)
|
|
16
|
-
- [Source for @flighthq/webcam@0.5.
|
|
17
|
-
- [License](https://github.com/flighthq/flight/blob/
|
|
16
|
+
- [Source for @flighthq/webcam@0.5.1-edge.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/webcam)
|
|
17
|
+
- [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
|
package/dist/contract.d.ts
CHANGED
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
package/dist/contract.js
CHANGED
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { explainMediaFileCaptureBackend, recordMediaFileCaptureVideo, selectMediaFileCaptureImage, takeMediaFileCapturePhoto, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { explainMediaFileCaptureBackend, recordMediaFileCaptureVideo, selectMediaFileCaptureImage, takeMediaFileCapturePhoto, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BackendExplanation } from '@flighthq/types/contract';
|
|
2
|
+
import type { MediaFileCaptureBackend, MediaFileCaptureOptions, MediaFileCapturePhoto, MediaFileCaptureVideo } from '@flighthq/types/contract';
|
|
3
|
+
export declare function createWebMediaFileCaptureBackend(): MediaFileCaptureBackend;
|
|
4
|
+
export declare function explainMediaFileCaptureBackend(): BackendExplanation;
|
|
5
|
+
export declare function getMediaFileCaptureBackend(): MediaFileCaptureBackend;
|
|
6
|
+
export declare function installMediaFileCaptureHostBackend(backend: MediaFileCaptureBackend): void;
|
|
7
|
+
export declare function observeMediaFileCaptureHostResult(operation: string, succeeded: boolean): void;
|
|
8
|
+
export declare function recordMediaFileCaptureVideo(options?: Readonly<MediaFileCaptureOptions>): Promise<MediaFileCaptureVideo | null>;
|
|
9
|
+
export declare function resetMediaFileCaptureBackendForTest(): void;
|
|
10
|
+
export declare function selectMediaFileCaptureImage(options?: Readonly<MediaFileCaptureOptions>): Promise<MediaFileCapturePhoto | null>;
|
|
11
|
+
export declare function setMediaFileCaptureBackend(backend: MediaFileCaptureBackend | null): void;
|
|
12
|
+
export declare function takeMediaFileCapturePhoto(options?: Readonly<MediaFileCaptureOptions>): Promise<MediaFileCapturePhoto | null>;
|
|
13
|
+
//# sourceMappingURL=mediaFileCapture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaFileCapture.d.ts","sourceRoot":"","sources":["../src/mediaFileCapture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,gCAAgC,IAAI,uBAAuB,CA0F1E;AAED,wBAAgB,8BAA8B,IAAI,kBAAkB,CAanE;AAED,wBAAgB,0BAA0B,IAAI,uBAAuB,CAEpE;AAED,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAMzF;AAED,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAK7F;AAGD,wBAAgB,2BAA2B,CACzC,OAAO,CAAC,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAC1C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAEvC;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAK1D;AAGD,wBAAgB,2BAA2B,CACzC,OAAO,CAAC,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAC1C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAEvC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI,CAExF;AAGD,wBAAgB,yBAAyB,CACvC,OAAO,CAAC,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAC1C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAEvC"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Builds the default web backend over a transient <input type="file">. capture resolves to null when
|
|
2
2
|
// the document is absent (jsdom), the user cancels, or the file cannot be read — capture is not
|
|
3
3
|
// guaranteed. Real pixel dimensions are not decoded; width/height resolve to 0.
|
|
4
|
-
export function
|
|
4
|
+
export function createWebMediaFileCaptureBackend() {
|
|
5
5
|
return {
|
|
6
6
|
capture(options) {
|
|
7
7
|
return new Promise((resolve) => {
|
|
8
8
|
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
|
|
9
|
-
|
|
9
|
+
observeMediaFileCaptureHostResult('capture', false);
|
|
10
10
|
resolve(null);
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
@@ -16,32 +16,35 @@ export function createWebWebcamBackend() {
|
|
|
16
16
|
input.accept = 'image/*';
|
|
17
17
|
if (options.source === 'camera')
|
|
18
18
|
input.capture = 'environment';
|
|
19
|
+
const settle = (value) => {
|
|
20
|
+
input.onchange = null;
|
|
21
|
+
input.oncancel = null;
|
|
22
|
+
observeMediaFileCaptureHostResult('capture', value !== null);
|
|
23
|
+
resolve(value);
|
|
24
|
+
};
|
|
19
25
|
input.onchange = () => {
|
|
20
26
|
const file = input.files?.[0] ?? null;
|
|
21
27
|
if (file === null) {
|
|
22
|
-
|
|
28
|
+
settle(null);
|
|
23
29
|
return;
|
|
24
30
|
}
|
|
25
31
|
const reader = new FileReader();
|
|
26
32
|
reader.onload = () => {
|
|
27
|
-
|
|
28
|
-
resolve({
|
|
33
|
+
settle({
|
|
29
34
|
dataUrl: typeof reader.result === 'string' ? reader.result : '',
|
|
30
35
|
width: 0,
|
|
31
36
|
height: 0,
|
|
32
37
|
format: file.type,
|
|
33
38
|
});
|
|
34
39
|
};
|
|
35
|
-
reader.onerror = () =>
|
|
36
|
-
observeWebcamHostResult('capture', false);
|
|
37
|
-
resolve(null);
|
|
38
|
-
};
|
|
40
|
+
reader.onerror = () => settle(null);
|
|
39
41
|
reader.readAsDataURL(file);
|
|
40
42
|
};
|
|
43
|
+
input.oncancel = () => settle(null);
|
|
41
44
|
input.click();
|
|
42
45
|
}
|
|
43
46
|
catch {
|
|
44
|
-
|
|
47
|
+
observeMediaFileCaptureHostResult('capture', false);
|
|
45
48
|
resolve(null);
|
|
46
49
|
}
|
|
47
50
|
});
|
|
@@ -49,7 +52,7 @@ export function createWebWebcamBackend() {
|
|
|
49
52
|
captureVideo(options) {
|
|
50
53
|
return new Promise((resolve) => {
|
|
51
54
|
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
|
|
52
|
-
|
|
55
|
+
observeMediaFileCaptureHostResult('captureVideo', false);
|
|
53
56
|
resolve(null);
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
@@ -59,59 +62,41 @@ export function createWebWebcamBackend() {
|
|
|
59
62
|
input.accept = 'video/*';
|
|
60
63
|
if (options.source === 'camera')
|
|
61
64
|
input.capture = 'environment';
|
|
65
|
+
const settle = (value) => {
|
|
66
|
+
input.onchange = null;
|
|
67
|
+
input.oncancel = null;
|
|
68
|
+
observeMediaFileCaptureHostResult('captureVideo', value !== null);
|
|
69
|
+
resolve(value);
|
|
70
|
+
};
|
|
62
71
|
input.onchange = () => {
|
|
63
72
|
const file = input.files?.[0] ?? null;
|
|
64
73
|
if (file === null) {
|
|
65
|
-
|
|
74
|
+
settle(null);
|
|
66
75
|
return;
|
|
67
76
|
}
|
|
68
77
|
const reader = new FileReader();
|
|
69
78
|
reader.onload = () => {
|
|
70
|
-
|
|
71
|
-
resolve({
|
|
79
|
+
settle({
|
|
72
80
|
dataUrl: typeof reader.result === 'string' ? reader.result : '',
|
|
73
81
|
duration: 0,
|
|
74
82
|
format: file.type,
|
|
75
83
|
});
|
|
76
84
|
};
|
|
77
|
-
reader.onerror = () =>
|
|
78
|
-
observeWebcamHostResult('captureVideo', false);
|
|
79
|
-
resolve(null);
|
|
80
|
-
};
|
|
85
|
+
reader.onerror = () => settle(null);
|
|
81
86
|
reader.readAsDataURL(file);
|
|
82
87
|
};
|
|
88
|
+
input.oncancel = () => settle(null);
|
|
83
89
|
input.click();
|
|
84
90
|
}
|
|
85
91
|
catch {
|
|
86
|
-
|
|
92
|
+
observeMediaFileCaptureHostResult('captureVideo', false);
|
|
87
93
|
resolve(null);
|
|
88
94
|
}
|
|
89
95
|
});
|
|
90
96
|
},
|
|
91
|
-
async requestPermission() {
|
|
92
|
-
if (typeof navigator === 'undefined') {
|
|
93
|
-
observeWebcamHostResult('requestPermission', false);
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const permissions = navigator.permissions;
|
|
98
|
-
if (permissions === undefined || typeof permissions.query !== 'function') {
|
|
99
|
-
observeWebcamHostResult('requestPermission', false);
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
const status = await permissions.query({ name: 'camera' });
|
|
103
|
-
const granted = status.state === 'granted';
|
|
104
|
-
observeWebcamHostResult('requestPermission', granted);
|
|
105
|
-
return granted;
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
observeWebcamHostResult('requestPermission', false);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
97
|
};
|
|
113
98
|
}
|
|
114
|
-
export function
|
|
99
|
+
export function explainMediaFileCaptureBackend() {
|
|
115
100
|
if (_custom !== null) {
|
|
116
101
|
return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
|
|
117
102
|
}
|
|
@@ -125,10 +110,10 @@ export function explainWebcamBackend() {
|
|
|
125
110
|
}
|
|
126
111
|
return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
|
|
127
112
|
}
|
|
128
|
-
export function
|
|
113
|
+
export function getMediaFileCaptureBackend() {
|
|
129
114
|
return _custom ?? _host ?? _sentinel;
|
|
130
115
|
}
|
|
131
|
-
export function
|
|
116
|
+
export function installMediaFileCaptureHostBackend(backend) {
|
|
132
117
|
if (_host !== null) {
|
|
133
118
|
if (_host !== backend)
|
|
134
119
|
_hostConflict = true;
|
|
@@ -136,36 +121,32 @@ export function installWebcamHostBackend(backend) {
|
|
|
136
121
|
}
|
|
137
122
|
_host = backend;
|
|
138
123
|
}
|
|
139
|
-
export function
|
|
124
|
+
export function observeMediaFileCaptureHostResult(operation, succeeded) {
|
|
140
125
|
_hostObservation = {
|
|
141
126
|
operation,
|
|
142
127
|
viability: succeeded ? 'available' : 'runtime-api-unavailable',
|
|
143
128
|
};
|
|
144
129
|
}
|
|
145
130
|
// Records a video from the device camera. Resolves null when cancelled, denied, or unavailable.
|
|
146
|
-
export function
|
|
147
|
-
return
|
|
131
|
+
export function recordMediaFileCaptureVideo(options) {
|
|
132
|
+
return getMediaFileCaptureBackend().captureVideo({ ...options, source: 'camera' });
|
|
148
133
|
}
|
|
149
|
-
|
|
150
|
-
export function requestWebcamPermission() {
|
|
151
|
-
return getWebcamBackend().requestPermission();
|
|
152
|
-
}
|
|
153
|
-
export function resetWebcamBackendForTest() {
|
|
134
|
+
export function resetMediaFileCaptureBackendForTest() {
|
|
154
135
|
_custom = null;
|
|
155
136
|
_host = null;
|
|
156
137
|
_hostConflict = false;
|
|
157
138
|
_hostObservation = null;
|
|
158
139
|
}
|
|
159
140
|
// Picks an existing image from the photo library. Resolves null when cancelled or unavailable.
|
|
160
|
-
export function
|
|
161
|
-
return
|
|
141
|
+
export function selectMediaFileCaptureImage(options) {
|
|
142
|
+
return getMediaFileCaptureBackend().capture({ ...options, source: 'photos' });
|
|
162
143
|
}
|
|
163
|
-
export function
|
|
144
|
+
export function setMediaFileCaptureBackend(backend) {
|
|
164
145
|
_custom = backend;
|
|
165
146
|
}
|
|
166
147
|
// Captures a photo from the device camera. Resolves null when cancelled, denied, or unavailable.
|
|
167
|
-
export function
|
|
168
|
-
return
|
|
148
|
+
export function takeMediaFileCapturePhoto(options) {
|
|
149
|
+
return getMediaFileCaptureBackend().capture({ ...options, source: 'camera' });
|
|
169
150
|
}
|
|
170
151
|
let _custom = null;
|
|
171
152
|
let _host = null;
|
|
@@ -178,8 +159,5 @@ const _sentinel = {
|
|
|
178
159
|
async captureVideo() {
|
|
179
160
|
return null;
|
|
180
161
|
},
|
|
181
|
-
async requestPermission() {
|
|
182
|
-
return false;
|
|
183
|
-
},
|
|
184
162
|
};
|
|
185
|
-
//# sourceMappingURL=
|
|
163
|
+
//# sourceMappingURL=mediaFileCapture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaFileCapture.js","sourceRoot":"","sources":["../src/mediaFileCapture.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/webcam",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1-edge.638.d4dbd4e",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/entity": "0.5.
|
|
42
|
-
"@flighthq/types": "0.5.
|
|
41
|
+
"@flighthq/entity": "0.5.1-edge.638.d4dbd4e",
|
|
42
|
+
"@flighthq/types": "0.5.1-edge.638.d4dbd4e"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.3.0"
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { MediaFileCaptureBackend, MediaFileCaptureOptions } from '@flighthq/types/contract';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createWebMediaFileCaptureBackend,
|
|
5
|
+
explainMediaFileCaptureBackend,
|
|
6
|
+
getMediaFileCaptureBackend,
|
|
7
|
+
installMediaFileCaptureHostBackend,
|
|
8
|
+
observeMediaFileCaptureHostResult,
|
|
9
|
+
recordMediaFileCaptureVideo,
|
|
10
|
+
resetMediaFileCaptureBackendForTest,
|
|
11
|
+
selectMediaFileCaptureImage,
|
|
12
|
+
setMediaFileCaptureBackend,
|
|
13
|
+
takeMediaFileCapturePhoto,
|
|
14
|
+
} from './mediaFileCapture';
|
|
15
|
+
|
|
16
|
+
function fakeBackend(): MediaFileCaptureBackend & { lastOptions: MediaFileCaptureOptions | null } {
|
|
17
|
+
return {
|
|
18
|
+
lastOptions: null,
|
|
19
|
+
async capture(options) {
|
|
20
|
+
this.lastOptions = { ...options };
|
|
21
|
+
return { dataUrl: 'data:image/png;base64,xx', width: 0, height: 0, format: 'image/png' };
|
|
22
|
+
},
|
|
23
|
+
async captureVideo(options) {
|
|
24
|
+
this.lastOptions = { ...options };
|
|
25
|
+
return { dataUrl: 'data:video/mp4;base64,xx', duration: 0, format: 'video/mp4' };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
afterEach(() => setMediaFileCaptureBackend(null));
|
|
31
|
+
|
|
32
|
+
describe('createWebMediaFileCaptureBackend', () => {
|
|
33
|
+
afterEach(() => vi.restoreAllMocks());
|
|
34
|
+
|
|
35
|
+
for (const method of ['capture', 'captureVideo'] as const) {
|
|
36
|
+
it(`${method} settles with null and detaches handlers when the picker is dismissed`, async () => {
|
|
37
|
+
const created: HTMLInputElement[] = [];
|
|
38
|
+
const createElement = document.createElement.bind(document);
|
|
39
|
+
vi.spyOn(document, 'createElement').mockImplementation(((tag: string) => {
|
|
40
|
+
const element = createElement(tag);
|
|
41
|
+
if (tag === 'input') created.push(element as HTMLInputElement);
|
|
42
|
+
return element;
|
|
43
|
+
}) as typeof document.createElement);
|
|
44
|
+
|
|
45
|
+
const pending = createWebMediaFileCaptureBackend()[method]({ source: 'photos' });
|
|
46
|
+
created[0].dispatchEvent(new Event('cancel'));
|
|
47
|
+
|
|
48
|
+
await expect(
|
|
49
|
+
Promise.race([pending, new Promise<'pending'>((resolve) => setTimeout(() => resolve('pending'), 200))]),
|
|
50
|
+
).resolves.toBeNull();
|
|
51
|
+
expect(created[0].onchange).toBeNull();
|
|
52
|
+
expect(created[0].oncancel).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
it('returns a backend whose capture yields a Promise without throwing synchronously', () => {
|
|
57
|
+
const backend = createWebMediaFileCaptureBackend();
|
|
58
|
+
expect(backend.capture({}) instanceof Promise).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('explainMediaFileCaptureBackend', () => {
|
|
63
|
+
afterEach(() => resetMediaFileCaptureBackendForTest());
|
|
64
|
+
|
|
65
|
+
it('reports host-not-enabled when no backend is installed', () => {
|
|
66
|
+
resetMediaFileCaptureBackendForTest();
|
|
67
|
+
const explanation = explainMediaFileCaptureBackend();
|
|
68
|
+
expect(explanation.layer).toBe('host-not-enabled');
|
|
69
|
+
expect(explanation.conflict).toBe(false);
|
|
70
|
+
expect(explanation.viability).toBe('unobserved');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('reports custom layer when a custom backend is set', () => {
|
|
74
|
+
setMediaFileCaptureBackend(fakeBackend());
|
|
75
|
+
expect(explainMediaFileCaptureBackend().layer).toBe('custom');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('reports host layer when a host backend is installed', () => {
|
|
79
|
+
installMediaFileCaptureHostBackend(fakeBackend());
|
|
80
|
+
expect(explainMediaFileCaptureBackend().layer).toBe('host');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('reports conflict when two different host backends are installed', () => {
|
|
84
|
+
installMediaFileCaptureHostBackend(fakeBackend());
|
|
85
|
+
installMediaFileCaptureHostBackend(fakeBackend());
|
|
86
|
+
expect(explainMediaFileCaptureBackend().conflict).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('getMediaFileCaptureBackend', () => {
|
|
91
|
+
it('falls back to a web backend', () => {
|
|
92
|
+
expect(getMediaFileCaptureBackend()).not.toBeNull();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns the registered backend', () => {
|
|
96
|
+
const backend = fakeBackend();
|
|
97
|
+
setMediaFileCaptureBackend(backend);
|
|
98
|
+
expect(getMediaFileCaptureBackend()).toBe(backend);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('installMediaFileCaptureHostBackend', () => {
|
|
103
|
+
afterEach(() => resetMediaFileCaptureBackendForTest());
|
|
104
|
+
|
|
105
|
+
it('installs a host backend that getMediaFileCaptureBackend returns', () => {
|
|
106
|
+
const backend = fakeBackend();
|
|
107
|
+
installMediaFileCaptureHostBackend(backend);
|
|
108
|
+
expect(getMediaFileCaptureBackend()).toBe(backend);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('is first-host-wins: a second different backend sets conflict', () => {
|
|
112
|
+
const first = fakeBackend();
|
|
113
|
+
const second = fakeBackend();
|
|
114
|
+
installMediaFileCaptureHostBackend(first);
|
|
115
|
+
installMediaFileCaptureHostBackend(second);
|
|
116
|
+
expect(getMediaFileCaptureBackend()).toBe(first);
|
|
117
|
+
expect(explainMediaFileCaptureBackend().conflict).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('mediaFileCapture public API boundary', () => {
|
|
122
|
+
it('web backend has exactly capture and captureVideo methods', () => {
|
|
123
|
+
const backend = createWebMediaFileCaptureBackend();
|
|
124
|
+
const methods = Object.keys(backend).sort();
|
|
125
|
+
expect(methods).toEqual(['capture', 'captureVideo']);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('sentinel backend has no requestPermission method', () => {
|
|
129
|
+
resetMediaFileCaptureBackendForTest();
|
|
130
|
+
setMediaFileCaptureBackend(null);
|
|
131
|
+
const sentinel = getMediaFileCaptureBackend();
|
|
132
|
+
expect('requestPermission' in sentinel).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('observeMediaFileCaptureHostResult', () => {
|
|
137
|
+
afterEach(() => resetMediaFileCaptureBackendForTest());
|
|
138
|
+
|
|
139
|
+
it('records a successful observation', () => {
|
|
140
|
+
installMediaFileCaptureHostBackend(fakeBackend());
|
|
141
|
+
observeMediaFileCaptureHostResult('capture', true);
|
|
142
|
+
const explanation = explainMediaFileCaptureBackend();
|
|
143
|
+
expect(explanation.operation).toBe('capture');
|
|
144
|
+
expect(explanation.viability).toBe('available');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('records a failed observation', () => {
|
|
148
|
+
installMediaFileCaptureHostBackend(fakeBackend());
|
|
149
|
+
observeMediaFileCaptureHostResult('capture', false);
|
|
150
|
+
expect(explainMediaFileCaptureBackend().viability).toBe('runtime-api-unavailable');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('recordMediaFileCaptureVideo', () => {
|
|
155
|
+
it('captures video with the camera source', async () => {
|
|
156
|
+
const backend = fakeBackend();
|
|
157
|
+
setMediaFileCaptureBackend(backend);
|
|
158
|
+
const video = await recordMediaFileCaptureVideo({ maxDurationMs: 5000 });
|
|
159
|
+
expect(video).not.toBeNull();
|
|
160
|
+
expect(backend.lastOptions).toEqual({ maxDurationMs: 5000, source: 'camera' });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns a Promise from the web backend without throwing', () => {
|
|
164
|
+
const backend = createWebMediaFileCaptureBackend();
|
|
165
|
+
expect(backend.captureVideo({}) instanceof Promise).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('resetMediaFileCaptureBackendForTest', () => {
|
|
170
|
+
it('clears all backend slots', () => {
|
|
171
|
+
setMediaFileCaptureBackend(fakeBackend());
|
|
172
|
+
installMediaFileCaptureHostBackend(fakeBackend());
|
|
173
|
+
observeMediaFileCaptureHostResult('capture', true);
|
|
174
|
+
resetMediaFileCaptureBackendForTest();
|
|
175
|
+
expect(explainMediaFileCaptureBackend().layer).toBe('host-not-enabled');
|
|
176
|
+
expect(explainMediaFileCaptureBackend().conflict).toBe(false);
|
|
177
|
+
expect(explainMediaFileCaptureBackend().viability).toBe('unobserved');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('selectMediaFileCaptureImage', () => {
|
|
182
|
+
it('captures with the photos source', async () => {
|
|
183
|
+
const backend = fakeBackend();
|
|
184
|
+
setMediaFileCaptureBackend(backend);
|
|
185
|
+
const photo = await selectMediaFileCaptureImage({ quality: 0.5 });
|
|
186
|
+
expect(photo).not.toBeNull();
|
|
187
|
+
expect(backend.lastOptions).toEqual({ quality: 0.5, source: 'photos' });
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('setMediaFileCaptureBackend', () => {
|
|
192
|
+
it('clears back to the web fallback when passed null', () => {
|
|
193
|
+
setMediaFileCaptureBackend(fakeBackend());
|
|
194
|
+
setMediaFileCaptureBackend(null);
|
|
195
|
+
expect(getMediaFileCaptureBackend()).not.toBeNull();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('takeMediaFileCapturePhoto', () => {
|
|
200
|
+
it('captures with the camera source', async () => {
|
|
201
|
+
const backend = fakeBackend();
|
|
202
|
+
setMediaFileCaptureBackend(backend);
|
|
203
|
+
const photo = await takeMediaFileCapturePhoto({ allowEditing: true });
|
|
204
|
+
expect(photo).not.toBeNull();
|
|
205
|
+
expect(backend.lastOptions).toEqual({ allowEditing: true, source: 'camera' });
|
|
206
|
+
});
|
|
207
|
+
});
|
package/dist/webcam.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { BackendExplanation } from '@flighthq/types/contract';
|
|
2
|
-
import type { WebcamBackend, WebcamCaptureOptions, WebcamPhoto, WebcamVideo } from '@flighthq/types/contract';
|
|
3
|
-
export declare function createWebWebcamBackend(): WebcamBackend;
|
|
4
|
-
export declare function explainWebcamBackend(): BackendExplanation;
|
|
5
|
-
export declare function getWebcamBackend(): WebcamBackend;
|
|
6
|
-
export declare function installWebcamHostBackend(backend: WebcamBackend): void;
|
|
7
|
-
export declare function observeWebcamHostResult(operation: string, succeeded: boolean): void;
|
|
8
|
-
export declare function recordWebcamVideo(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamVideo | null>;
|
|
9
|
-
export declare function requestWebcamPermission(): Promise<boolean>;
|
|
10
|
-
export declare function resetWebcamBackendForTest(): void;
|
|
11
|
-
export declare function selectWebcamImage(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamPhoto | null>;
|
|
12
|
-
export declare function setWebcamBackend(backend: WebcamBackend | null): void;
|
|
13
|
-
export declare function takeWebcamPhoto(options?: Readonly<WebcamCaptureOptions>): Promise<WebcamPhoto | null>;
|
|
14
|
-
//# sourceMappingURL=webcam.d.ts.map
|
package/dist/webcam.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webcam.d.ts","sourceRoot":"","sources":["../src/webcam.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAK9G,wBAAgB,sBAAsB,IAAI,aAAa,CAwGtD;AAED,wBAAgB,oBAAoB,IAAI,kBAAkB,CAazD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAMrE;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKnF;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAEvG;AAGD,wBAAgB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAKhD;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAEvG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAEpE;AAGD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAErG"}
|
package/dist/webcam.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webcam.js","sourceRoot":"","sources":["../src/webcam.ts"],"names":[],"mappings":"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"}
|
package/dist/webcamStream.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { WebcamFacingMode, WebcamStream, WebcamStreamRuntime } from '@flighthq/types/contract';
|
|
2
|
-
export declare function createWebcamStreamEntity(data: {
|
|
3
|
-
active: boolean;
|
|
4
|
-
deviceId: string;
|
|
5
|
-
facingMode: WebcamFacingMode | null;
|
|
6
|
-
frameRate: number;
|
|
7
|
-
height: number;
|
|
8
|
-
id: string;
|
|
9
|
-
width: number;
|
|
10
|
-
}): WebcamStream;
|
|
11
|
-
export declare function getWebcamStreamRuntime(stream: Readonly<WebcamStream>): WebcamStreamRuntime | null;
|
|
12
|
-
//# sourceMappingURL=webcamStream.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webcamStream.d.ts","sourceRoot":"","sources":["../src/webcamStream.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAMpG,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,YAAY,CAoBf;AAGD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,mBAAmB,GAAG,IAAI,CAIjG"}
|
package/dist/webcamStream.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
-
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
3
|
-
// Allocates a WebcamStream entity with an attached runtime slot holding the MediaStream.
|
|
4
|
-
// The fields in data become the entity's public fields; the runtime is initialized with a
|
|
5
|
-
// placeholder MediaStream that is replaced immediately by the caller.
|
|
6
|
-
export function createWebcamStreamEntity(data) {
|
|
7
|
-
const stream = createEntity({
|
|
8
|
-
active: data.active,
|
|
9
|
-
deviceId: data.deviceId,
|
|
10
|
-
facingMode: data.facingMode,
|
|
11
|
-
frameRate: data.frameRate,
|
|
12
|
-
height: data.height,
|
|
13
|
-
id: data.id,
|
|
14
|
-
width: data.width,
|
|
15
|
-
});
|
|
16
|
-
// Initialize with null; the caller overwrites rt.mediaStream once a live stream is acquired. Using
|
|
17
|
-
// null (not new MediaStream()) avoids depending on MediaStream in environments that lack it (jsdom
|
|
18
|
-
// without the media API).
|
|
19
|
-
const rt = {
|
|
20
|
-
binding: null,
|
|
21
|
-
mediaStream: null,
|
|
22
|
-
videoElement: null,
|
|
23
|
-
};
|
|
24
|
-
stream[EntityRuntimeKey] = rt;
|
|
25
|
-
return stream;
|
|
26
|
-
}
|
|
27
|
-
// Returns the WebcamStreamRuntime attached to stream, or null when stream has no runtime.
|
|
28
|
-
export function getWebcamStreamRuntime(stream) {
|
|
29
|
-
const rt = stream[EntityRuntimeKey];
|
|
30
|
-
if (rt === undefined || rt === null)
|
|
31
|
-
return null;
|
|
32
|
-
return rt;
|
|
33
|
-
}
|
|
34
|
-
//# sourceMappingURL=webcamStream.js.map
|
package/dist/webcamStream.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webcamStream.js","sourceRoot":"","sources":["../src/webcamStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,yFAAyF;AACzF,0FAA0F;AAC1F,sEAAsE;AACtE,MAAM,UAAU,wBAAwB,CAAC,IAQxC;IACC,MAAM,MAAM,GAAG,YAAY,CAAC;QAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;IACH,mGAAmG;IACnG,mGAAmG;IACnG,0BAA0B;IAC1B,MAAM,EAAE,GAAwB;QAC9B,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE,IAAI;KACnB,CAAC;IACF,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC;IAC9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,MAA8B;IACnE,MAAM,EAAE,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACpC,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjD,OAAO,EAAyB,CAAC;AACnC,CAAC"}
|
package/src/webcam.test.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import type { WebcamBackend, WebcamCaptureOptions } from '@flighthq/types/contract';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
createWebWebcamBackend,
|
|
5
|
-
getWebcamBackend,
|
|
6
|
-
recordWebcamVideo,
|
|
7
|
-
requestWebcamPermission,
|
|
8
|
-
selectWebcamImage,
|
|
9
|
-
setWebcamBackend,
|
|
10
|
-
takeWebcamPhoto,
|
|
11
|
-
explainWebcamBackend,
|
|
12
|
-
installWebcamHostBackend,
|
|
13
|
-
observeWebcamHostResult,
|
|
14
|
-
resetWebcamBackendForTest,
|
|
15
|
-
} from './webcam';
|
|
16
|
-
|
|
17
|
-
function fakeBackend(): WebcamBackend & { lastOptions: WebcamCaptureOptions | null } {
|
|
18
|
-
return {
|
|
19
|
-
lastOptions: null,
|
|
20
|
-
async capture(options) {
|
|
21
|
-
this.lastOptions = { ...options };
|
|
22
|
-
return { dataUrl: 'data:image/png;base64,xx', width: 0, height: 0, format: 'image/png' };
|
|
23
|
-
},
|
|
24
|
-
async captureVideo(options) {
|
|
25
|
-
this.lastOptions = { ...options };
|
|
26
|
-
return { dataUrl: 'data:video/mp4;base64,xx', duration: 0, format: 'video/mp4' };
|
|
27
|
-
},
|
|
28
|
-
async requestPermission() {
|
|
29
|
-
return true;
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
afterEach(() => setWebcamBackend(null));
|
|
35
|
-
|
|
36
|
-
describe('createWebWebcamBackend', () => {
|
|
37
|
-
it('returns a backend whose capture yields a Promise without throwing synchronously', () => {
|
|
38
|
-
const backend = createWebWebcamBackend();
|
|
39
|
-
expect(backend.capture({}) instanceof Promise).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('explainWebcamBackend', () => {
|
|
44
|
-
afterEach(() => resetWebcamBackendForTest());
|
|
45
|
-
|
|
46
|
-
it('reports host-not-enabled when no backend is installed', () => {
|
|
47
|
-
resetWebcamBackendForTest();
|
|
48
|
-
const explanation = explainWebcamBackend();
|
|
49
|
-
expect(explanation.layer).toBe('host-not-enabled');
|
|
50
|
-
expect(explanation.conflict).toBe(false);
|
|
51
|
-
expect(explanation.viability).toBe('unobserved');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('reports custom layer when a custom backend is set', () => {
|
|
55
|
-
setWebcamBackend(fakeBackend());
|
|
56
|
-
expect(explainWebcamBackend().layer).toBe('custom');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('reports host layer when a host backend is installed', () => {
|
|
60
|
-
installWebcamHostBackend(fakeBackend());
|
|
61
|
-
expect(explainWebcamBackend().layer).toBe('host');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('reports conflict when two different host backends are installed', () => {
|
|
65
|
-
installWebcamHostBackend(fakeBackend());
|
|
66
|
-
installWebcamHostBackend(fakeBackend());
|
|
67
|
-
expect(explainWebcamBackend().conflict).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('getWebcamBackend', () => {
|
|
72
|
-
it('falls back to a web backend', () => {
|
|
73
|
-
expect(getWebcamBackend()).not.toBeNull();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('returns the registered backend', () => {
|
|
77
|
-
const backend = fakeBackend();
|
|
78
|
-
setWebcamBackend(backend);
|
|
79
|
-
expect(getWebcamBackend()).toBe(backend);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('installWebcamHostBackend', () => {
|
|
84
|
-
afterEach(() => resetWebcamBackendForTest());
|
|
85
|
-
|
|
86
|
-
it('installs a host backend that getWebcamBackend returns', () => {
|
|
87
|
-
const backend = fakeBackend();
|
|
88
|
-
installWebcamHostBackend(backend);
|
|
89
|
-
expect(getWebcamBackend()).toBe(backend);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('is first-host-wins: a second different backend sets conflict', () => {
|
|
93
|
-
const first = fakeBackend();
|
|
94
|
-
const second = fakeBackend();
|
|
95
|
-
installWebcamHostBackend(first);
|
|
96
|
-
installWebcamHostBackend(second);
|
|
97
|
-
expect(getWebcamBackend()).toBe(first);
|
|
98
|
-
expect(explainWebcamBackend().conflict).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('observeWebcamHostResult', () => {
|
|
103
|
-
afterEach(() => resetWebcamBackendForTest());
|
|
104
|
-
|
|
105
|
-
it('records a successful observation', () => {
|
|
106
|
-
installWebcamHostBackend(fakeBackend());
|
|
107
|
-
observeWebcamHostResult('capture', true);
|
|
108
|
-
const explanation = explainWebcamBackend();
|
|
109
|
-
expect(explanation.operation).toBe('capture');
|
|
110
|
-
expect(explanation.viability).toBe('available');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('records a failed observation', () => {
|
|
114
|
-
installWebcamHostBackend(fakeBackend());
|
|
115
|
-
observeWebcamHostResult('capture', false);
|
|
116
|
-
expect(explainWebcamBackend().viability).toBe('runtime-api-unavailable');
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('recordWebcamVideo', () => {
|
|
121
|
-
it('captures video with the camera source', async () => {
|
|
122
|
-
const backend = fakeBackend();
|
|
123
|
-
setWebcamBackend(backend);
|
|
124
|
-
const video = await recordWebcamVideo({ maxDurationMs: 5000 });
|
|
125
|
-
expect(video).not.toBeNull();
|
|
126
|
-
expect(backend.lastOptions).toEqual({ maxDurationMs: 5000, source: 'camera' });
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('returns a Promise from the web backend without throwing', () => {
|
|
130
|
-
const backend = createWebWebcamBackend();
|
|
131
|
-
expect(backend.captureVideo({}) instanceof Promise).toBe(true);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe('requestWebcamPermission', () => {
|
|
136
|
-
it('delegates to the active backend', async () => {
|
|
137
|
-
setWebcamBackend(fakeBackend());
|
|
138
|
-
expect(await requestWebcamPermission()).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('returns a boolean from the web backend without throwing', async () => {
|
|
142
|
-
expect(typeof (await requestWebcamPermission())).toBe('boolean');
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe('resetWebcamBackendForTest', () => {
|
|
147
|
-
it('clears all backend slots', () => {
|
|
148
|
-
setWebcamBackend(fakeBackend());
|
|
149
|
-
installWebcamHostBackend(fakeBackend());
|
|
150
|
-
observeWebcamHostResult('capture', true);
|
|
151
|
-
resetWebcamBackendForTest();
|
|
152
|
-
expect(explainWebcamBackend().layer).toBe('host-not-enabled');
|
|
153
|
-
expect(explainWebcamBackend().conflict).toBe(false);
|
|
154
|
-
expect(explainWebcamBackend().viability).toBe('unobserved');
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe('selectWebcamImage', () => {
|
|
159
|
-
it('captures with the photos source', async () => {
|
|
160
|
-
const backend = fakeBackend();
|
|
161
|
-
setWebcamBackend(backend);
|
|
162
|
-
const photo = await selectWebcamImage({ quality: 0.5 });
|
|
163
|
-
expect(photo).not.toBeNull();
|
|
164
|
-
expect(backend.lastOptions).toEqual({ quality: 0.5, source: 'photos' });
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('setWebcamBackend', () => {
|
|
169
|
-
it('clears back to the web fallback when passed null', () => {
|
|
170
|
-
setWebcamBackend(fakeBackend());
|
|
171
|
-
setWebcamBackend(null);
|
|
172
|
-
expect(getWebcamBackend()).not.toBeNull();
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe('takeWebcamPhoto', () => {
|
|
177
|
-
it('captures with the camera source', async () => {
|
|
178
|
-
const backend = fakeBackend();
|
|
179
|
-
setWebcamBackend(backend);
|
|
180
|
-
const photo = await takeWebcamPhoto({ allowEditing: true });
|
|
181
|
-
expect(photo).not.toBeNull();
|
|
182
|
-
expect(backend.lastOptions).toEqual({ allowEditing: true, source: 'camera' });
|
|
183
|
-
});
|
|
184
|
-
});
|
package/src/webcamStream.test.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { WebcamStream } from '@flighthq/types/contract';
|
|
2
|
-
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
3
|
-
|
|
4
|
-
import { createWebcamStreamEntity, getWebcamStreamRuntime } from './webcamStream';
|
|
5
|
-
|
|
6
|
-
describe('createWebcamStreamEntity', () => {
|
|
7
|
-
it('creates a WebcamStream with the correct public fields', () => {
|
|
8
|
-
const stream = createWebcamStreamEntity({
|
|
9
|
-
active: true,
|
|
10
|
-
deviceId: 'device-1',
|
|
11
|
-
facingMode: 'environment',
|
|
12
|
-
frameRate: 30,
|
|
13
|
-
height: 480,
|
|
14
|
-
id: 'stream-123',
|
|
15
|
-
width: 640,
|
|
16
|
-
});
|
|
17
|
-
expect(stream.active).toBe(true);
|
|
18
|
-
expect(stream.deviceId).toBe('device-1');
|
|
19
|
-
expect(stream.facingMode).toBe('environment');
|
|
20
|
-
expect(stream.frameRate).toBe(30);
|
|
21
|
-
expect(stream.height).toBe(480);
|
|
22
|
-
expect(stream.id).toBe('stream-123');
|
|
23
|
-
expect(stream.width).toBe(640);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('attaches a runtime slot to the entity', () => {
|
|
27
|
-
const stream = createWebcamStreamEntity({
|
|
28
|
-
active: true,
|
|
29
|
-
deviceId: '',
|
|
30
|
-
facingMode: null,
|
|
31
|
-
frameRate: 0,
|
|
32
|
-
height: 0,
|
|
33
|
-
id: 'x',
|
|
34
|
-
width: 0,
|
|
35
|
-
});
|
|
36
|
-
expect(stream[EntityRuntimeKey]).not.toBeUndefined();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('runtime slot is initialized (mediaStream placeholder is present)', () => {
|
|
40
|
-
const stream = createWebcamStreamEntity({
|
|
41
|
-
active: false,
|
|
42
|
-
deviceId: '',
|
|
43
|
-
facingMode: null,
|
|
44
|
-
frameRate: 0,
|
|
45
|
-
height: 0,
|
|
46
|
-
id: 'x',
|
|
47
|
-
width: 0,
|
|
48
|
-
});
|
|
49
|
-
const rt = getWebcamStreamRuntime(stream);
|
|
50
|
-
expect(rt).not.toBeNull();
|
|
51
|
-
// mediaStream starts null; the caller attaches a live stream before use.
|
|
52
|
-
expect(rt?.mediaStream).toBeNull();
|
|
53
|
-
expect(rt?.videoElement).toBeNull();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('getWebcamStreamRuntime', () => {
|
|
58
|
-
it('returns null for a stream without a runtime', () => {
|
|
59
|
-
const stream = {
|
|
60
|
-
active: true,
|
|
61
|
-
deviceId: '',
|
|
62
|
-
facingMode: null,
|
|
63
|
-
frameRate: 0,
|
|
64
|
-
height: 0,
|
|
65
|
-
id: 'x',
|
|
66
|
-
width: 0,
|
|
67
|
-
[EntityRuntimeKey]: undefined,
|
|
68
|
-
} as WebcamStream;
|
|
69
|
-
expect(getWebcamStreamRuntime(stream)).toBeNull();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('returns the runtime for a stream created by createWebcamStreamEntity', () => {
|
|
73
|
-
const stream = createWebcamStreamEntity({
|
|
74
|
-
active: true,
|
|
75
|
-
deviceId: 'dev',
|
|
76
|
-
facingMode: 'user',
|
|
77
|
-
frameRate: 60,
|
|
78
|
-
height: 720,
|
|
79
|
-
id: 'stream-rt',
|
|
80
|
-
width: 1280,
|
|
81
|
-
});
|
|
82
|
-
const rt = getWebcamStreamRuntime(stream);
|
|
83
|
-
expect(rt).not.toBeNull();
|
|
84
|
-
expect(rt?.videoElement).toBeNull();
|
|
85
|
-
});
|
|
86
|
-
});
|