@vidtreo/recorder 1.7.3 → 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 +176 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1475,7 +1475,7 @@ async function buildDeviceCapabilityProfile(options = {}) {
|
|
|
1475
1475
|
|
|
1476
1476
|
// src/core/routing/recording-route-decision.ts
|
|
1477
1477
|
var DEFAULT_MIN_DEVICE_MEMORY_GB = 4;
|
|
1478
|
-
var DEFAULT_MIN_HARDWARE_CONCURRENCY =
|
|
1478
|
+
var DEFAULT_MIN_HARDWARE_CONCURRENCY = 4;
|
|
1479
1479
|
var PLATFORM_KEYWORD_ANDROID2 = "android";
|
|
1480
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.";
|
|
1481
1481
|
function hasSufficientDeviceMemory(profile, minDeviceMemory) {
|
|
@@ -1566,14 +1566,7 @@ function decideRecordingRoute(options) {
|
|
|
1566
1566
|
const hasSmoothEncoding = hasSmoothEncodingCapability(selectedCodec);
|
|
1567
1567
|
const hasPowerEfficientEncoding = hasPowerEfficientEncodingCapability(selectedCodec);
|
|
1568
1568
|
const localRouteBlockers = buildLocalRouteBlockers(options.profile, hasSupportedTargetCodec, hasEnoughDeviceMemory, hasEnoughHardwareConcurrency, hasSupportedEncoding, hasSmoothEncoding, hasPowerEfficientEncoding);
|
|
1569
|
-
if (
|
|
1570
|
-
return {
|
|
1571
|
-
route: "safe-capture-post-recording-transcode",
|
|
1572
|
-
reasonCodes: buildPostRecordingTranscodeReasonCodes(options.profile, localRouteBlockers),
|
|
1573
|
-
selectedCodecId
|
|
1574
|
-
};
|
|
1575
|
-
}
|
|
1576
|
-
if (options.profile.features.isSecureContext && options.profile.features.hasVideoEncoder && hasSupportedTargetCodec && options.profile.network.online !== false && hasEnoughDeviceMemory && hasEnoughHardwareConcurrency && hasSupportedEncoding && hasSmoothEncoding && hasPowerEfficientEncoding) {
|
|
1569
|
+
if (options.profile.features.isSecureContext && options.profile.features.hasVideoEncoder && hasSupportedTargetCodec && options.profile.network.online !== false && hasEnoughDeviceMemory && hasEnoughHardwareConcurrency && hasSupportedEncoding && hasSmoothEncoding) {
|
|
1577
1570
|
return {
|
|
1578
1571
|
route: "local-webcodecs",
|
|
1579
1572
|
reasonCodes: ["webcodecs-supported", "target-codec-supported"],
|
|
@@ -6319,7 +6312,7 @@ function resolveDeviceError(input) {
|
|
|
6319
6312
|
// package.json
|
|
6320
6313
|
var package_default = {
|
|
6321
6314
|
name: "@vidtreo/recorder",
|
|
6322
|
-
version: "1.7.
|
|
6315
|
+
version: "1.7.5",
|
|
6323
6316
|
type: "module",
|
|
6324
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.",
|
|
6325
6318
|
main: "./dist/index.js",
|
|
@@ -6459,6 +6452,8 @@ var TELEMETRY_EVENT_CATEGORY_MAP = {
|
|
|
6459
6452
|
"recording.stop.failed": "error",
|
|
6460
6453
|
"recording.route.selected": "lifecycle",
|
|
6461
6454
|
"upload.started": "performance",
|
|
6455
|
+
"upload.attempt.failed": "error",
|
|
6456
|
+
"upload.retry.scheduled": "performance",
|
|
6462
6457
|
"upload.succeeded": "performance",
|
|
6463
6458
|
"upload.failed": "error",
|
|
6464
6459
|
"upload.route.selected": "lifecycle",
|
|
@@ -7195,6 +7190,12 @@ class UploadQueueManager {
|
|
|
7195
7190
|
return Object.entries(expected).every(([key, value]) => actual[key] === value);
|
|
7196
7191
|
}
|
|
7197
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
|
+
};
|
|
7198
7199
|
try {
|
|
7199
7200
|
await this.storageService.updateUploadStatus(upload.id, {
|
|
7200
7201
|
status: "uploading"
|
|
@@ -7208,24 +7209,52 @@ class UploadQueueManager {
|
|
|
7208
7209
|
userMetadata: upload.userMetadata,
|
|
7209
7210
|
onProgress: (progress) => {
|
|
7210
7211
|
this.callbacks.onUploadProgress?.(upload.id, progress);
|
|
7212
|
+
},
|
|
7213
|
+
onProgressDetails: (details) => {
|
|
7214
|
+
progressDetails = details;
|
|
7215
|
+
this.callbacks.onUploadProgressDetails?.(upload.id, details);
|
|
7211
7216
|
}
|
|
7212
7217
|
});
|
|
7218
|
+
this.callbacks.onUploadAttemptSucceeded?.(upload.id, {
|
|
7219
|
+
attempt,
|
|
7220
|
+
maxAttempts: MAX_RETRIES2,
|
|
7221
|
+
loadedBytes: progressDetails.loadedBytes,
|
|
7222
|
+
totalBytes: progressDetails.totalBytes
|
|
7223
|
+
});
|
|
7213
7224
|
await this.storageService.deleteUpload(upload.id);
|
|
7214
7225
|
this.callbacks.onUploadComplete?.(upload.id, result);
|
|
7215
7226
|
return result;
|
|
7216
7227
|
} catch (error) {
|
|
7217
7228
|
const errorMessage = extractErrorMessage(error);
|
|
7218
7229
|
const retryCount = upload.retryCount + 1;
|
|
7230
|
+
const willRetry = retryCount < MAX_RETRIES2;
|
|
7231
|
+
const retryDelayMs = willRetry ? this.calculateRetryDelay(retryCount) : null;
|
|
7219
7232
|
await this.storageService.updateUploadStatus(upload.id, {
|
|
7220
7233
|
status: "failed",
|
|
7221
7234
|
retryCount,
|
|
7222
7235
|
lastError: errorMessage
|
|
7223
7236
|
});
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
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) {
|
|
7227
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
|
+
});
|
|
7228
7255
|
this.scheduleRetry(delay2);
|
|
7256
|
+
} else {
|
|
7257
|
+
this.callbacks.onUploadError?.(upload.id, new Error(`Upload failed after ${MAX_RETRIES2} attempts: ${errorMessage}`));
|
|
7229
7258
|
}
|
|
7230
7259
|
if (shouldThrowOnError) {
|
|
7231
7260
|
throw new Error(errorMessage);
|
|
@@ -7257,6 +7286,8 @@ class UploadQueueManager {
|
|
|
7257
7286
|
// src/core/upload/upload-service.ts
|
|
7258
7287
|
var API_UPLOAD_PATH = "/api/v1/videos/upload";
|
|
7259
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";
|
|
7260
7291
|
|
|
7261
7292
|
class VideoUploadService {
|
|
7262
7293
|
uploadVideo(blob, options) {
|
|
@@ -7272,27 +7303,61 @@ class VideoUploadService {
|
|
|
7272
7303
|
return new Promise((resolve, reject) => {
|
|
7273
7304
|
const xhr = new XMLHttpRequest;
|
|
7274
7305
|
const uploadBlob = this.createUploadBlob(blob, options.mimeType);
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
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
|
+
});
|
|
7284
7349
|
xhr.addEventListener("load", () => {
|
|
7285
7350
|
if (xhr.status >= 200 && xhr.status <= 299) {
|
|
7286
|
-
this.parseSuccessResponse(xhr,
|
|
7351
|
+
this.parseSuccessResponse(xhr, resolveRequest, rejectRequest);
|
|
7287
7352
|
return;
|
|
7288
7353
|
}
|
|
7289
|
-
this.parseErrorResponse(xhr,
|
|
7354
|
+
this.parseErrorResponse(xhr, rejectRequest);
|
|
7290
7355
|
});
|
|
7291
7356
|
xhr.addEventListener("error", () => {
|
|
7292
|
-
|
|
7357
|
+
rejectRequest(new Error("Network error during upload"));
|
|
7293
7358
|
});
|
|
7294
7359
|
xhr.addEventListener("abort", () => {
|
|
7295
|
-
|
|
7360
|
+
rejectRequest(new Error("Upload was aborted"));
|
|
7296
7361
|
});
|
|
7297
7362
|
const url = `${options.backendUrl}${API_UPLOAD_PATH}`;
|
|
7298
7363
|
xhr.open("POST", url);
|
|
@@ -7308,6 +7373,19 @@ class VideoUploadService {
|
|
|
7308
7373
|
}
|
|
7309
7374
|
formData.append("metadata", JSON.stringify(metadata));
|
|
7310
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
|
|
7311
7389
|
});
|
|
7312
7390
|
}
|
|
7313
7391
|
createUploadBlob(blob, mimeType) {
|
|
@@ -23217,6 +23295,8 @@ class RecorderController {
|
|
|
23217
23295
|
callbacks;
|
|
23218
23296
|
telemetryManager;
|
|
23219
23297
|
uploadMetadataManager;
|
|
23298
|
+
uploadAttemptFailureById = new Map;
|
|
23299
|
+
uploadAttemptSuccessById = new Map;
|
|
23220
23300
|
uploadQueueManager = null;
|
|
23221
23301
|
isInitialized = false;
|
|
23222
23302
|
isDemo = false;
|
|
@@ -23558,6 +23638,11 @@ class RecorderController {
|
|
|
23558
23638
|
}, error);
|
|
23559
23639
|
throw error;
|
|
23560
23640
|
}
|
|
23641
|
+
let progressDetails = {
|
|
23642
|
+
progress: 0,
|
|
23643
|
+
loadedBytes: 0,
|
|
23644
|
+
totalBytes: 0
|
|
23645
|
+
};
|
|
23561
23646
|
try {
|
|
23562
23647
|
const result = await this.uploadService.uploadVideo(options.blob, {
|
|
23563
23648
|
apiKey: options.apiKey,
|
|
@@ -23566,18 +23651,37 @@ class RecorderController {
|
|
|
23566
23651
|
mimeType: options.mimeType,
|
|
23567
23652
|
metadata: undefined,
|
|
23568
23653
|
userMetadata: options.userMetadata,
|
|
23569
|
-
onProgress: this.uploadCallbacks.onProgress
|
|
23654
|
+
onProgress: this.uploadCallbacks.onProgress,
|
|
23655
|
+
onProgressDetails: (details) => {
|
|
23656
|
+
progressDetails = details;
|
|
23657
|
+
}
|
|
23570
23658
|
});
|
|
23571
23659
|
this.uploadCallbacks.onSuccess(result);
|
|
23572
23660
|
this.telemetryManager.sendEvent("upload.succeeded", {
|
|
23573
23661
|
filename: options.filename,
|
|
23574
23662
|
duration: options.duration,
|
|
23575
23663
|
sourceType: options.sourceType,
|
|
23576
|
-
recordingId: result.id
|
|
23664
|
+
recordingId: result.id,
|
|
23665
|
+
attempt: 1,
|
|
23666
|
+
maxAttempts: 1,
|
|
23667
|
+
loadedBytes: progressDetails.loadedBytes,
|
|
23668
|
+
totalBytes: progressDetails.totalBytes
|
|
23577
23669
|
});
|
|
23578
23670
|
} catch (error) {
|
|
23579
23671
|
const uploadError = error instanceof Error ? error : new Error(extractErrorMessage(error));
|
|
23580
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);
|
|
23581
23685
|
this.telemetryManager.sendEvent("upload.failed", {
|
|
23582
23686
|
filename: options.filename,
|
|
23583
23687
|
duration: options.duration,
|
|
@@ -23826,19 +23930,60 @@ class RecorderController {
|
|
|
23826
23930
|
onUploadProgress: (_id, progress) => {
|
|
23827
23931
|
this.uploadCallbacks.onProgress(progress);
|
|
23828
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
|
+
},
|
|
23829
23970
|
onUploadComplete: (id, result) => {
|
|
23830
23971
|
this.isUploadInProgress = false;
|
|
23831
23972
|
this.uploadCallbacks.onSuccess(result);
|
|
23832
23973
|
const metadata = this.uploadMetadataManager.getMetadata(id);
|
|
23974
|
+
const successDetails = this.uploadAttemptSuccessById.get(id);
|
|
23833
23975
|
if (metadata) {
|
|
23834
23976
|
this.telemetryManager.sendEvent("upload.succeeded", {
|
|
23835
23977
|
filename: metadata.filename,
|
|
23836
23978
|
duration: metadata.duration,
|
|
23837
23979
|
sourceType: metadata.sourceType,
|
|
23838
|
-
recordingId: result.id
|
|
23980
|
+
recordingId: result.id,
|
|
23981
|
+
...successDetails && successDetails
|
|
23839
23982
|
});
|
|
23840
23983
|
this.uploadMetadataManager.clearMetadata(id);
|
|
23841
23984
|
}
|
|
23985
|
+
this.uploadAttemptFailureById.delete(id);
|
|
23986
|
+
this.uploadAttemptSuccessById.delete(id);
|
|
23842
23987
|
},
|
|
23843
23988
|
onUploadError: (id, error) => {
|
|
23844
23989
|
this.isUploadInProgress = false;
|
|
@@ -23852,6 +23997,8 @@ class RecorderController {
|
|
|
23852
23997
|
}, error);
|
|
23853
23998
|
this.uploadMetadataManager.clearMetadata(id);
|
|
23854
23999
|
}
|
|
24000
|
+
this.uploadAttemptFailureById.delete(id);
|
|
24001
|
+
this.uploadAttemptSuccessById.delete(id);
|
|
23855
24002
|
}
|
|
23856
24003
|
});
|
|
23857
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",
|