@vidtreo/recorder 1.7.1 → 1.7.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 +16 -0
- package/dist/index.js +115 -24
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export type RecoverPendingUploadOptions = {
|
|
2
|
+
match: PendingUploadMatch;
|
|
3
|
+
onProgress?: (progress: number) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare function recoverPendingUpload(options: RecoverPendingUploadOptions): Promise<VideoUploadResult | null>;
|
|
6
|
+
|
|
1
7
|
export type VideoUploadOptions = {
|
|
2
8
|
apiKey: string;
|
|
3
9
|
backendUrl: string;
|
|
@@ -35,6 +41,10 @@ type UploadCallbacks = {
|
|
|
35
41
|
onUploadComplete?: (id: string, result: VideoUploadResult) => void;
|
|
36
42
|
onUploadError?: (id: string, error: Error) => void;
|
|
37
43
|
};
|
|
44
|
+
export type PendingUploadMatch = {
|
|
45
|
+
metadata?: Record<string, unknown>;
|
|
46
|
+
userMetadata?: Record<string, unknown>;
|
|
47
|
+
};
|
|
38
48
|
export declare class UploadQueueManager {
|
|
39
49
|
private readonly storageService;
|
|
40
50
|
private readonly uploadService;
|
|
@@ -50,6 +60,7 @@ export declare class UploadQueueManager {
|
|
|
50
60
|
queueUpload(upload: Omit<PendingUpload, "id" | "createdAt" | "updatedAt" | "status" | "retryCount">): Promise<string>;
|
|
51
61
|
processQueue(): Promise<void>;
|
|
52
62
|
getPendingUploads(): Promise<PendingUpload[]>;
|
|
63
|
+
processMatchingUpload(match: PendingUploadMatch): Promise<VideoUploadResult | null>;
|
|
53
64
|
getStats(): Promise<{
|
|
54
65
|
pending: number;
|
|
55
66
|
uploading: number;
|
|
@@ -58,6 +69,10 @@ export declare class UploadQueueManager {
|
|
|
58
69
|
}>;
|
|
59
70
|
private getOldestUpload;
|
|
60
71
|
private getOldestFailedUpload;
|
|
72
|
+
private recoverStaleUploads;
|
|
73
|
+
private getOldestMatchingUpload;
|
|
74
|
+
private canProcessMatchingUpload;
|
|
75
|
+
private matchesMetadata;
|
|
61
76
|
private processUpload;
|
|
62
77
|
private calculateRetryDelay;
|
|
63
78
|
private scheduleRetry;
|
|
@@ -1936,6 +1951,7 @@ type AudioCodecCheckOptions = {
|
|
|
1936
1951
|
sampleRate?: number;
|
|
1937
1952
|
};
|
|
1938
1953
|
type MediabunnyModule = {
|
|
1954
|
+
canEncodeAudio?: (codec: AudioCodec, options: AudioCodecCheckOptions) => Promise<boolean>;
|
|
1939
1955
|
canEncodeVideo?: (codec: VideoCodec, options: VideoCodecCheckOptions) => Promise<boolean>;
|
|
1940
1956
|
getFirstEncodableAudioCodec?: (codecs: AudioCodec[], options: AudioCodecCheckOptions) => Promise<string | null>;
|
|
1941
1957
|
};
|
package/dist/index.js
CHANGED
|
@@ -1762,15 +1762,17 @@ async function resolveVideoCodecFromPolicy(options) {
|
|
|
1762
1762
|
}
|
|
1763
1763
|
throw createVideoCodecUnavailableError(options.format, candidates, probeErrors);
|
|
1764
1764
|
}
|
|
1765
|
+
function resolveUnavailableAudioCodec(options) {
|
|
1766
|
+
if (options.shouldThrowIfNoCodecAvailable) {
|
|
1767
|
+
throw new Error(`No encodable audio codec available for format: ${options.format}`);
|
|
1768
|
+
}
|
|
1769
|
+
return options.policy.preferredAudioCodec;
|
|
1770
|
+
}
|
|
1765
1771
|
async function resolveAudioCodecFromPolicy(options) {
|
|
1766
1772
|
const dependencies = resolveCodecPolicyResolverDependencies(options.dependencies);
|
|
1767
1773
|
const mediabunnyModule = await dependencies.loadMediabunnyModule();
|
|
1768
1774
|
if (mediabunnyModule === null) {
|
|
1769
|
-
return options
|
|
1770
|
-
}
|
|
1771
|
-
const getFirstEncodableAudioCodec = mediabunnyModule.getFirstEncodableAudioCodec;
|
|
1772
|
-
if (typeof getFirstEncodableAudioCodec !== "function") {
|
|
1773
|
-
return options.policy.preferredAudioCodec;
|
|
1775
|
+
return resolveUnavailableAudioCodec(options);
|
|
1774
1776
|
}
|
|
1775
1777
|
const candidates = buildAudioCandidates(options.overrideCodec, options.policy);
|
|
1776
1778
|
const audioCodecOptions = {};
|
|
@@ -1783,28 +1785,40 @@ async function resolveAudioCodecFromPolicy(options) {
|
|
|
1783
1785
|
if (options.sampleRate !== undefined) {
|
|
1784
1786
|
audioCodecOptions.sampleRate = options.sampleRate;
|
|
1785
1787
|
}
|
|
1788
|
+
const canEncodeAudio = mediabunnyModule.canEncodeAudio;
|
|
1789
|
+
if (typeof canEncodeAudio === "function") {
|
|
1790
|
+
for (const candidate of candidates) {
|
|
1791
|
+
const candidateProbeResults = await Promise.allSettled([
|
|
1792
|
+
canEncodeAudio(candidate, audioCodecOptions)
|
|
1793
|
+
]);
|
|
1794
|
+
const candidateProbeResult = candidateProbeResults[FIRST_RESULT_INDEX];
|
|
1795
|
+
const isCandidateSupported = candidateProbeResult.status === "fulfilled" && candidateProbeResult.value;
|
|
1796
|
+
if (isCandidateSupported) {
|
|
1797
|
+
return candidate;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return resolveUnavailableAudioCodec(options);
|
|
1801
|
+
}
|
|
1802
|
+
const getFirstEncodableAudioCodec = mediabunnyModule.getFirstEncodableAudioCodec;
|
|
1803
|
+
if (typeof getFirstEncodableAudioCodec !== "function") {
|
|
1804
|
+
return resolveUnavailableAudioCodec(options);
|
|
1805
|
+
}
|
|
1786
1806
|
const probeResults = await Promise.allSettled([
|
|
1787
1807
|
getFirstEncodableAudioCodec(candidates, audioCodecOptions)
|
|
1788
1808
|
]);
|
|
1789
1809
|
const firstProbeResult = probeResults[FIRST_RESULT_INDEX];
|
|
1790
1810
|
if (firstProbeResult.status === "rejected") {
|
|
1791
|
-
return options
|
|
1811
|
+
return resolveUnavailableAudioCodec(options);
|
|
1792
1812
|
}
|
|
1793
1813
|
const resolvedCodec = firstProbeResult.value;
|
|
1794
1814
|
if (resolvedCodec === null) {
|
|
1795
|
-
|
|
1796
|
-
throw new Error(`No encodable audio codec available for format: ${options.format}`);
|
|
1797
|
-
}
|
|
1798
|
-
return options.policy.preferredAudioCodec;
|
|
1815
|
+
return resolveUnavailableAudioCodec(options);
|
|
1799
1816
|
}
|
|
1800
1817
|
const canUseResolvedCodec = candidates.includes(resolvedCodec);
|
|
1801
1818
|
if (canUseResolvedCodec) {
|
|
1802
1819
|
return resolvedCodec;
|
|
1803
1820
|
}
|
|
1804
|
-
|
|
1805
|
-
throw new Error(`No encodable audio codec available for format: ${options.format}`);
|
|
1806
|
-
}
|
|
1807
|
-
return options.policy.preferredAudioCodec;
|
|
1821
|
+
return resolveUnavailableAudioCodec(options);
|
|
1808
1822
|
}
|
|
1809
1823
|
|
|
1810
1824
|
// src/core/transcode/output-format.ts
|
|
@@ -6305,7 +6319,7 @@ function resolveDeviceError(input) {
|
|
|
6305
6319
|
// package.json
|
|
6306
6320
|
var package_default = {
|
|
6307
6321
|
name: "@vidtreo/recorder",
|
|
6308
|
-
version: "1.7.
|
|
6322
|
+
version: "1.7.3",
|
|
6309
6323
|
type: "module",
|
|
6310
6324
|
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.",
|
|
6311
6325
|
main: "./dist/index.js",
|
|
@@ -7056,13 +7070,7 @@ class UploadQueueManager {
|
|
|
7056
7070
|
if (!this.storageService.isInitialized()) {
|
|
7057
7071
|
throw new Error("Database not initialized");
|
|
7058
7072
|
}
|
|
7059
|
-
|
|
7060
|
-
const staleUploads = await this.storageService.getPendingUploads("uploading");
|
|
7061
|
-
await Promise.all(staleUploads.map((upload) => this.storageService.updateUploadStatus(upload.id, {
|
|
7062
|
-
status: "pending"
|
|
7063
|
-
})));
|
|
7064
|
-
this.hasRecoveredStaleUploads = true;
|
|
7065
|
-
}
|
|
7073
|
+
await this.recoverStaleUploads();
|
|
7066
7074
|
const pendingUploads = await this.storageService.getPendingUploads("pending");
|
|
7067
7075
|
if (pendingUploads.length > 0) {
|
|
7068
7076
|
const upload = this.getOldestUpload(pendingUploads);
|
|
@@ -7096,6 +7104,27 @@ class UploadQueueManager {
|
|
|
7096
7104
|
getPendingUploads() {
|
|
7097
7105
|
return this.storageService.getPendingUploads();
|
|
7098
7106
|
}
|
|
7107
|
+
async processMatchingUpload(match) {
|
|
7108
|
+
if (this.isProcessing) {
|
|
7109
|
+
return null;
|
|
7110
|
+
}
|
|
7111
|
+
this.isProcessing = true;
|
|
7112
|
+
try {
|
|
7113
|
+
await this.recoverStaleUploads();
|
|
7114
|
+
const uploads = await this.storageService.getPendingUploads();
|
|
7115
|
+
const matchingUpload = this.getOldestMatchingUpload(uploads, match);
|
|
7116
|
+
if (!matchingUpload) {
|
|
7117
|
+
this.isProcessing = false;
|
|
7118
|
+
return null;
|
|
7119
|
+
}
|
|
7120
|
+
const result = await this.processUpload(matchingUpload, true);
|
|
7121
|
+
this.isProcessing = false;
|
|
7122
|
+
return result;
|
|
7123
|
+
} catch (error) {
|
|
7124
|
+
this.isProcessing = false;
|
|
7125
|
+
throw new Error(`Error processing matching upload: ${extractErrorMessage(error)}`);
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7099
7128
|
async getStats() {
|
|
7100
7129
|
const all = await this.storageService.getPendingUploads();
|
|
7101
7130
|
const stats = {
|
|
@@ -7127,7 +7156,45 @@ class UploadQueueManager {
|
|
|
7127
7156
|
}
|
|
7128
7157
|
return uploads.sort((a, b) => a.updatedAt - b.updatedAt)[0];
|
|
7129
7158
|
}
|
|
7130
|
-
async
|
|
7159
|
+
async recoverStaleUploads() {
|
|
7160
|
+
if (this.hasRecoveredStaleUploads) {
|
|
7161
|
+
return;
|
|
7162
|
+
}
|
|
7163
|
+
const staleUploads = await this.storageService.getPendingUploads("uploading");
|
|
7164
|
+
await Promise.all(staleUploads.map((upload) => this.storageService.updateUploadStatus(upload.id, {
|
|
7165
|
+
status: "pending"
|
|
7166
|
+
})));
|
|
7167
|
+
this.hasRecoveredStaleUploads = true;
|
|
7168
|
+
}
|
|
7169
|
+
getOldestMatchingUpload(uploads, match) {
|
|
7170
|
+
const matchingUploads = uploads.filter((upload) => this.canProcessMatchingUpload(upload, match));
|
|
7171
|
+
if (matchingUploads.length === 0) {
|
|
7172
|
+
return null;
|
|
7173
|
+
}
|
|
7174
|
+
return matchingUploads.sort((a, b) => a.createdAt - b.createdAt)[0];
|
|
7175
|
+
}
|
|
7176
|
+
canProcessMatchingUpload(upload, match) {
|
|
7177
|
+
if (upload.status === "completed") {
|
|
7178
|
+
return false;
|
|
7179
|
+
}
|
|
7180
|
+
if (upload.status === "failed" && upload.retryCount >= MAX_RETRIES2) {
|
|
7181
|
+
return false;
|
|
7182
|
+
}
|
|
7183
|
+
if (!this.matchesMetadata(upload.metadata, match.metadata)) {
|
|
7184
|
+
return false;
|
|
7185
|
+
}
|
|
7186
|
+
return this.matchesMetadata(upload.userMetadata, match.userMetadata);
|
|
7187
|
+
}
|
|
7188
|
+
matchesMetadata(actual, expected) {
|
|
7189
|
+
if (!expected || Object.keys(expected).length === 0) {
|
|
7190
|
+
return true;
|
|
7191
|
+
}
|
|
7192
|
+
if (!actual) {
|
|
7193
|
+
return false;
|
|
7194
|
+
}
|
|
7195
|
+
return Object.entries(expected).every(([key, value]) => actual[key] === value);
|
|
7196
|
+
}
|
|
7197
|
+
async processUpload(upload, shouldThrowOnError = false) {
|
|
7131
7198
|
try {
|
|
7132
7199
|
await this.storageService.updateUploadStatus(upload.id, {
|
|
7133
7200
|
status: "uploading"
|
|
@@ -7145,6 +7212,7 @@ class UploadQueueManager {
|
|
|
7145
7212
|
});
|
|
7146
7213
|
await this.storageService.deleteUpload(upload.id);
|
|
7147
7214
|
this.callbacks.onUploadComplete?.(upload.id, result);
|
|
7215
|
+
return result;
|
|
7148
7216
|
} catch (error) {
|
|
7149
7217
|
const errorMessage = extractErrorMessage(error);
|
|
7150
7218
|
const retryCount = upload.retryCount + 1;
|
|
@@ -7159,6 +7227,10 @@ class UploadQueueManager {
|
|
|
7159
7227
|
const delay2 = this.calculateRetryDelay(retryCount);
|
|
7160
7228
|
this.scheduleRetry(delay2);
|
|
7161
7229
|
}
|
|
7230
|
+
if (shouldThrowOnError) {
|
|
7231
|
+
throw new Error(errorMessage);
|
|
7232
|
+
}
|
|
7233
|
+
return null;
|
|
7162
7234
|
}
|
|
7163
7235
|
}
|
|
7164
7236
|
calculateRetryDelay(retryCount) {
|
|
@@ -21742,7 +21814,8 @@ class WorkerProcessor {
|
|
|
21742
21814
|
policy,
|
|
21743
21815
|
bitrate: audioBitrate,
|
|
21744
21816
|
numberOfChannels: audioConfig?.numberOfChannels,
|
|
21745
|
-
sampleRate: audioConfig?.sampleRate
|
|
21817
|
+
sampleRate: audioConfig?.sampleRate,
|
|
21818
|
+
shouldThrowIfNoCodecAvailable: audioConfig !== null
|
|
21746
21819
|
});
|
|
21747
21820
|
}
|
|
21748
21821
|
async resolveVideoCodec(config, format, policy) {
|
|
@@ -23899,6 +23972,23 @@ class QuotaManager {
|
|
|
23899
23972
|
return quota.percentage >= threshold;
|
|
23900
23973
|
}
|
|
23901
23974
|
}
|
|
23975
|
+
// src/core/upload/pending-upload-recovery.ts
|
|
23976
|
+
async function recoverPendingUpload(options) {
|
|
23977
|
+
const storageService = new VideoStorageService;
|
|
23978
|
+
await storageService.init();
|
|
23979
|
+
const uploadService = new VideoUploadService;
|
|
23980
|
+
const uploadQueueManager = new UploadQueueManager(storageService, uploadService);
|
|
23981
|
+
uploadQueueManager.setCallbacks({
|
|
23982
|
+
onUploadProgress: (_id, progress) => {
|
|
23983
|
+
options.onProgress?.(progress);
|
|
23984
|
+
}
|
|
23985
|
+
});
|
|
23986
|
+
try {
|
|
23987
|
+
return await uploadQueueManager.processMatchingUpload(options.match);
|
|
23988
|
+
} finally {
|
|
23989
|
+
uploadQueueManager.destroy();
|
|
23990
|
+
}
|
|
23991
|
+
}
|
|
23902
23992
|
// src/core/utils/audio-utils.ts
|
|
23903
23993
|
function calculateBarColor(position) {
|
|
23904
23994
|
if (position < 0.25) {
|
|
@@ -24294,6 +24384,7 @@ export {
|
|
|
24294
24384
|
requireInitialized,
|
|
24295
24385
|
requireDefined,
|
|
24296
24386
|
requireActive,
|
|
24387
|
+
recoverPendingUpload,
|
|
24297
24388
|
parseBrowserErrorLinkContent,
|
|
24298
24389
|
mapPresetToConfig,
|
|
24299
24390
|
logger,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidtreo/recorder",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.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",
|