@chipmobilesdk/rn-camera 0.3.0 → 0.5.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/CHANGELOG.md +152 -0
- package/README.md +47 -14
- package/lib/commonjs/config/types.js +54 -0
- package/lib/commonjs/config/types.js.map +1 -0
- package/lib/commonjs/core/capabilities.js +45 -0
- package/lib/commonjs/core/capabilities.js.map +1 -0
- package/lib/commonjs/core/captureController.js +107 -0
- package/lib/commonjs/core/captureController.js.map +1 -0
- package/lib/commonjs/core/contract.js +16 -0
- package/lib/commonjs/core/contract.js.map +1 -0
- package/lib/commonjs/core/controller.js +452 -0
- package/lib/commonjs/core/controller.js.map +1 -0
- package/lib/commonjs/core/sessionMachine.js +116 -0
- package/lib/commonjs/core/sessionMachine.js.map +1 -0
- package/lib/commonjs/core/sessionRuntime.js +218 -0
- package/lib/commonjs/core/sessionRuntime.js.map +1 -0
- package/lib/commonjs/diagnostics/errors.js +86 -0
- package/lib/commonjs/diagnostics/errors.js.map +1 -0
- package/lib/commonjs/diagnostics/logger.js +40 -0
- package/lib/commonjs/diagnostics/logger.js.map +1 -0
- package/lib/commonjs/geometry/coordinateMapping.js +156 -0
- package/lib/commonjs/geometry/coordinateMapping.js.map +1 -0
- package/lib/commonjs/index.js +130 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native/cameraEngine.js +86 -0
- package/lib/commonjs/native/cameraEngine.js.map +1 -0
- package/lib/commonjs/native/visionCameraPeer.js +28 -0
- package/lib/commonjs/native/visionCameraPeer.js.map +1 -0
- package/lib/commonjs/native/visionSurface.js +38 -0
- package/lib/commonjs/native/visionSurface.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/permissions/permissionPort.js +105 -0
- package/lib/commonjs/permissions/permissionPort.js.map +1 -0
- package/lib/commonjs/react/CameraPreview.js +136 -0
- package/lib/commonjs/react/CameraPreview.js.map +1 -0
- package/lib/commonjs/react/VisionCameraFeed.js +196 -0
- package/lib/commonjs/react/VisionCameraFeed.js.map +1 -0
- package/lib/commonjs/react/hooks.js +50 -0
- package/lib/commonjs/react/hooks.js.map +1 -0
- package/lib/commonjs/storage/artifactStore.js +98 -0
- package/lib/commonjs/storage/artifactStore.js.map +1 -0
- package/lib/commonjs/testing/conformance.js +109 -0
- package/lib/commonjs/testing/conformance.js.map +1 -0
- package/lib/commonjs/testing/index.js +50 -0
- package/lib/commonjs/testing/index.js.map +1 -0
- package/lib/commonjs/testing/testCameraAdapter.js +191 -0
- package/lib/commonjs/testing/testCameraAdapter.js.map +1 -0
- package/lib/module/config/types.js +49 -0
- package/lib/module/config/types.js.map +1 -0
- package/lib/module/core/capabilities.js +37 -0
- package/lib/module/core/capabilities.js.map +1 -0
- package/lib/module/core/captureController.js +98 -0
- package/lib/module/core/captureController.js.map +1 -0
- package/lib/module/core/contract.js +12 -0
- package/lib/module/core/contract.js.map +1 -0
- package/lib/module/core/controller.js +442 -0
- package/lib/module/core/controller.js.map +1 -0
- package/lib/module/core/sessionMachine.js +109 -0
- package/lib/module/core/sessionMachine.js.map +1 -0
- package/lib/module/core/sessionRuntime.js +215 -0
- package/lib/module/core/sessionRuntime.js.map +1 -0
- package/lib/module/diagnostics/errors.js +79 -0
- package/lib/module/diagnostics/errors.js.map +1 -0
- package/lib/module/diagnostics/logger.js +35 -0
- package/lib/module/diagnostics/logger.js.map +1 -0
- package/lib/module/geometry/coordinateMapping.js +149 -0
- package/lib/module/geometry/coordinateMapping.js.map +1 -0
- package/lib/module/index.js +44 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/cameraEngine.js +83 -0
- package/lib/module/native/cameraEngine.js.map +1 -0
- package/lib/module/native/visionCameraPeer.js +24 -0
- package/lib/module/native/visionCameraPeer.js.map +1 -0
- package/lib/module/native/visionSurface.js +34 -0
- package/lib/module/native/visionSurface.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/permissions/permissionPort.js +101 -0
- package/lib/module/permissions/permissionPort.js.map +1 -0
- package/lib/module/react/CameraPreview.js +130 -0
- package/lib/module/react/CameraPreview.js.map +1 -0
- package/lib/module/react/VisionCameraFeed.js +190 -0
- package/lib/module/react/VisionCameraFeed.js.map +1 -0
- package/lib/module/react/hooks.js +45 -0
- package/lib/module/react/hooks.js.map +1 -0
- package/lib/module/storage/artifactStore.js +94 -0
- package/lib/module/storage/artifactStore.js.map +1 -0
- package/lib/module/testing/conformance.js +102 -0
- package/lib/module/testing/conformance.js.map +1 -0
- package/lib/module/testing/index.js +13 -0
- package/lib/module/testing/index.js.map +1 -0
- package/lib/module/testing/testCameraAdapter.js +185 -0
- package/lib/module/testing/testCameraAdapter.js.map +1 -0
- package/lib/typescript/commonjs/config/types.d.ts +122 -0
- package/lib/typescript/commonjs/config/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/capabilities.d.ts +16 -0
- package/lib/typescript/commonjs/core/capabilities.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/captureController.d.ts +48 -0
- package/lib/typescript/commonjs/core/captureController.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/contract.d.ts +92 -0
- package/lib/typescript/commonjs/core/contract.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/controller.d.ts +15 -0
- package/lib/typescript/commonjs/core/controller.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/sessionMachine.d.ts +46 -0
- package/lib/typescript/commonjs/core/sessionMachine.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/sessionRuntime.d.ts +64 -0
- package/lib/typescript/commonjs/core/sessionRuntime.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts +24 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/logger.d.ts +24 -0
- package/lib/typescript/commonjs/diagnostics/logger.d.ts.map +1 -0
- package/lib/typescript/commonjs/geometry/coordinateMapping.d.ts +23 -0
- package/lib/typescript/commonjs/geometry/coordinateMapping.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +32 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/cameraEngine.d.ts +50 -0
- package/lib/typescript/commonjs/native/cameraEngine.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/visionCameraPeer.d.ts +2 -0
- package/lib/typescript/commonjs/native/visionCameraPeer.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/visionSurface.d.ts +41 -0
- package/lib/typescript/commonjs/native/visionSurface.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/permissions/permissionPort.d.ts +24 -0
- package/lib/typescript/commonjs/permissions/permissionPort.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/CameraPreview.d.ts +25 -0
- package/lib/typescript/commonjs/react/CameraPreview.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/VisionCameraFeed.d.ts +31 -0
- package/lib/typescript/commonjs/react/VisionCameraFeed.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/hooks.d.ts +12 -0
- package/lib/typescript/commonjs/react/hooks.d.ts.map +1 -0
- package/lib/typescript/commonjs/storage/artifactStore.d.ts +54 -0
- package/lib/typescript/commonjs/storage/artifactStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts +37 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/index.d.ts +12 -0
- package/lib/typescript/commonjs/testing/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/testCameraAdapter.d.ts +61 -0
- package/lib/typescript/commonjs/testing/testCameraAdapter.d.ts.map +1 -0
- package/lib/typescript/module/config/types.d.ts +122 -0
- package/lib/typescript/module/config/types.d.ts.map +1 -0
- package/lib/typescript/module/core/capabilities.d.ts +16 -0
- package/lib/typescript/module/core/capabilities.d.ts.map +1 -0
- package/lib/typescript/module/core/captureController.d.ts +48 -0
- package/lib/typescript/module/core/captureController.d.ts.map +1 -0
- package/lib/typescript/module/core/contract.d.ts +92 -0
- package/lib/typescript/module/core/contract.d.ts.map +1 -0
- package/lib/typescript/module/core/controller.d.ts +15 -0
- package/lib/typescript/module/core/controller.d.ts.map +1 -0
- package/lib/typescript/module/core/sessionMachine.d.ts +46 -0
- package/lib/typescript/module/core/sessionMachine.d.ts.map +1 -0
- package/lib/typescript/module/core/sessionRuntime.d.ts +64 -0
- package/lib/typescript/module/core/sessionRuntime.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/errors.d.ts +24 -0
- package/lib/typescript/module/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/logger.d.ts +24 -0
- package/lib/typescript/module/diagnostics/logger.d.ts.map +1 -0
- package/lib/typescript/module/geometry/coordinateMapping.d.ts +23 -0
- package/lib/typescript/module/geometry/coordinateMapping.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +32 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/native/cameraEngine.d.ts +50 -0
- package/lib/typescript/module/native/cameraEngine.d.ts.map +1 -0
- package/lib/typescript/module/native/visionCameraPeer.d.ts +2 -0
- package/lib/typescript/module/native/visionCameraPeer.d.ts.map +1 -0
- package/lib/typescript/module/native/visionSurface.d.ts +41 -0
- package/lib/typescript/module/native/visionSurface.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/permissions/permissionPort.d.ts +24 -0
- package/lib/typescript/module/permissions/permissionPort.d.ts.map +1 -0
- package/lib/typescript/module/react/CameraPreview.d.ts +25 -0
- package/lib/typescript/module/react/CameraPreview.d.ts.map +1 -0
- package/lib/typescript/module/react/VisionCameraFeed.d.ts +31 -0
- package/lib/typescript/module/react/VisionCameraFeed.d.ts.map +1 -0
- package/lib/typescript/module/react/hooks.d.ts +12 -0
- package/lib/typescript/module/react/hooks.d.ts.map +1 -0
- package/lib/typescript/module/storage/artifactStore.d.ts +54 -0
- package/lib/typescript/module/storage/artifactStore.d.ts.map +1 -0
- package/lib/typescript/module/testing/conformance.d.ts +37 -0
- package/lib/typescript/module/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/module/testing/index.d.ts +12 -0
- package/lib/typescript/module/testing/index.d.ts.map +1 -0
- package/lib/typescript/module/testing/testCameraAdapter.d.ts +61 -0
- package/lib/typescript/module/testing/testCameraAdapter.d.ts.map +1 -0
- package/package.json +49 -13
- package/skills/sdk-rn-camera/SKILL.md +25 -6
- package/src/config/types.ts +8 -0
- package/src/core/captureController.ts +11 -1
- package/src/core/contract.ts +109 -0
- package/src/core/controller.ts +96 -148
- package/src/core/sessionRuntime.ts +242 -0
- package/src/index.ts +7 -13
- package/src/native/cameraEngine.ts +28 -8
- package/src/native/visionSurface.ts +43 -18
- package/src/react/CameraPreview.tsx +40 -7
- package/src/react/VisionCameraFeed.tsx +61 -5
- package/src/react/hooks.ts +1 -1
- package/src/storage/artifactStore.ts +9 -2
- package/src/testing/testCameraAdapter.ts +29 -7
package/package.json
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipmobilesdk/rn-camera",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"source": "./src/index.ts",
|
|
7
|
+
"main": "./lib/commonjs/index.js",
|
|
8
|
+
"module": "./lib/module/index.js",
|
|
9
|
+
"types": "./lib/typescript/commonjs/index.d.ts",
|
|
8
10
|
"exports": {
|
|
9
11
|
".": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
12
|
+
"chipmobilesdk-source": "./src/index.ts",
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./lib/typescript/module/index.d.ts",
|
|
15
|
+
"default": "./lib/module/index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./lib/typescript/commonjs/index.d.ts",
|
|
19
|
+
"default": "./lib/commonjs/index.js"
|
|
20
|
+
}
|
|
13
21
|
},
|
|
14
22
|
"./testing": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
23
|
+
"chipmobilesdk-source": "./src/testing/index.ts",
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./lib/typescript/module/testing/index.d.ts",
|
|
26
|
+
"default": "./lib/module/testing/index.js"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./lib/typescript/commonjs/testing/index.d.ts",
|
|
30
|
+
"default": "./lib/commonjs/testing/index.js"
|
|
31
|
+
}
|
|
18
32
|
}
|
|
19
33
|
},
|
|
20
34
|
"files": [
|
|
@@ -30,23 +44,45 @@
|
|
|
30
44
|
"src/testing",
|
|
31
45
|
"skills",
|
|
32
46
|
"README.md",
|
|
33
|
-
"CHANGELOG.md"
|
|
47
|
+
"CHANGELOG.md",
|
|
48
|
+
"lib"
|
|
34
49
|
],
|
|
35
50
|
"sideEffects": false,
|
|
36
51
|
"scripts": {
|
|
52
|
+
"build": "bob build",
|
|
37
53
|
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json",
|
|
38
|
-
"pack:check": "npm pack --dry-run"
|
|
54
|
+
"pack:check": "npm pack --dry-run",
|
|
55
|
+
"prepack": "npm run build"
|
|
39
56
|
},
|
|
40
57
|
"dependencies": {
|
|
41
|
-
"@chipmobilesdk/rn-core": "^0.
|
|
58
|
+
"@chipmobilesdk/rn-core": "^0.2.0"
|
|
42
59
|
},
|
|
43
60
|
"peerDependencies": {
|
|
44
61
|
"react": ">=19",
|
|
45
62
|
"react-native": ">=0.85",
|
|
46
|
-
"react-native-vision-camera": "
|
|
63
|
+
"react-native-vision-camera": "^5.1.1"
|
|
47
64
|
},
|
|
48
65
|
"publishConfig": {
|
|
49
66
|
"access": "public",
|
|
50
67
|
"registry": "https://registry.npmjs.org/"
|
|
68
|
+
},
|
|
69
|
+
"react-native-builder-bob": {
|
|
70
|
+
"source": "src",
|
|
71
|
+
"output": "lib",
|
|
72
|
+
"targets": [
|
|
73
|
+
[
|
|
74
|
+
"commonjs",
|
|
75
|
+
{
|
|
76
|
+
"esm": true
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
"module",
|
|
81
|
+
{
|
|
82
|
+
"esm": true
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"typescript"
|
|
86
|
+
]
|
|
51
87
|
}
|
|
52
88
|
}
|
|
@@ -39,8 +39,10 @@ delete them. See `## Data behaviour` for what this means for App Privacy disclos
|
|
|
39
39
|
## Test without hardware
|
|
40
40
|
|
|
41
41
|
`createTestCamera`, `createMemoryFileSystem` and `DEFAULT_TEST_CAPABILITIES` let camera
|
|
42
|
-
flows be unit-tested with no device.
|
|
43
|
-
|
|
42
|
+
flows be unit-tested with no device. Import them from
|
|
43
|
+
`@chipmobilesdk/rn-camera/testing`, never the main entrypoint — as of 0.4.0 that is the
|
|
44
|
+
only place they are, so a fake camera cannot reach an app's bundle. Use these rather
|
|
45
|
+
than mocking VisionCamera by hand — see `## Testing without hardware`.
|
|
44
46
|
|
|
45
47
|
## Public API
|
|
46
48
|
|
|
@@ -52,13 +54,30 @@ Runtime:
|
|
|
52
54
|
- `createArtifactStore(...)`, `createCacheArtifactStore(...)`
|
|
53
55
|
- `CameraPreview` (+ `CameraPreviewProps`)
|
|
54
56
|
- `CameraError`, `isCameraError`, `redact` — `redact` strips sensitive paths before logging
|
|
55
|
-
- Testing: `createTestCamera`,
|
|
57
|
+
- Testing (`@chipmobilesdk/rn-camera/testing`): `createTestCamera`,
|
|
58
|
+
`createMemoryFileSystem`, `DEFAULT_TEST_CAPABILITIES`
|
|
56
59
|
- Conformance (`@chipmobilesdk/rn-camera/testing`): `runPermissionPortConformance`,
|
|
57
60
|
`runCameraEngineConformance` — run against any adapter you write or inject
|
|
58
61
|
|
|
59
|
-
Types: `CameraEngine`, `
|
|
60
|
-
`
|
|
61
|
-
`CameraLogEventType`, `CameraLogger`
|
|
62
|
+
Types: `CameraEngine`, `PermissionPort`, `ArtifactStore`, `FileSystemBridge`,
|
|
63
|
+
`StoreOptions`, `VisionSurface`, `SurfaceInfo`, `SurfaceRegistry`,
|
|
64
|
+
`CameraErrorCode`, `CameraLogEvent`, `CameraLogEventType`, `CameraLogger`
|
|
65
|
+
|
|
66
|
+
## Three things the lifecycle guarantees
|
|
67
|
+
|
|
68
|
+
- **`start()` resolves when frames arrive.** `capture()` straight after it works, and a
|
|
69
|
+
camera that never produces a frame fails the start (8 s by default) instead of leaving
|
|
70
|
+
the session in `starting`.
|
|
71
|
+
- **`pause()` keeps the surface mounted** and stops the camera, so `resume()` is cheap.
|
|
72
|
+
- **`capture({ cameraPosition })` must name the running camera.** A photo comes from the
|
|
73
|
+
device the preview mounted; asking for the other one is `notReady`. Switch with
|
|
74
|
+
`start({ cameraPosition })`.
|
|
75
|
+
|
|
76
|
+
## Coordinate mapping waits for the surface
|
|
77
|
+
|
|
78
|
+
`state.surface` is `null` until the preview reports its real format, and
|
|
79
|
+
`onGeometryChange` fires again with those numbers. Map rectangles after it is set —
|
|
80
|
+
before that the geometry is a nominal landscape 1920×1080.
|
|
62
81
|
|
|
63
82
|
Always pass camera log payloads through `redact` before logging.
|
|
64
83
|
|
package/src/config/types.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type FlashMode = 'auto' | 'on' | 'off';
|
|
|
21
21
|
export type ResizeMode = 'cover' | 'contain';
|
|
22
22
|
export type ArtifactOwnership = 'packageTemporary' | 'appOwned';
|
|
23
23
|
|
|
24
|
+
import type { SurfaceInfo } from '../native/visionSurface';
|
|
25
|
+
|
|
24
26
|
export type SessionStatus =
|
|
25
27
|
'idle' | 'starting' | 'active' | 'paused' | 'stopping' | 'stopped' | 'error';
|
|
26
28
|
|
|
@@ -129,6 +131,12 @@ export interface CameraState {
|
|
|
129
131
|
readonly flashMode: FlashMode;
|
|
130
132
|
readonly torchEnabled: boolean;
|
|
131
133
|
readonly capturing: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* What the mounted preview surface reported about itself: the capture
|
|
136
|
+
* dimensions of the selected format, its orientation, and the position of
|
|
137
|
+
* the device actually running. `null` until a surface reports (CAM-10).
|
|
138
|
+
*/
|
|
139
|
+
readonly surface: SurfaceInfo | null;
|
|
132
140
|
readonly error: CameraErrorLike | null;
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -67,6 +67,15 @@ export function assertCaptureAllowed(context: CaptureContext, request: CaptureRe
|
|
|
67
67
|
|
|
68
68
|
const position = request.cameraPosition ?? context.cameraPosition;
|
|
69
69
|
assertSupported(supportsPosition(context.capabilities, position), `${position} camera`);
|
|
70
|
+
if (position !== context.cameraPosition) {
|
|
71
|
+
// The photo would come from the camera that is running, while the
|
|
72
|
+
// metadata said it came from the one that was asked for (CAM-06).
|
|
73
|
+
throw new CameraError(
|
|
74
|
+
'notReady',
|
|
75
|
+
`The ${context.cameraPosition} camera is running. Call start({ cameraPosition: ` +
|
|
76
|
+
`'${position}' }) before capturing with the ${position} camera.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
/**
|
|
@@ -83,7 +92,8 @@ export function finalizeResult(
|
|
|
83
92
|
return {
|
|
84
93
|
...raw,
|
|
85
94
|
ownership,
|
|
86
|
-
|
|
95
|
+
// The camera that took it, which the guard above has made the running one.
|
|
96
|
+
cameraPosition: raw.cameraPosition ?? context.cameraPosition,
|
|
87
97
|
timestamp: raw.timestamp ?? Date.now(),
|
|
88
98
|
effectiveQuality: raw.effectiveQuality ?? clampQuality(request.quality),
|
|
89
99
|
effectiveMaxDimension: raw.effectiveMaxDimension ?? clampMaxDimension(request.maxDimension),
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a camera controller is, for the app that drives it.
|
|
3
|
+
*
|
|
4
|
+
* The contract lives apart from the orchestration in `controller.ts`: it is
|
|
5
|
+
* what a consumer reads, and what a test double has to satisfy, while the file
|
|
6
|
+
* next to it is how this package happens to satisfy it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
CameraPermissionState,
|
|
11
|
+
CameraState,
|
|
12
|
+
CapabilityProfile,
|
|
13
|
+
CapturedImage,
|
|
14
|
+
CaptureRequest,
|
|
15
|
+
FlashMode,
|
|
16
|
+
StartOptions,
|
|
17
|
+
} from '../config/types';
|
|
18
|
+
import type { CameraError } from '../diagnostics/errors';
|
|
19
|
+
import type { CameraLogger } from '../diagnostics/logger';
|
|
20
|
+
import type { CameraEngine } from '../native/cameraEngine';
|
|
21
|
+
import type { SurfaceInfo, VisionSurface } from '../native/visionSurface';
|
|
22
|
+
import type { PermissionPort } from '../permissions/permissionPort';
|
|
23
|
+
import type { ArtifactStore } from '../storage/artifactStore';
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_TEMPORARY_TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
|
|
27
|
+
export type AppStatus = 'active' | 'background';
|
|
28
|
+
|
|
29
|
+
export interface CameraControllerOptions {
|
|
30
|
+
/** Required. Use the default adapters, or `createTestCamera()` in tests. */
|
|
31
|
+
readonly engine: CameraEngine;
|
|
32
|
+
readonly permissions: PermissionPort;
|
|
33
|
+
readonly store?: ArtifactStore | undefined;
|
|
34
|
+
readonly logger?: CameraLogger | undefined;
|
|
35
|
+
readonly temporaryTtlMs?: number;
|
|
36
|
+
/**
|
|
37
|
+
* App foreground/background source. Defaults to rn-core's shared `appLifecycle()`,
|
|
38
|
+
* where `inactive` counts as background. Injectable so lifecycle rules are
|
|
39
|
+
* testable without an app (FR-015).
|
|
40
|
+
*/
|
|
41
|
+
readonly subscribeToAppState?: (listener: (status: AppStatus) => void) => () => void;
|
|
42
|
+
/**
|
|
43
|
+
* Set false to skip the sweep that runs when the controller is first used
|
|
44
|
+
* (tests). Defaults to true.
|
|
45
|
+
*/
|
|
46
|
+
readonly sweepOnInit?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* How long to wait for a preview surface to report frames before failing the
|
|
49
|
+
* start (FR-014). Only applies to an engine whose session belongs to the
|
|
50
|
+
* surface (`awaitsSurfaceStart`). Defaults to 8 s.
|
|
51
|
+
*/
|
|
52
|
+
readonly startTimeoutMs?: number;
|
|
53
|
+
/** Test seam for that timeout. */
|
|
54
|
+
readonly scheduler?: {
|
|
55
|
+
setTimeout(callback: () => void, ms: number): unknown;
|
|
56
|
+
clearTimeout(handle: unknown): void;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CameraController {
|
|
61
|
+
readonly state: CameraState;
|
|
62
|
+
subscribe(listener: (state: CameraState) => void): () => void;
|
|
63
|
+
|
|
64
|
+
getPermission(): Promise<CameraPermissionState>;
|
|
65
|
+
requestPermission(): Promise<CameraPermissionState>;
|
|
66
|
+
|
|
67
|
+
start(options?: StartOptions): Promise<void>;
|
|
68
|
+
pause(): Promise<void>;
|
|
69
|
+
resume(): Promise<void>;
|
|
70
|
+
stop(): Promise<void>;
|
|
71
|
+
|
|
72
|
+
capture(request?: CaptureRequest): Promise<CapturedImage>;
|
|
73
|
+
cleanup(image: CapturedImage | string): Promise<void>;
|
|
74
|
+
|
|
75
|
+
getCapabilities(): Promise<CapabilityProfile>;
|
|
76
|
+
setFlashMode(mode: FlashMode): void;
|
|
77
|
+
setTorch(enabled: boolean): Promise<void>;
|
|
78
|
+
|
|
79
|
+
/** Marks the preview surface mounted, enabling foreground auto-restart. */
|
|
80
|
+
setSurfaceMounted(mounted: boolean): void;
|
|
81
|
+
/**
|
|
82
|
+
* Attaches the mounted preview surface to the engine, for an engine that
|
|
83
|
+
* captures through it. Returns the detach function; a no-op for engines that
|
|
84
|
+
* own their own session.
|
|
85
|
+
*/
|
|
86
|
+
attachSurface(surface: VisionSurface): () => void;
|
|
87
|
+
/**
|
|
88
|
+
* The preview reported frames: the session is active for real, not merely
|
|
89
|
+
* requested (CAM-08). What the surface turned out to be — the format's pixel
|
|
90
|
+
* size, the orientation, the device's own position — is published as
|
|
91
|
+
* `state.surface` for coordinate mapping.
|
|
92
|
+
*/
|
|
93
|
+
reportSurfaceStarted(info: SurfaceInfo): void;
|
|
94
|
+
/** The preview's own session failed, or the OS took the camera away. */
|
|
95
|
+
reportSurfaceFailed(code: CameraError['code']): void;
|
|
96
|
+
/**
|
|
97
|
+
* Called by `CameraPreview` when the native preview surface cannot load (the
|
|
98
|
+
* VisionCamera module is missing or unlinked). Fails the session with
|
|
99
|
+
* `cameraUnavailable` rather than leaving a black preview and no error.
|
|
100
|
+
*/
|
|
101
|
+
reportSurfaceUnavailable(): void;
|
|
102
|
+
/**
|
|
103
|
+
* Releases ports, listeners, and the camera. Safe to call twice. Afterwards
|
|
104
|
+
* `start`, `resume`, `capture` and the permission and capability reads reject
|
|
105
|
+
* with `notReady`; `stop`, `pause` and `cleanup` stay safe no-ops so teardown
|
|
106
|
+
* code never has to check.
|
|
107
|
+
*/
|
|
108
|
+
dispose(): Promise<void>;
|
|
109
|
+
}
|
package/src/core/controller.ts
CHANGED
|
@@ -20,10 +20,15 @@ import {
|
|
|
20
20
|
type StartOptions,
|
|
21
21
|
} from '../config/types';
|
|
22
22
|
import { CameraError, isCameraError } from '../diagnostics/errors';
|
|
23
|
-
import { silentLogger, withSdkSinks
|
|
23
|
+
import { silentLogger, withSdkSinks } from '../diagnostics/logger';
|
|
24
24
|
import type { CameraEngine } from '../native/cameraEngine';
|
|
25
|
+
import {
|
|
26
|
+
DEFAULT_TEMPORARY_TTL_MS,
|
|
27
|
+
type AppStatus,
|
|
28
|
+
type CameraController,
|
|
29
|
+
type CameraControllerOptions,
|
|
30
|
+
} from './contract';
|
|
25
31
|
import type { PermissionPort } from '../permissions/permissionPort';
|
|
26
|
-
import type { ArtifactStore } from '../storage/artifactStore';
|
|
27
32
|
import { EMPTY_CAPABILITIES, assertSupported, supportsTorch } from './capabilities';
|
|
28
33
|
import {
|
|
29
34
|
SingleFlight,
|
|
@@ -32,66 +37,15 @@ import {
|
|
|
32
37
|
type CaptureContext,
|
|
33
38
|
} from './captureController';
|
|
34
39
|
import { canStart, initialSession, sessionReducer, type SessionSnapshot } from './sessionMachine';
|
|
40
|
+
import { createSessionRuntime } from './sessionRuntime';
|
|
35
41
|
|
|
36
42
|
/** 24 hours (FR-029). */
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
readonly permissions?: PermissionPort;
|
|
44
|
-
readonly store?: ArtifactStore | undefined;
|
|
45
|
-
readonly logger?: CameraLogger | undefined;
|
|
46
|
-
readonly temporaryTtlMs?: number;
|
|
47
|
-
/**
|
|
48
|
-
* App foreground/background source. Defaults to rn-core's shared `appLifecycle()`,
|
|
49
|
-
* where `inactive` counts as background. Injectable so lifecycle rules are
|
|
50
|
-
* testable without an app (FR-015).
|
|
51
|
-
*/
|
|
52
|
-
readonly subscribeToAppState?: (listener: (status: AppStatus) => void) => () => void;
|
|
53
|
-
/**
|
|
54
|
-
* Set false to skip the sweep that runs when the controller is first used
|
|
55
|
-
* (tests). Defaults to true.
|
|
56
|
-
*/
|
|
57
|
-
readonly sweepOnInit?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface CameraController {
|
|
61
|
-
readonly state: CameraState;
|
|
62
|
-
subscribe(listener: (state: CameraState) => void): () => void;
|
|
63
|
-
|
|
64
|
-
getPermission(): Promise<CameraPermissionState>;
|
|
65
|
-
requestPermission(): Promise<CameraPermissionState>;
|
|
66
|
-
|
|
67
|
-
start(options?: StartOptions): Promise<void>;
|
|
68
|
-
pause(): Promise<void>;
|
|
69
|
-
resume(): Promise<void>;
|
|
70
|
-
stop(): Promise<void>;
|
|
71
|
-
|
|
72
|
-
capture(request?: CaptureRequest): Promise<CapturedImage>;
|
|
73
|
-
cleanup(image: CapturedImage | string): Promise<void>;
|
|
74
|
-
|
|
75
|
-
getCapabilities(): Promise<CapabilityProfile>;
|
|
76
|
-
setFlashMode(mode: FlashMode): void;
|
|
77
|
-
setTorch(enabled: boolean): Promise<void>;
|
|
78
|
-
|
|
79
|
-
/** Marks the preview surface mounted, enabling foreground auto-restart. */
|
|
80
|
-
setSurfaceMounted(mounted: boolean): void;
|
|
81
|
-
/**
|
|
82
|
-
* Called by `CameraPreview` when the native preview surface cannot load (the
|
|
83
|
-
* VisionCamera module is missing or unlinked). Fails the session with
|
|
84
|
-
* `cameraUnavailable` rather than leaving a black preview and no error.
|
|
85
|
-
*/
|
|
86
|
-
reportSurfaceUnavailable(): void;
|
|
87
|
-
/**
|
|
88
|
-
* Releases ports, listeners, and the camera. Safe to call twice. Afterwards
|
|
89
|
-
* `start`, `resume`, `capture` and the permission and capability reads reject
|
|
90
|
-
* with `notReady`; `stop`, `pause` and `cleanup` stay safe no-ops so teardown
|
|
91
|
-
* code never has to check.
|
|
92
|
-
*/
|
|
93
|
-
dispose(): Promise<void>;
|
|
94
|
-
}
|
|
43
|
+
export {
|
|
44
|
+
DEFAULT_TEMPORARY_TTL_MS,
|
|
45
|
+
type AppStatus,
|
|
46
|
+
type CameraController,
|
|
47
|
+
type CameraControllerOptions,
|
|
48
|
+
} from './contract';
|
|
95
49
|
|
|
96
50
|
function defaultAppStateSubscribe(listener: (status: AppStatus) => void): () => void {
|
|
97
51
|
// rn-core's one lifecycle subscription, shared with every other package; it
|
|
@@ -99,8 +53,13 @@ function defaultAppStateSubscribe(listener: (status: AppStatus) => void): () =>
|
|
|
99
53
|
return appLifecycle().subscribe(state => listener(state === 'active' ? 'active' : 'background'));
|
|
100
54
|
}
|
|
101
55
|
|
|
102
|
-
|
|
103
|
-
|
|
56
|
+
/** 8 s: a cold camera on a slow device, with room to spare (PERF-001 is 1.5 s). */
|
|
57
|
+
export const DEFAULT_START_TIMEOUT_MS = 8_000;
|
|
58
|
+
|
|
59
|
+
export function createCameraController(options: CameraControllerOptions): CameraController {
|
|
60
|
+
if (!options?.engine || !options?.permissions) {
|
|
61
|
+
// A configuration fault, named as one. The ports are required by the type;
|
|
62
|
+
// this is the answer for a JavaScript caller or a cast.
|
|
104
63
|
throw new CameraError(
|
|
105
64
|
'unsupportedCapability',
|
|
106
65
|
'createCameraController requires `engine` and `permissions` ports. Use the ' +
|
|
@@ -108,13 +67,18 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
108
67
|
);
|
|
109
68
|
}
|
|
110
69
|
|
|
111
|
-
// Bound after the guard so the closures below see non-optional ports.
|
|
112
70
|
const engine: CameraEngine = options.engine;
|
|
113
71
|
const permissions: PermissionPort = options.permissions;
|
|
114
72
|
const store = options.store;
|
|
115
73
|
const logger = withSdkSinks(options.logger ?? silentLogger);
|
|
116
74
|
const ttlMs = options.temporaryTtlMs ?? DEFAULT_TEMPORARY_TTL_MS;
|
|
117
75
|
|
|
76
|
+
const startTimeoutMs = options.startTimeoutMs ?? DEFAULT_START_TIMEOUT_MS;
|
|
77
|
+
const scheduler = options.scheduler ?? {
|
|
78
|
+
setTimeout: (callback: () => void, ms: number) => setTimeout(callback, ms),
|
|
79
|
+
clearTimeout: (handle: unknown) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
80
|
+
};
|
|
81
|
+
|
|
118
82
|
let session: SessionSnapshot = initialSession;
|
|
119
83
|
let permission: CameraPermissionState = 'undetermined';
|
|
120
84
|
let capabilities: CapabilityProfile | null = null;
|
|
@@ -123,13 +87,6 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
123
87
|
let torchEnabled = false;
|
|
124
88
|
let surfaceMounted = false;
|
|
125
89
|
let disposed = false;
|
|
126
|
-
let appStatus: AppStatus = 'active';
|
|
127
|
-
// Set only when backgrounding cut a running session short. Foregrounding
|
|
128
|
-
// used to restart from any idle or stopped session, so a camera the user had
|
|
129
|
-
// stopped — or never started, or no longer had permission for — came back on
|
|
130
|
-
// with the app.
|
|
131
|
-
let resumeOnForeground = false;
|
|
132
|
-
let releasing: Promise<void> | null = null;
|
|
133
90
|
let warnedUnmanaged = false;
|
|
134
91
|
let detach: (() => void) | null = null;
|
|
135
92
|
|
|
@@ -138,6 +95,33 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
138
95
|
onListenerError: () => logger.log({ type: 'session', code: 'listener-threw' }),
|
|
139
96
|
});
|
|
140
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The session's own I/O (`sessionRuntime`): starting it, waiting for frames,
|
|
100
|
+
* releasing it, following the app in and out of the foreground. It reads and
|
|
101
|
+
* writes the state above through this context, so one snapshot reaches
|
|
102
|
+
* subscribers whichever part changed it.
|
|
103
|
+
*/
|
|
104
|
+
const runtime = createSessionRuntime({
|
|
105
|
+
engine,
|
|
106
|
+
logger,
|
|
107
|
+
startTimeoutMs,
|
|
108
|
+
scheduler,
|
|
109
|
+
session: () => session,
|
|
110
|
+
dispatch,
|
|
111
|
+
emit,
|
|
112
|
+
cameraPosition: () => cameraPosition,
|
|
113
|
+
setCameraPosition: position => {
|
|
114
|
+
cameraPosition = position;
|
|
115
|
+
},
|
|
116
|
+
torchEnabled: () => torchEnabled,
|
|
117
|
+
setTorchEnabled: enabled => {
|
|
118
|
+
torchEnabled = enabled;
|
|
119
|
+
},
|
|
120
|
+
surfaceMounted: () => surfaceMounted,
|
|
121
|
+
permissionGranted: () => permission === 'granted',
|
|
122
|
+
disposed: () => disposed,
|
|
123
|
+
});
|
|
124
|
+
|
|
141
125
|
function snapshot(): CameraState {
|
|
142
126
|
return {
|
|
143
127
|
session: session.status,
|
|
@@ -147,6 +131,9 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
147
131
|
flashMode,
|
|
148
132
|
torchEnabled,
|
|
149
133
|
capturing: capture.busy,
|
|
134
|
+
// What the mounted preview turned out to be, once it reported: the
|
|
135
|
+
// format's pixel size and the orientation coordinate mapping needs.
|
|
136
|
+
surface: runtime.surfaceInfo(),
|
|
150
137
|
error: session.error
|
|
151
138
|
? {
|
|
152
139
|
code: session.error.code,
|
|
@@ -179,72 +166,6 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
179
166
|
}
|
|
180
167
|
}
|
|
181
168
|
|
|
182
|
-
/** Torch must never survive leaving the active state (FR-034b). */
|
|
183
|
-
async function forceTorchOff(): Promise<void> {
|
|
184
|
-
if (!torchEnabled) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
torchEnabled = false;
|
|
188
|
-
try {
|
|
189
|
-
await engine.setTorch(false);
|
|
190
|
-
} catch {
|
|
191
|
-
// Torch-off is best effort; the session is going away regardless.
|
|
192
|
-
}
|
|
193
|
-
emit();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function releaseCamera(): Promise<void> {
|
|
197
|
-
await forceTorchOff();
|
|
198
|
-
// Nothing to release. Going through STOP/STOPPED here turned an idle
|
|
199
|
-
// controller into `stopped` on every preview unmount.
|
|
200
|
-
if (session.status === 'idle' || session.status === 'stopped') {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
dispatch({ type: 'STOP' });
|
|
204
|
-
try {
|
|
205
|
-
await engine.stop();
|
|
206
|
-
dispatch({ type: 'STOPPED' });
|
|
207
|
-
} catch (error) {
|
|
208
|
-
dispatch({ type: 'FAIL', code: isCameraError(error) ? error.code : 'captureFailed' });
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function startEngine(startOptions: StartOptions = {}): Promise<void> {
|
|
213
|
-
try {
|
|
214
|
-
await engine.start({ cameraPosition: startOptions.cameraPosition ?? cameraPosition });
|
|
215
|
-
dispatch({ type: 'STARTED' });
|
|
216
|
-
} catch (error) {
|
|
217
|
-
dispatch({ type: 'FAIL', code: isCameraError(error) ? error.code : 'cameraUnavailable' });
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function resumeAfterForeground(): void {
|
|
222
|
-
if (disposed || appStatus !== 'active' || !resumeOnForeground) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
resumeOnForeground = false;
|
|
226
|
-
if (!surfaceMounted || permission !== 'granted') {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
dispatch({ type: 'FOREGROUND', surfaceMounted });
|
|
230
|
-
if (session.status === 'starting') {
|
|
231
|
-
startEngine().catch(() => undefined);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function onAppState(status: AppStatus): void {
|
|
236
|
-
appStatus = status;
|
|
237
|
-
if (status === 'background') {
|
|
238
|
-
resumeOnForeground ||= session.status === 'active' || session.status === 'starting';
|
|
239
|
-
dispatch({ type: 'BACKGROUND' });
|
|
240
|
-
releasing = releaseCamera().catch(() => undefined);
|
|
241
|
-
} else {
|
|
242
|
-
// Wait for a release still in flight, or the restart lands on a
|
|
243
|
-
// `stopping` session and is dropped.
|
|
244
|
-
(releasing ?? Promise.resolve()).then(resumeAfterForeground).catch(() => undefined);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
169
|
/**
|
|
249
170
|
* Subscribes to permission and app-state changes and runs the sweep, on first
|
|
250
171
|
* use rather than at construction. React may build a controller during a
|
|
@@ -262,18 +183,23 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
262
183
|
// A revocation while running must tear the session down, not leave a
|
|
263
184
|
// frozen preview on screen (FR-007).
|
|
264
185
|
if (next !== 'granted') {
|
|
265
|
-
|
|
186
|
+
runtime.armResumeOnForeground();
|
|
266
187
|
if (session.status !== 'idle' && session.status !== 'stopped') {
|
|
267
|
-
|
|
188
|
+
runtime.release().catch(() => undefined);
|
|
268
189
|
}
|
|
269
190
|
}
|
|
270
191
|
});
|
|
271
|
-
const unsubscribeAppState = (options.subscribeToAppState ?? defaultAppStateSubscribe)(
|
|
272
|
-
onAppState,
|
|
192
|
+
const unsubscribeAppState = (options.subscribeToAppState ?? defaultAppStateSubscribe)(status =>
|
|
193
|
+
runtime.onAppState(status),
|
|
273
194
|
);
|
|
195
|
+
// The OS can take the camera at any moment — another app, an incoming
|
|
196
|
+
// call, a disconnected device. The session has to go with it (CAM-19).
|
|
197
|
+
const unsubscribeInterruptions =
|
|
198
|
+
engine.subscribeToInterruptions?.(code => runtime.onInterruption(code)) ?? (() => undefined);
|
|
274
199
|
detach = () => {
|
|
275
200
|
unsubscribePermission();
|
|
276
201
|
unsubscribeAppState();
|
|
202
|
+
unsubscribeInterruptions();
|
|
277
203
|
};
|
|
278
204
|
|
|
279
205
|
// FR-029: sweep our own temp directory once, asynchronously, so it can
|
|
@@ -351,7 +277,17 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
351
277
|
live('requestPermission');
|
|
352
278
|
permission = 'requesting';
|
|
353
279
|
emit();
|
|
354
|
-
|
|
280
|
+
try {
|
|
281
|
+
permission = await permissions.request();
|
|
282
|
+
} catch (error) {
|
|
283
|
+
// A native call that failed used to leave the state at `requesting`
|
|
284
|
+
// forever, with no way for the app to ask again (CAM-07).
|
|
285
|
+
permission = await permissions.getStatus().catch(() => 'unavailable' as const);
|
|
286
|
+
emit();
|
|
287
|
+
throw isCameraError(error)
|
|
288
|
+
? error
|
|
289
|
+
: new CameraError('permissionDenied', 'The permission request could not be completed');
|
|
290
|
+
}
|
|
355
291
|
emit();
|
|
356
292
|
return permission;
|
|
357
293
|
},
|
|
@@ -369,7 +305,7 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
369
305
|
cameraPosition = startOptions.cameraPosition;
|
|
370
306
|
}
|
|
371
307
|
dispatch({ type: 'START' });
|
|
372
|
-
await
|
|
308
|
+
await runtime.start(startOptions);
|
|
373
309
|
if (session.error) {
|
|
374
310
|
throw session.error;
|
|
375
311
|
}
|
|
@@ -379,7 +315,7 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
379
315
|
if (disposed) {
|
|
380
316
|
return;
|
|
381
317
|
}
|
|
382
|
-
await forceTorchOff();
|
|
318
|
+
await runtime.forceTorchOff();
|
|
383
319
|
dispatch({ type: 'PAUSE' });
|
|
384
320
|
},
|
|
385
321
|
|
|
@@ -387,17 +323,17 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
387
323
|
live('resume');
|
|
388
324
|
dispatch({ type: 'RESUME' });
|
|
389
325
|
if (session.status === 'starting') {
|
|
390
|
-
await
|
|
326
|
+
await runtime.start();
|
|
391
327
|
}
|
|
392
328
|
},
|
|
393
329
|
|
|
394
330
|
async stop() {
|
|
395
331
|
// An explicit stop is the user's decision; foregrounding must not undo it.
|
|
396
|
-
|
|
332
|
+
runtime.armResumeOnForeground();
|
|
397
333
|
if (disposed) {
|
|
398
334
|
return;
|
|
399
335
|
}
|
|
400
|
-
await
|
|
336
|
+
await runtime.release();
|
|
401
337
|
},
|
|
402
338
|
|
|
403
339
|
async capture(request: CaptureRequest = {}) {
|
|
@@ -498,6 +434,18 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
498
434
|
dispatch({ type: 'FAIL', code: 'cameraUnavailable' });
|
|
499
435
|
},
|
|
500
436
|
|
|
437
|
+
attachSurface(surface) {
|
|
438
|
+
return engine.attachSurface?.(surface) ?? (() => undefined);
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
reportSurfaceStarted(info) {
|
|
442
|
+
runtime.reportSurfaceStarted(info);
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
reportSurfaceFailed(code) {
|
|
446
|
+
runtime.reportSurfaceFailed(code);
|
|
447
|
+
},
|
|
448
|
+
|
|
501
449
|
setSurfaceMounted(mounted: boolean) {
|
|
502
450
|
if (disposed) {
|
|
503
451
|
return;
|
|
@@ -510,10 +458,10 @@ export function createCameraController(options: CameraControllerOptions = {}): C
|
|
|
510
458
|
return;
|
|
511
459
|
}
|
|
512
460
|
disposed = true;
|
|
513
|
-
|
|
461
|
+
runtime.armResumeOnForeground();
|
|
514
462
|
detach?.();
|
|
515
463
|
detach = null;
|
|
516
|
-
await
|
|
464
|
+
await runtime.release();
|
|
517
465
|
listeners.clear();
|
|
518
466
|
},
|
|
519
467
|
};
|