@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 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" | "android-post-recording-transcode" | "target-codec-supported" | "target-codec-unsupported" | "insufficient-device-memory" | "insufficient-hardware-concurrency" | "insecure-context" | "mediarecorder-supported" | "mediarecorder-unavailable" | "offline";
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 type RecordingLifecycleErrorCode = typeof ERROR_RECORDING_STOP_NOT_READY | typeof ERROR_RECORDING_FINALIZE_NOT_READY | typeof ERROR_RECORDING_FINALIZE_TIMEOUT;
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
- return {
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 = 4;
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 findSupportedTargetCodecId(profile, targetCodecIds) {
1488
+ function findSupportedTargetCodec(profile, targetCodecIds) {
1420
1489
  const targetCodecIdSet = new Set(targetCodecIds);
1421
- const supportedCodec = profile.codecProbeResults.find((result) => targetCodecIdSet.has(result.id) && result.supported);
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 isAndroidBrowser(profile) {
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 (isAndroidBrowser(profile)) {
1448
- reasonCodes.push("android-post-recording-transcode");
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 selectedCodecId = findSupportedTargetCodecId(options.profile, options.targetCodecIds);
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 localRouteBlockers = buildLocalRouteBlockers(options.profile, hasSupportedTargetCodec, hasEnoughDeviceMemory, hasEnoughHardwareConcurrency);
1470
- if (options.profile.features.isSecureContext && options.profile.features.hasVideoEncoder && hasSupportedTargetCodec && options.profile.network.online !== false && !isAndroidBrowser(options.profile) && hasEnoughDeviceMemory && hasEnoughHardwareConcurrency) {
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.2",
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": "error",
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
- async uploadVideo(blob, apiKey, backendUrl, metadata) {
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.2",
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",