@ai-matrx/capture 0.5.4 → 0.5.6
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 +9 -0
- package/README.md +5 -1
- package/dist/react.cjs +47 -10
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +30 -1
- package/dist/react.d.ts +30 -1
- package/dist/react.js +47 -10
- package/dist/react.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/capture
|
|
2
2
|
|
|
3
|
+
## 0.5.6 — 2026-09-01
|
|
4
|
+
|
|
5
|
+
- Release-only correction for 0.5.5: npm accepted its metadata but its immutable public tarball remained unavailable (HTTP 404), so consumers could not reproduce/install it. This release contains the same verified terminal video-contract source.
|
|
6
|
+
|
|
7
|
+
## 0.5.5 — 2026-09-01
|
|
8
|
+
|
|
9
|
+
- Added the package-owned `finalizeCapturedVideo` terminal contract for default engines and host adapters. Emitted/final Blob MIME outranks the recorder-request MIME, filename extension is derived from that same authority, and delivered duration is a positive integer.
|
|
10
|
+
- The default engine now uses the shared finalizer, preventing a WebM payload from being mislabeled as MP4 when `MediaRecorder.mimeType` disagrees with emitted chunks.
|
|
11
|
+
|
|
3
12
|
## 0.5.4 — 2026-09-01
|
|
4
13
|
|
|
5
14
|
- Persisted-media editing now has an explicit fresh-byte port (`resolveEditBlob`). The package creates and owns an editor-private object URL instead of reusing a viewer URL that the host byte cache may evict and revoke while the decoded viewer frame remains visible.
|
package/README.md
CHANGED
|
@@ -76,12 +76,16 @@ instead of the default one; persisted items provide `resolve()` instead of
|
|
|
76
76
|
|
|
77
77
|
## What's NOT inside (by design)
|
|
78
78
|
|
|
79
|
-
- **No
|
|
79
|
+
- **No forced host runtime.** The package includes `useDefaultCaptureEngine` (its sole `getUserMedia` implementation under `src/engine/`) for runtime-less hosts. Apps with an existing cross-feature camera runtime inject a `CaptureCameraEngine`; AI Matrx does this for lease management, recorder journaling, and diagnostics.
|
|
80
80
|
- **No network, no storage** — enforced by `src/laws.test.ts`.
|
|
81
81
|
- Persisted-media rendering (thumbnails, lightboxes, durable refs) — that's `@ai-matrx/media`.
|
|
82
82
|
- Audio-only recording UX — `@ai-matrx/browser-audio`.
|
|
83
83
|
- QR decoding — `@ai-matrx/kit/qr`.
|
|
84
84
|
|
|
85
|
+
Both the default engine and injected host adapters finalize recordings through
|
|
86
|
+
`finalizeCapturedVideo`: emitted/final Blob MIME is authoritative, the filename
|
|
87
|
+
extension matches it, and delivered duration is a positive integer.
|
|
88
|
+
|
|
85
89
|
## Usage sketch
|
|
86
90
|
|
|
87
91
|
```tsx
|
package/dist/react.cjs
CHANGED
|
@@ -44,6 +44,7 @@ __export(react_exports, {
|
|
|
44
44
|
buildWarmMicConstraints: () => buildWarmMicConstraints,
|
|
45
45
|
classifyCameraBlockReason: () => classifyCameraBlockReason,
|
|
46
46
|
cropBlobToAspect: () => cropBlobToAspect,
|
|
47
|
+
finalizeCapturedVideo: () => finalizeCapturedVideo,
|
|
47
48
|
getMediaUrl: () => getMediaUrl,
|
|
48
49
|
getWarmMicState: () => getWarmMicState,
|
|
49
50
|
hardStopWarmMic: () => hardStopWarmMic,
|
|
@@ -3088,6 +3089,43 @@ function warmMicDebug() {
|
|
|
3088
3089
|
};
|
|
3089
3090
|
}
|
|
3090
3091
|
|
|
3092
|
+
// src/engine/video-result.ts
|
|
3093
|
+
var VIDEO_EXTENSIONS = {
|
|
3094
|
+
"video/mp4": "mp4",
|
|
3095
|
+
"video/webm": "webm",
|
|
3096
|
+
"video/ogg": "ogv",
|
|
3097
|
+
"video/quicktime": "mov"
|
|
3098
|
+
};
|
|
3099
|
+
function firstNonEmpty(...values) {
|
|
3100
|
+
for (const value of values) {
|
|
3101
|
+
const normalized = value?.trim();
|
|
3102
|
+
if (normalized) return normalized;
|
|
3103
|
+
}
|
|
3104
|
+
return "video/webm";
|
|
3105
|
+
}
|
|
3106
|
+
function finalizeCapturedVideo(options) {
|
|
3107
|
+
if (!Number.isFinite(options.durationMs)) {
|
|
3108
|
+
throw new Error("finalizeCapturedVideo requires a finite duration.");
|
|
3109
|
+
}
|
|
3110
|
+
const mime = firstNonEmpty(options.emittedMime, options.recorderMime);
|
|
3111
|
+
const container = mime.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
3112
|
+
const extension = VIDEO_EXTENSIONS[container];
|
|
3113
|
+
if (!extension) {
|
|
3114
|
+
throw new Error(
|
|
3115
|
+
`finalizeCapturedVideo requires a recognized video MIME; received "${mime}".`
|
|
3116
|
+
);
|
|
3117
|
+
}
|
|
3118
|
+
const durationMs = Math.max(1, Math.round(options.durationMs));
|
|
3119
|
+
const blob = new Blob([...options.parts], { type: mime });
|
|
3120
|
+
const timestampMs = options.timestampMs ?? Date.now();
|
|
3121
|
+
const file = new File(
|
|
3122
|
+
[blob],
|
|
3123
|
+
`${options.fileNamePrefix}-video-${timestampMs}.${extension}`,
|
|
3124
|
+
{ type: mime }
|
|
3125
|
+
);
|
|
3126
|
+
return { file, mime, durationMs };
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3091
3129
|
// src/engine/useDefaultEngine.ts
|
|
3092
3130
|
var RECORDING_MIME_LADDER = [
|
|
3093
3131
|
'video/mp4;codecs="avc1.42E01E,mp4a.40.2"',
|
|
@@ -3342,16 +3380,15 @@ function useDefaultCaptureEngine(options) {
|
|
|
3342
3380
|
if (entry.micHeld) releaseWarmMic();
|
|
3343
3381
|
recorderRef.current = null;
|
|
3344
3382
|
setRecording(false);
|
|
3345
|
-
const
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
);
|
|
3383
|
+
const captured = finalizeCapturedVideo({
|
|
3384
|
+
parts: entry.chunks,
|
|
3385
|
+
// Emitted chunk MIME is stronger evidence than recorder.mimeType.
|
|
3386
|
+
emittedMime: entry.chunks.find((chunk) => chunk.type)?.type,
|
|
3387
|
+
recorderMime: recorder.mimeType,
|
|
3388
|
+
durationMs: performance.now() - entry.startedAt,
|
|
3389
|
+
fileNamePrefix
|
|
3390
|
+
});
|
|
3391
|
+
callbacksRef.current.onVideo(captured.file, captured.durationMs);
|
|
3355
3392
|
};
|
|
3356
3393
|
recorder.start(1e3);
|
|
3357
3394
|
setRecordElapsedSeconds(0);
|