@vidtreo/recorder 1.6.3 → 1.7.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/dist/index.d.ts +14 -1
- package/dist/index.js +214 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type StopRecordingResult = {
|
|
|
7
7
|
tabVisibilityIntervals: VisibilityInterval[];
|
|
8
8
|
recordingStats?: StreamRecordingStats;
|
|
9
9
|
encoderAcceleration?: VideoHardwareAcceleration;
|
|
10
|
+
mediaRecorderDiagnostics?: Record<string, unknown>;
|
|
10
11
|
};
|
|
11
12
|
type CheckRecorderSupportDependency = (options: SupportCheckOptions) => Promise<SupportReport>;
|
|
12
13
|
type GetCurrentTimestampDependency = () => number;
|
|
@@ -32,6 +33,8 @@ export declare class StreamRecordingState {
|
|
|
32
33
|
private mediaRecorderStopPromise;
|
|
33
34
|
private mediaRecorderStopResolve;
|
|
34
35
|
private mediaRecorderStopReject;
|
|
36
|
+
private mediaRecorderError;
|
|
37
|
+
private mediaRecorderErrorTimeoutId;
|
|
35
38
|
private readonly streamManager;
|
|
36
39
|
private readonly dependencies;
|
|
37
40
|
constructor(streamManager: StreamManager, dependencies?: Partial<StreamRecordingStateDependencies>);
|
|
@@ -60,6 +63,14 @@ export declare class StreamRecordingState {
|
|
|
60
63
|
private stopSafeCaptureRecording;
|
|
61
64
|
private resolveSafeCaptureMimeType;
|
|
62
65
|
private resolvePreferredSafeCaptureMimeType;
|
|
66
|
+
private shouldPreferWebmSafeCapture;
|
|
67
|
+
private scheduleMediaRecorderErrorFinalizeTimeout;
|
|
68
|
+
private clearMediaRecorderErrorTimeout;
|
|
69
|
+
private createMediaRecorderError;
|
|
70
|
+
private createEmptyMediaRecorderError;
|
|
71
|
+
private updateMediaRecorderErrorDiagnostics;
|
|
72
|
+
private buildMediaRecorderDiagnostics;
|
|
73
|
+
private getMediaRecorderChunkSize;
|
|
63
74
|
private resetPauseState;
|
|
64
75
|
private resolveTabVisibilityOverlayText;
|
|
65
76
|
private setupVisibilityUpdates;
|
|
@@ -1336,6 +1347,7 @@ export declare class TelemetryClient {
|
|
|
1336
1347
|
private isSafeCapabilityValue;
|
|
1337
1348
|
private isRouteSourceType;
|
|
1338
1349
|
private buildErrorProperties;
|
|
1350
|
+
private buildMediaRecorderErrorProperties;
|
|
1339
1351
|
private buildFingerprint;
|
|
1340
1352
|
private buildContext;
|
|
1341
1353
|
private buildError;
|
|
@@ -2453,7 +2465,7 @@ export declare function deserializeBitrate(bitrate: number | string | undefined)
|
|
|
2453
2465
|
import type { DeviceCapabilityProfile } from "../capabilities/device-capability-profile";
|
|
2454
2466
|
export type RecordingCapabilityProfile = DeviceCapabilityProfile;
|
|
2455
2467
|
export type RecordingRoute = "local-webcodecs" | "safe-capture-post-recording-transcode" | "unsupported-with-guidance";
|
|
2456
|
-
export type RecordingRouteReasonCode = "webcodecs-supported" | "webcodecs-unavailable" | "target-codec-supported" | "target-codec-unsupported" | "encoding-capability-unsupported" | "encoding-not-smooth" | "encoding-not-power-efficient" | "insufficient-device-memory" | "insufficient-hardware-concurrency" | "insecure-context" | "mediarecorder-supported" | "mediarecorder-unavailable" | "offline";
|
|
2468
|
+
export type RecordingRouteReasonCode = "webcodecs-supported" | "webcodecs-unavailable" | "target-codec-supported" | "target-codec-unsupported" | "encoding-capability-unsupported" | "encoding-not-smooth" | "encoding-not-power-efficient" | "insufficient-device-memory" | "insufficient-hardware-concurrency" | "insecure-context" | "mediarecorder-supported" | "mediarecorder-unavailable" | "android-post-recording-transcode" | "offline";
|
|
2457
2469
|
export type RecordingRouteDecision = {
|
|
2458
2470
|
route: RecordingRoute;
|
|
2459
2471
|
reasonCodes: RecordingRouteReasonCode[];
|
|
@@ -2781,6 +2793,7 @@ export declare class RecorderController {
|
|
|
2781
2793
|
switchAudioDevice(deviceId: string | null): Promise<MediaStream>;
|
|
2782
2794
|
startRecording(): Promise<void>;
|
|
2783
2795
|
stopRecording(): Promise<Blob>;
|
|
2796
|
+
private shouldStopStreamAfterStopFailure;
|
|
2784
2797
|
private sendAudioMissingTelemetry;
|
|
2785
2798
|
getTabVisibilityOverlayConfig(): {
|
|
2786
2799
|
enabled: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1476,6 +1476,7 @@ async function buildDeviceCapabilityProfile(options = {}) {
|
|
|
1476
1476
|
// src/core/routing/recording-route-decision.ts
|
|
1477
1477
|
var DEFAULT_MIN_DEVICE_MEMORY_GB = 4;
|
|
1478
1478
|
var DEFAULT_MIN_HARDWARE_CONCURRENCY = 5;
|
|
1479
|
+
var PLATFORM_KEYWORD_ANDROID2 = "android";
|
|
1479
1480
|
var UNSUPPORTED_GUIDANCE_MESSAGE = "Recording is not supported in this browser context. Use a secure connection, go online, and try a current Chrome or Edge browser.";
|
|
1480
1481
|
function hasSufficientDeviceMemory(profile, minDeviceMemory) {
|
|
1481
1482
|
const deviceMemory = profile.resources.deviceMemory;
|
|
@@ -1530,6 +1531,17 @@ function buildFallbackRouteBlockers(profile) {
|
|
|
1530
1531
|
}
|
|
1531
1532
|
return reasonCodes;
|
|
1532
1533
|
}
|
|
1534
|
+
function isAndroidProfile(profile) {
|
|
1535
|
+
return profile.os.name.toLowerCase().includes(PLATFORM_KEYWORD_ANDROID2);
|
|
1536
|
+
}
|
|
1537
|
+
function buildPostRecordingTranscodeReasonCodes(profile, localRouteBlockers) {
|
|
1538
|
+
const reasonCodes = [...localRouteBlockers];
|
|
1539
|
+
if (isAndroidProfile(profile)) {
|
|
1540
|
+
reasonCodes.push("android-post-recording-transcode");
|
|
1541
|
+
}
|
|
1542
|
+
reasonCodes.push("mediarecorder-supported");
|
|
1543
|
+
return reasonCodes;
|
|
1544
|
+
}
|
|
1533
1545
|
function hasSupportedEncodingCapability(selectedCodec) {
|
|
1534
1546
|
const encodingCapability = selectedCodec?.encodingCapability;
|
|
1535
1547
|
return encodingCapability === undefined || encodingCapability.errorCode !== undefined || encodingCapability.supported === true;
|
|
@@ -1554,6 +1566,13 @@ function decideRecordingRoute(options) {
|
|
|
1554
1566
|
const hasSmoothEncoding = hasSmoothEncodingCapability(selectedCodec);
|
|
1555
1567
|
const hasPowerEfficientEncoding = hasPowerEfficientEncodingCapability(selectedCodec);
|
|
1556
1568
|
const localRouteBlockers = buildLocalRouteBlockers(options.profile, hasSupportedTargetCodec, hasEnoughDeviceMemory, hasEnoughHardwareConcurrency, hasSupportedEncoding, hasSmoothEncoding, hasPowerEfficientEncoding);
|
|
1569
|
+
if (isAndroidProfile(options.profile) && canUsePostRecordingTranscodeRoute(options.profile, hasSupportedTargetCodec)) {
|
|
1570
|
+
return {
|
|
1571
|
+
route: "safe-capture-post-recording-transcode",
|
|
1572
|
+
reasonCodes: buildPostRecordingTranscodeReasonCodes(options.profile, localRouteBlockers),
|
|
1573
|
+
selectedCodecId
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1557
1576
|
if (options.profile.features.isSecureContext && options.profile.features.hasVideoEncoder && hasSupportedTargetCodec && options.profile.network.online !== false && hasEnoughDeviceMemory && hasEnoughHardwareConcurrency && hasSupportedEncoding && hasSmoothEncoding && hasPowerEfficientEncoding) {
|
|
1558
1577
|
return {
|
|
1559
1578
|
route: "local-webcodecs",
|
|
@@ -1564,7 +1583,7 @@ function decideRecordingRoute(options) {
|
|
|
1564
1583
|
if (canUsePostRecordingTranscodeRoute(options.profile, hasSupportedTargetCodec)) {
|
|
1565
1584
|
return {
|
|
1566
1585
|
route: "safe-capture-post-recording-transcode",
|
|
1567
|
-
reasonCodes:
|
|
1586
|
+
reasonCodes: buildPostRecordingTranscodeReasonCodes(options.profile, localRouteBlockers),
|
|
1568
1587
|
selectedCodecId
|
|
1569
1588
|
};
|
|
1570
1589
|
}
|
|
@@ -5246,6 +5265,16 @@ var LOW_RESOURCE_SAFE_CAPTURE_CONSTRAINTS = {
|
|
|
5246
5265
|
height: { ideal: 480, max: 480 },
|
|
5247
5266
|
frameRate: { ideal: 20, max: 20 }
|
|
5248
5267
|
};
|
|
5268
|
+
var ANDROID_SAFE_CAPTURE_CONSTRAINTS = {
|
|
5269
|
+
width: { ideal: 1280, max: 1280 },
|
|
5270
|
+
height: { ideal: 720, max: 720 },
|
|
5271
|
+
frameRate: { ideal: 24, max: 24 }
|
|
5272
|
+
};
|
|
5273
|
+
var SAFE_CAPTURE_TIMESLICE_MS = 1000;
|
|
5274
|
+
var MEDIA_RECORDER_ERROR_FINALIZE_TIMEOUT_MS = 5000;
|
|
5275
|
+
var ANDROID_POST_RECORDING_TRANSCODE_REASON = "android-post-recording-transcode";
|
|
5276
|
+
var MEDIA_RECORDER_ERROR_CODE = "recording.mediarecorder-error";
|
|
5277
|
+
var MEDIA_RECORDER_EMPTY_ERROR_CODE = "recording.mediarecorder-empty";
|
|
5249
5278
|
function createUnsupportedRouteError(routeDecision) {
|
|
5250
5279
|
const guidance = routeDecision.guidance?.message ?? "This browser or device cannot safely record video. Try a supported browser or upload an existing video file.";
|
|
5251
5280
|
const error = new Error(guidance);
|
|
@@ -5277,6 +5306,8 @@ class StreamRecordingState {
|
|
|
5277
5306
|
mediaRecorderStopPromise = null;
|
|
5278
5307
|
mediaRecorderStopResolve = null;
|
|
5279
5308
|
mediaRecorderStopReject = null;
|
|
5309
|
+
mediaRecorderError = null;
|
|
5310
|
+
mediaRecorderErrorTimeoutId = null;
|
|
5280
5311
|
streamManager;
|
|
5281
5312
|
dependencies;
|
|
5282
5313
|
constructor(streamManager, dependencies) {
|
|
@@ -5449,9 +5480,11 @@ class StreamRecordingState {
|
|
|
5449
5480
|
}
|
|
5450
5481
|
let result;
|
|
5451
5482
|
if (this.activeRouteDecision.route === "safe-capture-post-recording-transcode") {
|
|
5483
|
+
const safeCaptureResult = await this.stopSafeCaptureRecording();
|
|
5452
5484
|
logger.debug("[StreamRecordingState] Finalizing MediaRecorder capture");
|
|
5453
5485
|
result = {
|
|
5454
|
-
blob:
|
|
5486
|
+
blob: safeCaptureResult.blob,
|
|
5487
|
+
mediaRecorderDiagnostics: safeCaptureResult.diagnostics
|
|
5455
5488
|
};
|
|
5456
5489
|
} else {
|
|
5457
5490
|
logger.debug("[StreamRecordingState] Finalizing stream processor");
|
|
@@ -5470,7 +5503,8 @@ class StreamRecordingState {
|
|
|
5470
5503
|
blob: result.blob,
|
|
5471
5504
|
tabVisibilityIntervals,
|
|
5472
5505
|
recordingStats: result.recordingStats,
|
|
5473
|
-
encoderAcceleration: result.encoderAcceleration
|
|
5506
|
+
encoderAcceleration: result.encoderAcceleration,
|
|
5507
|
+
mediaRecorderDiagnostics: result.mediaRecorderDiagnostics
|
|
5474
5508
|
};
|
|
5475
5509
|
}
|
|
5476
5510
|
pauseRecording() {
|
|
@@ -5481,6 +5515,9 @@ class StreamRecordingState {
|
|
|
5481
5515
|
if (this.tabVisibilityTracker) {
|
|
5482
5516
|
this.tabVisibilityTracker.pause();
|
|
5483
5517
|
}
|
|
5518
|
+
if (this.mediaRecorder?.state === "recording") {
|
|
5519
|
+
this.mediaRecorder.pause?.();
|
|
5520
|
+
}
|
|
5484
5521
|
if (this.streamProcessor && this.isRecording()) {
|
|
5485
5522
|
this.streamProcessor.pause();
|
|
5486
5523
|
}
|
|
@@ -5494,6 +5531,9 @@ class StreamRecordingState {
|
|
|
5494
5531
|
if (this.tabVisibilityTracker) {
|
|
5495
5532
|
this.tabVisibilityTracker.resume();
|
|
5496
5533
|
}
|
|
5534
|
+
if (this.mediaRecorder?.state === "paused") {
|
|
5535
|
+
this.mediaRecorder.resume?.();
|
|
5536
|
+
}
|
|
5497
5537
|
this.startRecordingTimer();
|
|
5498
5538
|
if (this.streamProcessor && this.isRecording()) {
|
|
5499
5539
|
this.streamProcessor.resume();
|
|
@@ -5593,6 +5633,7 @@ class StreamRecordingState {
|
|
|
5593
5633
|
}
|
|
5594
5634
|
await this.applySafeCaptureTrackConstraints(mediaStream);
|
|
5595
5635
|
this.mediaRecorderChunks = [];
|
|
5636
|
+
this.mediaRecorderError = null;
|
|
5596
5637
|
const preferredMimeType = this.resolvePreferredSafeCaptureMimeType(config);
|
|
5597
5638
|
const recorderOptions = preferredMimeType === undefined ? undefined : { mimeType: preferredMimeType };
|
|
5598
5639
|
const recorder = new MediaRecorder(mediaStream, recorderOptions);
|
|
@@ -5601,20 +5642,34 @@ class StreamRecordingState {
|
|
|
5601
5642
|
this.mediaRecorderStopResolve = resolve;
|
|
5602
5643
|
this.mediaRecorderStopReject = reject;
|
|
5603
5644
|
});
|
|
5645
|
+
this.mediaRecorderStopPromise.catch(() => {
|
|
5646
|
+
return;
|
|
5647
|
+
});
|
|
5604
5648
|
recorder.ondataavailable = (event) => {
|
|
5605
5649
|
if (event.data.size > 0) {
|
|
5606
5650
|
this.mediaRecorderChunks.push(event.data);
|
|
5607
5651
|
}
|
|
5608
5652
|
};
|
|
5609
5653
|
recorder.onstop = () => {
|
|
5654
|
+
this.clearMediaRecorderErrorTimeout();
|
|
5655
|
+
if (this.mediaRecorderError !== null) {
|
|
5656
|
+
this.updateMediaRecorderErrorDiagnostics(recorder);
|
|
5657
|
+
this.mediaRecorderStopReject?.(this.mediaRecorderError);
|
|
5658
|
+
return;
|
|
5659
|
+
}
|
|
5610
5660
|
const mimeType = recorder.mimeType || this.resolveSafeCaptureMimeType();
|
|
5611
5661
|
const blob = new Blob(this.mediaRecorderChunks, { type: mimeType });
|
|
5612
|
-
this.
|
|
5662
|
+
const diagnostics = this.buildMediaRecorderDiagnostics(recorder);
|
|
5663
|
+
if (blob.size > 0) {
|
|
5664
|
+
this.mediaRecorderStopResolve?.({ blob, diagnostics });
|
|
5665
|
+
return;
|
|
5666
|
+
}
|
|
5667
|
+
this.mediaRecorderStopReject?.(this.createEmptyMediaRecorderError(recorder));
|
|
5613
5668
|
};
|
|
5614
|
-
recorder.onerror = () => {
|
|
5615
|
-
this.
|
|
5669
|
+
recorder.onerror = (event) => {
|
|
5670
|
+
this.mediaRecorderError = this.createMediaRecorderError(event, recorder);
|
|
5616
5671
|
};
|
|
5617
|
-
recorder.start();
|
|
5672
|
+
recorder.start(SAFE_CAPTURE_TIMESLICE_MS);
|
|
5618
5673
|
}
|
|
5619
5674
|
async applySafeCaptureTrackConstraints(mediaStream) {
|
|
5620
5675
|
const constraints = this.resolveSafeCaptureVideoConstraints();
|
|
@@ -5635,6 +5690,9 @@ class StreamRecordingState {
|
|
|
5635
5690
|
if (reasonCodes.includes("insufficient-device-memory") || reasonCodes.includes("insufficient-hardware-concurrency") || reasonCodes.includes("encoding-capability-unsupported") || reasonCodes.includes("encoding-not-smooth") || reasonCodes.includes("encoding-not-power-efficient")) {
|
|
5636
5691
|
return LOW_RESOURCE_SAFE_CAPTURE_CONSTRAINTS;
|
|
5637
5692
|
}
|
|
5693
|
+
if (reasonCodes.includes(ANDROID_POST_RECORDING_TRANSCODE_REASON)) {
|
|
5694
|
+
return ANDROID_SAFE_CAPTURE_CONSTRAINTS;
|
|
5695
|
+
}
|
|
5638
5696
|
return null;
|
|
5639
5697
|
}
|
|
5640
5698
|
async stopSafeCaptureRecording() {
|
|
@@ -5646,6 +5704,7 @@ class StreamRecordingState {
|
|
|
5646
5704
|
if (recorder.state !== "inactive") {
|
|
5647
5705
|
recorder.stop();
|
|
5648
5706
|
}
|
|
5707
|
+
this.scheduleMediaRecorderErrorFinalizeTimeout(recorder);
|
|
5649
5708
|
try {
|
|
5650
5709
|
return await stopPromise;
|
|
5651
5710
|
} finally {
|
|
@@ -5654,6 +5713,8 @@ class StreamRecordingState {
|
|
|
5654
5713
|
this.mediaRecorderStopPromise = null;
|
|
5655
5714
|
this.mediaRecorderStopResolve = null;
|
|
5656
5715
|
this.mediaRecorderStopReject = null;
|
|
5716
|
+
this.mediaRecorderError = null;
|
|
5717
|
+
this.clearMediaRecorderErrorTimeout();
|
|
5657
5718
|
this.activeRouteDecision = DEFAULT_ROUTE_DECISION;
|
|
5658
5719
|
}
|
|
5659
5720
|
}
|
|
@@ -5669,17 +5730,110 @@ class StreamRecordingState {
|
|
|
5669
5730
|
}
|
|
5670
5731
|
return MediaRecorder.isTypeSupported(mimeType);
|
|
5671
5732
|
};
|
|
5672
|
-
const
|
|
5673
|
-
"video/mp4;codecs=avc1.42E01E,mp4a.40.2",
|
|
5674
|
-
"video/mp4;codecs=avc1.42001f,mp4a.40.2",
|
|
5675
|
-
"video/mp4"
|
|
5676
|
-
] : [
|
|
5733
|
+
const webmCandidateMimeTypes = [
|
|
5677
5734
|
"video/webm;codecs=vp8,opus",
|
|
5678
5735
|
"video/webm;codecs=vp9,opus",
|
|
5679
5736
|
"video/webm"
|
|
5680
5737
|
];
|
|
5738
|
+
const mp4CandidateMimeTypes = [
|
|
5739
|
+
"video/mp4;codecs=avc1.42E01E,mp4a.40.2",
|
|
5740
|
+
"video/mp4;codecs=avc1.42001f,mp4a.40.2",
|
|
5741
|
+
"video/mp4"
|
|
5742
|
+
];
|
|
5743
|
+
let candidateMimeTypes = mp4CandidateMimeTypes;
|
|
5744
|
+
if (this.shouldPreferWebmSafeCapture(config)) {
|
|
5745
|
+
candidateMimeTypes = [
|
|
5746
|
+
...webmCandidateMimeTypes,
|
|
5747
|
+
...mp4CandidateMimeTypes
|
|
5748
|
+
];
|
|
5749
|
+
} else if (config.format !== "mp4") {
|
|
5750
|
+
candidateMimeTypes = webmCandidateMimeTypes;
|
|
5751
|
+
}
|
|
5681
5752
|
return candidateMimeTypes.find(canUseMimeType);
|
|
5682
5753
|
}
|
|
5754
|
+
shouldPreferWebmSafeCapture(config) {
|
|
5755
|
+
if (config.format !== "mp4") {
|
|
5756
|
+
return false;
|
|
5757
|
+
}
|
|
5758
|
+
return this.activeRouteDecision.reasonCodes.includes(ANDROID_POST_RECORDING_TRANSCODE_REASON);
|
|
5759
|
+
}
|
|
5760
|
+
scheduleMediaRecorderErrorFinalizeTimeout(recorder) {
|
|
5761
|
+
if (this.mediaRecorderErrorTimeoutId !== null) {
|
|
5762
|
+
return;
|
|
5763
|
+
}
|
|
5764
|
+
this.mediaRecorderErrorTimeoutId = window.setTimeout(() => {
|
|
5765
|
+
if (this.mediaRecorderError !== null) {
|
|
5766
|
+
this.mediaRecorderStopReject?.(this.mediaRecorderError);
|
|
5767
|
+
return;
|
|
5768
|
+
}
|
|
5769
|
+
this.mediaRecorderStopReject?.(this.createEmptyMediaRecorderError(recorder));
|
|
5770
|
+
}, MEDIA_RECORDER_ERROR_FINALIZE_TIMEOUT_MS);
|
|
5771
|
+
}
|
|
5772
|
+
clearMediaRecorderErrorTimeout() {
|
|
5773
|
+
if (this.mediaRecorderErrorTimeoutId === null) {
|
|
5774
|
+
return;
|
|
5775
|
+
}
|
|
5776
|
+
window.clearTimeout(this.mediaRecorderErrorTimeoutId);
|
|
5777
|
+
this.mediaRecorderErrorTimeoutId = null;
|
|
5778
|
+
}
|
|
5779
|
+
createMediaRecorderError(event, recorder) {
|
|
5780
|
+
const mediaRecorderEvent = event;
|
|
5781
|
+
const browserError = mediaRecorderEvent.error;
|
|
5782
|
+
const error = new Error("Browser recording failed before upload could start");
|
|
5783
|
+
error.name = "MediaRecorderRecordingError";
|
|
5784
|
+
error.code = MEDIA_RECORDER_ERROR_CODE;
|
|
5785
|
+
error.mediaRecorderMimeType = recorder.mimeType;
|
|
5786
|
+
error.mediaRecorderChunkCount = this.mediaRecorderChunks.length;
|
|
5787
|
+
error.mediaRecorderChunkSize = this.getMediaRecorderChunkSize();
|
|
5788
|
+
if (browserError?.name) {
|
|
5789
|
+
error.mediaRecorderErrorName = browserError.name;
|
|
5790
|
+
}
|
|
5791
|
+
if (browserError?.message) {
|
|
5792
|
+
error.mediaRecorderErrorMessage = browserError.message;
|
|
5793
|
+
}
|
|
5794
|
+
return error;
|
|
5795
|
+
}
|
|
5796
|
+
createEmptyMediaRecorderError(recorder) {
|
|
5797
|
+
const error = new Error("Browser recording produced no data before upload could start");
|
|
5798
|
+
error.name = "MediaRecorderEmptyDataError";
|
|
5799
|
+
error.code = MEDIA_RECORDER_EMPTY_ERROR_CODE;
|
|
5800
|
+
error.mediaRecorderMimeType = recorder.mimeType;
|
|
5801
|
+
error.mediaRecorderChunkCount = this.mediaRecorderChunks.length;
|
|
5802
|
+
error.mediaRecorderChunkSize = this.getMediaRecorderChunkSize();
|
|
5803
|
+
if (this.mediaRecorderError?.mediaRecorderErrorName) {
|
|
5804
|
+
error.mediaRecorderErrorName = this.mediaRecorderError.mediaRecorderErrorName;
|
|
5805
|
+
}
|
|
5806
|
+
if (this.mediaRecorderError?.mediaRecorderErrorMessage) {
|
|
5807
|
+
error.mediaRecorderErrorMessage = this.mediaRecorderError.mediaRecorderErrorMessage;
|
|
5808
|
+
}
|
|
5809
|
+
return error;
|
|
5810
|
+
}
|
|
5811
|
+
updateMediaRecorderErrorDiagnostics(recorder) {
|
|
5812
|
+
if (this.mediaRecorderError === null) {
|
|
5813
|
+
return;
|
|
5814
|
+
}
|
|
5815
|
+
this.mediaRecorderError.mediaRecorderMimeType = recorder.mimeType;
|
|
5816
|
+
this.mediaRecorderError.mediaRecorderChunkCount = this.mediaRecorderChunks.length;
|
|
5817
|
+
this.mediaRecorderError.mediaRecorderChunkSize = this.getMediaRecorderChunkSize();
|
|
5818
|
+
}
|
|
5819
|
+
buildMediaRecorderDiagnostics(recorder) {
|
|
5820
|
+
const diagnostics = {
|
|
5821
|
+
mediaRecorderMimeType: recorder.mimeType,
|
|
5822
|
+
mediaRecorderChunkCount: this.mediaRecorderChunks.length,
|
|
5823
|
+
mediaRecorderChunkSize: this.getMediaRecorderChunkSize(),
|
|
5824
|
+
mediaRecorderHadError: this.mediaRecorderError !== null
|
|
5825
|
+
};
|
|
5826
|
+
if (this.mediaRecorderError?.mediaRecorderErrorName) {
|
|
5827
|
+
diagnostics.mediaRecorderErrorName = this.mediaRecorderError.mediaRecorderErrorName;
|
|
5828
|
+
}
|
|
5829
|
+
if (this.mediaRecorderError?.mediaRecorderErrorMessage) {
|
|
5830
|
+
diagnostics.mediaRecorderErrorMessage = this.mediaRecorderError.mediaRecorderErrorMessage;
|
|
5831
|
+
}
|
|
5832
|
+
return diagnostics;
|
|
5833
|
+
}
|
|
5834
|
+
getMediaRecorderChunkSize() {
|
|
5835
|
+
return this.mediaRecorderChunks.reduce((total, chunk) => total + chunk.size, 0);
|
|
5836
|
+
}
|
|
5683
5837
|
resetPauseState() {
|
|
5684
5838
|
this.totalPausedTime = 0;
|
|
5685
5839
|
this.pauseStartTime = null;
|
|
@@ -5759,6 +5913,8 @@ class StreamRecordingState {
|
|
|
5759
5913
|
this.mediaRecorderStopPromise = null;
|
|
5760
5914
|
this.mediaRecorderStopResolve = null;
|
|
5761
5915
|
this.mediaRecorderStopReject = null;
|
|
5916
|
+
this.mediaRecorderError = null;
|
|
5917
|
+
this.clearMediaRecorderErrorTimeout();
|
|
5762
5918
|
this.activeRouteDecision = DEFAULT_ROUTE_DECISION;
|
|
5763
5919
|
if (this.streamProcessor) {
|
|
5764
5920
|
this.streamProcessor.destroy();
|
|
@@ -5906,6 +6062,8 @@ var RECORDING_CODEC_UNSUPPORTED_CODES = new Set([
|
|
|
5906
6062
|
]);
|
|
5907
6063
|
var RECORDING_SERVER_TRANSCODE_FALLBACK_CODES = new Set([
|
|
5908
6064
|
"recording.post-recording-transcode-fallback",
|
|
6065
|
+
"recording.mediarecorder-empty",
|
|
6066
|
+
"recording.mediarecorder-error",
|
|
5909
6067
|
"transcode.post-recording-fallback",
|
|
5910
6068
|
"route.post-recording-transcode-fallback"
|
|
5911
6069
|
]);
|
|
@@ -6141,7 +6299,7 @@ function resolveDeviceError(input) {
|
|
|
6141
6299
|
// package.json
|
|
6142
6300
|
var package_default = {
|
|
6143
6301
|
name: "@vidtreo/recorder",
|
|
6144
|
-
version: "1.
|
|
6302
|
+
version: "1.7.0",
|
|
6145
6303
|
type: "module",
|
|
6146
6304
|
description: "Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications.",
|
|
6147
6305
|
main: "./dist/index.js",
|
|
@@ -6226,6 +6384,13 @@ var MAX_RETRY_ATTEMPTS = 3;
|
|
|
6226
6384
|
var MAX_PENDING_EVENTS = 100;
|
|
6227
6385
|
var BRACKET_ERROR_CODE_PATTERN = /\[([a-z]+(?:[.-][a-z0-9]+)+)\]/i;
|
|
6228
6386
|
var INLINE_ERROR_CODE_PATTERN = /\b([a-z]+(?:[.-][a-z0-9]+)+)\b/i;
|
|
6387
|
+
var MEDIA_RECORDER_ERROR_PROPERTY_KEYS = [
|
|
6388
|
+
"mediaRecorderErrorName",
|
|
6389
|
+
"mediaRecorderErrorMessage",
|
|
6390
|
+
"mediaRecorderMimeType",
|
|
6391
|
+
"mediaRecorderChunkCount",
|
|
6392
|
+
"mediaRecorderChunkSize"
|
|
6393
|
+
];
|
|
6229
6394
|
function resolveInstallationId(dependencies) {
|
|
6230
6395
|
const storageProvider = dependencies.storageProvider;
|
|
6231
6396
|
const stored = storageProvider?.getItem(TELEMETRY_STORAGE_KEY);
|
|
@@ -6567,6 +6732,23 @@ class TelemetryClient {
|
|
|
6567
6732
|
recordingPipelineErrorCode: recordingPipelineCode
|
|
6568
6733
|
};
|
|
6569
6734
|
}
|
|
6735
|
+
return {
|
|
6736
|
+
...properties,
|
|
6737
|
+
...this.buildMediaRecorderErrorProperties(error)
|
|
6738
|
+
};
|
|
6739
|
+
}
|
|
6740
|
+
buildMediaRecorderErrorProperties(error) {
|
|
6741
|
+
if (!(error && typeof error === "object")) {
|
|
6742
|
+
return {};
|
|
6743
|
+
}
|
|
6744
|
+
const candidate = error;
|
|
6745
|
+
let properties = {};
|
|
6746
|
+
for (const key of MEDIA_RECORDER_ERROR_PROPERTY_KEYS) {
|
|
6747
|
+
const value = candidate[key];
|
|
6748
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
6749
|
+
properties = { ...properties, [key]: value };
|
|
6750
|
+
}
|
|
6751
|
+
}
|
|
6570
6752
|
return properties;
|
|
6571
6753
|
}
|
|
6572
6754
|
buildFingerprint() {
|
|
@@ -22637,6 +22819,9 @@ class RecordingManager {
|
|
|
22637
22819
|
if (stopResult.encoderAcceleration !== undefined) {
|
|
22638
22820
|
telemetryProperties.encoderAcceleration = stopResult.encoderAcceleration;
|
|
22639
22821
|
}
|
|
22822
|
+
if (stopResult.mediaRecorderDiagnostics !== undefined) {
|
|
22823
|
+
Object.assign(telemetryProperties, stopResult.mediaRecorderDiagnostics);
|
|
22824
|
+
}
|
|
22640
22825
|
return {
|
|
22641
22826
|
blob: finalBlob,
|
|
22642
22827
|
telemetryProperties
|
|
@@ -22646,6 +22831,10 @@ class RecordingManager {
|
|
|
22646
22831
|
this.handleError(normalizedError);
|
|
22647
22832
|
this.recordingState = RECORDING_STATE_IDLE;
|
|
22648
22833
|
this.callbacks.onStateChange(this.recordingState);
|
|
22834
|
+
if (this.streamProcessor) {
|
|
22835
|
+
this.streamProcessor.destroy();
|
|
22836
|
+
this.streamProcessor = null;
|
|
22837
|
+
}
|
|
22649
22838
|
throw normalizedError;
|
|
22650
22839
|
} finally {
|
|
22651
22840
|
this.activeRecordingConfig = null;
|
|
@@ -23105,6 +23294,7 @@ class RecorderController {
|
|
|
23105
23294
|
}
|
|
23106
23295
|
async stopRecording() {
|
|
23107
23296
|
const sourceType = this.getCurrentSourceType();
|
|
23297
|
+
let shouldStopStream = true;
|
|
23108
23298
|
try {
|
|
23109
23299
|
const stopResult = await this.telemetryManager.executeActionWithResult({
|
|
23110
23300
|
requestedEvent: "recording.stop.requested",
|
|
@@ -23115,7 +23305,6 @@ class RecorderController {
|
|
|
23115
23305
|
await this.sourceSwitchManager.handleRecordingStop().catch(() => {
|
|
23116
23306
|
throw new Error("Source switch cleanup failed");
|
|
23117
23307
|
});
|
|
23118
|
-
this.streamManager.stopStream();
|
|
23119
23308
|
return recordingStopResult;
|
|
23120
23309
|
},
|
|
23121
23310
|
properties: {
|
|
@@ -23127,11 +23316,22 @@ class RecorderController {
|
|
|
23127
23316
|
});
|
|
23128
23317
|
return stopResult.blob;
|
|
23129
23318
|
} catch (error) {
|
|
23319
|
+
shouldStopStream = this.shouldStopStreamAfterStopFailure(error);
|
|
23130
23320
|
if (isAudioMissingError(error)) {
|
|
23131
23321
|
this.sendAudioMissingTelemetry(error);
|
|
23132
23322
|
}
|
|
23133
23323
|
throw error;
|
|
23324
|
+
} finally {
|
|
23325
|
+
if (shouldStopStream) {
|
|
23326
|
+
this.streamManager.stopStream();
|
|
23327
|
+
}
|
|
23328
|
+
}
|
|
23329
|
+
}
|
|
23330
|
+
shouldStopStreamAfterStopFailure(error) {
|
|
23331
|
+
if (!(error && typeof error === "object" && ("code" in error))) {
|
|
23332
|
+
return true;
|
|
23134
23333
|
}
|
|
23334
|
+
return error.code !== ERROR_RECORDING_STOP_NOT_READY;
|
|
23135
23335
|
}
|
|
23136
23336
|
sendAudioMissingTelemetry(error) {
|
|
23137
23337
|
const properties = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidtreo/recorder",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications.",
|
|
6
6
|
"main": "./dist/index.js",
|