@vidtreo/recorder 1.6.2 → 1.6.3
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 +16 -2
- package/dist/index.js +136 -33
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -683,9 +683,17 @@ export type VideoEncoderProbeResult = {
|
|
|
683
683
|
id: string;
|
|
684
684
|
config: VideoEncoderConfig;
|
|
685
685
|
supported: boolean;
|
|
686
|
+
encodingCapability?: VideoEncodingCapabilityProbeResult;
|
|
686
687
|
errorCode?: "codec-probe-error";
|
|
687
688
|
errorMessage?: string;
|
|
688
689
|
};
|
|
690
|
+
export type VideoEncodingCapabilityProbeResult = {
|
|
691
|
+
supported: boolean;
|
|
692
|
+
smooth: boolean;
|
|
693
|
+
powerEfficient: boolean;
|
|
694
|
+
errorCode?: "encoding-capability-probe-error";
|
|
695
|
+
errorMessage?: string;
|
|
696
|
+
};
|
|
689
697
|
export type DeviceCapabilityProfile = {
|
|
690
698
|
browser: {
|
|
691
699
|
name: string;
|
|
@@ -712,12 +720,14 @@ export type DeviceCapabilityProfile = {
|
|
|
712
720
|
codecProbeResults: VideoEncoderProbeResult[];
|
|
713
721
|
};
|
|
714
722
|
export type VideoEncoderSupportProbe = (config: VideoEncoderConfig) => Promise<VideoEncoderSupport>;
|
|
723
|
+
export type VideoEncodingCapabilityProbe = (config: VideoEncoderConfig) => Promise<MediaCapabilitiesEncodingInfo | null>;
|
|
715
724
|
type DeviceCapabilityProfileDependencies = {
|
|
716
725
|
navigatorProvider: CapabilityNavigatorProvider | null;
|
|
717
726
|
secureContextProvider: () => boolean;
|
|
718
727
|
mediaRecorderAvailabilityProvider: () => boolean;
|
|
719
728
|
videoEncoderAvailabilityProvider: () => boolean;
|
|
720
729
|
codecSupportProbe: VideoEncoderSupportProbe;
|
|
730
|
+
encodingCapabilityProbe: VideoEncodingCapabilityProbe;
|
|
721
731
|
};
|
|
722
732
|
export type BuildDeviceCapabilityProfileOptions = {
|
|
723
733
|
codecCandidates?: VideoEncoderConfigCandidate[];
|
|
@@ -2443,7 +2453,7 @@ export declare function deserializeBitrate(bitrate: number | string | undefined)
|
|
|
2443
2453
|
import type { DeviceCapabilityProfile } from "../capabilities/device-capability-profile";
|
|
2444
2454
|
export type RecordingCapabilityProfile = DeviceCapabilityProfile;
|
|
2445
2455
|
export type RecordingRoute = "local-webcodecs" | "safe-capture-post-recording-transcode" | "unsupported-with-guidance";
|
|
2446
|
-
export type RecordingRouteReasonCode = "webcodecs-supported" | "webcodecs-unavailable" | "
|
|
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";
|
|
2447
2457
|
export type RecordingRouteDecision = {
|
|
2448
2458
|
route: RecordingRoute;
|
|
2449
2459
|
reasonCodes: RecordingRouteReasonCode[];
|
|
@@ -2466,7 +2476,8 @@ export declare function decideRecordingRoute(options: DecideRecordingRouteOption
|
|
|
2466
2476
|
export declare const ERROR_RECORDING_STOP_NOT_READY = "recording.stop-not-ready";
|
|
2467
2477
|
export declare const ERROR_RECORDING_FINALIZE_NOT_READY = "recording.finalize-not-ready";
|
|
2468
2478
|
export declare const ERROR_RECORDING_FINALIZE_TIMEOUT = "recording.finalize-timeout";
|
|
2469
|
-
export
|
|
2479
|
+
export declare const ERROR_RECORDING_UPLOAD_IN_PROGRESS = "recording.upload-in-progress";
|
|
2480
|
+
export type RecordingLifecycleErrorCode = typeof ERROR_RECORDING_STOP_NOT_READY | typeof ERROR_RECORDING_FINALIZE_NOT_READY | typeof ERROR_RECORDING_FINALIZE_TIMEOUT | typeof ERROR_RECORDING_UPLOAD_IN_PROGRESS;
|
|
2470
2481
|
export type RecordingLifecycleError = Error & {
|
|
2471
2482
|
code: RecordingLifecycleErrorCode;
|
|
2472
2483
|
isRecoverable: true;
|
|
@@ -2759,6 +2770,7 @@ export declare class RecorderController {
|
|
|
2759
2770
|
private isDestroyed;
|
|
2760
2771
|
private enableTabVisibilityOverlay;
|
|
2761
2772
|
private tabVisibilityOverlayText;
|
|
2773
|
+
private isUploadInProgress;
|
|
2762
2774
|
private recordingWarmupTimeoutId;
|
|
2763
2775
|
private audioTelemetryUnsub;
|
|
2764
2776
|
constructor(callbacks?: RecorderCallbacks);
|
|
@@ -2788,6 +2800,7 @@ export declare class RecorderController {
|
|
|
2788
2800
|
stopAudioLevelTracking(): void;
|
|
2789
2801
|
getAudioLevel(): number;
|
|
2790
2802
|
uploadVideo(blob: Blob, apiKey: string, backendUrl: string, metadata: Record<string, unknown>): Promise<void>;
|
|
2803
|
+
private uploadVideoAfterStatusClear;
|
|
2791
2804
|
private uploadDirectlyWithoutQueue;
|
|
2792
2805
|
getStream(): MediaStream | null;
|
|
2793
2806
|
isConfigReady(): boolean;
|
|
@@ -2802,6 +2815,7 @@ export declare class RecorderController {
|
|
|
2802
2815
|
getDeviceManager(): DeviceManager;
|
|
2803
2816
|
getConfig(): Promise<TranscodeConfig>;
|
|
2804
2817
|
getUploadService(): VideoUploadService | null;
|
|
2818
|
+
isUploading(): boolean;
|
|
2805
2819
|
isRecording(): boolean;
|
|
2806
2820
|
isActive(): boolean;
|
|
2807
2821
|
isAudioReady(): boolean;
|
package/dist/index.js
CHANGED
|
@@ -1321,6 +1321,48 @@ function isMediaRecorderAvailable() {
|
|
|
1321
1321
|
function isVideoEncoderAvailable() {
|
|
1322
1322
|
return typeof globalThis.VideoEncoder !== "undefined";
|
|
1323
1323
|
}
|
|
1324
|
+
function getGlobalMediaCapabilities() {
|
|
1325
|
+
if (typeof navigator === "undefined") {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
return navigator.mediaCapabilities;
|
|
1329
|
+
}
|
|
1330
|
+
function resolveEncodingContentType(codec) {
|
|
1331
|
+
const normalizedCodec = codec.toLowerCase();
|
|
1332
|
+
if (normalizedCodec.startsWith("avc1") || normalizedCodec.startsWith("av01") || normalizedCodec.startsWith("hvc1") || normalizedCodec.startsWith("hev1")) {
|
|
1333
|
+
return `video/mp4;codecs="${codec}"`;
|
|
1334
|
+
}
|
|
1335
|
+
if (normalizedCodec === "vp8" || normalizedCodec.startsWith("vp09") || normalizedCodec.startsWith("vp9")) {
|
|
1336
|
+
return `video/webm;codecs="${codec}"`;
|
|
1337
|
+
}
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
function probeVideoEncodingCapability(config) {
|
|
1341
|
+
const mediaCapabilities = getGlobalMediaCapabilities();
|
|
1342
|
+
if (!mediaCapabilities) {
|
|
1343
|
+
return Promise.resolve(null);
|
|
1344
|
+
}
|
|
1345
|
+
const contentType = resolveEncodingContentType(config.codec);
|
|
1346
|
+
if (contentType === null) {
|
|
1347
|
+
return Promise.resolve(null);
|
|
1348
|
+
}
|
|
1349
|
+
if (!(typeof config.bitrate === "number")) {
|
|
1350
|
+
return Promise.resolve(null);
|
|
1351
|
+
}
|
|
1352
|
+
if (!(typeof config.framerate === "number")) {
|
|
1353
|
+
return Promise.resolve(null);
|
|
1354
|
+
}
|
|
1355
|
+
return mediaCapabilities.encodingInfo({
|
|
1356
|
+
type: "record",
|
|
1357
|
+
video: {
|
|
1358
|
+
contentType,
|
|
1359
|
+
width: config.width,
|
|
1360
|
+
height: config.height,
|
|
1361
|
+
bitrate: config.bitrate,
|
|
1362
|
+
framerate: config.framerate
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1324
1366
|
function probeVideoEncoderConfig(config) {
|
|
1325
1367
|
if (typeof globalThis.VideoEncoder === "undefined") {
|
|
1326
1368
|
return Promise.resolve({
|
|
@@ -1336,7 +1378,8 @@ function resolveDependencies(dependencies) {
|
|
|
1336
1378
|
secureContextProvider: dependencies?.secureContextProvider ?? isSecureContextAvailable,
|
|
1337
1379
|
mediaRecorderAvailabilityProvider: dependencies?.mediaRecorderAvailabilityProvider ?? isMediaRecorderAvailable,
|
|
1338
1380
|
videoEncoderAvailabilityProvider: dependencies?.videoEncoderAvailabilityProvider ?? isVideoEncoderAvailable,
|
|
1339
|
-
codecSupportProbe: dependencies?.codecSupportProbe ?? probeVideoEncoderConfig
|
|
1381
|
+
codecSupportProbe: dependencies?.codecSupportProbe ?? probeVideoEncoderConfig,
|
|
1382
|
+
encodingCapabilityProbe: dependencies?.encodingCapabilityProbe ?? probeVideoEncodingCapability
|
|
1340
1383
|
};
|
|
1341
1384
|
}
|
|
1342
1385
|
function parseUserAgent(userAgent) {
|
|
@@ -1362,14 +1405,22 @@ function normalizeProbeErrorMessage(error) {
|
|
|
1362
1405
|
}
|
|
1363
1406
|
return String(error);
|
|
1364
1407
|
}
|
|
1365
|
-
async function probeCodecCandidate(candidate, codecSupportProbe) {
|
|
1408
|
+
async function probeCodecCandidate(candidate, codecSupportProbe, encodingCapabilityProbe) {
|
|
1366
1409
|
try {
|
|
1367
1410
|
const result = await codecSupportProbe(candidate.config);
|
|
1368
|
-
|
|
1411
|
+
const encodingCapability = await probeEncodingCapability(candidate.config, encodingCapabilityProbe);
|
|
1412
|
+
const probeResult = {
|
|
1369
1413
|
id: candidate.id,
|
|
1370
1414
|
config: candidate.config,
|
|
1371
1415
|
supported: result.supported === true
|
|
1372
1416
|
};
|
|
1417
|
+
if (encodingCapability === undefined) {
|
|
1418
|
+
return probeResult;
|
|
1419
|
+
}
|
|
1420
|
+
return {
|
|
1421
|
+
...probeResult,
|
|
1422
|
+
encodingCapability
|
|
1423
|
+
};
|
|
1373
1424
|
} catch (error) {
|
|
1374
1425
|
return {
|
|
1375
1426
|
id: candidate.id,
|
|
@@ -1380,10 +1431,28 @@ async function probeCodecCandidate(candidate, codecSupportProbe) {
|
|
|
1380
1431
|
};
|
|
1381
1432
|
}
|
|
1382
1433
|
}
|
|
1434
|
+
function probeEncodingCapability(config, encodingCapabilityProbe) {
|
|
1435
|
+
return Promise.resolve().then(() => encodingCapabilityProbe(config)).then((result) => {
|
|
1436
|
+
if (result === null) {
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
return {
|
|
1440
|
+
supported: result.supported === true,
|
|
1441
|
+
smooth: result.smooth === true,
|
|
1442
|
+
powerEfficient: result.powerEfficient === true
|
|
1443
|
+
};
|
|
1444
|
+
}).catch((error) => ({
|
|
1445
|
+
supported: false,
|
|
1446
|
+
smooth: false,
|
|
1447
|
+
powerEfficient: false,
|
|
1448
|
+
errorCode: "encoding-capability-probe-error",
|
|
1449
|
+
errorMessage: normalizeProbeErrorMessage(error)
|
|
1450
|
+
}));
|
|
1451
|
+
}
|
|
1383
1452
|
async function buildDeviceCapabilityProfile(options = {}) {
|
|
1384
1453
|
const dependencies = resolveDependencies(options.dependencies);
|
|
1385
1454
|
const { browser, os } = parseUserAgent(dependencies.navigatorProvider?.userAgent);
|
|
1386
|
-
const codecProbeResults = await Promise.all((options.codecCandidates ?? []).map((candidate) => probeCodecCandidate(candidate, dependencies.codecSupportProbe)));
|
|
1455
|
+
const codecProbeResults = await Promise.all((options.codecCandidates ?? []).map((candidate) => probeCodecCandidate(candidate, dependencies.codecSupportProbe, dependencies.encodingCapabilityProbe)));
|
|
1387
1456
|
return {
|
|
1388
1457
|
browser,
|
|
1389
1458
|
os,
|
|
@@ -1406,7 +1475,7 @@ async function buildDeviceCapabilityProfile(options = {}) {
|
|
|
1406
1475
|
|
|
1407
1476
|
// src/core/routing/recording-route-decision.ts
|
|
1408
1477
|
var DEFAULT_MIN_DEVICE_MEMORY_GB = 4;
|
|
1409
|
-
var DEFAULT_MIN_HARDWARE_CONCURRENCY =
|
|
1478
|
+
var DEFAULT_MIN_HARDWARE_CONCURRENCY = 5;
|
|
1410
1479
|
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.";
|
|
1411
1480
|
function hasSufficientDeviceMemory(profile, minDeviceMemory) {
|
|
1412
1481
|
const deviceMemory = profile.resources.deviceMemory;
|
|
@@ -1416,18 +1485,14 @@ function hasSufficientHardwareConcurrency(profile, minHardwareConcurrency) {
|
|
|
1416
1485
|
const hardwareConcurrency = profile.resources.hardwareConcurrency;
|
|
1417
1486
|
return hardwareConcurrency === undefined || hardwareConcurrency >= minHardwareConcurrency;
|
|
1418
1487
|
}
|
|
1419
|
-
function
|
|
1488
|
+
function findSupportedTargetCodec(profile, targetCodecIds) {
|
|
1420
1489
|
const targetCodecIdSet = new Set(targetCodecIds);
|
|
1421
|
-
|
|
1422
|
-
return supportedCodec?.id;
|
|
1490
|
+
return profile.codecProbeResults.find((result) => targetCodecIdSet.has(result.id) && result.supported);
|
|
1423
1491
|
}
|
|
1424
1492
|
function canUsePostRecordingTranscodeRoute(profile, hasSupportedTargetCodec) {
|
|
1425
1493
|
return profile.features.isSecureContext && profile.features.hasMediaRecorder && profile.features.hasVideoEncoder && hasSupportedTargetCodec && profile.network.online !== false;
|
|
1426
1494
|
}
|
|
1427
|
-
function
|
|
1428
|
-
return profile.os.name.toLowerCase() === "android";
|
|
1429
|
-
}
|
|
1430
|
-
function buildLocalRouteBlockers(profile, hasSupportedTargetCodec, hasEnoughDeviceMemory, hasEnoughHardwareConcurrency) {
|
|
1495
|
+
function buildLocalRouteBlockers(profile, hasSupportedTargetCodec, hasEnoughDeviceMemory, hasEnoughHardwareConcurrency, hasSupportedEncodingCapability, hasSmoothEncoding, hasPowerEfficientEncoding) {
|
|
1431
1496
|
const reasonCodes = [];
|
|
1432
1497
|
if (!profile.features.isSecureContext) {
|
|
1433
1498
|
reasonCodes.push("insecure-context");
|
|
@@ -1444,8 +1509,14 @@ function buildLocalRouteBlockers(profile, hasSupportedTargetCodec, hasEnoughDevi
|
|
|
1444
1509
|
if (!hasEnoughHardwareConcurrency) {
|
|
1445
1510
|
reasonCodes.push("insufficient-hardware-concurrency");
|
|
1446
1511
|
}
|
|
1447
|
-
if (
|
|
1448
|
-
reasonCodes.push("
|
|
1512
|
+
if (!hasSupportedEncodingCapability) {
|
|
1513
|
+
reasonCodes.push("encoding-capability-unsupported");
|
|
1514
|
+
}
|
|
1515
|
+
if (!hasSmoothEncoding) {
|
|
1516
|
+
reasonCodes.push("encoding-not-smooth");
|
|
1517
|
+
}
|
|
1518
|
+
if (!hasPowerEfficientEncoding) {
|
|
1519
|
+
reasonCodes.push("encoding-not-power-efficient");
|
|
1449
1520
|
}
|
|
1450
1521
|
return reasonCodes;
|
|
1451
1522
|
}
|
|
@@ -1459,15 +1530,31 @@ function buildFallbackRouteBlockers(profile) {
|
|
|
1459
1530
|
}
|
|
1460
1531
|
return reasonCodes;
|
|
1461
1532
|
}
|
|
1533
|
+
function hasSupportedEncodingCapability(selectedCodec) {
|
|
1534
|
+
const encodingCapability = selectedCodec?.encodingCapability;
|
|
1535
|
+
return encodingCapability === undefined || encodingCapability.errorCode !== undefined || encodingCapability.supported === true;
|
|
1536
|
+
}
|
|
1537
|
+
function hasSmoothEncodingCapability(selectedCodec) {
|
|
1538
|
+
const encodingCapability = selectedCodec?.encodingCapability;
|
|
1539
|
+
return encodingCapability === undefined || encodingCapability.errorCode !== undefined || encodingCapability.supported !== true || encodingCapability.smooth === true;
|
|
1540
|
+
}
|
|
1541
|
+
function hasPowerEfficientEncodingCapability(selectedCodec) {
|
|
1542
|
+
const encodingCapability = selectedCodec?.encodingCapability;
|
|
1543
|
+
return encodingCapability === undefined || encodingCapability.errorCode !== undefined || encodingCapability.supported !== true || encodingCapability.powerEfficient === true;
|
|
1544
|
+
}
|
|
1462
1545
|
function decideRecordingRoute(options) {
|
|
1463
1546
|
const minDeviceMemory = options.resourceThresholds?.minDeviceMemory ?? DEFAULT_MIN_DEVICE_MEMORY_GB;
|
|
1464
1547
|
const minHardwareConcurrency = options.resourceThresholds?.minHardwareConcurrency ?? DEFAULT_MIN_HARDWARE_CONCURRENCY;
|
|
1465
|
-
const
|
|
1548
|
+
const selectedCodec = findSupportedTargetCodec(options.profile, options.targetCodecIds);
|
|
1549
|
+
const selectedCodecId = selectedCodec?.id;
|
|
1466
1550
|
const hasSupportedTargetCodec = selectedCodecId !== undefined;
|
|
1467
1551
|
const hasEnoughDeviceMemory = hasSufficientDeviceMemory(options.profile, minDeviceMemory);
|
|
1468
1552
|
const hasEnoughHardwareConcurrency = hasSufficientHardwareConcurrency(options.profile, minHardwareConcurrency);
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1553
|
+
const hasSupportedEncoding = hasSupportedEncodingCapability(selectedCodec);
|
|
1554
|
+
const hasSmoothEncoding = hasSmoothEncodingCapability(selectedCodec);
|
|
1555
|
+
const hasPowerEfficientEncoding = hasPowerEfficientEncodingCapability(selectedCodec);
|
|
1556
|
+
const localRouteBlockers = buildLocalRouteBlockers(options.profile, hasSupportedTargetCodec, hasEnoughDeviceMemory, hasEnoughHardwareConcurrency, hasSupportedEncoding, hasSmoothEncoding, hasPowerEfficientEncoding);
|
|
1557
|
+
if (options.profile.features.isSecureContext && options.profile.features.hasVideoEncoder && hasSupportedTargetCodec && options.profile.network.online !== false && hasEnoughDeviceMemory && hasEnoughHardwareConcurrency && hasSupportedEncoding && hasSmoothEncoding && hasPowerEfficientEncoding) {
|
|
1471
1558
|
return {
|
|
1472
1559
|
route: "local-webcodecs",
|
|
1473
1560
|
reasonCodes: ["webcodecs-supported", "target-codec-supported"],
|
|
@@ -4952,6 +5039,7 @@ class StreamManager {
|
|
|
4952
5039
|
var ERROR_RECORDING_STOP_NOT_READY = "recording.stop-not-ready";
|
|
4953
5040
|
var ERROR_RECORDING_FINALIZE_NOT_READY = "recording.finalize-not-ready";
|
|
4954
5041
|
var ERROR_RECORDING_FINALIZE_TIMEOUT = "recording.finalize-timeout";
|
|
5042
|
+
var ERROR_RECORDING_UPLOAD_IN_PROGRESS = "recording.upload-in-progress";
|
|
4955
5043
|
function createRecordingLifecycleError(code, message, details) {
|
|
4956
5044
|
const error = new Error(`${message} [${code}]`);
|
|
4957
5045
|
error.code = code;
|
|
@@ -4978,7 +5066,7 @@ function normalizeRecordingLifecycleError(error) {
|
|
|
4978
5066
|
}
|
|
4979
5067
|
function isRecordingLifecycleError(error) {
|
|
4980
5068
|
const errorWithCode = error;
|
|
4981
|
-
return errorWithCode.code === ERROR_RECORDING_STOP_NOT_READY || errorWithCode.code === ERROR_RECORDING_FINALIZE_NOT_READY || errorWithCode.code === ERROR_RECORDING_FINALIZE_TIMEOUT;
|
|
5069
|
+
return errorWithCode.code === ERROR_RECORDING_STOP_NOT_READY || errorWithCode.code === ERROR_RECORDING_FINALIZE_NOT_READY || errorWithCode.code === ERROR_RECORDING_FINALIZE_TIMEOUT || errorWithCode.code === ERROR_RECORDING_UPLOAD_IN_PROGRESS;
|
|
4982
5070
|
}
|
|
4983
5071
|
|
|
4984
5072
|
// src/core/utils/formatters.ts
|
|
@@ -5158,11 +5246,6 @@ var LOW_RESOURCE_SAFE_CAPTURE_CONSTRAINTS = {
|
|
|
5158
5246
|
height: { ideal: 480, max: 480 },
|
|
5159
5247
|
frameRate: { ideal: 20, max: 20 }
|
|
5160
5248
|
};
|
|
5161
|
-
var ANDROID_SAFE_CAPTURE_CONSTRAINTS = {
|
|
5162
|
-
width: { ideal: 1280, max: 1280 },
|
|
5163
|
-
height: { ideal: 720, max: 720 },
|
|
5164
|
-
frameRate: { ideal: 24, max: 24 }
|
|
5165
|
-
};
|
|
5166
5249
|
function createUnsupportedRouteError(routeDecision) {
|
|
5167
5250
|
const guidance = routeDecision.guidance?.message ?? "This browser or device cannot safely record video. Try a supported browser or upload an existing video file.";
|
|
5168
5251
|
const error = new Error(guidance);
|
|
@@ -5549,12 +5632,9 @@ class StreamRecordingState {
|
|
|
5549
5632
|
}
|
|
5550
5633
|
resolveSafeCaptureVideoConstraints() {
|
|
5551
5634
|
const reasonCodes = this.activeRouteDecision.reasonCodes;
|
|
5552
|
-
if (reasonCodes.includes("insufficient-device-memory") || reasonCodes.includes("insufficient-hardware-concurrency")) {
|
|
5635
|
+
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")) {
|
|
5553
5636
|
return LOW_RESOURCE_SAFE_CAPTURE_CONSTRAINTS;
|
|
5554
5637
|
}
|
|
5555
|
-
if (reasonCodes.includes("android-post-recording-transcode")) {
|
|
5556
|
-
return ANDROID_SAFE_CAPTURE_CONSTRAINTS;
|
|
5557
|
-
}
|
|
5558
5638
|
return null;
|
|
5559
5639
|
}
|
|
5560
5640
|
async stopSafeCaptureRecording() {
|
|
@@ -6061,7 +6141,7 @@ function resolveDeviceError(input) {
|
|
|
6061
6141
|
// package.json
|
|
6062
6142
|
var package_default = {
|
|
6063
6143
|
name: "@vidtreo/recorder",
|
|
6064
|
-
version: "1.6.
|
|
6144
|
+
version: "1.6.3",
|
|
6065
6145
|
type: "module",
|
|
6066
6146
|
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.",
|
|
6067
6147
|
main: "./dist/index.js",
|
|
@@ -6206,7 +6286,7 @@ var TELEMETRY_EVENT_CATEGORY_MAP = {
|
|
|
6206
6286
|
"audio.acquisition.retry": "lifecycle",
|
|
6207
6287
|
"audio.acquisition.recovered": "lifecycle",
|
|
6208
6288
|
"audio.acquisition.failed": "error",
|
|
6209
|
-
"audio.warning": "
|
|
6289
|
+
"audio.warning": "performance",
|
|
6210
6290
|
"recording.audio-missing": "error",
|
|
6211
6291
|
"storage.init.failed": "error",
|
|
6212
6292
|
"storage.write.probe.failed": "error"
|
|
@@ -22867,6 +22947,7 @@ class RecorderController {
|
|
|
22867
22947
|
isDestroyed = false;
|
|
22868
22948
|
enableTabVisibilityOverlay = false;
|
|
22869
22949
|
tabVisibilityOverlayText;
|
|
22950
|
+
isUploadInProgress = false;
|
|
22870
22951
|
recordingWarmupTimeoutId = null;
|
|
22871
22952
|
audioTelemetryUnsub = null;
|
|
22872
22953
|
constructor(callbacks = {}) {
|
|
@@ -23010,6 +23091,9 @@ class RecorderController {
|
|
|
23010
23091
|
succeededEvent: "recording.start.succeeded",
|
|
23011
23092
|
failedEvent: "recording.start.failed",
|
|
23012
23093
|
action: async () => {
|
|
23094
|
+
if (this.isUploading()) {
|
|
23095
|
+
throw createRecordingLifecycleError(ERROR_RECORDING_UPLOAD_IN_PROGRESS, "Previous recording is still being uploaded");
|
|
23096
|
+
}
|
|
23013
23097
|
await this.ensureConfigReady();
|
|
23014
23098
|
await this.streamManager.waitForAudio();
|
|
23015
23099
|
await this.recordingManager.startRecording();
|
|
@@ -23063,7 +23147,7 @@ class RecorderController {
|
|
|
23063
23147
|
properties.silenceRatio = error.details.durationMs > 0 ? error.details.mutedDurationMs / error.details.durationMs : 0;
|
|
23064
23148
|
properties.hadTrackEndedWarning = error.details.hadTrackEndedWarning;
|
|
23065
23149
|
}
|
|
23066
|
-
this.telemetryManager.sendEvent("recording.audio-missing", properties);
|
|
23150
|
+
this.telemetryManager.sendEvent("recording.audio-missing", properties, error);
|
|
23067
23151
|
}
|
|
23068
23152
|
getTabVisibilityOverlayConfig() {
|
|
23069
23153
|
return {
|
|
@@ -23124,8 +23208,15 @@ class RecorderController {
|
|
|
23124
23208
|
getAudioLevel() {
|
|
23125
23209
|
return this.audioLevelAnalyzer.getAudioLevel();
|
|
23126
23210
|
}
|
|
23127
|
-
|
|
23211
|
+
uploadVideo(blob, apiKey, backendUrl, metadata) {
|
|
23128
23212
|
this.uploadCallbacks.onClearStatus();
|
|
23213
|
+
this.isUploadInProgress = true;
|
|
23214
|
+
return this.uploadVideoAfterStatusClear(blob, apiKey, backendUrl, metadata).catch((error) => {
|
|
23215
|
+
this.isUploadInProgress = false;
|
|
23216
|
+
throw error;
|
|
23217
|
+
});
|
|
23218
|
+
}
|
|
23219
|
+
async uploadVideoAfterStatusClear(blob, apiKey, backendUrl, metadata) {
|
|
23129
23220
|
const duration = await extractVideoDuration(blob);
|
|
23130
23221
|
const outputFormat = this.configManager.getConfigForRecording().format;
|
|
23131
23222
|
const filename = `recording-${Date.now()}.${getFileExtensionForOutputFormat(outputFormat)}`;
|
|
@@ -23206,6 +23297,8 @@ class RecorderController {
|
|
|
23206
23297
|
sourceType: options.sourceType
|
|
23207
23298
|
}, uploadError);
|
|
23208
23299
|
throw uploadError;
|
|
23300
|
+
} finally {
|
|
23301
|
+
this.isUploadInProgress = false;
|
|
23209
23302
|
}
|
|
23210
23303
|
}
|
|
23211
23304
|
getStream() {
|
|
@@ -23229,6 +23322,7 @@ class RecorderController {
|
|
|
23229
23322
|
}
|
|
23230
23323
|
cleanup() {
|
|
23231
23324
|
this.isDestroyed = true;
|
|
23325
|
+
this.isUploadInProgress = false;
|
|
23232
23326
|
if (this.recordingWarmupTimeoutId !== null) {
|
|
23233
23327
|
clearTimeout(this.recordingWarmupTimeoutId);
|
|
23234
23328
|
this.recordingWarmupTimeoutId = null;
|
|
@@ -23270,6 +23364,9 @@ class RecorderController {
|
|
|
23270
23364
|
getUploadService() {
|
|
23271
23365
|
return this.uploadService;
|
|
23272
23366
|
}
|
|
23367
|
+
isUploading() {
|
|
23368
|
+
return this.isUploadInProgress;
|
|
23369
|
+
}
|
|
23273
23370
|
isRecording() {
|
|
23274
23371
|
return this.streamManager.isRecording();
|
|
23275
23372
|
}
|
|
@@ -23368,7 +23465,10 @@ class RecorderController {
|
|
|
23368
23465
|
isSecureContext: profile.features.isSecureContext,
|
|
23369
23466
|
hasMediaRecorder: profile.features.hasMediaRecorder,
|
|
23370
23467
|
hasVideoEncoder: profile.features.hasVideoEncoder,
|
|
23371
|
-
supportedCodecIds: profile.codecProbeResults.filter((result) => result.supported).map((result) => result.id)
|
|
23468
|
+
supportedCodecIds: profile.codecProbeResults.filter((result) => result.supported).map((result) => result.id),
|
|
23469
|
+
supportedEncodingCodecIds: profile.codecProbeResults.filter((result) => result.encodingCapability?.supported === true).map((result) => result.id),
|
|
23470
|
+
smoothEncodingCodecIds: profile.codecProbeResults.filter((result) => result.encodingCapability?.smooth === true).map((result) => result.id),
|
|
23471
|
+
powerEfficientEncodingCodecIds: profile.codecProbeResults.filter((result) => result.encodingCapability?.powerEfficient === true).map((result) => result.id)
|
|
23372
23472
|
}
|
|
23373
23473
|
};
|
|
23374
23474
|
}
|
|
@@ -23422,9 +23522,10 @@ class RecorderController {
|
|
|
23422
23522
|
const probeResult = this.storageManager.getWriteProbeResult();
|
|
23423
23523
|
if (!probeResult?.ok) {
|
|
23424
23524
|
const reason = probeResult?.reason ?? "Storage write probe did not complete";
|
|
23525
|
+
const storageWriteProbeError = new Error(reason);
|
|
23425
23526
|
this.telemetryManager.sendEvent("storage.write.probe.failed", {
|
|
23426
23527
|
reason
|
|
23427
|
-
});
|
|
23528
|
+
}, storageWriteProbeError);
|
|
23428
23529
|
const onStorageWriteError = resolveStorageWriteErrorCallback(this.callbacks);
|
|
23429
23530
|
onStorageWriteError(reason);
|
|
23430
23531
|
return;
|
|
@@ -23439,6 +23540,7 @@ class RecorderController {
|
|
|
23439
23540
|
this.uploadCallbacks.onProgress(progress);
|
|
23440
23541
|
},
|
|
23441
23542
|
onUploadComplete: (id, result) => {
|
|
23543
|
+
this.isUploadInProgress = false;
|
|
23442
23544
|
this.uploadCallbacks.onSuccess(result);
|
|
23443
23545
|
const metadata = this.uploadMetadataManager.getMetadata(id);
|
|
23444
23546
|
if (metadata) {
|
|
@@ -23452,6 +23554,7 @@ class RecorderController {
|
|
|
23452
23554
|
}
|
|
23453
23555
|
},
|
|
23454
23556
|
onUploadError: (id, error) => {
|
|
23557
|
+
this.isUploadInProgress = false;
|
|
23455
23558
|
this.uploadCallbacks.onError(error);
|
|
23456
23559
|
const metadata = this.uploadMetadataManager.getMetadata(id);
|
|
23457
23560
|
if (metadata) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidtreo/recorder",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
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",
|