@ai-matrx/capture 0.5.3 → 0.5.5
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 +10 -0
- package/README.md +5 -1
- package/dist/react.cjs +112 -15
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +33 -1
- package/dist/react.d.ts +33 -1
- package/dist/react.js +112 -15
- package/dist/react.js.map +1 -1
- package/package.json +2 -2
package/dist/react.d.cts
CHANGED
|
@@ -231,6 +231,9 @@ interface CaptureMediaItem {
|
|
|
231
231
|
/** Async URL resolution for persisted items. Called at most once per key
|
|
232
232
|
* while cached; concurrent callers share the in-flight promise. */
|
|
233
233
|
resolve?: () => Promise<ResolvedMedia>;
|
|
234
|
+
/** Fresh bytes for pixel editing. Persisted hosts should provide this
|
|
235
|
+
* instead of asking the editor to borrow a cache-owned viewer URL. */
|
|
236
|
+
resolveEditBlob?: () => Promise<Blob>;
|
|
234
237
|
/** Upload/processing state — the filmstrip renders it honestly. */
|
|
235
238
|
status?: "ready" | "uploading" | "error";
|
|
236
239
|
/** Accent ring on the filmstrip tile (e.g. a delineator frame). */
|
|
@@ -773,6 +776,35 @@ declare function nextCameraDevice<T extends {
|
|
|
773
776
|
deviceId: string;
|
|
774
777
|
}>(cameras: readonly T[], currentDeviceId: string | null | undefined): T | null;
|
|
775
778
|
|
|
779
|
+
/**
|
|
780
|
+
* The one terminal video contract shared by the package-owned MediaRecorder
|
|
781
|
+
* engine and host adapters. Emitted bytes are stronger MIME evidence than a
|
|
782
|
+
* requested/recorder format, and a delivered recording always carries a
|
|
783
|
+
* positive integer duration.
|
|
784
|
+
*/
|
|
785
|
+
interface FinalizeCapturedVideoOptions {
|
|
786
|
+
parts: readonly BlobPart[];
|
|
787
|
+
/** MIME observed on emitted/final Blob data — the strongest authority. */
|
|
788
|
+
emittedMime?: string | null | undefined;
|
|
789
|
+
/** MediaRecorder-reported MIME — fallback when emitted bytes have none. */
|
|
790
|
+
recorderMime?: string | null | undefined;
|
|
791
|
+
durationMs: number;
|
|
792
|
+
fileNamePrefix: string;
|
|
793
|
+
/** Injectable filename clock for deterministic tests. */
|
|
794
|
+
timestampMs?: number;
|
|
795
|
+
}
|
|
796
|
+
interface CapturedVideoResult {
|
|
797
|
+
file: File;
|
|
798
|
+
mime: string;
|
|
799
|
+
durationMs: number;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Finalize recording bytes without letting requested MIME, filename, and
|
|
803
|
+
* duration drift apart. Unknown/non-video formats fail loudly rather than
|
|
804
|
+
* producing a mislabeled upload.
|
|
805
|
+
*/
|
|
806
|
+
declare function finalizeCapturedVideo(options: FinalizeCapturedVideoOptions): CapturedVideoResult;
|
|
807
|
+
|
|
776
808
|
/**
|
|
777
809
|
* src/engine/permissions.ts — permission pre-checks + error classification
|
|
778
810
|
* for the default engine (and for hosts, so they stop re-implementing the
|
|
@@ -983,4 +1015,4 @@ interface CloudLibrarySheetProps {
|
|
|
983
1015
|
}
|
|
984
1016
|
declare function CloudLibrarySheet({ open, onClose, items, loading, busy, onOpenItem, onUpload, title, }: CloudLibrarySheetProps): React__default.JSX.Element | null;
|
|
985
1017
|
|
|
986
|
-
export { CameraCapture, type CameraCaptureProps, CameraCaptureV3, type CameraCaptureV3Props, CameraFeed, type CaptureAspect, type CaptureCameraEngine, type CaptureCameraMode, type CaptureCameraSlots, type CaptureCameraV3Slots, type CaptureCloudLibraryItem, type CaptureCloudPort, CaptureExpandingField, type CaptureExpandingFieldProps, CaptureFilmstrip, type CaptureFilmstripProps, type CaptureMediaItem, type CaptureMediaSession, type CaptureOptionTile, CaptureRail, type CaptureRailAction, type CaptureRailProps, CaptureSheet, type CaptureSheetAction, type CaptureSheetProps, type CaptureTimerSetting, CloudLibrarySheet, type CloudLibrarySheetProps, CountdownOverlay, type DefaultCaptureEngine, type DefaultEngineOptions, GridOverlay, HoldShutter, type HoldShutterProps, ImageEditSheet, type ImageEditSheetProps, type MediaPermissionState, MediaViewer, type MediaViewerProps, type MicInterruptionReason, type MicStreamState, ModeSelector, type ModeSelectorProps, OptionsGridPanel, type OptionsGridPanelProps, PHOTO_JPEG_QUALITY, type ResolvedMedia, ShutterButton, type ShutterButtonProps, type TrackControls, ZoomRow, type ZoomRowProps, acquireWarmMic, adoptWarmAudioStream, buildWarmMicConstraints, classifyCameraBlockReason, cropBlobToAspect, getMediaUrl, getWarmMicState, hardStopWarmMic, invalidateMedia, isMediaDenialError, nextCameraDevice, notifyMicPermissionRevoked, primeMedia, queryCameraPermission, queryMicPermission, releaseWarmMic, setPreferredMicDeviceId, shouldCombineMicPrompt, subscribeWarmMic, subscribeWarmMicInterruption, useDefaultCaptureEngine, useMediaUrl, useTrackControls, warmMicDebug };
|
|
1018
|
+
export { CameraCapture, type CameraCaptureProps, CameraCaptureV3, type CameraCaptureV3Props, CameraFeed, type CaptureAspect, type CaptureCameraEngine, type CaptureCameraMode, type CaptureCameraSlots, type CaptureCameraV3Slots, type CaptureCloudLibraryItem, type CaptureCloudPort, CaptureExpandingField, type CaptureExpandingFieldProps, CaptureFilmstrip, type CaptureFilmstripProps, type CaptureMediaItem, type CaptureMediaSession, type CaptureOptionTile, CaptureRail, type CaptureRailAction, type CaptureRailProps, CaptureSheet, type CaptureSheetAction, type CaptureSheetProps, type CaptureTimerSetting, type CapturedVideoResult, CloudLibrarySheet, type CloudLibrarySheetProps, CountdownOverlay, type DefaultCaptureEngine, type DefaultEngineOptions, type FinalizeCapturedVideoOptions, GridOverlay, HoldShutter, type HoldShutterProps, ImageEditSheet, type ImageEditSheetProps, type MediaPermissionState, MediaViewer, type MediaViewerProps, type MicInterruptionReason, type MicStreamState, ModeSelector, type ModeSelectorProps, OptionsGridPanel, type OptionsGridPanelProps, PHOTO_JPEG_QUALITY, type ResolvedMedia, ShutterButton, type ShutterButtonProps, type TrackControls, ZoomRow, type ZoomRowProps, acquireWarmMic, adoptWarmAudioStream, buildWarmMicConstraints, classifyCameraBlockReason, cropBlobToAspect, finalizeCapturedVideo, getMediaUrl, getWarmMicState, hardStopWarmMic, invalidateMedia, isMediaDenialError, nextCameraDevice, notifyMicPermissionRevoked, primeMedia, queryCameraPermission, queryMicPermission, releaseWarmMic, setPreferredMicDeviceId, shouldCombineMicPrompt, subscribeWarmMic, subscribeWarmMicInterruption, useDefaultCaptureEngine, useMediaUrl, useTrackControls, warmMicDebug };
|
package/dist/react.d.ts
CHANGED
|
@@ -231,6 +231,9 @@ interface CaptureMediaItem {
|
|
|
231
231
|
/** Async URL resolution for persisted items. Called at most once per key
|
|
232
232
|
* while cached; concurrent callers share the in-flight promise. */
|
|
233
233
|
resolve?: () => Promise<ResolvedMedia>;
|
|
234
|
+
/** Fresh bytes for pixel editing. Persisted hosts should provide this
|
|
235
|
+
* instead of asking the editor to borrow a cache-owned viewer URL. */
|
|
236
|
+
resolveEditBlob?: () => Promise<Blob>;
|
|
234
237
|
/** Upload/processing state — the filmstrip renders it honestly. */
|
|
235
238
|
status?: "ready" | "uploading" | "error";
|
|
236
239
|
/** Accent ring on the filmstrip tile (e.g. a delineator frame). */
|
|
@@ -773,6 +776,35 @@ declare function nextCameraDevice<T extends {
|
|
|
773
776
|
deviceId: string;
|
|
774
777
|
}>(cameras: readonly T[], currentDeviceId: string | null | undefined): T | null;
|
|
775
778
|
|
|
779
|
+
/**
|
|
780
|
+
* The one terminal video contract shared by the package-owned MediaRecorder
|
|
781
|
+
* engine and host adapters. Emitted bytes are stronger MIME evidence than a
|
|
782
|
+
* requested/recorder format, and a delivered recording always carries a
|
|
783
|
+
* positive integer duration.
|
|
784
|
+
*/
|
|
785
|
+
interface FinalizeCapturedVideoOptions {
|
|
786
|
+
parts: readonly BlobPart[];
|
|
787
|
+
/** MIME observed on emitted/final Blob data — the strongest authority. */
|
|
788
|
+
emittedMime?: string | null | undefined;
|
|
789
|
+
/** MediaRecorder-reported MIME — fallback when emitted bytes have none. */
|
|
790
|
+
recorderMime?: string | null | undefined;
|
|
791
|
+
durationMs: number;
|
|
792
|
+
fileNamePrefix: string;
|
|
793
|
+
/** Injectable filename clock for deterministic tests. */
|
|
794
|
+
timestampMs?: number;
|
|
795
|
+
}
|
|
796
|
+
interface CapturedVideoResult {
|
|
797
|
+
file: File;
|
|
798
|
+
mime: string;
|
|
799
|
+
durationMs: number;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Finalize recording bytes without letting requested MIME, filename, and
|
|
803
|
+
* duration drift apart. Unknown/non-video formats fail loudly rather than
|
|
804
|
+
* producing a mislabeled upload.
|
|
805
|
+
*/
|
|
806
|
+
declare function finalizeCapturedVideo(options: FinalizeCapturedVideoOptions): CapturedVideoResult;
|
|
807
|
+
|
|
776
808
|
/**
|
|
777
809
|
* src/engine/permissions.ts — permission pre-checks + error classification
|
|
778
810
|
* for the default engine (and for hosts, so they stop re-implementing the
|
|
@@ -983,4 +1015,4 @@ interface CloudLibrarySheetProps {
|
|
|
983
1015
|
}
|
|
984
1016
|
declare function CloudLibrarySheet({ open, onClose, items, loading, busy, onOpenItem, onUpload, title, }: CloudLibrarySheetProps): React__default.JSX.Element | null;
|
|
985
1017
|
|
|
986
|
-
export { CameraCapture, type CameraCaptureProps, CameraCaptureV3, type CameraCaptureV3Props, CameraFeed, type CaptureAspect, type CaptureCameraEngine, type CaptureCameraMode, type CaptureCameraSlots, type CaptureCameraV3Slots, type CaptureCloudLibraryItem, type CaptureCloudPort, CaptureExpandingField, type CaptureExpandingFieldProps, CaptureFilmstrip, type CaptureFilmstripProps, type CaptureMediaItem, type CaptureMediaSession, type CaptureOptionTile, CaptureRail, type CaptureRailAction, type CaptureRailProps, CaptureSheet, type CaptureSheetAction, type CaptureSheetProps, type CaptureTimerSetting, CloudLibrarySheet, type CloudLibrarySheetProps, CountdownOverlay, type DefaultCaptureEngine, type DefaultEngineOptions, GridOverlay, HoldShutter, type HoldShutterProps, ImageEditSheet, type ImageEditSheetProps, type MediaPermissionState, MediaViewer, type MediaViewerProps, type MicInterruptionReason, type MicStreamState, ModeSelector, type ModeSelectorProps, OptionsGridPanel, type OptionsGridPanelProps, PHOTO_JPEG_QUALITY, type ResolvedMedia, ShutterButton, type ShutterButtonProps, type TrackControls, ZoomRow, type ZoomRowProps, acquireWarmMic, adoptWarmAudioStream, buildWarmMicConstraints, classifyCameraBlockReason, cropBlobToAspect, getMediaUrl, getWarmMicState, hardStopWarmMic, invalidateMedia, isMediaDenialError, nextCameraDevice, notifyMicPermissionRevoked, primeMedia, queryCameraPermission, queryMicPermission, releaseWarmMic, setPreferredMicDeviceId, shouldCombineMicPrompt, subscribeWarmMic, subscribeWarmMicInterruption, useDefaultCaptureEngine, useMediaUrl, useTrackControls, warmMicDebug };
|
|
1018
|
+
export { CameraCapture, type CameraCaptureProps, CameraCaptureV3, type CameraCaptureV3Props, CameraFeed, type CaptureAspect, type CaptureCameraEngine, type CaptureCameraMode, type CaptureCameraSlots, type CaptureCameraV3Slots, type CaptureCloudLibraryItem, type CaptureCloudPort, CaptureExpandingField, type CaptureExpandingFieldProps, CaptureFilmstrip, type CaptureFilmstripProps, type CaptureMediaItem, type CaptureMediaSession, type CaptureOptionTile, CaptureRail, type CaptureRailAction, type CaptureRailProps, CaptureSheet, type CaptureSheetAction, type CaptureSheetProps, type CaptureTimerSetting, type CapturedVideoResult, CloudLibrarySheet, type CloudLibrarySheetProps, CountdownOverlay, type DefaultCaptureEngine, type DefaultEngineOptions, type FinalizeCapturedVideoOptions, GridOverlay, HoldShutter, type HoldShutterProps, ImageEditSheet, type ImageEditSheetProps, type MediaPermissionState, MediaViewer, type MediaViewerProps, type MicInterruptionReason, type MicStreamState, ModeSelector, type ModeSelectorProps, OptionsGridPanel, type OptionsGridPanelProps, PHOTO_JPEG_QUALITY, type ResolvedMedia, ShutterButton, type ShutterButtonProps, type TrackControls, ZoomRow, type ZoomRowProps, acquireWarmMic, adoptWarmAudioStream, buildWarmMicConstraints, classifyCameraBlockReason, cropBlobToAspect, finalizeCapturedVideo, getMediaUrl, getWarmMicState, hardStopWarmMic, invalidateMedia, isMediaDenialError, nextCameraDevice, notifyMicPermissionRevoked, primeMedia, queryCameraPermission, queryMicPermission, releaseWarmMic, setPreferredMicDeviceId, shouldCombineMicPrompt, subscribeWarmMic, subscribeWarmMicInterruption, useDefaultCaptureEngine, useMediaUrl, useTrackControls, warmMicDebug };
|
package/dist/react.js
CHANGED
|
@@ -661,6 +661,16 @@ function invalidateMedia(key) {
|
|
|
661
661
|
if (entry?.revoke && entry.url) URL.revokeObjectURL(entry.url);
|
|
662
662
|
notify(key);
|
|
663
663
|
}
|
|
664
|
+
async function prepareMediaForEdit(item) {
|
|
665
|
+
if (item.resolveEditBlob) {
|
|
666
|
+
const blob = await item.resolveEditBlob();
|
|
667
|
+
if (blob.size === 0) throw new Error("media snapshot was empty");
|
|
668
|
+
return { url: URL.createObjectURL(blob), revoke: true };
|
|
669
|
+
}
|
|
670
|
+
const url = item.src ?? getMediaUrl(item);
|
|
671
|
+
if (!url) throw new Error("media item has no editable source");
|
|
672
|
+
return { url, revoke: false };
|
|
673
|
+
}
|
|
664
674
|
function useMediaUrl(item) {
|
|
665
675
|
const [, bump] = useState2(0);
|
|
666
676
|
const key = item?.key ?? null;
|
|
@@ -1549,6 +1559,10 @@ function CameraCapture({
|
|
|
1549
1559
|
const [optionsOpen, setOptionsOpen] = useState5(false);
|
|
1550
1560
|
const [viewerKey, setViewerKey] = useState5(null);
|
|
1551
1561
|
const [editTarget, setEditTarget] = useState5(null);
|
|
1562
|
+
const [editPreparing, setEditPreparing] = useState5(false);
|
|
1563
|
+
const [editPreparationError, setEditPreparationError] = useState5(
|
|
1564
|
+
null
|
|
1565
|
+
);
|
|
1552
1566
|
const [gridOn, setGridOn] = useState5(false);
|
|
1553
1567
|
const [timerSetting, setTimerSetting] = useState5(0);
|
|
1554
1568
|
const [aspect, setAspect] = useState5("full");
|
|
@@ -1573,6 +1587,12 @@ function CameraCapture({
|
|
|
1573
1587
|
useEffect5(() => {
|
|
1574
1588
|
onReviewOpenChange?.(reviewOpen);
|
|
1575
1589
|
}, [reviewOpen, onReviewOpenChange]);
|
|
1590
|
+
useEffect5(
|
|
1591
|
+
() => () => {
|
|
1592
|
+
if (editTarget?.revoke) URL.revokeObjectURL(editTarget.url);
|
|
1593
|
+
},
|
|
1594
|
+
[editTarget]
|
|
1595
|
+
);
|
|
1576
1596
|
const clearCountdown = useCallback4(() => {
|
|
1577
1597
|
if (countdownRef.current) clearInterval(countdownRef.current);
|
|
1578
1598
|
countdownRef.current = null;
|
|
@@ -1886,8 +1906,16 @@ function CameraCapture({
|
|
|
1886
1906
|
invalidateMedia(item.key);
|
|
1887
1907
|
} : void 0,
|
|
1888
1908
|
onEdit: media.onReplacePhoto ? (item) => {
|
|
1889
|
-
|
|
1890
|
-
|
|
1909
|
+
if (editPreparing) return;
|
|
1910
|
+
setEditPreparing(true);
|
|
1911
|
+
setEditPreparationError(null);
|
|
1912
|
+
void prepareMediaForEdit(item).then(
|
|
1913
|
+
(prepared) => setEditTarget({ key: item.key, ...prepared })
|
|
1914
|
+
).catch(
|
|
1915
|
+
() => setEditPreparationError(
|
|
1916
|
+
"This photo could not be prepared for editing. Try again."
|
|
1917
|
+
)
|
|
1918
|
+
).finally(() => setEditPreparing(false));
|
|
1891
1919
|
} : void 0
|
|
1892
1920
|
}
|
|
1893
1921
|
),
|
|
@@ -1908,6 +1936,14 @@ function CameraCapture({
|
|
|
1908
1936
|
}
|
|
1909
1937
|
}
|
|
1910
1938
|
),
|
|
1939
|
+
editPreparationError && /* @__PURE__ */ jsx12(
|
|
1940
|
+
"p",
|
|
1941
|
+
{
|
|
1942
|
+
role: "alert",
|
|
1943
|
+
className: "fixed inset-x-4 bottom-24 z-[70] rounded-xl bg-black/85 px-4 py-3 text-center text-sm text-[#FF6961]",
|
|
1944
|
+
children: editPreparationError
|
|
1945
|
+
}
|
|
1946
|
+
),
|
|
1911
1947
|
slots.overlays
|
|
1912
1948
|
] });
|
|
1913
1949
|
}
|
|
@@ -2206,7 +2242,9 @@ function CameraCaptureV3({
|
|
|
2206
2242
|
slots = {}
|
|
2207
2243
|
}) {
|
|
2208
2244
|
const [viewerKey, setViewerKey] = useState8(null);
|
|
2209
|
-
const [editTarget, setEditTarget] = useState8(
|
|
2245
|
+
const [editTarget, setEditTarget] = useState8(null);
|
|
2246
|
+
const [editPreparing, setEditPreparing] = useState8(false);
|
|
2247
|
+
const [editPreparationError, setEditPreparationError] = useState8(
|
|
2210
2248
|
null
|
|
2211
2249
|
);
|
|
2212
2250
|
const [gridOn, setGridOn] = useState8(false);
|
|
@@ -2220,6 +2258,12 @@ function CameraCaptureV3({
|
|
|
2220
2258
|
useEffect7(() => {
|
|
2221
2259
|
onReviewOpenChange?.(reviewOpen);
|
|
2222
2260
|
}, [reviewOpen, onReviewOpenChange]);
|
|
2261
|
+
useEffect7(
|
|
2262
|
+
() => () => {
|
|
2263
|
+
if (editTarget?.revoke) URL.revokeObjectURL(editTarget.url);
|
|
2264
|
+
},
|
|
2265
|
+
[editTarget]
|
|
2266
|
+
);
|
|
2223
2267
|
const clearCountdown = useCallback6(() => {
|
|
2224
2268
|
if (countdownRef.current) clearInterval(countdownRef.current);
|
|
2225
2269
|
countdownRef.current = null;
|
|
@@ -2438,8 +2482,16 @@ function CameraCaptureV3({
|
|
|
2438
2482
|
invalidateMedia(item.key);
|
|
2439
2483
|
} : void 0,
|
|
2440
2484
|
onEdit: media.onReplacePhoto ? (item) => {
|
|
2441
|
-
|
|
2442
|
-
|
|
2485
|
+
if (editPreparing) return;
|
|
2486
|
+
setEditPreparing(true);
|
|
2487
|
+
setEditPreparationError(null);
|
|
2488
|
+
void prepareMediaForEdit(item).then(
|
|
2489
|
+
(prepared) => setEditTarget({ key: item.key, ...prepared })
|
|
2490
|
+
).catch(
|
|
2491
|
+
() => setEditPreparationError(
|
|
2492
|
+
"This photo could not be prepared for editing. Try again."
|
|
2493
|
+
)
|
|
2494
|
+
).finally(() => setEditPreparing(false));
|
|
2443
2495
|
} : void 0
|
|
2444
2496
|
}
|
|
2445
2497
|
),
|
|
@@ -2460,6 +2512,14 @@ function CameraCaptureV3({
|
|
|
2460
2512
|
}
|
|
2461
2513
|
}
|
|
2462
2514
|
),
|
|
2515
|
+
editPreparationError && /* @__PURE__ */ jsx15(
|
|
2516
|
+
"p",
|
|
2517
|
+
{
|
|
2518
|
+
role: "alert",
|
|
2519
|
+
className: "fixed inset-x-4 bottom-24 z-[70] rounded-xl bg-black/85 px-4 py-3 text-center text-sm text-[#FF6961]",
|
|
2520
|
+
children: editPreparationError
|
|
2521
|
+
}
|
|
2522
|
+
),
|
|
2463
2523
|
slots.overlays
|
|
2464
2524
|
] });
|
|
2465
2525
|
}
|
|
@@ -2973,6 +3033,43 @@ function warmMicDebug() {
|
|
|
2973
3033
|
};
|
|
2974
3034
|
}
|
|
2975
3035
|
|
|
3036
|
+
// src/engine/video-result.ts
|
|
3037
|
+
var VIDEO_EXTENSIONS = {
|
|
3038
|
+
"video/mp4": "mp4",
|
|
3039
|
+
"video/webm": "webm",
|
|
3040
|
+
"video/ogg": "ogv",
|
|
3041
|
+
"video/quicktime": "mov"
|
|
3042
|
+
};
|
|
3043
|
+
function firstNonEmpty(...values) {
|
|
3044
|
+
for (const value of values) {
|
|
3045
|
+
const normalized = value?.trim();
|
|
3046
|
+
if (normalized) return normalized;
|
|
3047
|
+
}
|
|
3048
|
+
return "video/webm";
|
|
3049
|
+
}
|
|
3050
|
+
function finalizeCapturedVideo(options) {
|
|
3051
|
+
if (!Number.isFinite(options.durationMs)) {
|
|
3052
|
+
throw new Error("finalizeCapturedVideo requires a finite duration.");
|
|
3053
|
+
}
|
|
3054
|
+
const mime = firstNonEmpty(options.emittedMime, options.recorderMime);
|
|
3055
|
+
const container = mime.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
3056
|
+
const extension = VIDEO_EXTENSIONS[container];
|
|
3057
|
+
if (!extension) {
|
|
3058
|
+
throw new Error(
|
|
3059
|
+
`finalizeCapturedVideo requires a recognized video MIME; received "${mime}".`
|
|
3060
|
+
);
|
|
3061
|
+
}
|
|
3062
|
+
const durationMs = Math.max(1, Math.round(options.durationMs));
|
|
3063
|
+
const blob = new Blob([...options.parts], { type: mime });
|
|
3064
|
+
const timestampMs = options.timestampMs ?? Date.now();
|
|
3065
|
+
const file = new File(
|
|
3066
|
+
[blob],
|
|
3067
|
+
`${options.fileNamePrefix}-video-${timestampMs}.${extension}`,
|
|
3068
|
+
{ type: mime }
|
|
3069
|
+
);
|
|
3070
|
+
return { file, mime, durationMs };
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2976
3073
|
// src/engine/useDefaultEngine.ts
|
|
2977
3074
|
var RECORDING_MIME_LADDER = [
|
|
2978
3075
|
'video/mp4;codecs="avc1.42E01E,mp4a.40.2"',
|
|
@@ -3227,16 +3324,15 @@ function useDefaultCaptureEngine(options) {
|
|
|
3227
3324
|
if (entry.micHeld) releaseWarmMic();
|
|
3228
3325
|
recorderRef.current = null;
|
|
3229
3326
|
setRecording(false);
|
|
3230
|
-
const
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
);
|
|
3327
|
+
const captured = finalizeCapturedVideo({
|
|
3328
|
+
parts: entry.chunks,
|
|
3329
|
+
// Emitted chunk MIME is stronger evidence than recorder.mimeType.
|
|
3330
|
+
emittedMime: entry.chunks.find((chunk) => chunk.type)?.type,
|
|
3331
|
+
recorderMime: recorder.mimeType,
|
|
3332
|
+
durationMs: performance.now() - entry.startedAt,
|
|
3333
|
+
fileNamePrefix
|
|
3334
|
+
});
|
|
3335
|
+
callbacksRef.current.onVideo(captured.file, captured.durationMs);
|
|
3240
3336
|
};
|
|
3241
3337
|
recorder.start(1e3);
|
|
3242
3338
|
setRecordElapsedSeconds(0);
|
|
@@ -3439,6 +3535,7 @@ export {
|
|
|
3439
3535
|
buildWarmMicConstraints,
|
|
3440
3536
|
classifyCameraBlockReason,
|
|
3441
3537
|
cropBlobToAspect,
|
|
3538
|
+
finalizeCapturedVideo,
|
|
3442
3539
|
getMediaUrl,
|
|
3443
3540
|
getWarmMicState,
|
|
3444
3541
|
hardStopWarmMic,
|