@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.
- package/README.ja.md +106 -0
- package/README.md +106 -0
- package/dist/auto.js +3 -0
- package/dist/auto.min.js +1 -0
- package/dist/index.d.ts +469 -0
- package/dist/index.esm.js +1154 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.esm.min.js +2 -0
- package/dist/index.esm.min.js.map +1 -0
- package/package.json +77 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @wcstack/camera
|
|
2
|
+
|
|
3
|
+
Web Components 向けの宣言的な**カメラ取得**(`<wcs-camera>`)と**メディア録画**(`<wcs-recorder>`)。`getUserMedia` + `MediaRecorder` がベース。フレームワーク非依存・ランタイム依存ゼロで、[wc-bindable プロトコル](../../docs)経由で公開します。
|
|
4
|
+
|
|
5
|
+
> English version: [README.md](./README.md).
|
|
6
|
+
|
|
7
|
+
## 着想: state を通らない生ハンドル
|
|
8
|
+
|
|
9
|
+
他のすべての `@wcstack` IO ノードは **シリアライズ可能な値** を state に出し入れします。`MediaStream` は異質です——**生のシリアライズ不能なライブハンドル**で、意味を持つのは参照同一性のみ、「確定」せず、リークが*物理的に観測可能*(カメラのインジケータが消えない)です。
|
|
10
|
+
|
|
11
|
+
そこで本パッケージは生ストリームを **state の外** に置きます:
|
|
12
|
+
|
|
13
|
+
- `<wcs-camera>` は shadow root に `<video>` プレビューを内包し、`srcObject` を**内部で**代入します——ハンドルは state 境界を越えません。
|
|
14
|
+
- 他の利用者(recorder や外部 `<video>`)へは `wcs-camera:stream-ready` **event-token** で公開し、**command-token の引数**として手渡します——トークンバスを transient に通過するだけで、reactive なパスには書かれません。
|
|
15
|
+
- state に入るのは **派生した値** だけです: `active`・`permission`・録画 `Blob`・object URL など。
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<wcs-camera data-wcs="
|
|
19
|
+
command.start: $command.camStart;
|
|
20
|
+
eventToken.streamReady: gotStream;
|
|
21
|
+
active: camActive; permission: camPerm"></wcs-camera>
|
|
22
|
+
|
|
23
|
+
<wcs-recorder data-wcs="
|
|
24
|
+
command.attachStream: $command.feed;
|
|
25
|
+
command.start: $command.recStart;
|
|
26
|
+
command.stop: $command.recStop;
|
|
27
|
+
recording: recording; objectURL: clipUrl;
|
|
28
|
+
eventToken.recorded: onRecorded"></wcs-recorder>
|
|
29
|
+
```
|
|
30
|
+
```js
|
|
31
|
+
$commandTokens: ["camStart", "feed", "recStart", "recStop"],
|
|
32
|
+
$eventTokens: ["gotStream", "onRecorded"],
|
|
33
|
+
$on: {
|
|
34
|
+
// 生の MediaStream は command 引数として転送するだけ——保存しない。
|
|
35
|
+
gotStream: (state, e) => state.$command.feed.emit(e.detail),
|
|
36
|
+
// 録画 Blob は値——state に入れてよい。
|
|
37
|
+
onRecorded: (state, e) => { state.clipBlob = e.detail.blob; },
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## `<wcs-camera>`
|
|
42
|
+
|
|
43
|
+
カメラストリームを取得しプレビューを描画します。取得は**明示的**で、`start()`(または `autostart` 属性)でプロンプトします。接続しただけでは取得しません。
|
|
44
|
+
|
|
45
|
+
**属性:** `facing-mode`(`user`/`environment`)・`device-id`・`audio`(マイクを有効化)・`width`・`height`・`autostart`・`keep-alive`(ページ非表示時に停止しない・録画中に立てる)。
|
|
46
|
+
|
|
47
|
+
**コマンド:** `start()`・`stop()`・`switchCamera()`(前後カメラ切替)。
|
|
48
|
+
|
|
49
|
+
**バインド可能な値:** `active`(ストリーム生存)・`permission` / `audioPermission`(`prompt`/`granted`/`denied`/`unsupported`)・`deviceId`・`devices`・`error`。
|
|
50
|
+
|
|
51
|
+
**イベント(event-token):** `streamReady`(`wcs-camera:stream-ready`、detail = 生 `MediaStream`)・`error`・`ended`(OS によるトラック剥奪)。`streamReady` の「プロパティ」は event-token 配線用で、値としてバインドしないでください。
|
|
52
|
+
|
|
53
|
+
### ライフサイクル
|
|
54
|
+
|
|
55
|
+
- `disconnectedCallback` で全トラックを `track.stop()` し、ハードウェアインジケータを消します。ストリームのリークは本ノード固有の唯一の失敗様式です。
|
|
56
|
+
- 要素を DOM 内で移動(remove → 再 append)すると `disconnectedCallback`(dispose・トラック停止)の後に `connectedCallback`(再 observe)が走ります。`autostart` 付きなら reconnect 時に再取得します(再プロンプトの可能性あり)。移動をまたいでストリームを維持したい場合は `autostart` を使わず自分で再 `start()` するか、要素を detach しないでください。
|
|
57
|
+
- 制約変更(`device-id`・`facing-mode`・`switchCamera()`)は **再取得**(stop → 新 `getUserMedia`)を起こし、世代カウンタでガードするため、supersede された取得が orphan ストリームを生かしたまま残すことはありません。
|
|
58
|
+
- ページ非表示中はストリームを suspend し、復帰時に再取得します——`keep-alive` がある場合を除く。`keep-alive: recording` をバインドすると録画中はカメラを生かし続けられます。
|
|
59
|
+
|
|
60
|
+
## `<wcs-recorder>`
|
|
61
|
+
|
|
62
|
+
`attachStream`(カメラの `stream-ready` からの直結チャネル)で受け取った**借用**ストリームを録画します。ストリームを所有・停止することはありません——それはカメラの責務です。
|
|
63
|
+
|
|
64
|
+
**属性:** `mime-type`・`timeslice`(この間隔で `dataavailable` を出す。省略時は stop で 1 つの `Blob`)・`audio-bits`・`video-bits`。
|
|
65
|
+
|
|
66
|
+
**コマンド:** `attachStream(stream)`・`start()`・`stop()`・`pause()`・`resume()`。
|
|
67
|
+
|
|
68
|
+
**バインド可能な値:** `recording`・`paused`・`duration`(ms・下記注記参照)・`mimeType`(**解決後**の録画タイプ。要求した `mime-type` 属性と異なる、または未指定時にブラウザが補完した値)・`blob`・`objectURL`・`error`。
|
|
69
|
+
|
|
70
|
+
**イベント(event-token):** `recorded`(`wcs-recorder:recorded`、detail = `{ blob, objectURL, mimeType, duration }`)・`dataavailable`(`timeslice` モードのみ)・`error`。
|
|
71
|
+
|
|
72
|
+
> **`duration` は stop/pause で確定する値で、録画中はライブ更新しません。** 内部に経過タイマーは持たないため、`start()` から最初の `pause()`/`stop()` までは `0` のままです。録画中の経過時間を表示したい場合は `recording` フラグを起点にクライアント側でタイマーを回してください。
|
|
73
|
+
|
|
74
|
+
> **`mimeType` は「要求」と「解決後」の二面を持ちます。** **入力**は `mime-type` *属性*(recorder に使ってほしいタイプの要求)、**出力**は `mimeType` *バインド可能値*(ブラウザが実際に選んだタイプ。`wcs-recorder:mimetype-changed` で publish)です。基底名を共有しますが別サーフェスで、属性側で要求を設定し(`mime-type` 属性/要素 setter)、値プロパティ側で解決後タイプを読みます。`mimeType` を読んでも書いた値はエコーされません——録画結果を反映します。
|
|
75
|
+
|
|
76
|
+
組み立てた `Blob` は structured-clone 可能なので *値* であり、state に流せます——例えば `new File([blob], "clip.webm")` を [`@wcstack/upload`](../upload/) へ。object URL は管理され、新しいクリップ前と **`disconnectedCallback`(dispose)時** に前の URL を revoke します。
|
|
77
|
+
|
|
78
|
+
> **`objectURL` の寿命は recorder に束縛されます。** dispose が最後の object URL を revoke し、**さらに次の録画は前のクリップの URL を新規発行前に revoke する**ため、古い URL を指したままの `<video src>` / `<wcs-upload>` は `<wcs-recorder>` が外れる・次のクリップが完成した時点で壊れます。常に最新の `objectURL` / `recorded` 値を追従し、古い URL を保持しないでください。URL を寿命の長い消費側へ渡す場合は、URL を使い終わるまで recorder を接続したままにするか、`Blob` から自前で URL を作り(`URL.createObjectURL(blob)`)revoke も自分で管理してください。structured-clone 可能な **`blob`** にはこの結合がありません——`Blob` を state に流し、利用箇所で URL を生成するのが安全です。
|
|
79
|
+
|
|
80
|
+
## ヘッドレス Core
|
|
81
|
+
|
|
82
|
+
`CameraCore` / `RecorderCore` を非 DOM 用途向けにエクスポートしています(`@wc-bindable/core` の `bind()`)。Shell は薄いラッパです。
|
|
83
|
+
|
|
84
|
+
## 注意・落とし穴
|
|
85
|
+
|
|
86
|
+
- **セキュアコンテキスト(https)必須。** `getUserMedia` は `file://` / 素の `http://` では使えません。
|
|
87
|
+
- **カメラインジケータ=リーク検出器。** 終了後も点灯したままなら、トラックが停止されていません。
|
|
88
|
+
- **ユーザージェスチャ。** 一部ブラウザは `getUserMedia` をユーザー操作起点で要求します。タイマーから撃つと無言で失敗することがあります(`error` に出ます・throw しません)。
|
|
89
|
+
- **エラーは分類され throw されません:** `NotAllowedError`(拒否)・`NotFoundError`(デバイス無し)・`NotReadableError`(他アプリ使用中)・`OverconstrainedError`。
|
|
90
|
+
- **ストリームの所有権はカメラ側。** recorder は借用するだけ。録画中のカメラ切替は非対応(先に録画停止)。
|
|
91
|
+
- **mimeType の対応はブラウザ差**(webm/mp4)。非対応の `mime-type` は無視され既定が使われます。
|
|
92
|
+
|
|
93
|
+
## インストール
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<script type="module" src="https://esm.run/@wcstack/camera/auto"></script>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
またはプログラムから:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import { bootstrapCamera } from "@wcstack/camera";
|
|
103
|
+
bootstrapCamera();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
MIT © mogera551
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @wcstack/camera
|
|
2
|
+
|
|
3
|
+
Declarative **camera capture** (`<wcs-camera>`) and **media recording** (`<wcs-recorder>`) for Web Components, built on `getUserMedia` + `MediaRecorder`. Framework-agnostic, zero runtime dependencies, exposed through the [wc-bindable protocol](../../docs).
|
|
4
|
+
|
|
5
|
+
> 日本語版は [README.ja.md](./README.ja.md)。
|
|
6
|
+
|
|
7
|
+
## The idea: a live handle that never touches state
|
|
8
|
+
|
|
9
|
+
Every other `@wcstack` IO node moves **serializable values** in and out of state. A `MediaStream` is different — it is a **live, non-serializable resource handle**: reference identity is all that matters, it never "settles", and leaking it is *physically observable* (the camera indicator stays on).
|
|
10
|
+
|
|
11
|
+
So this package keeps the live stream **out of state entirely**:
|
|
12
|
+
|
|
13
|
+
- `<wcs-camera>` owns a `<video>` preview in its shadow root and assigns `srcObject` **internally** — the handle never crosses the state boundary.
|
|
14
|
+
- For other consumers (a recorder, an external `<video>`), the stream is published via the `wcs-camera:stream-ready` **event-token**, and handed on as a **command-token argument** — it passes through the token bus transiently and is never written to a reactive path.
|
|
15
|
+
- Only **derived values** live in state: `active`, `permission`, the recorded `Blob`, an object URL, etc.
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<wcs-camera data-wcs="
|
|
19
|
+
command.start: $command.camStart;
|
|
20
|
+
eventToken.streamReady: gotStream;
|
|
21
|
+
active: camActive; permission: camPerm"></wcs-camera>
|
|
22
|
+
|
|
23
|
+
<wcs-recorder data-wcs="
|
|
24
|
+
command.attachStream: $command.feed;
|
|
25
|
+
command.start: $command.recStart;
|
|
26
|
+
command.stop: $command.recStop;
|
|
27
|
+
recording: recording; objectURL: clipUrl;
|
|
28
|
+
eventToken.recorded: onRecorded"></wcs-recorder>
|
|
29
|
+
```
|
|
30
|
+
```js
|
|
31
|
+
$commandTokens: ["camStart", "feed", "recStart", "recStop"],
|
|
32
|
+
$eventTokens: ["gotStream", "onRecorded"],
|
|
33
|
+
$on: {
|
|
34
|
+
// The raw MediaStream is forwarded as a command argument — never stored.
|
|
35
|
+
gotStream: (state, e) => state.$command.feed.emit(e.detail),
|
|
36
|
+
// The recorded Blob is a value — it may live in state.
|
|
37
|
+
onRecorded: (state, e) => { state.clipBlob = e.detail.blob; },
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## `<wcs-camera>`
|
|
42
|
+
|
|
43
|
+
Acquires a camera stream and renders a preview. Acquisition is **explicit** — `start()` (or the `autostart` attribute) prompts; merely connecting does not.
|
|
44
|
+
|
|
45
|
+
**Attributes:** `facing-mode` (`user`/`environment`), `device-id`, `audio` (opt the microphone in), `width`, `height`, `autostart`, `keep-alive` (do not suspend on page-hidden — set while recording).
|
|
46
|
+
|
|
47
|
+
**Commands:** `start()`, `stop()`, `switchCamera()` (toggle front/back).
|
|
48
|
+
|
|
49
|
+
**Bindable values:** `active` (a stream is live), `permission` / `audioPermission` (`prompt`/`granted`/`denied`/`unsupported`), `deviceId`, `devices`, `error`.
|
|
50
|
+
|
|
51
|
+
**Events (event-token):** `streamReady` (`wcs-camera:stream-ready`, detail = the live `MediaStream`), `error`, `ended` (a track was revoked by the OS). The `streamReady` "property" exists for event-token wiring only — never bind it as a value.
|
|
52
|
+
|
|
53
|
+
### Lifecycle
|
|
54
|
+
|
|
55
|
+
- On `disconnectedCallback` every track is stopped (`track.stop()`), clearing the hardware indicator. Leaking a stream is the one failure mode unique to this node.
|
|
56
|
+
- Moving the element in the DOM (remove → re-append) runs `disconnectedCallback` (dispose, stop tracks) then `connectedCallback` (observe again). With `autostart` it re-acquires on reconnect (and may re-prompt). To keep a stream across a move, avoid `autostart` and re-`start()` yourself, or don't detach the element.
|
|
57
|
+
- A constraints change (`device-id`, `facing-mode`, `switchCamera()`) **re-acquires** (stop → new `getUserMedia`), guarded by a generation counter so a superseded acquire cannot leave an orphan stream live.
|
|
58
|
+
- While the page is hidden the stream is suspended and re-acquired on return — unless `keep-alive` is set. Bind `keep-alive: recording` to keep the camera alive while recording.
|
|
59
|
+
|
|
60
|
+
## `<wcs-recorder>`
|
|
61
|
+
|
|
62
|
+
Records a **borrowed** stream received via `attachStream` (the direct channel from a camera's `stream-ready`). It never owns or stops the stream — that is the camera's job.
|
|
63
|
+
|
|
64
|
+
**Attributes:** `mime-type`, `timeslice` (emit `dataavailable` chunks on this interval; omit for one `Blob` on stop), `audio-bits`, `video-bits`.
|
|
65
|
+
|
|
66
|
+
**Commands:** `attachStream(stream)`, `start()`, `stop()`, `pause()`, `resume()`.
|
|
67
|
+
|
|
68
|
+
**Bindable values:** `recording`, `paused`, `duration` (ms — see note below), `mimeType` (the **resolved** recording type, which may differ from the requested `mime-type` attribute or be filled in when none was requested), `blob`, `objectURL`, `error`.
|
|
69
|
+
|
|
70
|
+
**Events (event-token):** `recorded` (`wcs-recorder:recorded`, detail = `{ blob, objectURL, mimeType, duration }`), `dataavailable` (only in `timeslice` mode), `error`.
|
|
71
|
+
|
|
72
|
+
> **`duration` is finalized at stop/pause, not live.** There is no internal ticking timer: `duration` stays `0` from `start()` until the first `pause()` or `stop()`. For a live elapsed counter while recording, drive your own client-side timer off the `recording` flag.
|
|
73
|
+
|
|
74
|
+
> **`mimeType` has two sides — request vs. resolved.** The **input** is the `mime-type` *attribute* (what you ask the recorder to use). The **output** is the `mimeType` *bindable value* (what the browser actually picked, published via `wcs-recorder:mimetype-changed`). They share a base name by design but are distinct surfaces: bind the attribute to set the request (`mime-type` attribute / element setter), and bind the value property to read the resolved type. Don't expect reading `mimeType` to echo back what you wrote — it reflects the recording, not the request.
|
|
75
|
+
|
|
76
|
+
The assembled `Blob` is structured-clone friendly, so it *is* a value and may flow through state — for example `new File([blob], "clip.webm")` into [`@wcstack/upload`](../upload/). The object URL is managed: the previous one is revoked before a new clip **and on `disconnectedCallback` (dispose)**.
|
|
77
|
+
|
|
78
|
+
> **`objectURL` lifetime is bound to the recorder.** Because dispose revokes the last object URL — **and a new recording revokes the previous clip's URL before minting the next** — any `<video src>` / `<wcs-upload>` still pointing at an old URL breaks once the `<wcs-recorder>` is removed or the next clip completes. Always follow the latest `objectURL` / `recorded` value; never pin a stale one. If you hand the URL to a longer-lived consumer, either keep the recorder mounted for as long as the URL is in use, or build your own URL from the `Blob` (`URL.createObjectURL(blob)`) and own its revoke. The structured-clone-friendly **`blob`** has no such coupling — prefer flowing the `Blob` through state and minting URLs at the point of use.
|
|
79
|
+
|
|
80
|
+
## Headless cores
|
|
81
|
+
|
|
82
|
+
`CameraCore` and `RecorderCore` are exported for non-DOM use (`bind()` from `@wc-bindable/core`). The Shells are thin wrappers.
|
|
83
|
+
|
|
84
|
+
## Notes & gotchas
|
|
85
|
+
|
|
86
|
+
- **Secure context (https) required.** `getUserMedia` is unavailable on `file://` / plain `http://`.
|
|
87
|
+
- **The camera indicator = a leak detector.** If it stays on after you are done, a track was not stopped.
|
|
88
|
+
- **User gesture.** Some browsers require `getUserMedia` to be triggered by a user action; firing it from a timer may silently fail (surfaced via `error`, never thrown).
|
|
89
|
+
- **Errors are classified, never thrown:** `NotAllowedError` (denied), `NotFoundError` (no device), `NotReadableError` (in use by another app), `OverconstrainedError`.
|
|
90
|
+
- **Stream ownership stays with the camera.** A recorder borrows it; switching cameras while recording is not supported (stop recording first).
|
|
91
|
+
- **mimeType support varies** (webm/mp4). Unsupported `mime-type` values are ignored and the browser default is used.
|
|
92
|
+
|
|
93
|
+
## Install
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<script type="module" src="https://esm.run/@wcstack/camera/auto"></script>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Or programmatically:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import { bootstrapCamera } from "@wcstack/camera";
|
|
103
|
+
bootstrapCamera();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
MIT © mogera551
|
package/dist/auto.js
ADDED
package/dist/auto.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bootstrapCamera}from"./index.esm.min.js";bootstrapCamera();
|