@vidtreo/recorder 1.7.5 → 1.7.7

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +2884 -2878
  2. package/dist/index.js +64 -19
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3075,7 +3075,6 @@ var DEFAULT_RETENTION_HOURS = 24;
3075
3075
  var MAX_RETRIES = 10;
3076
3076
  var MILLISECONDS_PER_HOUR = 60 * 60 * 1000;
3077
3077
  var ID_PREFIX = "upload-";
3078
- var ID_RANDOM_LENGTH = 9;
3079
3078
  var VERSION_ERROR_NAME = "VersionError";
3080
3079
  var ERROR_SCHEMA_MISSING_STORE = "Database schema is missing required object store: pending-uploads";
3081
3080
  var ERROR_SCHEMA_MISSING_STATUS_INDEX = "Database schema is missing required index: status";
@@ -3371,7 +3370,7 @@ class VideoStorageService {
3371
3370
  });
3372
3371
  }
3373
3372
  generateUploadId() {
3374
- return `${ID_PREFIX}${Date.now()}-${Math.random().toString(36).substring(2, 2 + ID_RANDOM_LENGTH)}`;
3373
+ return `${ID_PREFIX}${crypto.randomUUID()}`;
3375
3374
  }
3376
3375
  executeTransaction(mode, operation) {
3377
3376
  if (!this.db) {
@@ -6075,8 +6074,6 @@ var RECORDING_CODEC_UNSUPPORTED_CODES = new Set([
6075
6074
  ]);
6076
6075
  var RECORDING_SERVER_TRANSCODE_FALLBACK_CODES = new Set([
6077
6076
  "recording.post-recording-transcode-fallback",
6078
- "recording.mediarecorder-empty",
6079
- "recording.mediarecorder-error",
6080
6077
  "transcode.post-recording-fallback",
6081
6078
  "route.post-recording-transcode-fallback"
6082
6079
  ]);
@@ -6129,6 +6126,12 @@ function classifyRecordingPipelineCode(errorCode) {
6129
6126
  if (RECORDING_CODEC_UNSUPPORTED_CODES.has(errorCode)) {
6130
6127
  return "recording.codec-unsupported";
6131
6128
  }
6129
+ if (errorCode === "recording.mediarecorder-empty") {
6130
+ return "recording.mediarecorder-empty";
6131
+ }
6132
+ if (errorCode === "recording.mediarecorder-error") {
6133
+ return "recording.mediarecorder-error";
6134
+ }
6132
6135
  if (RECORDING_SERVER_TRANSCODE_FALLBACK_CODES.has(errorCode)) {
6133
6136
  return "recording.post-recording-transcode-fallback";
6134
6137
  }
@@ -6312,7 +6315,7 @@ function resolveDeviceError(input) {
6312
6315
  // package.json
6313
6316
  var package_default = {
6314
6317
  name: "@vidtreo/recorder",
6315
- version: "1.7.5",
6318
+ version: "1.7.7",
6316
6319
  type: "module",
6317
6320
  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.",
6318
6321
  main: "./dist/index.js",
@@ -7204,6 +7207,7 @@ class UploadQueueManager {
7204
7207
  apiKey: upload.apiKey,
7205
7208
  backendUrl: upload.backendUrl,
7206
7209
  filename: upload.filename,
7210
+ idempotencyKey: upload.id,
7207
7211
  mimeType: upload.mimeType,
7208
7212
  metadata: upload.metadata,
7209
7213
  userMetadata: upload.userMetadata,
@@ -7286,6 +7290,7 @@ class UploadQueueManager {
7286
7290
  // src/core/upload/upload-service.ts
7287
7291
  var API_UPLOAD_PATH = "/api/v1/videos/upload";
7288
7292
  var MIME_TYPE_PARAMETER_SEPARATOR = ";";
7293
+ var MAX_TRANSFER_PROGRESS = 0.99;
7289
7294
  var UPLOAD_INACTIVITY_TIMEOUT_MS = 45000;
7290
7295
  var UPLOAD_INACTIVITY_ERROR = "Upload stalled after 45 seconds without progress";
7291
7296
 
@@ -7297,15 +7302,17 @@ class VideoUploadService {
7297
7302
  if (!blob.type || blob.type.trim() === "") {
7298
7303
  throw new Error("Blob type is required");
7299
7304
  }
7300
- return this.uploadVideoFile(blob, options);
7305
+ const idempotencyKey = this.resolveIdempotencyKey(options.idempotencyKey);
7306
+ return this.uploadVideoFile(blob, options, idempotencyKey);
7301
7307
  }
7302
- uploadVideoFile(blob, options) {
7308
+ uploadVideoFile(blob, options, idempotencyKey) {
7303
7309
  return new Promise((resolve, reject) => {
7304
7310
  const xhr = new XMLHttpRequest;
7305
7311
  const uploadBlob = this.createUploadBlob(blob, options.mimeType);
7306
7312
  let inactivityTimeoutId = null;
7307
7313
  let isSettled = false;
7308
7314
  let loadedBytes = 0;
7315
+ let totalBytes = 0;
7309
7316
  const clearInactivityTimeout = () => {
7310
7317
  if (inactivityTimeoutId === null) {
7311
7318
  return;
@@ -7330,6 +7337,7 @@ class VideoUploadService {
7330
7337
  }
7331
7338
  isSettled = true;
7332
7339
  clearInactivityTimeout();
7340
+ this.reportCompletion(options, loadedBytes, totalBytes);
7333
7341
  resolve(result);
7334
7342
  };
7335
7343
  const restartInactivityTimeout = () => {
@@ -7344,6 +7352,7 @@ class VideoUploadService {
7344
7352
  return;
7345
7353
  }
7346
7354
  loadedBytes = event.loaded;
7355
+ totalBytes = event.total;
7347
7356
  restartInactivityTimeout();
7348
7357
  });
7349
7358
  xhr.addEventListener("load", () => {
@@ -7362,6 +7371,7 @@ class VideoUploadService {
7362
7371
  const url = `${options.backendUrl}${API_UPLOAD_PATH}`;
7363
7372
  xhr.open("POST", url);
7364
7373
  xhr.setRequestHeader("Authorization", `Bearer ${options.apiKey}`);
7374
+ xhr.setRequestHeader("Idempotency-Key", idempotencyKey);
7365
7375
  const formData = new FormData;
7366
7376
  formData.append("file", uploadBlob, options.filename);
7367
7377
  const metadata = {
@@ -7376,11 +7386,17 @@ class VideoUploadService {
7376
7386
  restartInactivityTimeout();
7377
7387
  });
7378
7388
  }
7389
+ resolveIdempotencyKey(idempotencyKey) {
7390
+ if (idempotencyKey) {
7391
+ return idempotencyKey;
7392
+ }
7393
+ return crypto.randomUUID();
7394
+ }
7379
7395
  reportProgress(event, options) {
7380
7396
  if (!event.lengthComputable) {
7381
7397
  return;
7382
7398
  }
7383
- const progress = event.loaded / event.total;
7399
+ const progress = Math.min(event.loaded / event.total, MAX_TRANSFER_PROGRESS);
7384
7400
  options.onProgress?.(progress);
7385
7401
  options.onProgressDetails?.({
7386
7402
  progress,
@@ -7388,6 +7404,14 @@ class VideoUploadService {
7388
7404
  totalBytes: event.total
7389
7405
  });
7390
7406
  }
7407
+ reportCompletion(options, loadedBytes, totalBytes) {
7408
+ options.onProgress?.(1);
7409
+ options.onProgressDetails?.({
7410
+ progress: 1,
7411
+ loadedBytes,
7412
+ totalBytes
7413
+ });
7414
+ }
7391
7415
  createUploadBlob(blob, mimeType) {
7392
7416
  const normalizedMimeType = this.resolveUploadMimeType(blob, mimeType);
7393
7417
  if (normalizedMimeType === blob.type) {
@@ -22773,6 +22797,7 @@ class RecordingManager {
22773
22797
  startupAborted = false;
22774
22798
  activeRecordingConfig = null;
22775
22799
  activeRouteDecision = DEFAULT_ROUTE_DECISION2;
22800
+ isFinalizingRecording = false;
22776
22801
  constructor(streamManager, callbacks, dependencies) {
22777
22802
  this.streamManager = streamManager;
22778
22803
  this.callbacks = callbacks;
@@ -22823,6 +22848,9 @@ class RecordingManager {
22823
22848
  }
22824
22849
  }
22825
22850
  async startRecording() {
22851
+ if (this.recordingState !== RECORDING_STATE_IDLE || this.isFinalizingRecording) {
22852
+ throw new Error("Recording is not ready to start");
22853
+ }
22826
22854
  try {
22827
22855
  this.callbacks.onClearUploadStatus();
22828
22856
  if (this.countdownDuration > 0) {
@@ -22947,16 +22975,26 @@ class RecordingManager {
22947
22975
  }
22948
22976
  async stopRecording() {
22949
22977
  logger.debug("[RecordingManager] stopRecording called");
22950
- try {
22951
- if (this.recordingState !== RECORDING_STATE_RECORDING) {
22952
- const stopRequestedState = this.recordingState;
22953
- this.cancelCountdown();
22954
- throw createRecordingLifecycleError(ERROR_RECORDING_STOP_NOT_READY, "Recording is not ready to stop", {
22955
- recordingState: stopRequestedState,
22956
- hasStreamProcessor: !!this.streamProcessor
22957
- });
22958
- }
22978
+ if (this.isFinalizingRecording) {
22979
+ throw createRecordingLifecycleError(ERROR_RECORDING_STOP_NOT_READY, "Recording is not ready to stop", {
22980
+ recordingState: this.recordingState,
22981
+ hasStreamProcessor: !!this.streamProcessor
22982
+ });
22983
+ }
22984
+ if (this.recordingState !== RECORDING_STATE_RECORDING) {
22985
+ const stopRequestedState = this.recordingState;
22959
22986
  this.cancelCountdown();
22987
+ const error = createRecordingLifecycleError(ERROR_RECORDING_STOP_NOT_READY, "Recording is not ready to stop", {
22988
+ recordingState: stopRequestedState,
22989
+ hasStreamProcessor: !!this.streamProcessor
22990
+ });
22991
+ this.handleError(error);
22992
+ throw error;
22993
+ }
22994
+ this.isFinalizingRecording = true;
22995
+ try {
22996
+ this.callbacks.onFinalizationStart?.();
22997
+ this.clearCountdownTimers();
22960
22998
  this.clearTimer(this.recordingIntervalId, clearInterval);
22961
22999
  this.recordingIntervalId = null;
22962
23000
  this.clearTimer(this.maxTimeTimer, clearTimeout);
@@ -23002,6 +23040,7 @@ class RecordingManager {
23002
23040
  }
23003
23041
  throw normalizedError;
23004
23042
  } finally {
23043
+ this.isFinalizingRecording = false;
23005
23044
  this.activeRecordingConfig = null;
23006
23045
  this.activeRouteDecision = DEFAULT_ROUTE_DECISION2;
23007
23046
  }
@@ -23035,14 +23074,17 @@ class RecordingManager {
23035
23074
  }
23036
23075
  }
23037
23076
  cancelCountdown() {
23077
+ this.clearCountdownTimers();
23078
+ this.recordingState = RECORDING_STATE_IDLE;
23079
+ this.callbacks.onCountdownUpdate(this.recordingState, this.countdownRemaining);
23080
+ }
23081
+ clearCountdownTimers() {
23038
23082
  this.clearTimer(this.countdownTimeoutId, clearTimeout);
23039
23083
  this.countdownTimeoutId = null;
23040
23084
  this.clearTimer(this.countdownIntervalId, clearInterval);
23041
23085
  this.countdownIntervalId = null;
23042
- this.recordingState = RECORDING_STATE_IDLE;
23043
23086
  this.countdownRemaining = 0;
23044
23087
  this.countdownStartTime = null;
23045
- this.callbacks.onCountdownUpdate(this.recordingState, this.countdownRemaining);
23046
23088
  }
23047
23089
  cleanup() {
23048
23090
  this.cancelCountdown();
@@ -23142,6 +23184,9 @@ class RecordingManager {
23142
23184
  this.callbacks.onError(errorMessage);
23143
23185
  }
23144
23186
  handleFatalProcessorError(error) {
23187
+ if (this.isFinalizingRecording) {
23188
+ return;
23189
+ }
23145
23190
  if (this.recordingState === RECORDING_STATE_RECORDING) {
23146
23191
  this.clearTimer(this.recordingIntervalId, clearInterval);
23147
23192
  this.recordingIntervalId = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vidtreo/recorder",
3
- "version": "1.7.5",
3
+ "version": "1.7.7",
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",