@vidtreo/recorder 1.7.4 → 1.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2293 -2258
- package/dist/index.js +174 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6312,7 +6312,7 @@ function resolveDeviceError(input) {
|
|
|
6312
6312
|
// package.json
|
|
6313
6313
|
var package_default = {
|
|
6314
6314
|
name: "@vidtreo/recorder",
|
|
6315
|
-
version: "1.7.
|
|
6315
|
+
version: "1.7.5",
|
|
6316
6316
|
type: "module",
|
|
6317
6317
|
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
6318
|
main: "./dist/index.js",
|
|
@@ -6452,6 +6452,8 @@ var TELEMETRY_EVENT_CATEGORY_MAP = {
|
|
|
6452
6452
|
"recording.stop.failed": "error",
|
|
6453
6453
|
"recording.route.selected": "lifecycle",
|
|
6454
6454
|
"upload.started": "performance",
|
|
6455
|
+
"upload.attempt.failed": "error",
|
|
6456
|
+
"upload.retry.scheduled": "performance",
|
|
6455
6457
|
"upload.succeeded": "performance",
|
|
6456
6458
|
"upload.failed": "error",
|
|
6457
6459
|
"upload.route.selected": "lifecycle",
|
|
@@ -7188,6 +7190,12 @@ class UploadQueueManager {
|
|
|
7188
7190
|
return Object.entries(expected).every(([key, value]) => actual[key] === value);
|
|
7189
7191
|
}
|
|
7190
7192
|
async processUpload(upload, shouldThrowOnError = false) {
|
|
7193
|
+
const attempt = upload.retryCount + 1;
|
|
7194
|
+
let progressDetails = {
|
|
7195
|
+
progress: 0,
|
|
7196
|
+
loadedBytes: 0,
|
|
7197
|
+
totalBytes: 0
|
|
7198
|
+
};
|
|
7191
7199
|
try {
|
|
7192
7200
|
await this.storageService.updateUploadStatus(upload.id, {
|
|
7193
7201
|
status: "uploading"
|
|
@@ -7201,24 +7209,52 @@ class UploadQueueManager {
|
|
|
7201
7209
|
userMetadata: upload.userMetadata,
|
|
7202
7210
|
onProgress: (progress) => {
|
|
7203
7211
|
this.callbacks.onUploadProgress?.(upload.id, progress);
|
|
7212
|
+
},
|
|
7213
|
+
onProgressDetails: (details) => {
|
|
7214
|
+
progressDetails = details;
|
|
7215
|
+
this.callbacks.onUploadProgressDetails?.(upload.id, details);
|
|
7204
7216
|
}
|
|
7205
7217
|
});
|
|
7218
|
+
this.callbacks.onUploadAttemptSucceeded?.(upload.id, {
|
|
7219
|
+
attempt,
|
|
7220
|
+
maxAttempts: MAX_RETRIES2,
|
|
7221
|
+
loadedBytes: progressDetails.loadedBytes,
|
|
7222
|
+
totalBytes: progressDetails.totalBytes
|
|
7223
|
+
});
|
|
7206
7224
|
await this.storageService.deleteUpload(upload.id);
|
|
7207
7225
|
this.callbacks.onUploadComplete?.(upload.id, result);
|
|
7208
7226
|
return result;
|
|
7209
7227
|
} catch (error) {
|
|
7210
7228
|
const errorMessage = extractErrorMessage(error);
|
|
7211
7229
|
const retryCount = upload.retryCount + 1;
|
|
7230
|
+
const willRetry = retryCount < MAX_RETRIES2;
|
|
7231
|
+
const retryDelayMs = willRetry ? this.calculateRetryDelay(retryCount) : null;
|
|
7212
7232
|
await this.storageService.updateUploadStatus(upload.id, {
|
|
7213
7233
|
status: "failed",
|
|
7214
7234
|
retryCount,
|
|
7215
7235
|
lastError: errorMessage
|
|
7216
7236
|
});
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7237
|
+
this.callbacks.onUploadAttemptFailed?.(upload.id, {
|
|
7238
|
+
attempt,
|
|
7239
|
+
maxAttempts: MAX_RETRIES2,
|
|
7240
|
+
loadedBytes: progressDetails.loadedBytes,
|
|
7241
|
+
totalBytes: progressDetails.totalBytes,
|
|
7242
|
+
reason: errorMessage,
|
|
7243
|
+
retryDelayMs,
|
|
7244
|
+
willRetry
|
|
7245
|
+
});
|
|
7246
|
+
if (willRetry) {
|
|
7220
7247
|
const delay2 = this.calculateRetryDelay(retryCount);
|
|
7248
|
+
this.callbacks.onUploadProgress?.(upload.id, 0);
|
|
7249
|
+
this.callbacks.onUploadRetry?.(upload.id, {
|
|
7250
|
+
attempt: attempt + 1,
|
|
7251
|
+
maxAttempts: MAX_RETRIES2,
|
|
7252
|
+
reason: errorMessage,
|
|
7253
|
+
retryDelayMs: delay2
|
|
7254
|
+
});
|
|
7221
7255
|
this.scheduleRetry(delay2);
|
|
7256
|
+
} else {
|
|
7257
|
+
this.callbacks.onUploadError?.(upload.id, new Error(`Upload failed after ${MAX_RETRIES2} attempts: ${errorMessage}`));
|
|
7222
7258
|
}
|
|
7223
7259
|
if (shouldThrowOnError) {
|
|
7224
7260
|
throw new Error(errorMessage);
|
|
@@ -7250,6 +7286,8 @@ class UploadQueueManager {
|
|
|
7250
7286
|
// src/core/upload/upload-service.ts
|
|
7251
7287
|
var API_UPLOAD_PATH = "/api/v1/videos/upload";
|
|
7252
7288
|
var MIME_TYPE_PARAMETER_SEPARATOR = ";";
|
|
7289
|
+
var UPLOAD_INACTIVITY_TIMEOUT_MS = 45000;
|
|
7290
|
+
var UPLOAD_INACTIVITY_ERROR = "Upload stalled after 45 seconds without progress";
|
|
7253
7291
|
|
|
7254
7292
|
class VideoUploadService {
|
|
7255
7293
|
uploadVideo(blob, options) {
|
|
@@ -7265,27 +7303,61 @@ class VideoUploadService {
|
|
|
7265
7303
|
return new Promise((resolve, reject) => {
|
|
7266
7304
|
const xhr = new XMLHttpRequest;
|
|
7267
7305
|
const uploadBlob = this.createUploadBlob(blob, options.mimeType);
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7306
|
+
let inactivityTimeoutId = null;
|
|
7307
|
+
let isSettled = false;
|
|
7308
|
+
let loadedBytes = 0;
|
|
7309
|
+
const clearInactivityTimeout = () => {
|
|
7310
|
+
if (inactivityTimeoutId === null) {
|
|
7311
|
+
return;
|
|
7312
|
+
}
|
|
7313
|
+
window.clearTimeout(inactivityTimeoutId);
|
|
7314
|
+
inactivityTimeoutId = null;
|
|
7315
|
+
};
|
|
7316
|
+
const rejectRequest = (reason, shouldAbort = false) => {
|
|
7317
|
+
if (isSettled) {
|
|
7318
|
+
return;
|
|
7319
|
+
}
|
|
7320
|
+
isSettled = true;
|
|
7321
|
+
clearInactivityTimeout();
|
|
7322
|
+
if (shouldAbort) {
|
|
7323
|
+
xhr.abort();
|
|
7324
|
+
}
|
|
7325
|
+
reject(reason);
|
|
7326
|
+
};
|
|
7327
|
+
const resolveRequest = (result) => {
|
|
7328
|
+
if (isSettled) {
|
|
7329
|
+
return;
|
|
7330
|
+
}
|
|
7331
|
+
isSettled = true;
|
|
7332
|
+
clearInactivityTimeout();
|
|
7333
|
+
resolve(result);
|
|
7334
|
+
};
|
|
7335
|
+
const restartInactivityTimeout = () => {
|
|
7336
|
+
clearInactivityTimeout();
|
|
7337
|
+
inactivityTimeoutId = window.setTimeout(() => {
|
|
7338
|
+
rejectRequest(new Error(UPLOAD_INACTIVITY_ERROR), true);
|
|
7339
|
+
}, UPLOAD_INACTIVITY_TIMEOUT_MS);
|
|
7340
|
+
};
|
|
7341
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
7342
|
+
this.reportProgress(event, options);
|
|
7343
|
+
if (event.loaded <= loadedBytes) {
|
|
7344
|
+
return;
|
|
7345
|
+
}
|
|
7346
|
+
loadedBytes = event.loaded;
|
|
7347
|
+
restartInactivityTimeout();
|
|
7348
|
+
});
|
|
7277
7349
|
xhr.addEventListener("load", () => {
|
|
7278
7350
|
if (xhr.status >= 200 && xhr.status <= 299) {
|
|
7279
|
-
this.parseSuccessResponse(xhr,
|
|
7351
|
+
this.parseSuccessResponse(xhr, resolveRequest, rejectRequest);
|
|
7280
7352
|
return;
|
|
7281
7353
|
}
|
|
7282
|
-
this.parseErrorResponse(xhr,
|
|
7354
|
+
this.parseErrorResponse(xhr, rejectRequest);
|
|
7283
7355
|
});
|
|
7284
7356
|
xhr.addEventListener("error", () => {
|
|
7285
|
-
|
|
7357
|
+
rejectRequest(new Error("Network error during upload"));
|
|
7286
7358
|
});
|
|
7287
7359
|
xhr.addEventListener("abort", () => {
|
|
7288
|
-
|
|
7360
|
+
rejectRequest(new Error("Upload was aborted"));
|
|
7289
7361
|
});
|
|
7290
7362
|
const url = `${options.backendUrl}${API_UPLOAD_PATH}`;
|
|
7291
7363
|
xhr.open("POST", url);
|
|
@@ -7301,6 +7373,19 @@ class VideoUploadService {
|
|
|
7301
7373
|
}
|
|
7302
7374
|
formData.append("metadata", JSON.stringify(metadata));
|
|
7303
7375
|
xhr.send(formData);
|
|
7376
|
+
restartInactivityTimeout();
|
|
7377
|
+
});
|
|
7378
|
+
}
|
|
7379
|
+
reportProgress(event, options) {
|
|
7380
|
+
if (!event.lengthComputable) {
|
|
7381
|
+
return;
|
|
7382
|
+
}
|
|
7383
|
+
const progress = event.loaded / event.total;
|
|
7384
|
+
options.onProgress?.(progress);
|
|
7385
|
+
options.onProgressDetails?.({
|
|
7386
|
+
progress,
|
|
7387
|
+
loadedBytes: event.loaded,
|
|
7388
|
+
totalBytes: event.total
|
|
7304
7389
|
});
|
|
7305
7390
|
}
|
|
7306
7391
|
createUploadBlob(blob, mimeType) {
|
|
@@ -23210,6 +23295,8 @@ class RecorderController {
|
|
|
23210
23295
|
callbacks;
|
|
23211
23296
|
telemetryManager;
|
|
23212
23297
|
uploadMetadataManager;
|
|
23298
|
+
uploadAttemptFailureById = new Map;
|
|
23299
|
+
uploadAttemptSuccessById = new Map;
|
|
23213
23300
|
uploadQueueManager = null;
|
|
23214
23301
|
isInitialized = false;
|
|
23215
23302
|
isDemo = false;
|
|
@@ -23551,6 +23638,11 @@ class RecorderController {
|
|
|
23551
23638
|
}, error);
|
|
23552
23639
|
throw error;
|
|
23553
23640
|
}
|
|
23641
|
+
let progressDetails = {
|
|
23642
|
+
progress: 0,
|
|
23643
|
+
loadedBytes: 0,
|
|
23644
|
+
totalBytes: 0
|
|
23645
|
+
};
|
|
23554
23646
|
try {
|
|
23555
23647
|
const result = await this.uploadService.uploadVideo(options.blob, {
|
|
23556
23648
|
apiKey: options.apiKey,
|
|
@@ -23559,18 +23651,37 @@ class RecorderController {
|
|
|
23559
23651
|
mimeType: options.mimeType,
|
|
23560
23652
|
metadata: undefined,
|
|
23561
23653
|
userMetadata: options.userMetadata,
|
|
23562
|
-
onProgress: this.uploadCallbacks.onProgress
|
|
23654
|
+
onProgress: this.uploadCallbacks.onProgress,
|
|
23655
|
+
onProgressDetails: (details) => {
|
|
23656
|
+
progressDetails = details;
|
|
23657
|
+
}
|
|
23563
23658
|
});
|
|
23564
23659
|
this.uploadCallbacks.onSuccess(result);
|
|
23565
23660
|
this.telemetryManager.sendEvent("upload.succeeded", {
|
|
23566
23661
|
filename: options.filename,
|
|
23567
23662
|
duration: options.duration,
|
|
23568
23663
|
sourceType: options.sourceType,
|
|
23569
|
-
recordingId: result.id
|
|
23664
|
+
recordingId: result.id,
|
|
23665
|
+
attempt: 1,
|
|
23666
|
+
maxAttempts: 1,
|
|
23667
|
+
loadedBytes: progressDetails.loadedBytes,
|
|
23668
|
+
totalBytes: progressDetails.totalBytes
|
|
23570
23669
|
});
|
|
23571
23670
|
} catch (error) {
|
|
23572
23671
|
const uploadError = error instanceof Error ? error : new Error(extractErrorMessage(error));
|
|
23573
23672
|
this.uploadCallbacks.onError(uploadError);
|
|
23673
|
+
this.telemetryManager.sendEvent("upload.attempt.failed", {
|
|
23674
|
+
filename: options.filename,
|
|
23675
|
+
duration: options.duration,
|
|
23676
|
+
sourceType: options.sourceType,
|
|
23677
|
+
attempt: 1,
|
|
23678
|
+
maxAttempts: 1,
|
|
23679
|
+
loadedBytes: progressDetails.loadedBytes,
|
|
23680
|
+
totalBytes: progressDetails.totalBytes,
|
|
23681
|
+
reason: uploadError.message,
|
|
23682
|
+
willRetry: false,
|
|
23683
|
+
retryDelayMs: null
|
|
23684
|
+
}, uploadError);
|
|
23574
23685
|
this.telemetryManager.sendEvent("upload.failed", {
|
|
23575
23686
|
filename: options.filename,
|
|
23576
23687
|
duration: options.duration,
|
|
@@ -23819,19 +23930,60 @@ class RecorderController {
|
|
|
23819
23930
|
onUploadProgress: (_id, progress) => {
|
|
23820
23931
|
this.uploadCallbacks.onProgress(progress);
|
|
23821
23932
|
},
|
|
23933
|
+
onUploadAttemptFailed: (id, details) => {
|
|
23934
|
+
this.uploadAttemptFailureById.set(id, details);
|
|
23935
|
+
const metadata = this.uploadMetadataManager.getMetadata(id);
|
|
23936
|
+
if (!metadata) {
|
|
23937
|
+
return;
|
|
23938
|
+
}
|
|
23939
|
+
const error = new Error(details.reason);
|
|
23940
|
+
this.telemetryManager.sendEvent("upload.attempt.failed", {
|
|
23941
|
+
filename: metadata.filename,
|
|
23942
|
+
duration: metadata.duration,
|
|
23943
|
+
sourceType: metadata.sourceType,
|
|
23944
|
+
...details
|
|
23945
|
+
}, error);
|
|
23946
|
+
},
|
|
23947
|
+
onUploadAttemptSucceeded: (id, details) => {
|
|
23948
|
+
this.uploadAttemptSuccessById.set(id, details);
|
|
23949
|
+
},
|
|
23950
|
+
onUploadRetry: (id, details) => {
|
|
23951
|
+
this.uploadCallbacks.onRetry?.(details);
|
|
23952
|
+
const metadata = this.uploadMetadataManager.getMetadata(id);
|
|
23953
|
+
const failureDetails = this.uploadAttemptFailureById.get(id);
|
|
23954
|
+
if (!(metadata && failureDetails)) {
|
|
23955
|
+
return;
|
|
23956
|
+
}
|
|
23957
|
+
this.telemetryManager.sendEvent("upload.retry.scheduled", {
|
|
23958
|
+
filename: metadata.filename,
|
|
23959
|
+
duration: metadata.duration,
|
|
23960
|
+
sourceType: metadata.sourceType,
|
|
23961
|
+
attempt: details.attempt,
|
|
23962
|
+
maxAttempts: details.maxAttempts,
|
|
23963
|
+
previousAttempt: failureDetails.attempt,
|
|
23964
|
+
loadedBytes: failureDetails.loadedBytes,
|
|
23965
|
+
totalBytes: failureDetails.totalBytes,
|
|
23966
|
+
reason: details.reason,
|
|
23967
|
+
retryDelayMs: details.retryDelayMs
|
|
23968
|
+
});
|
|
23969
|
+
},
|
|
23822
23970
|
onUploadComplete: (id, result) => {
|
|
23823
23971
|
this.isUploadInProgress = false;
|
|
23824
23972
|
this.uploadCallbacks.onSuccess(result);
|
|
23825
23973
|
const metadata = this.uploadMetadataManager.getMetadata(id);
|
|
23974
|
+
const successDetails = this.uploadAttemptSuccessById.get(id);
|
|
23826
23975
|
if (metadata) {
|
|
23827
23976
|
this.telemetryManager.sendEvent("upload.succeeded", {
|
|
23828
23977
|
filename: metadata.filename,
|
|
23829
23978
|
duration: metadata.duration,
|
|
23830
23979
|
sourceType: metadata.sourceType,
|
|
23831
|
-
recordingId: result.id
|
|
23980
|
+
recordingId: result.id,
|
|
23981
|
+
...successDetails && successDetails
|
|
23832
23982
|
});
|
|
23833
23983
|
this.uploadMetadataManager.clearMetadata(id);
|
|
23834
23984
|
}
|
|
23985
|
+
this.uploadAttemptFailureById.delete(id);
|
|
23986
|
+
this.uploadAttemptSuccessById.delete(id);
|
|
23835
23987
|
},
|
|
23836
23988
|
onUploadError: (id, error) => {
|
|
23837
23989
|
this.isUploadInProgress = false;
|
|
@@ -23845,6 +23997,8 @@ class RecorderController {
|
|
|
23845
23997
|
}, error);
|
|
23846
23998
|
this.uploadMetadataManager.clearMetadata(id);
|
|
23847
23999
|
}
|
|
24000
|
+
this.uploadAttemptFailureById.delete(id);
|
|
24001
|
+
this.uploadAttemptSuccessById.delete(id);
|
|
23848
24002
|
}
|
|
23849
24003
|
});
|
|
23850
24004
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidtreo/recorder",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.5",
|
|
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",
|