@wcstack/camera 1.14.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.
@@ -0,0 +1,469 @@
1
+ interface ITagNames {
2
+ readonly camera: string;
3
+ readonly recorder: string;
4
+ }
5
+ interface IWritableTagNames {
6
+ camera?: string;
7
+ recorder?: string;
8
+ }
9
+ interface IConfig {
10
+ readonly tagNames: ITagNames;
11
+ }
12
+ interface IWritableConfig {
13
+ tagNames?: IWritableTagNames;
14
+ }
15
+ interface IWcBindableProperty {
16
+ readonly name: string;
17
+ readonly event: string;
18
+ readonly getter?: (event: Event) => any;
19
+ }
20
+ interface IWcBindableInput {
21
+ readonly name: string;
22
+ readonly attribute?: string;
23
+ }
24
+ interface IWcBindableCommand {
25
+ readonly name: string;
26
+ readonly async?: boolean;
27
+ }
28
+ interface IWcBindable {
29
+ readonly protocol: "wc-bindable";
30
+ readonly version: number;
31
+ readonly properties: IWcBindableProperty[];
32
+ readonly inputs?: IWcBindableInput[];
33
+ readonly commands?: IWcBindableCommand[];
34
+ }
35
+ /**
36
+ * Permission state for camera / microphone, mirroring the Permissions API
37
+ * `PermissionState` plus `"unsupported"` for environments without
38
+ * `navigator.permissions` (or where the `camera` / `microphone` descriptor
39
+ * cannot be queried, e.g. Firefox).
40
+ */
41
+ type MediaPermissionState = "prompt" | "granted" | "denied" | "unsupported";
42
+ type FacingMode = "user" | "environment";
43
+ /**
44
+ * Normalized getUserMedia / MediaRecorder failure. `name` mirrors the DOMException
45
+ * name (`"NotAllowedError"`, `"NotFoundError"`, `"NotReadableError"`,
46
+ * `"OverconstrainedError"`, …); `"unsupported"` is surfaced when the API is absent
47
+ * or the context is insecure.
48
+ */
49
+ interface WcsMediaErrorDetail {
50
+ name: string;
51
+ message: string;
52
+ }
53
+ /**
54
+ * Settable camera constraints (Shell attributes). `audio` opts microphone in;
55
+ * `facingMode` / `deviceId` / `width` / `height` shape the requested track.
56
+ */
57
+ interface CameraConstraints {
58
+ audio?: boolean;
59
+ facingMode?: FacingMode;
60
+ deviceId?: string;
61
+ width?: number;
62
+ height?: number;
63
+ }
64
+ /**
65
+ * Structured-clone-friendly snapshot of a `MediaDeviceInfo` (video inputs). The
66
+ * live device objects are exposed as plain copies so the list flows through data
67
+ * binding as a value.
68
+ */
69
+ interface MediaDeviceSnapshot {
70
+ deviceId: string;
71
+ label: string;
72
+ groupId: string;
73
+ kind: string;
74
+ }
75
+ /**
76
+ * Value types for CameraCore (headless) — the observable state properties.
77
+ *
78
+ * Note: the live `MediaStream` is intentionally NOT here. It is a non-serializable
79
+ * live handle and never flows through reactive state — it is published only via
80
+ * the `wcs-camera:stream-ready` event for the direct element→element channel.
81
+ * See docs/camera-recorder-tag-design.md §1.
82
+ */
83
+ interface WcsCameraCoreValues {
84
+ active: boolean;
85
+ permission: MediaPermissionState;
86
+ audioPermission: MediaPermissionState | null;
87
+ deviceId: string | null;
88
+ devices: MediaDeviceSnapshot[];
89
+ error: WcsMediaErrorDetail | null;
90
+ }
91
+ type WcsCameraValues = WcsCameraCoreValues;
92
+ interface WcsCameraInputs {
93
+ audio: boolean;
94
+ facingMode: FacingMode;
95
+ deviceId: string;
96
+ width: number;
97
+ height: number;
98
+ /** Acquire the camera automatically on connect (otherwise wait for `start()`). */
99
+ autostart: boolean;
100
+ /** Do not suspend the stream when the page is hidden (set while recording). */
101
+ keepAlive: boolean;
102
+ }
103
+ interface WcsCameraCoreCommands {
104
+ start(): void;
105
+ stop(): void;
106
+ switchCamera(): void;
107
+ }
108
+ type WcsCameraCommands = WcsCameraCoreCommands;
109
+ /**
110
+ * Per-recording parameters accepted by `start()`, mirroring the settable fields
111
+ * of `MediaRecorder`.
112
+ */
113
+ interface RecorderOptions {
114
+ mimeType?: string;
115
+ /** Emit `dataavailable` chunks on this interval (ms). Omit to receive one Blob on stop. */
116
+ timeslice?: number;
117
+ audioBitsPerSecond?: number;
118
+ videoBitsPerSecond?: number;
119
+ }
120
+ /** Detail of the `wcs-recorder:recorded` event — the assembled clip. */
121
+ interface WcsRecordedDetail {
122
+ blob: Blob;
123
+ objectURL: string;
124
+ mimeType: string;
125
+ duration: number;
126
+ }
127
+ /**
128
+ * Value types for RecorderCore (headless) — the observable state properties.
129
+ * `blob` is structured-clone-friendly (a settled value, unlike MediaStream) so it
130
+ * may flow through state. `objectURL` is a string with a revoke lifecycle the Core
131
+ * manages.
132
+ */
133
+ interface WcsRecorderCoreValues {
134
+ recording: boolean;
135
+ paused: boolean;
136
+ /**
137
+ * Recorded length in ms, **finalized at stop/pause** — there is no live ticking
138
+ * during recording (no internal timer). It stays 0 from start() until the first
139
+ * pause() or stop(). For a live elapsed display, drive your own client-side timer
140
+ * off the `recording` flag.
141
+ */
142
+ duration: number;
143
+ mimeType: string;
144
+ /**
145
+ * The last assembled clip — **retained across a new recording**. start() does NOT
146
+ * clear `blob` / `objectURL`; they hold the previous clip until the next one is
147
+ * assembled at stop() (the old object URL is revoked then, atomically). So while a
148
+ * 2nd recording is in flight, `recording` is true *and* these still expose the prior
149
+ * clip — intentional, so a "download last clip" affordance never goes briefly empty.
150
+ */
151
+ blob: Blob | null;
152
+ objectURL: string | null;
153
+ error: WcsMediaErrorDetail | null;
154
+ }
155
+ type WcsRecorderValues = WcsRecorderCoreValues;
156
+ interface WcsRecorderInputs {
157
+ mimeType: string;
158
+ timeslice: number;
159
+ audioBitsPerSecond: number;
160
+ videoBitsPerSecond: number;
161
+ }
162
+ interface WcsRecorderCoreCommands {
163
+ attachStream(stream: MediaStream): void;
164
+ start(options?: RecorderOptions): void;
165
+ stop(): void;
166
+ pause(): void;
167
+ resume(): void;
168
+ }
169
+ interface WcsRecorderCommands {
170
+ /** Borrow a stream for recording — the direct-channel sink (`command.attachStream`). */
171
+ attachStream(stream: MediaStream): void;
172
+ start(): void;
173
+ stop(): void;
174
+ pause(): void;
175
+ resume(): void;
176
+ }
177
+
178
+ declare function bootstrapCamera(userConfig?: IWritableConfig): void;
179
+
180
+ declare function getConfig(): IConfig;
181
+
182
+ /**
183
+ * Headless camera-capture primitive. Wraps getUserMedia + the Permissions API and
184
+ * exposes a `MediaStream` through the wc-bindable protocol — but the live stream is
185
+ * NEVER published as a reactive value. It is a non-serializable live handle: it
186
+ * flows out only via the `wcs-camera:stream-ready` event so a consumer (the preview
187
+ * `<video>`, a `<wcs-recorder>`) can bind it directly to an element property,
188
+ * bypassing serializable state. See docs/camera-recorder-tag-design.md §1/§2.
189
+ *
190
+ * The observable value surface is strictly derived data: `active` (is a stream
191
+ * live — the "actual" half of the desired/actual pair), `permission` /
192
+ * `audioPermission` (two-phase: Permissions API monitor + getUserMedia outcome),
193
+ * `deviceId` / `devices`, and `error`. Failures never throw — they surface through
194
+ * `error`.
195
+ */
196
+ declare class CameraCore extends EventTarget {
197
+ static wcBindable: IWcBindable;
198
+ private _target;
199
+ private _active;
200
+ private _permission;
201
+ private _audioPermission;
202
+ private _deviceId;
203
+ private _devices;
204
+ private _error;
205
+ private _stream;
206
+ private _desired;
207
+ private _constraints;
208
+ private _gen;
209
+ private _subscribed;
210
+ private _camWatcher;
211
+ private _micWatcher;
212
+ private _ready;
213
+ constructor(target?: EventTarget);
214
+ get active(): boolean;
215
+ get permission(): MediaPermissionState;
216
+ get audioPermission(): MediaPermissionState | null;
217
+ get deviceId(): string | null;
218
+ get devices(): MediaDeviceSnapshot[];
219
+ get error(): WcsMediaErrorDetail | null;
220
+ get ready(): Promise<void>;
221
+ private _setActive;
222
+ private _setPermission;
223
+ private _setAudioPermission;
224
+ private _setDeviceId;
225
+ private _setDevices;
226
+ private _setError;
227
+ private _dispatch;
228
+ private _devicesEqual;
229
+ /**
230
+ * Begin observing permissions for the given constraints. Idempotent while
231
+ * already subscribed. The first call (or one after dispose()) starts the
232
+ * camera/microphone permission monitors. Acquisition itself is driven separately
233
+ * by start() / autostart — observing does not prompt.
234
+ */
235
+ observe(constraints: CameraConstraints): Promise<void>;
236
+ /** Acquire the camera (sets desired=true). Prompts on first use. */
237
+ start(): void;
238
+ /** Release the camera (sets desired=false), stopping all tracks. */
239
+ stop(): void;
240
+ /**
241
+ * Toggle facingMode (user ↔ environment) and re-acquire if active. This is the
242
+ * headless, DOM-free path: it flips the Core's internal `_constraints` (the single
243
+ * source of truth for a standalone Core) and re-acquires while desired.
244
+ *
245
+ * Note: the `<wcs-camera>` Shell does NOT delegate to this — it keeps the DOM
246
+ * attributes authoritative and drives its own single re-acquire (see Camera.ts
247
+ * switchCamera). Both reach the same end state; the split exists because the Shell
248
+ * must keep its declared attributes in sync, which a Core has no notion of.
249
+ */
250
+ switchCamera(): void;
251
+ /**
252
+ * Suspend the live stream while keeping `desired` — for page-hidden. Stops tracks
253
+ * (clearing the hardware indicator) but remembers that the camera should resume.
254
+ *
255
+ * Bumps `_gen` to supersede any in-flight acquire: without this, an acquire that
256
+ * resolves *after* the page went hidden would assign `_stream` and set active —
257
+ * re-lighting the camera behind a no-op suspend (the stream had not been assigned
258
+ * yet, so the `if (_stream)` release below could not reach it). The superseded
259
+ * acquire stops its just-acquired orphan stream on resolve (see `_acquire`).
260
+ */
261
+ suspend(): void;
262
+ /** Re-acquire if the camera is desired but not currently active — for page-visible. */
263
+ resume(): void;
264
+ /** Tear down: stop the stream and detach permission listeners. */
265
+ dispose(): void;
266
+ private _initPermissions;
267
+ private _onDeviceChange;
268
+ private _reconcileAudioWatcher;
269
+ private _restart;
270
+ private _acquire;
271
+ private _release;
272
+ private _onTrackEnded;
273
+ private _updateDeviceId;
274
+ }
275
+
276
+ /**
277
+ * `<wcs-camera>` — declarative camera capture with a built-in preview.
278
+ *
279
+ * The element owns a `<video>` in its shadow root and assigns the live
280
+ * `MediaStream` to `video.srcObject` internally, so the non-serializable handle
281
+ * never crosses the state boundary (design §1, case B). For consumers (a
282
+ * `<wcs-recorder>`, an external `<video>`), the stream is also published via the
283
+ * `wcs-camera:stream-ready` event-token for the direct element→element channel
284
+ * (design §2).
285
+ *
286
+ * Acquisition is explicit: `start()` / the `autostart` attribute prompt and
287
+ * acquire; merely connecting does not. While the page is hidden the stream is
288
+ * suspended (clearing the camera indicator) and re-acquired on return, unless
289
+ * `keep-alive` is set (e.g. during recording).
290
+ */
291
+ declare class WcsCamera extends HTMLElement {
292
+ static hasConnectedCallbackPromise: boolean;
293
+ static wcBindable: IWcBindable;
294
+ static observedAttributes: string[];
295
+ private _core;
296
+ private _video;
297
+ private _connectedCallbackPromise;
298
+ private _connected;
299
+ private _batchingAttrs;
300
+ constructor();
301
+ get audio(): boolean;
302
+ set audio(value: boolean);
303
+ get facingMode(): FacingMode;
304
+ set facingMode(value: FacingMode);
305
+ get deviceId(): string;
306
+ set deviceId(value: string);
307
+ get width(): number;
308
+ set width(value: number);
309
+ get height(): number;
310
+ set height(value: number);
311
+ get autostart(): boolean;
312
+ set autostart(value: boolean);
313
+ get keepAlive(): boolean;
314
+ set keepAlive(value: boolean);
315
+ /** The internal preview `<video>` (for advanced styling/measurement). */
316
+ get videoElement(): HTMLVideoElement;
317
+ get active(): boolean;
318
+ get permission(): MediaPermissionState;
319
+ get audioPermission(): MediaPermissionState | null;
320
+ get devices(): MediaDeviceSnapshot[];
321
+ get error(): WcsMediaErrorDetail | null;
322
+ get connectedCallbackPromise(): Promise<void>;
323
+ start(): void;
324
+ stop(): void;
325
+ /**
326
+ * Toggle the front/back camera by updating the DOM attributes (the single source
327
+ * of truth), not just the Core's internal constraints. Deliberately does NOT call
328
+ * `CameraCore.switchCamera()` (which would mutate the Core's constraints behind the
329
+ * DOM's back, leaving the declared attributes stale). `device-id` is removed because
330
+ * it would otherwise take precedence over `facing-mode` (see buildConstraints) —
331
+ * leaving it pinned would silently undo the switch on the next re-acquire. Both
332
+ * attribute writes are batched (see `_batchingAttrs`) so they drive exactly ONE
333
+ * re-acquire here, with the final constraints — never an early acquire on a
334
+ * half-updated state. The DOM and the live camera stay in agreement.
335
+ */
336
+ switchCamera(): void;
337
+ private _toggleAttr;
338
+ private _numberAttr;
339
+ private _constraints;
340
+ private _onStreamReady;
341
+ private _onActiveChanged;
342
+ connectedCallback(): void;
343
+ disconnectedCallback(): void;
344
+ attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
345
+ private _onVisibilityChange;
346
+ }
347
+
348
+ /**
349
+ * Headless media-recording primitive. Wraps MediaRecorder, consuming a borrowed
350
+ * `MediaStream` (received via `attachStream` over the direct channel — see
351
+ * docs/camera-recorder-tag-design.md §2) and producing a `Blob` clip.
352
+ *
353
+ * Ownership: the stream is BORROWED, never owned. The Core never stops its tracks
354
+ * — that is the camera's job (the acquirer owns release). Stopping here would tear
355
+ * down a stream that may still be previewing.
356
+ *
357
+ * Non-goal: the Core does NOT subscribe to the borrowed tracks' `ended` and does NOT
358
+ * auto-terminate a recording when the underlying stream dies (camera stop / OS
359
+ * revoke / switchCamera re-acquire). Reacting would mean reaching into a stream we do
360
+ * not own. The MediaRecorder keeps running against the now-dead source until an
361
+ * explicit stop(); the consumer (which owns the camera lifecycle) is responsible for
362
+ * stopping the recording when it tears the stream down. stop() then assembles
363
+ * whatever chunks were captured — never throws.
364
+ *
365
+ * Output: `dataavailable` chunks are collected and assembled into one `Blob` on
366
+ * stop, published via `wcs-recorder:recorded`. The `Blob` is structured-clone
367
+ * friendly (a settled value, unlike MediaStream) so it may flow through state.
368
+ * `objectURL` is a managed string — the Core revokes the previous URL before
369
+ * issuing a new one and on dispose. Failures never throw.
370
+ */
371
+ declare class RecorderCore extends EventTarget {
372
+ static wcBindable: IWcBindable;
373
+ private _target;
374
+ private _recording;
375
+ private _paused;
376
+ private _duration;
377
+ private _mimeType;
378
+ private _blob;
379
+ private _objectURL;
380
+ private _error;
381
+ private _recorder;
382
+ private _stream;
383
+ private _chunks;
384
+ private _timeslice;
385
+ private _startTime;
386
+ private _gen;
387
+ constructor(target?: EventTarget);
388
+ get recording(): boolean;
389
+ get paused(): boolean;
390
+ get duration(): number;
391
+ get mimeType(): string;
392
+ get blob(): Blob | null;
393
+ get objectURL(): string | null;
394
+ get error(): WcsMediaErrorDetail | null;
395
+ private _setRecording;
396
+ private _setPaused;
397
+ private _setDuration;
398
+ private _setMimeType;
399
+ private _setError;
400
+ private _dispatch;
401
+ /**
402
+ * Borrow a stream for recording (the direct-channel sink). Synchronous, no
403
+ * await: the live handle is captured by reference and never stored in state.
404
+ * Does NOT stop any previously-borrowed stream — ownership stays with the camera.
405
+ *
406
+ * Re-attaching mid-recording takes effect on the NEXT start(), not the current
407
+ * recording: the live MediaRecorder was already constructed around the previous
408
+ * stream and keeps recording it until stop(). The borrowed reference is swapped so
409
+ * the following start() uses the new stream.
410
+ */
411
+ attachStream(stream: MediaStream): void;
412
+ /** Start recording the borrowed stream. Never throws — surfaces `error`. */
413
+ start(options?: RecorderOptions): void;
414
+ /** Stop recording; the assembled Blob is published from the recorder's onstop. */
415
+ stop(): void;
416
+ pause(): void;
417
+ resume(): void;
418
+ /** Stop in-flight recording, revoke the last object URL, drop the borrowed stream. */
419
+ dispose(): void;
420
+ private _assembleBlob;
421
+ private _revokeUrl;
422
+ private _createUrl;
423
+ private _isTypeSupported;
424
+ private _now;
425
+ private _elapsed;
426
+ }
427
+
428
+ /**
429
+ * `<wcs-recorder>` — declarative media recording. Wraps RecorderCore and records a
430
+ * borrowed `MediaStream` received via the `attachStream` command (the direct
431
+ * channel from `<wcs-camera>`'s `stream-ready`). It never owns or stops the stream.
432
+ *
433
+ * Recording parameters (`mime-type` / `timeslice` / bitrates) are mirrored
434
+ * attributes. The assembled clip is published as `wcs-recorder:recorded`
435
+ * (`{ blob, objectURL, mimeType, duration }`) and the `blob` / `objectURL` value
436
+ * properties — a settled `Blob` is a value and may flow through state.
437
+ */
438
+ declare class WcsRecorder extends HTMLElement {
439
+ static wcBindable: IWcBindable;
440
+ private _core;
441
+ constructor();
442
+ get mimeType(): string;
443
+ set mimeType(value: string);
444
+ get timeslice(): number;
445
+ set timeslice(value: number);
446
+ get audioBitsPerSecond(): number;
447
+ set audioBitsPerSecond(value: number);
448
+ get videoBitsPerSecond(): number;
449
+ set videoBitsPerSecond(value: number);
450
+ get recording(): boolean;
451
+ get paused(): boolean;
452
+ get duration(): number;
453
+ get blob(): Blob | null;
454
+ get objectURL(): string | null;
455
+ get error(): WcsMediaErrorDetail | null;
456
+ /** Borrow a stream (the direct-channel sink). */
457
+ attachStream(stream: MediaStream): void;
458
+ start(): void;
459
+ stop(): void;
460
+ pause(): void;
461
+ resume(): void;
462
+ private _numberAttr;
463
+ private _options;
464
+ connectedCallback(): void;
465
+ disconnectedCallback(): void;
466
+ }
467
+
468
+ export { CameraCore, RecorderCore, WcsCamera, WcsRecorder, bootstrapCamera, getConfig };
469
+ export type { CameraConstraints, FacingMode, IWritableConfig, IWritableTagNames, MediaDeviceSnapshot, MediaPermissionState, RecorderOptions, WcsCameraCommands, WcsCameraCoreCommands, WcsCameraCoreValues, WcsCameraInputs, WcsCameraValues, WcsMediaErrorDetail, WcsRecordedDetail, WcsRecorderCommands, WcsRecorderCoreCommands, WcsRecorderCoreValues, WcsRecorderInputs, WcsRecorderValues };