@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.
- package/dist/index.d.ts +2884 -2878
- package/dist/index.js +64 -19
- 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}${
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
this.
|
|
22954
|
-
|
|
22955
|
-
|
|
22956
|
-
|
|
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.
|
|
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",
|