videomail-client 16.1.1 → 16.2.0
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/README.md +1 -1
- package/dist/cjs/index.cjs +1857 -1721
- package/dist/esm/index.js +1989 -1853
- package/dist/esm/types/events/params.d.ts +4 -3
- package/dist/esm/util/email/isRecipientSelectionValid.d.ts +4 -0
- package/dist/esm/wrappers/container.d.ts +1 -1
- package/dist/esm/wrappers/visuals/recorder/capture.d.ts +87 -0
- package/dist/esm/wrappers/visuals/recorder/connection.d.ts +46 -0
- package/dist/esm/wrappers/visuals/recorder/dimensions.d.ts +21 -0
- package/dist/esm/wrappers/visuals/recorder/media.d.ts +51 -0
- package/dist/esm/wrappers/visuals/recorder/mediaConstraints.d.ts +14 -0
- package/dist/esm/wrappers/visuals/recorder/progress.d.ts +11 -0
- package/dist/esm/wrappers/visuals/recorder/streamWriter.d.ts +56 -0
- package/dist/esm/wrappers/visuals/recorder.d.ts +13 -165
- package/dist/umd/index.js +1908 -1821
- package/package.json +5 -5
- /package/dist/esm/util/{trimEmail.d.ts → email/trimEmail.d.ts} +0 -0
package/dist/esm/index.js
CHANGED
|
@@ -3620,7 +3620,7 @@ const constants = {
|
|
|
3620
3620
|
}
|
|
3621
3621
|
};
|
|
3622
3622
|
var package_namespaceObject = {
|
|
3623
|
-
rE: "16.
|
|
3623
|
+
rE: "16.2.0"
|
|
3624
3624
|
};
|
|
3625
3625
|
function isAudioEnabled(options) {
|
|
3626
3626
|
return Boolean(options.audio.enabled);
|
|
@@ -4142,6 +4142,14 @@ class Despot {
|
|
|
4142
4142
|
}
|
|
4143
4143
|
Despot_define_property(Despot, "emitters", new WeakMap());
|
|
4144
4144
|
const util_Despot = Despot;
|
|
4145
|
+
const EMAIL_SEPARATOR_REGEX = /[ ,]+/gu;
|
|
4146
|
+
function trimEmail(email) {
|
|
4147
|
+
return email.replace(EMAIL_SEPARATOR_REGEX, "");
|
|
4148
|
+
}
|
|
4149
|
+
function trimEmails(emails) {
|
|
4150
|
+
const trimmedEmails = emails.split(EMAIL_SEPARATOR_REGEX).map((item)=>item.trim()).filter(Boolean);
|
|
4151
|
+
return trimmedEmails;
|
|
4152
|
+
}
|
|
4145
4153
|
function getValidity(element) {
|
|
4146
4154
|
const validity = "validity" in element ? element.validity : void 0;
|
|
4147
4155
|
return validity;
|
|
@@ -4161,14 +4169,6 @@ function showElement(element) {
|
|
|
4161
4169
|
element.style.removeProperty("display");
|
|
4162
4170
|
}
|
|
4163
4171
|
const html_showElement = showElement;
|
|
4164
|
-
const EMAIL_SEPARATOR_REGEX = /[ ,]+/gu;
|
|
4165
|
-
function trimEmail(email) {
|
|
4166
|
-
return email.replace(EMAIL_SEPARATOR_REGEX, "");
|
|
4167
|
-
}
|
|
4168
|
-
function trimEmails(emails) {
|
|
4169
|
-
const trimmedEmails = emails.split(EMAIL_SEPARATOR_REGEX).map((item)=>item.trim()).filter(Boolean);
|
|
4170
|
-
return trimmedEmails;
|
|
4171
|
-
}
|
|
4172
4172
|
function form_define_property(obj, key, value) {
|
|
4173
4173
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
4174
4174
|
value: value,
|
|
@@ -4876,6 +4876,19 @@ main_options.domAPI = styleDomAPI_default();
|
|
|
4876
4876
|
main_options.insertStyleElement = insertStyleElement_default();
|
|
4877
4877
|
injectStylesIntoStyleTag_default()(main_0.A, main_options);
|
|
4878
4878
|
main_0.A && main_0.A.locals && main_0.A.locals;
|
|
4879
|
+
function isRecipientSelectionValid(recipients) {
|
|
4880
|
+
const hasTo = Boolean(recipients.to?.length);
|
|
4881
|
+
const hasCc = Boolean(recipients.cc?.length);
|
|
4882
|
+
const hasBcc = Boolean(recipients.bcc?.length);
|
|
4883
|
+
const hasConfiguredTo = "to" in recipients;
|
|
4884
|
+
const hasConfiguredCc = "cc" in recipients;
|
|
4885
|
+
const hasConfiguredBcc = "bcc" in recipients;
|
|
4886
|
+
let isValid = true;
|
|
4887
|
+
if (hasConfiguredTo) isValid = hasTo || hasConfiguredCc && hasCc || hasConfiguredBcc && hasBcc;
|
|
4888
|
+
else if (hasConfiguredCc) isValid = hasCc || !hasConfiguredBcc || hasBcc;
|
|
4889
|
+
return isValid;
|
|
4890
|
+
}
|
|
4891
|
+
const email_isRecipientSelectionValid = isRecipientSelectionValid;
|
|
4879
4892
|
function limitHeight(height, options, calledFrom) {
|
|
4880
4893
|
const dimension = {
|
|
4881
4894
|
unit: "px"
|
|
@@ -5487,7 +5500,7 @@ class FacingMode extends util_Despot {
|
|
|
5487
5500
|
this.visuals = visuals;
|
|
5488
5501
|
}
|
|
5489
5502
|
}
|
|
5490
|
-
const
|
|
5503
|
+
const recorder_facingMode = FacingMode;
|
|
5491
5504
|
function pausedNote_define_property(obj, key, value) {
|
|
5492
5505
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
5493
5506
|
value: value,
|
|
@@ -5831,7 +5844,7 @@ class RecorderInsides extends util_Despot {
|
|
|
5831
5844
|
this.recordTimer = new recordTimer(visuals, this.recordNote, options);
|
|
5832
5845
|
const browser = util_getBrowser(options);
|
|
5833
5846
|
if (options.video.countdown) this.countdown = new countdown(visuals, options);
|
|
5834
|
-
if (options.video.facingModeButton && browser.isMobile()) this.facingMode = new
|
|
5847
|
+
if (options.video.facingModeButton && browser.isMobile()) this.facingMode = new recorder_facingMode(visuals, options);
|
|
5835
5848
|
if (options.enablePause) this.pausedNote = new pausedNote(visuals, options);
|
|
5836
5849
|
}
|
|
5837
5850
|
}
|
|
@@ -6078,46 +6091,18 @@ class Notifier extends util_Despot {
|
|
|
6078
6091
|
}
|
|
6079
6092
|
}
|
|
6080
6093
|
const notifier = Notifier;
|
|
6081
|
-
function
|
|
6082
|
-
|
|
6083
|
-
value: value
|
|
6084
|
-
|
|
6085
|
-
configurable: true,
|
|
6086
|
-
writable: true
|
|
6094
|
+
function summarize(title, contents) {
|
|
6095
|
+
const lines = Object.entries(contents).sort(([keyA], [keyB])=>keyA.localeCompare(keyB)).map(([key, value])=>{
|
|
6096
|
+
const formattedValue = void 0 === value ? "undefined" : "object" == typeof value ? string_pretty(value) : value;
|
|
6097
|
+
return ` • ${key}: ${formattedValue}`;
|
|
6087
6098
|
});
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
enqueue(buffer, params) {
|
|
6094
|
-
if (this.queuedBytes + buffer.length > this.maxBytes) return false;
|
|
6095
|
-
this.writes.push({
|
|
6096
|
-
buffer,
|
|
6097
|
-
params
|
|
6098
|
-
});
|
|
6099
|
-
this.queuedBytes += buffer.length;
|
|
6100
|
-
return true;
|
|
6101
|
-
}
|
|
6102
|
-
dequeue() {
|
|
6103
|
-
const write = this.writes.shift();
|
|
6104
|
-
if (write) this.queuedBytes -= write.buffer.length;
|
|
6105
|
-
return write;
|
|
6106
|
-
}
|
|
6107
|
-
clear() {
|
|
6108
|
-
this.writes.length = 0;
|
|
6109
|
-
this.queuedBytes = 0;
|
|
6110
|
-
}
|
|
6111
|
-
constructor(maxBytes = DEFAULT_MAX_QUEUED_BYTES){
|
|
6112
|
-
boundedWriteQueue_define_property(this, "maxBytes", void 0);
|
|
6113
|
-
boundedWriteQueue_define_property(this, "writes", void 0);
|
|
6114
|
-
boundedWriteQueue_define_property(this, "queuedBytes", void 0);
|
|
6115
|
-
this.maxBytes = maxBytes;
|
|
6116
|
-
this.writes = [];
|
|
6117
|
-
this.queuedBytes = 0;
|
|
6118
|
-
}
|
|
6099
|
+
const line = [
|
|
6100
|
+
`🔎 ${title}`,
|
|
6101
|
+
...lines
|
|
6102
|
+
].join("\n");
|
|
6103
|
+
return line;
|
|
6119
6104
|
}
|
|
6120
|
-
const
|
|
6105
|
+
const string_summarize = summarize;
|
|
6121
6106
|
function getEdgeCodes(value, amount, fromEnd) {
|
|
6122
6107
|
const chars = fromEnd ? value.slice(-amount) : value.slice(0, amount);
|
|
6123
6108
|
const codes = Array.from(chars, (char)=>String(char.codePointAt(0))).join(".");
|
|
@@ -6197,117 +6182,11 @@ function getWebSocketDiagnostic(url2Connect) {
|
|
|
6197
6182
|
};
|
|
6198
6183
|
}
|
|
6199
6184
|
const error_getWebSocketDiagnostic = getWebSocketDiagnostic;
|
|
6200
|
-
function figureMinHeight(height, options) {
|
|
6201
|
-
let minHeight;
|
|
6202
|
-
if (options.video.height) {
|
|
6203
|
-
minHeight = height ? Math.min(options.video.height, height) : options.video.height;
|
|
6204
|
-
if (minHeight < 1) throw error_createError({
|
|
6205
|
-
message: `Got a min height less than 1 (${minHeight})!`,
|
|
6206
|
-
options
|
|
6207
|
-
});
|
|
6208
|
-
} else minHeight = height;
|
|
6209
|
-
return minHeight;
|
|
6210
|
-
}
|
|
6211
|
-
const dimensions_figureMinHeight = figureMinHeight;
|
|
6212
|
-
function getRatio(options, videoHeight, videoWidth) {
|
|
6213
|
-
let ratio = 1;
|
|
6214
|
-
const hasVideoDimensions = videoHeight && videoWidth;
|
|
6215
|
-
const desiredHeight = options.video.height;
|
|
6216
|
-
const desiredWidth = options.video.width;
|
|
6217
|
-
const hasDesiredDimensions = desiredHeight && desiredWidth;
|
|
6218
|
-
if (hasDesiredDimensions) ratio = hasVideoDimensions ? videoHeight < desiredHeight || videoWidth < desiredWidth ? videoHeight / videoWidth : desiredHeight / desiredWidth : desiredHeight / desiredWidth;
|
|
6219
|
-
else if (hasVideoDimensions) ratio = videoHeight / videoWidth;
|
|
6220
|
-
return ratio;
|
|
6221
|
-
}
|
|
6222
|
-
const dimensions_getRatio = getRatio;
|
|
6223
|
-
function calculateHeight(responsive, videoWidth, options, ratio, element) {
|
|
6224
|
-
const dimension = {
|
|
6225
|
-
unit: "px"
|
|
6226
|
-
};
|
|
6227
|
-
let width = videoWidth;
|
|
6228
|
-
if (responsive && element) {
|
|
6229
|
-
const limitedDimension = dimensions_limitWidth(element, options, width);
|
|
6230
|
-
width = limitedDimension.value;
|
|
6231
|
-
}
|
|
6232
|
-
const chosenRatio = ratio ?? dimensions_getRatio(options, void 0, videoWidth);
|
|
6233
|
-
const height = width ? Math.round(width * chosenRatio) : void 0;
|
|
6234
|
-
if (height && Number.isInteger(height) && height < 1) throw error_createError({
|
|
6235
|
-
message: "Just calculated a height less than 1 which is wrong.",
|
|
6236
|
-
options
|
|
6237
|
-
});
|
|
6238
|
-
const minHeight = dimensions_figureMinHeight(height, options);
|
|
6239
|
-
dimension.value = minHeight;
|
|
6240
|
-
return dimension;
|
|
6241
|
-
}
|
|
6242
|
-
const dimensions_calculateHeight = calculateHeight;
|
|
6243
|
-
function calculateWidth(responsive, options, videoHeight, ratio) {
|
|
6244
|
-
const dimension = {
|
|
6245
|
-
unit: "px"
|
|
6246
|
-
};
|
|
6247
|
-
let height = dimensions_figureMinHeight(videoHeight, options);
|
|
6248
|
-
if (responsive) {
|
|
6249
|
-
const limitedDimension = dimensions_limitHeight(height, options, "calculateWidth");
|
|
6250
|
-
height = limitedDimension.value;
|
|
6251
|
-
}
|
|
6252
|
-
if (!height || height < 1) throw error_createError({
|
|
6253
|
-
message: `Height ${height} cannot be smaller than 1 when calculating width.`,
|
|
6254
|
-
options
|
|
6255
|
-
});
|
|
6256
|
-
const chosenRatio = ratio ?? dimensions_getRatio(options, videoHeight);
|
|
6257
|
-
const calculatedWidth = Math.round(height / chosenRatio);
|
|
6258
|
-
if (calculatedWidth < 1) throw error_createError({
|
|
6259
|
-
message: "Calculated width cannot be smaller than 1!",
|
|
6260
|
-
options
|
|
6261
|
-
});
|
|
6262
|
-
dimension.value = calculatedWidth;
|
|
6263
|
-
return dimension;
|
|
6264
|
-
}
|
|
6265
|
-
const dimensions_calculateWidth = calculateWidth;
|
|
6266
6185
|
function isAutomatedUserAgent(userAgent = navigator.userAgent) {
|
|
6267
6186
|
return /bot|crawler|spider|headlesschrome|phantomjs|puppeteer|playwright/iu.test(userAgent);
|
|
6268
6187
|
}
|
|
6269
6188
|
const util_isAutomatedUserAgent = isAutomatedUserAgent;
|
|
6270
|
-
function
|
|
6271
|
-
const lines = Object.entries(contents).sort(([keyA], [keyB])=>keyA.localeCompare(keyB)).map(([key, value])=>{
|
|
6272
|
-
const formattedValue = void 0 === value ? "undefined" : "object" == typeof value ? string_pretty(value) : value;
|
|
6273
|
-
return ` • ${key}: ${formattedValue}`;
|
|
6274
|
-
});
|
|
6275
|
-
const line = [
|
|
6276
|
-
`🔎 ${title}`,
|
|
6277
|
-
...lines
|
|
6278
|
-
].join("\n");
|
|
6279
|
-
return line;
|
|
6280
|
-
}
|
|
6281
|
-
const string_summarize = summarize;
|
|
6282
|
-
function getFirstVideoTrack(localMediaStream) {
|
|
6283
|
-
const videoTracks = localMediaStream.getVideoTracks();
|
|
6284
|
-
let videoTrack;
|
|
6285
|
-
if (videoTracks[0]) videoTrack = videoTracks[0];
|
|
6286
|
-
return videoTrack;
|
|
6287
|
-
}
|
|
6288
|
-
const media_getFirstVideoTrack = getFirstVideoTrack;
|
|
6289
|
-
const MEDIA_EVENTS = [
|
|
6290
|
-
"loadstart",
|
|
6291
|
-
"suspend",
|
|
6292
|
-
"progress",
|
|
6293
|
-
"abort",
|
|
6294
|
-
"emptied",
|
|
6295
|
-
"stalled",
|
|
6296
|
-
"pause",
|
|
6297
|
-
"loadeddata",
|
|
6298
|
-
"waiting",
|
|
6299
|
-
"playing",
|
|
6300
|
-
"canplay",
|
|
6301
|
-
"canplaythrough",
|
|
6302
|
-
"seeking",
|
|
6303
|
-
"seeked",
|
|
6304
|
-
"ended",
|
|
6305
|
-
"ratechange",
|
|
6306
|
-
"durationchange",
|
|
6307
|
-
"volumechange"
|
|
6308
|
-
];
|
|
6309
|
-
const mediaEvents = MEDIA_EVENTS;
|
|
6310
|
-
function AudioRecorder_define_property(obj, key, value) {
|
|
6189
|
+
function boundedWriteQueue_define_property(obj, key, value) {
|
|
6311
6190
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
6312
6191
|
value: value,
|
|
6313
6192
|
enumerable: true,
|
|
@@ -6317,120 +6196,223 @@ function AudioRecorder_define_property(obj, key, value) {
|
|
|
6317
6196
|
else obj[key] = value;
|
|
6318
6197
|
return obj;
|
|
6319
6198
|
}
|
|
6320
|
-
const
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6199
|
+
const DEFAULT_MAX_QUEUED_BYTES = 1048576;
|
|
6200
|
+
class BoundedWriteQueue {
|
|
6201
|
+
enqueue(buffer, params) {
|
|
6202
|
+
if (this.queuedBytes + buffer.length > this.maxBytes) return false;
|
|
6203
|
+
this.writes.push({
|
|
6204
|
+
buffer,
|
|
6205
|
+
params
|
|
6206
|
+
});
|
|
6207
|
+
this.queuedBytes += buffer.length;
|
|
6208
|
+
return true;
|
|
6327
6209
|
}
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
}
|
|
6333
|
-
return this.vcAudioContext;
|
|
6210
|
+
dequeue() {
|
|
6211
|
+
const write = this.writes.shift();
|
|
6212
|
+
if (write) this.queuedBytes -= write.buffer.length;
|
|
6213
|
+
return write;
|
|
6334
6214
|
}
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
cb(new audio_sample(float32Array));
|
|
6215
|
+
clear() {
|
|
6216
|
+
this.writes.length = 0;
|
|
6217
|
+
this.queuedBytes = 0;
|
|
6339
6218
|
}
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
this.
|
|
6346
|
-
this.
|
|
6219
|
+
constructor(maxBytes = DEFAULT_MAX_QUEUED_BYTES){
|
|
6220
|
+
boundedWriteQueue_define_property(this, "maxBytes", void 0);
|
|
6221
|
+
boundedWriteQueue_define_property(this, "writes", void 0);
|
|
6222
|
+
boundedWriteQueue_define_property(this, "queuedBytes", void 0);
|
|
6223
|
+
this.maxBytes = maxBytes;
|
|
6224
|
+
this.writes = [];
|
|
6225
|
+
this.queuedBytes = 0;
|
|
6347
6226
|
}
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6227
|
+
}
|
|
6228
|
+
const boundedWriteQueue = BoundedWriteQueue;
|
|
6229
|
+
function streamWriter_define_property(obj, key, value) {
|
|
6230
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
6231
|
+
value: value,
|
|
6232
|
+
enumerable: true,
|
|
6233
|
+
configurable: true,
|
|
6234
|
+
writable: true
|
|
6235
|
+
});
|
|
6236
|
+
else obj[key] = value;
|
|
6237
|
+
return obj;
|
|
6238
|
+
}
|
|
6239
|
+
const PIPE_SYMBOL = "°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸ ";
|
|
6240
|
+
class RecorderStreamWriter extends util_Despot {
|
|
6241
|
+
hasExceededWebSocketSendBuffer(buffer, opts) {
|
|
6242
|
+
return buffer.length > 0 && !this.pendingWrites.enqueue(buffer, opts);
|
|
6243
|
+
}
|
|
6244
|
+
writeStream(buffer, opts) {
|
|
6245
|
+
if (this.stream) if (this.stream.destroyed) {
|
|
6246
|
+
this.stopPings();
|
|
6247
|
+
const err = error_createError({
|
|
6248
|
+
message: "Already disconnected",
|
|
6249
|
+
explanation: "Sorry, connection to the server has been destroyed. Please reload.",
|
|
6359
6250
|
options: this.options
|
|
6360
6251
|
});
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6252
|
+
this.handleWriteFailure(err);
|
|
6253
|
+
} else if (this.streamBackpressured) {
|
|
6254
|
+
if (this.hasExceededWebSocketSendBuffer(buffer, opts)) this.failMediaTransmission(error_createError({
|
|
6255
|
+
message: "WebSocket send buffer limit exceeded",
|
|
6256
|
+
explanation: "The connection to the server is too slow to keep up with this recording. The recording has been stopped; please start a new one.",
|
|
6257
|
+
options: this.options
|
|
6258
|
+
}));
|
|
6259
|
+
} else this.writeToStream(this.stream, buffer, opts);
|
|
6260
|
+
}
|
|
6261
|
+
writeToStream(stream, buffer, opts) {
|
|
6262
|
+
const onFlushedCallback = opts?.onFlushedCallback;
|
|
6263
|
+
try {
|
|
6264
|
+
const canContinue = stream.write(buffer, ()=>{
|
|
6265
|
+
if (!onFlushedCallback) return;
|
|
6266
|
+
try {
|
|
6267
|
+
onFlushedCallback(opts);
|
|
6268
|
+
} catch (exc) {
|
|
6269
|
+
const err = error_createError({
|
|
6270
|
+
message: "Failed to write stream buffer",
|
|
6271
|
+
explanation: `stream.write() failed because of ${string_pretty(exc)}`,
|
|
6272
|
+
options: this.options,
|
|
6273
|
+
exc
|
|
6274
|
+
});
|
|
6275
|
+
this.emit("ERROR", {
|
|
6276
|
+
err
|
|
6277
|
+
});
|
|
6278
|
+
}
|
|
6279
|
+
});
|
|
6280
|
+
if (!canContinue) {
|
|
6281
|
+
this.streamBackpressured = true;
|
|
6282
|
+
this.startBackpressureTimeout();
|
|
6283
|
+
this.pauseForBackpressure();
|
|
6284
|
+
return false;
|
|
6285
|
+
}
|
|
6286
|
+
return true;
|
|
6287
|
+
} catch (exc) {
|
|
6288
|
+
const err = error_createError({
|
|
6289
|
+
message: "Failed writing to server",
|
|
6290
|
+
explanation: `stream.write() failed because of ${string_pretty(exc)}`,
|
|
6291
|
+
options: this.options,
|
|
6292
|
+
exc
|
|
6293
|
+
});
|
|
6294
|
+
this.handleWriteFailure(err);
|
|
6295
|
+
return false;
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
flushPendingWrites(stream) {
|
|
6299
|
+
if (this.isStaleStream(stream)) return;
|
|
6300
|
+
this.streamBackpressured = false;
|
|
6301
|
+
this.clearBackpressureTimeout();
|
|
6302
|
+
let pendingWrite = this.pendingWrites.dequeue();
|
|
6303
|
+
while(pendingWrite){
|
|
6304
|
+
if (!this.writeToStream(stream, pendingWrite.buffer, pendingWrite.params)) return;
|
|
6305
|
+
pendingWrite = this.pendingWrites.dequeue();
|
|
6306
|
+
}
|
|
6307
|
+
this.resumeRecordingAfterDrain();
|
|
6308
|
+
}
|
|
6309
|
+
isStaleStream(stream) {
|
|
6310
|
+
return this.stream !== stream || stream.destroyed;
|
|
6311
|
+
}
|
|
6312
|
+
canPauseForBackpressure() {
|
|
6313
|
+
return this.recordingActive && !this.isUserMediaPaused() && this.isCaptureLoopRunning();
|
|
6314
|
+
}
|
|
6315
|
+
pauseForBackpressure() {
|
|
6316
|
+
if (!this.canPauseForBackpressure()) return;
|
|
6317
|
+
this.resumeAfterDrain = true;
|
|
6318
|
+
this.stopCaptureLoop();
|
|
6319
|
+
}
|
|
6320
|
+
resumeRecordingAfterDrain() {
|
|
6321
|
+
if (!this.resumeAfterDrain) return;
|
|
6322
|
+
this.resumeAfterDrain = false;
|
|
6323
|
+
if (this.canResumeAfterDrain()) this.startCaptureLoop();
|
|
6324
|
+
}
|
|
6325
|
+
canResumeAfterDrain() {
|
|
6326
|
+
return this.recordingActive && !this.isUserMediaPaused();
|
|
6327
|
+
}
|
|
6328
|
+
handleWriteFailure(err) {
|
|
6329
|
+
if (this.mediaTransmissionStarted) return void this.failMediaTransmission(err);
|
|
6330
|
+
this.emit("ERROR", {
|
|
6331
|
+
err
|
|
6367
6332
|
});
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6333
|
+
}
|
|
6334
|
+
failMediaTransmission(err) {
|
|
6335
|
+
if (this.recordingTransportFailed) return;
|
|
6336
|
+
this.recordingTransportFailed = true;
|
|
6337
|
+
this.recordingActive = false;
|
|
6338
|
+
this.mediaTransmissionStarted = false;
|
|
6339
|
+
this.streamBackpressured = false;
|
|
6340
|
+
this.resumeAfterDrain = false;
|
|
6341
|
+
this.pendingWrites.clear();
|
|
6342
|
+
this.connected = false;
|
|
6343
|
+
this.connecting = false;
|
|
6344
|
+
this.reconnecting = false;
|
|
6345
|
+
this.clearReconnectTimeout();
|
|
6346
|
+
this.clearBackpressureTimeout();
|
|
6347
|
+
this.discardSocket();
|
|
6348
|
+
this.reset();
|
|
6349
|
+
this.framesCount = this.samplesCount = 0;
|
|
6350
|
+
this.confirmedFrameNumber = this.confirmedSampleNumber = 0;
|
|
6351
|
+
delete this.recordingStats;
|
|
6352
|
+
this.recordingDimensions = void 0;
|
|
6353
|
+
this.emit("ERROR", {
|
|
6354
|
+
err
|
|
6371
6355
|
});
|
|
6372
|
-
volume.gain.value = this.options.audio.volume;
|
|
6373
|
-
this.scriptProcessor = this.getAudioContext().createScriptProcessor(bufferSize, CHANNELS, CHANNELS);
|
|
6374
|
-
this.audioInput.connect(volume);
|
|
6375
|
-
volume.connect(this.scriptProcessor);
|
|
6376
|
-
this.scriptProcessor.connect(this.getAudioContext().destination);
|
|
6377
6356
|
}
|
|
6378
|
-
|
|
6379
|
-
this.
|
|
6380
|
-
|
|
6381
|
-
this.
|
|
6382
|
-
|
|
6357
|
+
startBackpressureTimeout() {
|
|
6358
|
+
if (!this.shouldStartBackpressureTimeout()) return;
|
|
6359
|
+
this.backpressureTimeout = window.setTimeout(()=>{
|
|
6360
|
+
this.backpressureTimeout = void 0;
|
|
6361
|
+
if (this.shouldIgnoreBackpressureTimeout()) return;
|
|
6362
|
+
const err = error_createError({
|
|
6363
|
+
message: "WebSocket write timed out",
|
|
6364
|
+
explanation: "The server stopped accepting recording data. The recording has been stopped; please check your connection and start a new one.",
|
|
6365
|
+
options: this.options
|
|
6366
|
+
});
|
|
6367
|
+
this.failMediaTransmission(err);
|
|
6368
|
+
}, this.options.timeouts.connection);
|
|
6383
6369
|
}
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
if ("running" !== context.state) await context.resume();
|
|
6370
|
+
shouldStartBackpressureTimeout() {
|
|
6371
|
+
return void 0 === this.backpressureTimeout && this.mediaTransmissionStarted;
|
|
6387
6372
|
}
|
|
6388
|
-
|
|
6389
|
-
this.
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6373
|
+
shouldIgnoreBackpressureTimeout() {
|
|
6374
|
+
return !this.streamBackpressured || this.recordingTransportFailed;
|
|
6375
|
+
}
|
|
6376
|
+
failInterruptedRecording() {
|
|
6377
|
+
const unconfirmedFrames = Math.max(0, this.framesCount - this.confirmedFrameNumber);
|
|
6378
|
+
const unconfirmedSamples = Math.max(0, this.samplesCount - this.confirmedSampleNumber);
|
|
6379
|
+
const err = error_createError({
|
|
6380
|
+
message: "Recording interrupted",
|
|
6381
|
+
explanation: "The server connection closed before this recording completed. It cannot be safely resumed, so it has been stopped. Please start a new recording.",
|
|
6382
|
+
options: this.options,
|
|
6383
|
+
cause: {
|
|
6384
|
+
unconfirmedFrames,
|
|
6385
|
+
unconfirmedSamples
|
|
6386
|
+
}
|
|
6400
6387
|
});
|
|
6388
|
+
this.failMediaTransmission(err);
|
|
6401
6389
|
}
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6390
|
+
handleStreamDrain(stream) {
|
|
6391
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *drain* event emitted`);
|
|
6392
|
+
this.flushPendingWrites(stream);
|
|
6405
6393
|
}
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
this.
|
|
6414
|
-
this.
|
|
6394
|
+
clearBackpressureTimeout() {
|
|
6395
|
+
if (void 0 === this.backpressureTimeout) return;
|
|
6396
|
+
window.clearTimeout(this.backpressureTimeout);
|
|
6397
|
+
this.backpressureTimeout = void 0;
|
|
6398
|
+
}
|
|
6399
|
+
discardSocket() {
|
|
6400
|
+
this.options.logger.debug("Recorder: discarding socket stream ...");
|
|
6401
|
+
this.clearBackpressureTimeout();
|
|
6402
|
+
this.pendingWrites.clear();
|
|
6403
|
+
this.streamBackpressured = false;
|
|
6404
|
+
this.resumeAfterDrain = false;
|
|
6405
|
+
const stream = this.stream;
|
|
6406
|
+
this.stream = void 0;
|
|
6407
|
+
if (stream && !stream.destroyed) stream.destroy();
|
|
6408
|
+
}
|
|
6409
|
+
constructor(options){
|
|
6410
|
+
super("Recorder", options), streamWriter_define_property(this, "pendingWrites", new boundedWriteQueue()), streamWriter_define_property(this, "samplesCount", 0), streamWriter_define_property(this, "framesCount", 0), streamWriter_define_property(this, "recordingStats", void 0), streamWriter_define_property(this, "confirmedFrameNumber", 0), streamWriter_define_property(this, "confirmedSampleNumber", 0), streamWriter_define_property(this, "backpressureTimeout", void 0), streamWriter_define_property(this, "recordingDimensions", void 0), streamWriter_define_property(this, "stream", void 0), streamWriter_define_property(this, "streamBackpressured", false), streamWriter_define_property(this, "resumeAfterDrain", false), streamWriter_define_property(this, "mediaTransmissionStarted", false), streamWriter_define_property(this, "recordingActive", false), streamWriter_define_property(this, "recordingTransportFailed", false), streamWriter_define_property(this, "connecting", false), streamWriter_define_property(this, "connected", false), streamWriter_define_property(this, "reconnecting", false);
|
|
6415
6411
|
}
|
|
6416
6412
|
}
|
|
6417
|
-
const
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
"virtual",
|
|
6421
|
-
"manycam",
|
|
6422
|
-
"vcam",
|
|
6423
|
-
"splitcam",
|
|
6424
|
-
"droidcam"
|
|
6425
|
-
];
|
|
6426
|
-
function isVirtualCamera(videoTrack) {
|
|
6427
|
-
const capabilities = videoTrack.getCapabilities();
|
|
6428
|
-
if (!capabilities.frameRate) return true;
|
|
6429
|
-
const isVirtual = VIRTUAL_KEYWORDS.some((keyword)=>videoTrack.label.toLowerCase().includes(keyword));
|
|
6430
|
-
return isVirtual;
|
|
6431
|
-
}
|
|
6432
|
-
const util_isVirtualCamera = isVirtualCamera;
|
|
6433
|
-
function userMedia_define_property(obj, key, value) {
|
|
6413
|
+
const streamWriter = RecorderStreamWriter;
|
|
6414
|
+
var Buffer = __webpack_require__("dY").hp;
|
|
6415
|
+
function connection_define_property(obj, key, value) {
|
|
6434
6416
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
6435
6417
|
value: value,
|
|
6436
6418
|
enumerable: true,
|
|
@@ -6440,1487 +6422,1859 @@ function userMedia_define_property(obj, key, value) {
|
|
|
6440
6422
|
else obj[key] = value;
|
|
6441
6423
|
return obj;
|
|
6442
6424
|
}
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
if (this.rawVisualUserMedia) {
|
|
6447
|
-
this.rawVisualUserMedia.srcObject = stream;
|
|
6448
|
-
const previousStream = this.currentVisualStream;
|
|
6449
|
-
this.currentVisualStream = stream;
|
|
6450
|
-
if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
|
|
6451
|
-
track.stop();
|
|
6452
|
-
});
|
|
6453
|
-
} else throw error_createError({
|
|
6454
|
-
message: "Error attaching stream to element.",
|
|
6455
|
-
explanation: "Contact the developer about this",
|
|
6456
|
-
options: this.options
|
|
6457
|
-
});
|
|
6458
|
-
}
|
|
6459
|
-
setVisualStream(localMediaStream) {
|
|
6460
|
-
if (localMediaStream) this.attachMediaStream(localMediaStream);
|
|
6461
|
-
else {
|
|
6462
|
-
if (this.rawVisualUserMedia) {
|
|
6463
|
-
this.rawVisualUserMedia.srcObject = null;
|
|
6464
|
-
this.rawVisualUserMedia.removeAttribute("src");
|
|
6465
|
-
}
|
|
6466
|
-
this.currentVisualStream = void 0;
|
|
6467
|
-
}
|
|
6468
|
-
}
|
|
6469
|
-
async restorePlayback() {
|
|
6470
|
-
if (!this.rawVisualUserMedia) throw new Error("No camera preview is available.");
|
|
6471
|
-
await this.rawVisualUserMedia.play();
|
|
6472
|
-
await this.audioRecorder?.resume();
|
|
6473
|
-
}
|
|
6474
|
-
hasLiveStream() {
|
|
6475
|
-
const videoReady = this.currentVisualStream?.getVideoTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
|
|
6476
|
-
const audioReady = !isAudioEnabled(this.options) || this.currentVisualStream?.getAudioTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
|
|
6477
|
-
return Boolean(this.currentVisualStream?.active && videoReady && audioReady);
|
|
6478
|
-
}
|
|
6479
|
-
hasEnded() {
|
|
6480
|
-
if (this.rawVisualUserMedia?.ended) return this.rawVisualUserMedia.ended;
|
|
6481
|
-
return !this.currentVisualStream?.active;
|
|
6425
|
+
class RecorderConnection extends streamWriter {
|
|
6426
|
+
canReconnectAfterStreamClose() {
|
|
6427
|
+
return Boolean(this.connected && this.userMediaLoaded);
|
|
6482
6428
|
}
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
const { videoWidth, videoHeight } = this.rawVisualUserMedia;
|
|
6486
|
-
return videoWidth > 0 && videoWidth < 3 || videoHeight > 0 && videoHeight < 3;
|
|
6429
|
+
shouldReportStreamClose() {
|
|
6430
|
+
return !this.connecting && !this.blocking;
|
|
6487
6431
|
}
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
mediaEvents.forEach((eventName)=>{
|
|
6495
|
-
this.rawVisualUserMedia?.removeEventListener(eventName, this.outputEvent);
|
|
6432
|
+
handleStreamClose(url2Connect) {
|
|
6433
|
+
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream has closed:`, {
|
|
6434
|
+
connecting: this.connecting,
|
|
6435
|
+
connected: this.connected,
|
|
6436
|
+
userMediaLoaded: this.userMediaLoaded,
|
|
6437
|
+
blocking: this.blocking
|
|
6496
6438
|
});
|
|
6497
|
-
this.
|
|
6439
|
+
this.options.logger.debug(debugLine);
|
|
6440
|
+
const canReconnect = this.canReconnectAfterStreamClose();
|
|
6441
|
+
this.connected = false;
|
|
6442
|
+
this.stopPings();
|
|
6443
|
+
if (this.shouldIgnoreStreamClose()) return;
|
|
6444
|
+
if (this.mediaTransmissionStarted) this.failInterruptedRecording();
|
|
6445
|
+
else if (canReconnect) {
|
|
6446
|
+
this.pendingWrites.clear();
|
|
6447
|
+
this.streamBackpressured = false;
|
|
6448
|
+
this.resumeAfterDrain = false;
|
|
6449
|
+
this.reconnecting = true;
|
|
6450
|
+
this.initSocket();
|
|
6451
|
+
} else if (this.shouldReportStreamClose()) window.setTimeout(()=>{
|
|
6452
|
+
this.handleConnectionFailure({
|
|
6453
|
+
url2Connect,
|
|
6454
|
+
cause: "closed"
|
|
6455
|
+
});
|
|
6456
|
+
}, 0);
|
|
6498
6457
|
}
|
|
6499
|
-
|
|
6500
|
-
this.
|
|
6501
|
-
this.audioRecorder?.record(audioCallback);
|
|
6458
|
+
shouldIgnoreStreamClose() {
|
|
6459
|
+
return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
|
|
6502
6460
|
}
|
|
6503
|
-
|
|
6504
|
-
this.
|
|
6505
|
-
this.
|
|
6506
|
-
|
|
6507
|
-
|
|
6461
|
+
handleStreamError(url2Connect, err) {
|
|
6462
|
+
if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
|
|
6463
|
+
else this.lastSocketError = serializeError(err);
|
|
6464
|
+
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
|
|
6465
|
+
error: serializeError(err)
|
|
6508
6466
|
});
|
|
6509
|
-
|
|
6510
|
-
this.
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6467
|
+
this.options.logger.debug(debugLine);
|
|
6468
|
+
if (this.validate()) {
|
|
6469
|
+
this.connected = false;
|
|
6470
|
+
this.stopPings();
|
|
6471
|
+
this.discardSocket();
|
|
6472
|
+
return;
|
|
6473
|
+
}
|
|
6474
|
+
if (!this.connected) return void this.handleConnectionFailure({
|
|
6475
|
+
url2Connect,
|
|
6476
|
+
cause: "error"
|
|
6477
|
+
});
|
|
6478
|
+
const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
|
|
6479
|
+
cause: this.lastSocketError ?? err
|
|
6480
|
+
});
|
|
6481
|
+
if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
|
|
6482
|
+
this.emit("ERROR", {
|
|
6483
|
+
err: error_createError({
|
|
6484
|
+
message: "WebSocket stream error",
|
|
6485
|
+
explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
|
|
6486
|
+
options: this.options,
|
|
6487
|
+
exc: streamError
|
|
6488
|
+
})
|
|
6489
|
+
});
|
|
6490
|
+
}
|
|
6491
|
+
sendPings() {
|
|
6492
|
+
this.stopPings();
|
|
6493
|
+
this.pingInterval = window.setInterval(()=>{
|
|
6494
|
+
this.options.logger.debug("Recorder: pinging...");
|
|
6495
|
+
this.writeStream(Buffer.from(""));
|
|
6496
|
+
}, this.options.timeouts.pingInterval);
|
|
6497
|
+
}
|
|
6498
|
+
stopPings() {
|
|
6499
|
+
if (void 0 === this.pingInterval) return;
|
|
6500
|
+
this.options.logger.debug("Recorder: stopping pings...");
|
|
6501
|
+
window.clearInterval(this.pingInterval);
|
|
6502
|
+
this.pingInterval = void 0;
|
|
6503
|
+
}
|
|
6504
|
+
clearConnectionTimeout() {
|
|
6505
|
+
if (void 0 === this.connectionTimeout) return;
|
|
6506
|
+
this.options.logger.debug("Recorder: clearConnectionTimeout()");
|
|
6507
|
+
window.clearTimeout(this.connectionTimeout);
|
|
6508
|
+
this.connectionTimeout = void 0;
|
|
6509
|
+
}
|
|
6510
|
+
clearReconnectTimeout() {
|
|
6511
|
+
if (void 0 === this.reconnectTimeout) return;
|
|
6512
|
+
this.options.logger.debug("Recorder: clearReconnectTimeout()");
|
|
6513
|
+
window.clearTimeout(this.reconnectTimeout);
|
|
6514
|
+
this.reconnectTimeout = void 0;
|
|
6515
|
+
}
|
|
6516
|
+
handleConnectionFailure(params) {
|
|
6517
|
+
if (this.retryConnection(params)) return;
|
|
6518
|
+
this.failConnection(params);
|
|
6519
|
+
}
|
|
6520
|
+
retryConnection(params) {
|
|
6521
|
+
if (!this.canRetryConnection()) return false;
|
|
6522
|
+
if (void 0 !== this.reconnectTimeout) return true;
|
|
6523
|
+
const delayMs = Math.min(1000 * 2 ** this.reconnectAttempts, 10000);
|
|
6524
|
+
this.reconnectAttempts++;
|
|
6525
|
+
this.connecting = false;
|
|
6526
|
+
this.clearConnectionTimeout();
|
|
6527
|
+
this.discardSocket();
|
|
6528
|
+
this.options.logger.debug(`Recorder: retrying WebSocket connection in ${delayMs}ms (cause ${params.cause}).`);
|
|
6529
|
+
this.reconnectTimeout = window.setTimeout(()=>{
|
|
6530
|
+
this.reconnectTimeout = void 0;
|
|
6531
|
+
if (this.shouldContinueReconnect()) this.initSocket();
|
|
6532
|
+
}, delayMs);
|
|
6533
|
+
return true;
|
|
6534
|
+
}
|
|
6535
|
+
canRetryConnection() {
|
|
6536
|
+
return Boolean(this.reconnecting && this.userMediaLoaded && this.isOnline() && !this.unloaded && !this.blocking);
|
|
6537
|
+
}
|
|
6538
|
+
shouldContinueReconnect() {
|
|
6539
|
+
return this.reconnecting && !this.unloaded && !this.blocking;
|
|
6540
|
+
}
|
|
6541
|
+
isOnline() {
|
|
6542
|
+
return navigator.onLine;
|
|
6543
|
+
}
|
|
6544
|
+
failConnection(params) {
|
|
6545
|
+
if (this.shouldIgnoreConnectionFailure()) return;
|
|
6546
|
+
this.connectionFailed = true;
|
|
6547
|
+
this.connecting = false;
|
|
6548
|
+
this.reconnecting = false;
|
|
6549
|
+
this.reconnectAttempts = 0;
|
|
6550
|
+
this.clearReconnectTimeout();
|
|
6551
|
+
this.clearConnectionTimeout();
|
|
6552
|
+
const { url2Connect, cause } = params;
|
|
6553
|
+
const online = this.isOnline();
|
|
6554
|
+
const elapsedMs = this.connectingStartedAt ? Date.now() - this.connectingStartedAt : void 0;
|
|
6555
|
+
const closeEvent = this.lastCloseEvent;
|
|
6556
|
+
const socketError = this.lastSocketError;
|
|
6557
|
+
const contents = {
|
|
6558
|
+
cause,
|
|
6559
|
+
closeCode: closeEvent?.code,
|
|
6560
|
+
closeReason: closeEvent?.reason,
|
|
6561
|
+
elapsedMs,
|
|
6562
|
+
online,
|
|
6563
|
+
secureContext: globalThis.isSecureContext,
|
|
6564
|
+
socketReadyState: this.stream?.socket.readyState,
|
|
6565
|
+
timeoutMs: this.options.timeouts.connection,
|
|
6566
|
+
url: url2Connect,
|
|
6567
|
+
unloaded: this.unloaded,
|
|
6568
|
+
userMediaLoaded: this.userMediaLoaded,
|
|
6569
|
+
userMediaLoading: this.userMediaLoading,
|
|
6570
|
+
wasClean: closeEvent?.wasClean,
|
|
6571
|
+
socketError,
|
|
6572
|
+
blocking: this.blocking,
|
|
6573
|
+
visibilityState: document.visibilityState
|
|
6526
6574
|
};
|
|
6527
|
-
const
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6575
|
+
const debugLine = string_summarize("Recorder: failConnection() diagnostic", contents);
|
|
6576
|
+
this.options.logger.debug(debugLine);
|
|
6577
|
+
let explanation;
|
|
6578
|
+
if (online) if ("timeout" === cause) explanation = `The server at ${url2Connect} did not respond within ${this.options.timeouts.connection}ms, even though your device is online. This usually points to a firewall or proxy silently dropping the connection. Please try a different network. If the problem persists, contact us.`;
|
|
6579
|
+
else if ("error" === cause) explanation = `An error occurred while connecting to ${url2Connect}. Please check your internet connection and try again. If the problem persists, contact us.`;
|
|
6580
|
+
else {
|
|
6581
|
+
const closeSuffix = closeEvent ? ` (code ${closeEvent.code})` : "";
|
|
6582
|
+
explanation = `Connection to ${url2Connect} is closed${closeSuffix}. Please check your internet connection and try again. If the problem persists, contact us.`;
|
|
6583
|
+
}
|
|
6584
|
+
else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
|
|
6585
|
+
this.discardSocket();
|
|
6586
|
+
const err = error_createError({
|
|
6587
|
+
message: "Unable to connect to the server",
|
|
6588
|
+
explanation,
|
|
6589
|
+
options: this.options,
|
|
6590
|
+
cause: contents,
|
|
6591
|
+
exc: socketError ? new Error("WebSocket connection error", {
|
|
6592
|
+
cause: socketError
|
|
6593
|
+
}) : void 0
|
|
6594
|
+
});
|
|
6595
|
+
this.emit("ERROR", {
|
|
6596
|
+
err
|
|
6597
|
+
});
|
|
6598
|
+
}
|
|
6599
|
+
shouldIgnoreConnectionFailure() {
|
|
6600
|
+
return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
|
|
6601
|
+
}
|
|
6602
|
+
initSocket(cb) {
|
|
6603
|
+
if (this.connected) {
|
|
6604
|
+
this.options.logger.debug("Recorder: already **connected**. Not going to initialize a new WebSocket connection.");
|
|
6605
|
+
cb?.();
|
|
6606
|
+
return;
|
|
6607
|
+
}
|
|
6608
|
+
if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
|
|
6609
|
+
this.clearReconnectTimeout();
|
|
6610
|
+
if (util_isAutomatedUserAgent()) {
|
|
6611
|
+
this.connecting = false;
|
|
6612
|
+
this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
|
|
6613
|
+
return;
|
|
6614
|
+
}
|
|
6615
|
+
this.connecting = true;
|
|
6616
|
+
this.connectionFailed = false;
|
|
6617
|
+
this.connectingStartedAt = Date.now();
|
|
6618
|
+
this.lastCloseEvent = void 0;
|
|
6619
|
+
this.lastSocketError = void 0;
|
|
6620
|
+
this.emit("CONNECTING");
|
|
6621
|
+
let url2Connect;
|
|
6622
|
+
try {
|
|
6623
|
+
const socketUrlObj = new URL(this.options.socketUrl);
|
|
6624
|
+
socketUrlObj.searchParams.set(constants.WHITELIST_KEY_LABEL, this.options.whitelistKey);
|
|
6625
|
+
url2Connect = socketUrlObj.toString();
|
|
6626
|
+
} catch (exc) {
|
|
6627
|
+
this.connecting = this.connected = false;
|
|
6628
|
+
const err = error_createError({
|
|
6629
|
+
message: "Invalid WebSocket URL",
|
|
6630
|
+
explanation: `The configured socketUrl "${this.options.socketUrl}" is not a valid URL. Please check your videomail-client configuration.`,
|
|
6631
|
+
options: this.options,
|
|
6632
|
+
exc
|
|
6633
|
+
});
|
|
6634
|
+
this.emit("ERROR", {
|
|
6635
|
+
err
|
|
6636
|
+
});
|
|
6637
|
+
return;
|
|
6638
|
+
}
|
|
6639
|
+
this.options.logger.debug(`Recorder: initializing web socket stream to ${url2Connect}`);
|
|
6640
|
+
let nativeSocket;
|
|
6641
|
+
try {
|
|
6642
|
+
nativeSocket = new WebSocket(url2Connect);
|
|
6643
|
+
nativeSocket.addEventListener("close", (event)=>{
|
|
6644
|
+
this.lastCloseEvent = {
|
|
6645
|
+
code: event.code,
|
|
6646
|
+
reason: event.reason,
|
|
6647
|
+
wasClean: event.wasClean
|
|
6648
|
+
};
|
|
6649
|
+
});
|
|
6650
|
+
nativeSocket.addEventListener("error", (event)=>{
|
|
6651
|
+
this.lastSocketError = error_getEventDetails(event);
|
|
6652
|
+
});
|
|
6653
|
+
} catch (exc) {
|
|
6654
|
+
this.connecting = this.connected = false;
|
|
6655
|
+
const diagnostic = error_getWebSocketDiagnostic(url2Connect);
|
|
6656
|
+
const message = diagnostic.looksAutomated ? "Automated crawler: WebSocket not supported in this environment" : `Failed to construct WebSocket to ${url2Connect}`;
|
|
6657
|
+
const explanation = diagnostic.looksAutomated ? "Headless crawlers cannot use Videomail's WebSocket. No action needed." : `Please check your connection and try again. If the problem persists, contact us. Diagnostic: ${diagnostic.text}`;
|
|
6658
|
+
const err = error_createError({
|
|
6659
|
+
message,
|
|
6660
|
+
explanation,
|
|
6661
|
+
options: this.options,
|
|
6662
|
+
exc
|
|
6663
|
+
});
|
|
6664
|
+
this.emit("ERROR", {
|
|
6665
|
+
err
|
|
6666
|
+
});
|
|
6667
|
+
return;
|
|
6668
|
+
}
|
|
6669
|
+
try {
|
|
6670
|
+
this.stream = websocket_stream(nativeSocket);
|
|
6671
|
+
} catch (exc) {
|
|
6672
|
+
this.connecting = this.connected = false;
|
|
6673
|
+
const err = error_createError({
|
|
6674
|
+
message: `Failed to create a stream to ${url2Connect}`,
|
|
6675
|
+
explanation: "Please check your connection and try again. If the problem persists, contact us.",
|
|
6676
|
+
options: this.options,
|
|
6677
|
+
exc
|
|
6678
|
+
});
|
|
6679
|
+
this.emit("ERROR", {
|
|
6680
|
+
err
|
|
6681
|
+
});
|
|
6682
|
+
}
|
|
6683
|
+
const stream = this.stream;
|
|
6684
|
+
if (stream) {
|
|
6685
|
+
const connectionTimeoutMs = this.options.timeouts.connection;
|
|
6686
|
+
this.connectionTimeout = window.setTimeout(()=>{
|
|
6687
|
+
if (this.isOnline() && this.isUserMediaLoaded()) {
|
|
6688
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
|
|
6689
|
+
this.reconnecting = true;
|
|
6690
|
+
this.handleConnectionFailure({
|
|
6691
|
+
url2Connect,
|
|
6692
|
+
cause: "timeout"
|
|
6549
6693
|
});
|
|
6694
|
+
} else this.handleConnectionFailure({
|
|
6695
|
+
url2Connect,
|
|
6696
|
+
cause: "timeout"
|
|
6697
|
+
});
|
|
6698
|
+
}, connectionTimeoutMs);
|
|
6699
|
+
stream.on("close", ()=>{
|
|
6700
|
+
this.handleStreamClose(url2Connect);
|
|
6701
|
+
});
|
|
6702
|
+
stream.on("connect", (args)=>{
|
|
6703
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *connect* event emitted with args: ${string_pretty(args)}`);
|
|
6704
|
+
this.clearConnectionTimeout();
|
|
6705
|
+
const isClosing = this.stream?.socket.readyState === WebSocket.CLOSING;
|
|
6706
|
+
if (!this.connected && !isClosing && !this.unloaded) {
|
|
6707
|
+
this.connected = true;
|
|
6708
|
+
this.connecting = this.unloaded = false;
|
|
6709
|
+
this.reconnecting = false;
|
|
6710
|
+
this.reconnectAttempts = 0;
|
|
6711
|
+
this.clearReconnectTimeout();
|
|
6712
|
+
this.emit("CONNECTED");
|
|
6713
|
+
cb?.();
|
|
6550
6714
|
}
|
|
6551
|
-
}
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
else this.on("SENDING_FIRST_FRAME", ()=>{
|
|
6565
|
-
this.audioRecord(audioCallback);
|
|
6715
|
+
});
|
|
6716
|
+
stream.on("data", (data)=>{
|
|
6717
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *data* event emitted`);
|
|
6718
|
+
try {
|
|
6719
|
+
const command = JSON.parse(data.toString());
|
|
6720
|
+
this.executeCommand(command);
|
|
6721
|
+
} catch (exc) {
|
|
6722
|
+
this.options.logger.error(`Failed to parse command: ${exc}`);
|
|
6723
|
+
const err = error_createError({
|
|
6724
|
+
message: "Invalid server command",
|
|
6725
|
+
explanation: `Contact us. The invalid command was: ${data.toString()}.`,
|
|
6726
|
+
options: this.options,
|
|
6727
|
+
exc
|
|
6566
6728
|
});
|
|
6567
|
-
this.
|
|
6568
|
-
|
|
6729
|
+
this.emit("ERROR", {
|
|
6730
|
+
err
|
|
6731
|
+
});
|
|
6732
|
+
}
|
|
6733
|
+
});
|
|
6734
|
+
stream.on("error", (err)=>{
|
|
6735
|
+
this.handleStreamError(url2Connect, err);
|
|
6736
|
+
});
|
|
6737
|
+
stream.on("drain", ()=>{
|
|
6738
|
+
this.handleStreamDrain(stream);
|
|
6739
|
+
});
|
|
6740
|
+
stream.on("preend", (args)=>{
|
|
6741
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *preend* event emitted with args: ${string_pretty(args)}`);
|
|
6742
|
+
});
|
|
6743
|
+
stream.on("end", ()=>{
|
|
6744
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *end* event emitted`);
|
|
6745
|
+
});
|
|
6746
|
+
stream.on("pipe", (src)=>{
|
|
6747
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *pipe* event emitted with src: ${string_pretty(src)}`);
|
|
6748
|
+
});
|
|
6749
|
+
stream.on("unpipe", (src)=>{
|
|
6750
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *unpipe* event emitted with src: ${string_pretty(src)}`);
|
|
6751
|
+
});
|
|
6752
|
+
stream.on("resume", (args)=>{
|
|
6753
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *resume* event emitted with args: ${string_pretty(args)}`);
|
|
6754
|
+
});
|
|
6755
|
+
stream.on("uncork", (args)=>{
|
|
6756
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *uncork* event emitted with args: ${string_pretty(args)}`);
|
|
6757
|
+
});
|
|
6758
|
+
stream.on("readable", (args)=>{
|
|
6759
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *readable* event emitted with args: ${string_pretty(args)}`);
|
|
6760
|
+
});
|
|
6761
|
+
stream.on("prefinish", (args)=>{
|
|
6762
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *prefinish* event emitted with args: ${string_pretty(args)}`);
|
|
6763
|
+
});
|
|
6764
|
+
stream.on("finish", (args)=>{
|
|
6765
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *finish* event emitted with args: ${string_pretty(args)}`);
|
|
6766
|
+
});
|
|
6767
|
+
}
|
|
6768
|
+
}
|
|
6769
|
+
writeCommand(command, args, cb) {
|
|
6770
|
+
if (this.connected) {
|
|
6771
|
+
if (this.stream) {
|
|
6772
|
+
if (args) this.options.logger.debug(`$ ${command} with ${string_pretty(args)}`);
|
|
6773
|
+
else this.options.logger.debug(`$ ${command}`);
|
|
6774
|
+
const commandObj = {
|
|
6775
|
+
command,
|
|
6776
|
+
args
|
|
6569
6777
|
};
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
preserveRecordingState: Boolean(params?.recovering)
|
|
6576
|
-
});
|
|
6577
|
-
endedEarlyCallback(exc);
|
|
6578
|
-
}
|
|
6579
|
-
else {
|
|
6580
|
-
this.clearInitializationListeners?.();
|
|
6581
|
-
videoCallback();
|
|
6778
|
+
this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
|
|
6779
|
+
onFlushedCallback: ()=>{
|
|
6780
|
+
queueMicrotask(cb);
|
|
6781
|
+
}
|
|
6782
|
+
} : void 0);
|
|
6582
6783
|
}
|
|
6583
|
-
}
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
this.
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6784
|
+
} else {
|
|
6785
|
+
this.options.logger.debug(`Reconnecting for the command ${command} …`);
|
|
6786
|
+
this.initSocket(()=>{
|
|
6787
|
+
this.writeCommand(command, args, cb);
|
|
6788
|
+
});
|
|
6789
|
+
}
|
|
6790
|
+
}
|
|
6791
|
+
constructor(options){
|
|
6792
|
+
super(options), connection_define_property(this, "reconnectTimeout", void 0), connection_define_property(this, "connectionTimeout", void 0), connection_define_property(this, "userMediaLoaded", void 0), connection_define_property(this, "userMediaLoading", false), connection_define_property(this, "unloaded", void 0), connection_define_property(this, "connectionFailed", false), connection_define_property(this, "reconnectAttempts", 0), connection_define_property(this, "blocking", false), connection_define_property(this, "connectingStartedAt", void 0), connection_define_property(this, "lastCloseEvent", void 0), connection_define_property(this, "lastSocketError", void 0), connection_define_property(this, "pingInterval", void 0);
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
const connection = RecorderConnection;
|
|
6796
|
+
function createUserMediaConstraints({ options, facingMode, limitedWidth, userMediaParams }) {
|
|
6797
|
+
const video = {
|
|
6798
|
+
frameRate: {
|
|
6799
|
+
ideal: options.video.fps
|
|
6800
|
+
}
|
|
6801
|
+
};
|
|
6802
|
+
if (userMediaParams?.switchingFacingMode || userMediaParams?.recovering) video.facingMode = userMediaParams.switchingFacingMode || facingMode;
|
|
6803
|
+
if (options.video.width) video.width = {
|
|
6804
|
+
ideal: options.video.width
|
|
6805
|
+
};
|
|
6806
|
+
else if (limitedWidth?.value) video.width = {
|
|
6807
|
+
ideal: limitedWidth.value
|
|
6808
|
+
};
|
|
6809
|
+
if (options.video.height) video.height = {
|
|
6810
|
+
ideal: options.video.height
|
|
6811
|
+
};
|
|
6812
|
+
return {
|
|
6813
|
+
audio: isAudioEnabled(options),
|
|
6814
|
+
video
|
|
6815
|
+
};
|
|
6816
|
+
}
|
|
6817
|
+
const mediaConstraints = createUserMediaConstraints;
|
|
6818
|
+
function media_define_property(obj, key, value) {
|
|
6819
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
6820
|
+
value: value,
|
|
6821
|
+
enumerable: true,
|
|
6822
|
+
configurable: true,
|
|
6823
|
+
writable: true
|
|
6824
|
+
});
|
|
6825
|
+
else obj[key] = value;
|
|
6826
|
+
return obj;
|
|
6827
|
+
}
|
|
6828
|
+
class RecorderMedia extends connection {
|
|
6829
|
+
show() {
|
|
6830
|
+
html_showElement(this.recorderElement);
|
|
6831
|
+
}
|
|
6832
|
+
clearRetryTimeout() {
|
|
6833
|
+
if (void 0 === this.retryTimeout) return;
|
|
6834
|
+
this.options.logger.debug("Recorder: clearRetryTimeout()");
|
|
6835
|
+
window.clearTimeout(this.retryTimeout);
|
|
6836
|
+
this.retryTimeout = void 0;
|
|
6837
|
+
}
|
|
6838
|
+
showUserMedia() {
|
|
6839
|
+
if (!this.connected) return false;
|
|
6840
|
+
const hidden = this.isHidden();
|
|
6841
|
+
if (!hidden) return true;
|
|
6842
|
+
const notifying = this.isNotifying();
|
|
6843
|
+
if (notifying) return true;
|
|
6844
|
+
return this.blocking;
|
|
6845
|
+
}
|
|
6846
|
+
userMediaErrorCallback(err, usedConstraints) {
|
|
6847
|
+
if (this.cameraRecovery) return void this.failCameraRecovery(err);
|
|
6848
|
+
if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${string_pretty(err)}`);
|
|
6849
|
+
this.userMediaLoading = false;
|
|
6850
|
+
this.clearUserMediaTimeout();
|
|
6851
|
+
const characteristics = this.userMedia?.getCharacteristics();
|
|
6852
|
+
this.options.logger.debug(`Recorder: userMediaErrorCallback(), name: ${err.name}, message: ${err.message} and Webcam characteristics: ${characteristics ? string_pretty(characteristics) : "none"}`);
|
|
6853
|
+
const errorListeners = this.getListeners("ERROR");
|
|
6854
|
+
if (errorListeners?.length) if (err.name !== error_VideomailError.MEDIA_DEVICE_NOT_SUPPORTED) {
|
|
6855
|
+
const videomailError = error_createError({
|
|
6856
|
+
err,
|
|
6857
|
+
options: this.options,
|
|
6858
|
+
usedConstraints
|
|
6859
|
+
});
|
|
6860
|
+
this.emit("ERROR", {
|
|
6861
|
+
err: videomailError
|
|
6862
|
+
});
|
|
6863
|
+
} else {
|
|
6864
|
+
this.options.logger.debug(`Recorder: ignore user media error ${string_pretty(err)}`);
|
|
6865
|
+
this.retryTimeout = window.setTimeout(()=>{
|
|
6866
|
+
this.retryTimeout = void 0;
|
|
6867
|
+
if (!this.unloaded) this.loadUserMedia();
|
|
6868
|
+
}, this.options.timeouts.userMedia);
|
|
6869
|
+
}
|
|
6870
|
+
else {
|
|
6871
|
+
this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${string_pretty(err)}`);
|
|
6872
|
+
throw error_createError({
|
|
6873
|
+
err,
|
|
6874
|
+
message: "Unable to process this error since there are no error listeners anymore.",
|
|
6875
|
+
options: this.options
|
|
6876
|
+
});
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
getUserMediaCallback(localStream, params, requestId = this.mediaRequestId) {
|
|
6880
|
+
if (!this.userMedia) {
|
|
6881
|
+
localStream.getTracks().forEach((track)=>{
|
|
6882
|
+
track.stop();
|
|
6883
|
+
});
|
|
6884
|
+
throw error_createError({
|
|
6885
|
+
message: "No user media is defined",
|
|
6886
|
+
options: this.options
|
|
6887
|
+
});
|
|
6888
|
+
}
|
|
6889
|
+
this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? string_pretty(params) : ""})`);
|
|
6890
|
+
if (this.showUserMedia()) try {
|
|
6891
|
+
this.userMedia.init(localStream, ()=>{
|
|
6892
|
+
if (!this.unloaded && requestId === this.mediaRequestId) this.onUserMediaReady(params);
|
|
6893
|
+
}, this.onAudioSample.bind(this), (err)=>{
|
|
6894
|
+
if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(err, {
|
|
6895
|
+
audio: isAudioEnabled(this.options)
|
|
6593
6896
|
});
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
options: this.options
|
|
6599
|
-
}));
|
|
6600
|
-
else {
|
|
6601
|
-
this.onPlayReached = true;
|
|
6602
|
-
fireCallbacks();
|
|
6603
|
-
}
|
|
6604
|
-
} catch (exc) {
|
|
6605
|
-
unloadAllEventListeners();
|
|
6606
|
-
endedEarlyCallback(exc);
|
|
6607
|
-
}
|
|
6608
|
-
};
|
|
6609
|
-
const onLoadedMetaData = ()=>{
|
|
6610
|
-
this.logEvent("loadedmetadata", {
|
|
6611
|
-
readyState: this.rawVisualUserMedia?.readyState,
|
|
6612
|
-
paused: this.rawVisualUserMedia?.paused,
|
|
6613
|
-
width: this.rawVisualUserMedia?.width,
|
|
6614
|
-
height: this.rawVisualUserMedia?.height,
|
|
6615
|
-
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
6616
|
-
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
6897
|
+
}, params);
|
|
6898
|
+
} catch (exc) {
|
|
6899
|
+
localStream.getTracks().forEach((track)=>{
|
|
6900
|
+
track.stop();
|
|
6617
6901
|
});
|
|
6618
|
-
this.
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
this.
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6902
|
+
this.userMediaLoading = false;
|
|
6903
|
+
this.clearUserMediaTimeout();
|
|
6904
|
+
this.userMediaErrorCallback(exc, {
|
|
6905
|
+
audio: isAudioEnabled(this.options)
|
|
6906
|
+
});
|
|
6907
|
+
}
|
|
6908
|
+
else {
|
|
6909
|
+
localStream.getTracks().forEach((track)=>{
|
|
6910
|
+
track.stop();
|
|
6911
|
+
});
|
|
6912
|
+
this.userMediaLoading = false;
|
|
6913
|
+
this.clearUserMediaTimeout();
|
|
6914
|
+
this.cancelCameraRecovery();
|
|
6915
|
+
}
|
|
6916
|
+
}
|
|
6917
|
+
loadGenuineUserMedia(params) {
|
|
6918
|
+
this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? string_pretty(params) : ""})`);
|
|
6919
|
+
this.emit("ASKING_WEBCAM_PERMISSION");
|
|
6920
|
+
this.userMediaLoading = true;
|
|
6921
|
+
const requestId = ++this.mediaRequestId;
|
|
6922
|
+
const limitedWidth = this.options.video.width ? void 0 : this.visuals.limitWidth();
|
|
6923
|
+
const constraints = mediaConstraints({
|
|
6924
|
+
facingMode: this.facingMode,
|
|
6925
|
+
limitedWidth,
|
|
6926
|
+
options: this.options,
|
|
6927
|
+
userMediaParams: params
|
|
6928
|
+
});
|
|
6929
|
+
this.options.logger.debug(string_summarize("Recorder: our webcam constraints are:", constraints));
|
|
6930
|
+
this.options.logger.debug(string_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
|
|
6931
|
+
const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
|
|
6932
|
+
streamPromise.then((localStream)=>{
|
|
6933
|
+
if (this.unloaded || requestId !== this.mediaRequestId) return void localStream.getTracks().forEach((track)=>{
|
|
6934
|
+
track.stop();
|
|
6935
|
+
});
|
|
6936
|
+
this.getUserMediaCallback(localStream, params, requestId);
|
|
6937
|
+
}).catch((reason)=>{
|
|
6938
|
+
if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(reason, constraints);
|
|
6939
|
+
});
|
|
6940
|
+
}
|
|
6941
|
+
loadUserMedia(params) {
|
|
6942
|
+
if (this.userMediaLoaded) {
|
|
6943
|
+
this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already loaded");
|
|
6944
|
+
this.onUserMediaReady(params);
|
|
6945
|
+
return;
|
|
6946
|
+
} else if (this.userMediaLoading) return void this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already asking for permission");
|
|
6947
|
+
this.options.logger.debug(`Recorder: loadUserMedia(${params ? string_pretty(params) : ""})`);
|
|
6948
|
+
this.emit("LOADING_USER_MEDIA");
|
|
6625
6949
|
try {
|
|
6626
|
-
|
|
6627
|
-
this.
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6950
|
+
if (!navigator.mediaDevices) throw new Error("No media devices are available.");
|
|
6951
|
+
this.userMediaTimeout = window.setTimeout(()=>{
|
|
6952
|
+
if (!this.userMediaLoaded) {
|
|
6953
|
+
this.userMediaLoading = false;
|
|
6954
|
+
const err = util_getBrowser(this.options).getNoAccessIssue();
|
|
6955
|
+
this.userMediaErrorCallback(err, {
|
|
6956
|
+
audio: isAudioEnabled(this.options)
|
|
6957
|
+
});
|
|
6958
|
+
}
|
|
6959
|
+
}, this.options.timeouts.userMedia);
|
|
6960
|
+
this.userMediaLoading = true;
|
|
6961
|
+
this.loadGenuineUserMedia(params);
|
|
6962
|
+
} catch (exc) {
|
|
6963
|
+
this.clearUserMediaTimeout();
|
|
6964
|
+
this.options.logger.debug("Recorder: failed to load genuine user media");
|
|
6965
|
+
this.userMediaLoading = false;
|
|
6966
|
+
if (this.cameraRecovery) return void this.failCameraRecovery(exc);
|
|
6967
|
+
const errorListeners = this.getListeners("ERROR");
|
|
6968
|
+
if (errorListeners?.length) this.emit("ERROR", {
|
|
6969
|
+
exc
|
|
6632
6970
|
});
|
|
6633
6971
|
else {
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
description = description.concat(` with enabled=${videoTrack.enabled}, muted=${videoTrack.muted}, readyState=${videoTrack.readyState}`);
|
|
6637
|
-
this.options.logger.debug(`UserMedia: ${videoTrack.kind} detected. ${description}`);
|
|
6972
|
+
this.options.logger.debug("Recorder: no error listeners attached but throwing exception further");
|
|
6973
|
+
throw exc;
|
|
6638
6974
|
}
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
isNotifying() {
|
|
6978
|
+
return this.visuals.isNotifying();
|
|
6979
|
+
}
|
|
6980
|
+
isHidden() {
|
|
6981
|
+
return !this.recorderElement || html_isHidden(this.recorderElement);
|
|
6982
|
+
}
|
|
6983
|
+
clearUserMediaTimeout() {
|
|
6984
|
+
if (void 0 !== this.userMediaTimeout) {
|
|
6985
|
+
this.options.logger.debug("Recorder: clearUserMediaTimeout()");
|
|
6986
|
+
window.clearTimeout(this.userMediaTimeout);
|
|
6987
|
+
this.userMediaTimeout = void 0;
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6990
|
+
clearStopTimeout() {
|
|
6991
|
+
if (void 0 === this.stopTimeout) return;
|
|
6992
|
+
window.clearTimeout(this.stopTimeout);
|
|
6993
|
+
this.stopTimeout = void 0;
|
|
6994
|
+
}
|
|
6995
|
+
isReady() {
|
|
6996
|
+
return this.userMedia?.isReady();
|
|
6997
|
+
}
|
|
6998
|
+
finishCameraRecovery(restored) {
|
|
6999
|
+
this.clearUserMediaTimeout();
|
|
7000
|
+
const resolve = this.resolveCameraRecovery;
|
|
7001
|
+
const resumeRequested = restored && this.resumeAfterCameraRecovery;
|
|
7002
|
+
this.cameraRecovery = void 0;
|
|
7003
|
+
this.resolveCameraRecovery = void 0;
|
|
7004
|
+
this.resumeAfterCameraRecovery = false;
|
|
7005
|
+
resolve?.(restored);
|
|
7006
|
+
if (resumeRequested && this.isPaused()) this.resume();
|
|
7007
|
+
}
|
|
7008
|
+
cancelCameraRecovery() {
|
|
7009
|
+
if (!this.cameraRecovery) return;
|
|
7010
|
+
this.mediaRequestId++;
|
|
7011
|
+
this.userMediaLoading = false;
|
|
7012
|
+
this.userMediaLoaded = false;
|
|
7013
|
+
this.clearUserMediaTimeout();
|
|
7014
|
+
this.userMedia?.stop(void 0, {
|
|
7015
|
+
preserveRecordingState: true
|
|
7016
|
+
});
|
|
7017
|
+
this.finishCameraRecovery(false);
|
|
7018
|
+
}
|
|
7019
|
+
failCameraRecovery(exc) {
|
|
7020
|
+
this.cancelCameraRecovery();
|
|
7021
|
+
const err = error_createError({
|
|
7022
|
+
message: "Camera recovery failed",
|
|
7023
|
+
explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
|
|
7024
|
+
exc,
|
|
7025
|
+
options: this.options
|
|
7026
|
+
});
|
|
7027
|
+
this.emit("CAMERA_RECOVERY_FAILED", {
|
|
7028
|
+
err
|
|
7029
|
+
});
|
|
7030
|
+
}
|
|
7031
|
+
recoverCamera() {
|
|
7032
|
+
if (this.cameraRecovery) return this.cameraRecovery;
|
|
7033
|
+
if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
|
|
7034
|
+
const liveStream = this.userMedia.hasLiveStream();
|
|
7035
|
+
if (liveStream && this.isRecording()) return Promise.resolve(true);
|
|
7036
|
+
if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
|
|
7037
|
+
const recovery = new Promise((resolve)=>{
|
|
7038
|
+
this.resolveCameraRecovery = resolve;
|
|
7039
|
+
});
|
|
7040
|
+
this.cameraRecovery = recovery;
|
|
7041
|
+
this.emit("CAMERA_RECOVERING");
|
|
7042
|
+
if (liveStream) {
|
|
7043
|
+
this.userMediaTimeout = window.setTimeout(()=>{
|
|
7044
|
+
this.failCameraRecovery(new Error("Camera playback recovery timed out."));
|
|
7045
|
+
}, this.options.timeouts.userMedia);
|
|
7046
|
+
this.userMedia.restorePlayback().then(()=>{
|
|
7047
|
+
if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
|
|
7048
|
+
}).catch((exc)=>{
|
|
7049
|
+
if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
|
|
6643
7050
|
});
|
|
6644
|
-
|
|
6645
|
-
this.
|
|
6646
|
-
this.
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
7051
|
+
} else {
|
|
7052
|
+
this.userMediaLoaded = false;
|
|
7053
|
+
this.loadUserMedia({
|
|
7054
|
+
recovering: true
|
|
7055
|
+
});
|
|
7056
|
+
}
|
|
7057
|
+
return recovery;
|
|
7058
|
+
}
|
|
7059
|
+
switchFacingMode() {
|
|
7060
|
+
if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
|
|
7061
|
+
if ("user" === this.facingMode) this.facingMode = "environment";
|
|
7062
|
+
else if ("environment" === this.facingMode) this.facingMode = "user";
|
|
7063
|
+
else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
|
|
7064
|
+
this.loadGenuineUserMedia({
|
|
7065
|
+
switchingFacingMode: this.facingMode
|
|
7066
|
+
});
|
|
7067
|
+
}
|
|
7068
|
+
hide() {
|
|
7069
|
+
if (!this.isHidden()) {
|
|
7070
|
+
if (this.recorderElement) html_hideElement(this.recorderElement);
|
|
7071
|
+
this.clearUserMediaTimeout();
|
|
7072
|
+
this.clearRetryTimeout();
|
|
6654
7073
|
}
|
|
6655
7074
|
}
|
|
6656
|
-
|
|
6657
|
-
return this.
|
|
7075
|
+
isUnloaded() {
|
|
7076
|
+
return this.unloaded;
|
|
7077
|
+
}
|
|
7078
|
+
isUserMediaLoaded() {
|
|
7079
|
+
return this.userMediaLoaded;
|
|
6658
7080
|
}
|
|
6659
|
-
|
|
6660
|
-
|
|
7081
|
+
constructor(visuals, options){
|
|
7082
|
+
super(options), media_define_property(this, "visuals", void 0), media_define_property(this, "recorderElement", void 0), media_define_property(this, "userMedia", void 0), media_define_property(this, "userMediaTimeout", void 0), media_define_property(this, "retryTimeout", void 0), media_define_property(this, "stopTimeout", void 0), media_define_property(this, "mediaRequestId", 0), media_define_property(this, "cameraRecovery", void 0), media_define_property(this, "resolveCameraRecovery", void 0), media_define_property(this, "resumeAfterCameraRecovery", false), media_define_property(this, "submitting", false), media_define_property(this, "built", false), media_define_property(this, "key", void 0), media_define_property(this, "facingMode", void 0);
|
|
7083
|
+
this.visuals = visuals;
|
|
7084
|
+
this.facingMode = options.video.facingMode;
|
|
6661
7085
|
}
|
|
6662
|
-
|
|
7086
|
+
}
|
|
7087
|
+
const media = RecorderMedia;
|
|
7088
|
+
function calculateProgress(confirmedCount, totalCount) {
|
|
7089
|
+
return `${(confirmedCount / (totalCount || 1) * 100).toFixed(2)}%`;
|
|
7090
|
+
}
|
|
7091
|
+
function getConfirmedProgressNumber({ args, kind, options }) {
|
|
7092
|
+
const label = "frame" === kind ? "frame" : "audio sample";
|
|
7093
|
+
if (!args) throw error_createError({
|
|
7094
|
+
message: `Arguments are missing for updating the ${label} progress`,
|
|
7095
|
+
options
|
|
7096
|
+
});
|
|
7097
|
+
const confirmedNumber = args[kind];
|
|
7098
|
+
if (!confirmedNumber) throw error_createError({
|
|
7099
|
+
message: `The ${label} number is missing`,
|
|
7100
|
+
options
|
|
7101
|
+
});
|
|
7102
|
+
return confirmedNumber;
|
|
7103
|
+
}
|
|
7104
|
+
var capture_Buffer = __webpack_require__("dY").hp;
|
|
7105
|
+
function capture_define_property(obj, key, value) {
|
|
7106
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
7107
|
+
value: value,
|
|
7108
|
+
enumerable: true,
|
|
7109
|
+
configurable: true,
|
|
7110
|
+
writable: true
|
|
7111
|
+
});
|
|
7112
|
+
else obj[key] = value;
|
|
7113
|
+
return obj;
|
|
7114
|
+
}
|
|
7115
|
+
class RecorderCapture extends media {
|
|
7116
|
+
onAudioSample(audioSample) {
|
|
7117
|
+
if (this.recordingTransportFailed || this.cameraRecovery) return;
|
|
7118
|
+
const sampleRate = this.userMedia?.getAudioSampleRate();
|
|
7119
|
+
if (sampleRate && sampleRate > 0) this.recordingAudioSampleRate ??= sampleRate;
|
|
7120
|
+
this.samplesCount++;
|
|
7121
|
+
this.mediaTransmissionStarted = true;
|
|
7122
|
+
const audioBuffer = audioSample.toBuffer();
|
|
7123
|
+
this.writeStream(audioBuffer);
|
|
7124
|
+
}
|
|
7125
|
+
onUserMediaReady(params) {
|
|
6663
7126
|
try {
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
this.
|
|
6668
|
-
|
|
6669
|
-
const tracks = chosenStream?.getTracks();
|
|
6670
|
-
if (tracks) tracks.forEach((track)=>{
|
|
6671
|
-
track.stop();
|
|
6672
|
-
});
|
|
6673
|
-
this.setVisualStream();
|
|
6674
|
-
this.audioRecorder?.stop();
|
|
6675
|
-
this.audioRecorder = void 0;
|
|
7127
|
+
this.options.logger.debug(`Recorder: onUserMediaReady(${params ? string_pretty(params) : ""})`);
|
|
7128
|
+
const switchingFacingMode = params?.switchingFacingMode;
|
|
7129
|
+
if (params?.recovering) {
|
|
7130
|
+
if (!this.userMedia?.hasLiveStream()) return void this.failCameraRecovery(new Error("The restored camera has no live tracks."));
|
|
7131
|
+
if (void 0 !== this.recordingAudioSampleRate && this.userMedia.getAudioSampleRate() !== this.recordingAudioSampleRate) return void this.failCameraRecovery(new Error("The audio sample rate changed during recovery."));
|
|
6676
7132
|
}
|
|
6677
|
-
|
|
7133
|
+
this.userMediaLoading = this.blocking = this.unloaded = this.submitting = false;
|
|
7134
|
+
this.userMediaLoaded = true;
|
|
7135
|
+
this.clearUserMediaTimeout();
|
|
7136
|
+
if (!switchingFacingMode && !params?.recovering) this.loop = this.createLoop();
|
|
7137
|
+
this.show();
|
|
7138
|
+
if (params?.recordWhenReady) this.record();
|
|
7139
|
+
this.emit("USER_MEDIA_READY", {
|
|
7140
|
+
switchingFacingMode: params?.switchingFacingMode,
|
|
7141
|
+
recovering: params?.recovering,
|
|
7142
|
+
paused: this.isPaused(),
|
|
7143
|
+
recordWhenReady: params?.recordWhenReady
|
|
7144
|
+
});
|
|
7145
|
+
if (params?.recovering) this.finishCameraRecovery(true);
|
|
6678
7146
|
} catch (exc) {
|
|
6679
|
-
this.
|
|
7147
|
+
if (params?.recovering) this.failCameraRecovery(exc);
|
|
7148
|
+
else this.emit("ERROR", {
|
|
6680
7149
|
exc
|
|
6681
7150
|
});
|
|
6682
7151
|
}
|
|
6683
7152
|
}
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
const rawWidthDimension = this.getRawWidth(true);
|
|
6687
|
-
if (rawWidthDimension.value) canvas.width = rawWidthDimension.value;
|
|
6688
|
-
const rawHeight = this.getRawHeight(true);
|
|
6689
|
-
if (rawHeight) canvas.height = rawHeight;
|
|
6690
|
-
return canvas;
|
|
7153
|
+
calculateFrameProgress() {
|
|
7154
|
+
return calculateProgress(this.confirmedFrameNumber, this.framesCount);
|
|
6691
7155
|
}
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
return this.rawVisualUserMedia.videoHeight || this.rawVisualUserMedia.height;
|
|
7156
|
+
calculateSampleProgress() {
|
|
7157
|
+
return calculateProgress(this.confirmedSampleNumber, this.samplesCount);
|
|
6695
7158
|
}
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
7159
|
+
updateOverallProgress() {
|
|
7160
|
+
this.frameProgress = this.calculateFrameProgress();
|
|
7161
|
+
if (isAudioEnabled(this.options)) this.sampleProgress = this.calculateSampleProgress();
|
|
7162
|
+
this.emit("PROGRESS", {
|
|
7163
|
+
frameProgress: this.frameProgress,
|
|
7164
|
+
sampleProgress: this.sampleProgress
|
|
7165
|
+
});
|
|
6699
7166
|
}
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
7167
|
+
updateFrameProgress(args) {
|
|
7168
|
+
this.confirmedFrameNumber = getConfirmedProgressNumber({
|
|
7169
|
+
args,
|
|
7170
|
+
kind: "frame",
|
|
7171
|
+
options: this.options
|
|
7172
|
+
});
|
|
7173
|
+
this.frameProgress = this.calculateFrameProgress();
|
|
7174
|
+
this.updateOverallProgress();
|
|
6703
7175
|
}
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
rawWidth = widthDimension?.value;
|
|
6713
|
-
}
|
|
6714
|
-
widthDimension.value = rawWidth;
|
|
6715
|
-
return widthDimension;
|
|
7176
|
+
updateSampleProgress(args) {
|
|
7177
|
+
this.confirmedSampleNumber = getConfirmedProgressNumber({
|
|
7178
|
+
args,
|
|
7179
|
+
kind: "sample",
|
|
7180
|
+
options: this.options
|
|
7181
|
+
});
|
|
7182
|
+
this.sampleProgress = this.calculateSampleProgress();
|
|
7183
|
+
this.updateOverallProgress();
|
|
6716
7184
|
}
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
7185
|
+
preview(args) {
|
|
7186
|
+
if (!args) throw error_createError({
|
|
7187
|
+
message: "Preview arguments are missing.",
|
|
7188
|
+
options: this.options
|
|
7189
|
+
});
|
|
7190
|
+
const hasAudio = this.samplesCount > 0;
|
|
7191
|
+
this.mediaTransmissionStarted = false;
|
|
7192
|
+
this.recordingActive = false;
|
|
7193
|
+
this.recordingTransportFailed = false;
|
|
7194
|
+
this.confirmedFrameNumber = this.confirmedSampleNumber = this.samplesCount = this.framesCount = 0;
|
|
7195
|
+
this.sampleProgress = this.frameProgress = void 0;
|
|
7196
|
+
this.key = args.key;
|
|
7197
|
+
if (args.mp4) this.replay.setMp4Source(`${args.mp4 + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.mp4`, true);
|
|
7198
|
+
if (args.webm) this.replay.setWebMSource(`${args.webm + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.webm`, true);
|
|
7199
|
+
this.hide();
|
|
7200
|
+
const widthDimension = this.getRecorderWidth(true);
|
|
7201
|
+
const heightDimension = this.getRecorderHeight(true);
|
|
7202
|
+
const duration = args.duration ?? -1;
|
|
7203
|
+
this.emit("PREVIEW", {
|
|
7204
|
+
key: this.key,
|
|
7205
|
+
width: widthDimension?.value,
|
|
7206
|
+
height: heightDimension?.value,
|
|
7207
|
+
hasAudio,
|
|
7208
|
+
duration
|
|
7209
|
+
});
|
|
7210
|
+
if (this.stopTime) this.waitingTime = Date.now() - this.stopTime;
|
|
7211
|
+
this.recordingStats ??= {};
|
|
7212
|
+
this.recordingStats.waitingTime = this.waitingTime;
|
|
6730
7213
|
}
|
|
6731
|
-
|
|
6732
|
-
|
|
7214
|
+
cancelAnimationFrame() {
|
|
7215
|
+
this.loop?.dispose();
|
|
6733
7216
|
}
|
|
6734
|
-
|
|
6735
|
-
this.
|
|
7217
|
+
getIntervalSum() {
|
|
7218
|
+
if (!this.loop) return;
|
|
7219
|
+
return this.loop.getElapsedTime();
|
|
6736
7220
|
}
|
|
6737
|
-
|
|
6738
|
-
|
|
7221
|
+
getAvgInterval() {
|
|
7222
|
+
const intervalSum = this.getIntervalSum() ?? 0;
|
|
7223
|
+
if (!this.hasValidInterval(intervalSum)) return;
|
|
7224
|
+
return intervalSum / this.framesCount;
|
|
6739
7225
|
}
|
|
6740
|
-
|
|
6741
|
-
this.
|
|
7226
|
+
getAvgFps() {
|
|
7227
|
+
const intervalSum = this.getIntervalSum() ?? 0;
|
|
7228
|
+
if (!this.hasValidInterval(intervalSum)) return;
|
|
7229
|
+
return this.framesCount / intervalSum * 1000;
|
|
6742
7230
|
}
|
|
6743
|
-
|
|
6744
|
-
|
|
7231
|
+
hasValidInterval(intervalSum) {
|
|
7232
|
+
return intervalSum > 0;
|
|
6745
7233
|
}
|
|
6746
|
-
|
|
6747
|
-
return this.
|
|
7234
|
+
getRecordingStats() {
|
|
7235
|
+
return this.recordingStats;
|
|
6748
7236
|
}
|
|
6749
7237
|
getAudioSampleRate() {
|
|
6750
|
-
|
|
6751
|
-
|
|
7238
|
+
return this.recordingAudioSampleRate ?? this.userMedia?.getAudioSampleRate();
|
|
7239
|
+
}
|
|
7240
|
+
stop(params) {
|
|
7241
|
+
this.cancelCameraRecovery();
|
|
7242
|
+
if (!this.userMedia) throw new Error("No user media defined, unable to stop");
|
|
7243
|
+
this.options.logger.debug(`Recorder: stop(${params ? string_pretty(params) : ""})`);
|
|
7244
|
+
this.recordingActive = false;
|
|
7245
|
+
const limitReached = params?.limitReached;
|
|
7246
|
+
this.emit("STOPPING", {
|
|
7247
|
+
limitReached
|
|
7248
|
+
});
|
|
7249
|
+
this.loop?.complete?.();
|
|
7250
|
+
this.stopTimeout = window.setTimeout(()=>{
|
|
7251
|
+
this.stopTimeout = void 0;
|
|
7252
|
+
this.stopTime = Date.now();
|
|
7253
|
+
const videoType = this.replay.getVideoType();
|
|
7254
|
+
if (!videoType) throw new Error("Unable to video record when no video type is defined.");
|
|
7255
|
+
this.recordingStats = {
|
|
7256
|
+
avgFps: this.getAvgFps(),
|
|
7257
|
+
wantedFps: this.options.video.fps,
|
|
7258
|
+
avgInterval: this.getAvgInterval(),
|
|
7259
|
+
wantedInterval: 1e3 / this.options.video.fps,
|
|
7260
|
+
intervalSum: this.getIntervalSum(),
|
|
7261
|
+
framesCount: this.framesCount,
|
|
7262
|
+
videoType
|
|
7263
|
+
};
|
|
7264
|
+
if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
|
|
7265
|
+
if (isAudioEnabled(this.options) && this.userMedia) {
|
|
7266
|
+
this.recordingStats.samplesCount = this.samplesCount;
|
|
7267
|
+
this.recordingStats.sampleRate = this.recordingAudioSampleRate ?? this.userMedia.getAudioSampleRate();
|
|
7268
|
+
}
|
|
7269
|
+
this.writeCommand("stop", this.recordingStats, ()=>{
|
|
7270
|
+
this.emit("STOPPED", {
|
|
7271
|
+
recordingStats: this.recordingStats
|
|
7272
|
+
});
|
|
7273
|
+
});
|
|
7274
|
+
this.reset();
|
|
7275
|
+
}, 60);
|
|
7276
|
+
}
|
|
7277
|
+
back(cb) {
|
|
7278
|
+
this.emit("GOING_BACK");
|
|
7279
|
+
this.key = void 0;
|
|
7280
|
+
this.recordingDimensions = void 0;
|
|
7281
|
+
this.unloaded = false;
|
|
7282
|
+
this.show();
|
|
7283
|
+
this.writeCommand("back", void 0, cb);
|
|
7284
|
+
}
|
|
7285
|
+
reInitializeAudio() {
|
|
7286
|
+
this.options.logger.debug("Recorder: reInitializeAudio()");
|
|
7287
|
+
this.clearUserMediaTimeout();
|
|
7288
|
+
this.userMediaLoading = false;
|
|
7289
|
+
this.userMedia?.stop();
|
|
7290
|
+
this.recordingDimensions = void 0;
|
|
7291
|
+
this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
|
|
7292
|
+
this.loadUserMedia();
|
|
7293
|
+
}
|
|
7294
|
+
reset() {
|
|
7295
|
+
this.cancelCameraRecovery();
|
|
7296
|
+
if (!this.unloaded) {
|
|
7297
|
+
this.options.logger.debug("Recorder: reset()");
|
|
7298
|
+
this.emit("RESETTING");
|
|
7299
|
+
this.cancelAnimationFrame();
|
|
7300
|
+
this.userMedia?.stop();
|
|
7301
|
+
this.replay.reset();
|
|
7302
|
+
this.userMediaLoaded = this.key = this.canvas = this.ctx = this.recordingBuffer = void 0;
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7305
|
+
validate() {
|
|
7306
|
+
return Boolean(this.key) && void 0 === this.canvas;
|
|
6752
7307
|
}
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
7308
|
+
pause(params) {
|
|
7309
|
+
this.cancelCameraRecovery();
|
|
7310
|
+
this.resumeAfterDrain = false;
|
|
7311
|
+
const loop = this.loop;
|
|
7312
|
+
if (!loop) throw new Error("No recording loop defined");
|
|
7313
|
+
if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
|
|
7314
|
+
else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
|
|
7315
|
+
this.userMedia?.pause();
|
|
7316
|
+
loop.stop();
|
|
7317
|
+
this.emit("PAUSED");
|
|
7318
|
+
this.sendPings();
|
|
6762
7319
|
}
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
this.
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
});
|
|
6772
|
-
this.
|
|
6773
|
-
this.
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
7320
|
+
resume() {
|
|
7321
|
+
if (this.cameraRecovery || !this.userMedia?.hasLiveStream()) {
|
|
7322
|
+
this.recoverCamera();
|
|
7323
|
+
this.resumeAfterCameraRecovery = Boolean(this.cameraRecovery);
|
|
7324
|
+
return;
|
|
7325
|
+
}
|
|
7326
|
+
const loop = this.loop;
|
|
7327
|
+
if (!loop) throw new Error("No recording loop defined");
|
|
7328
|
+
this.options.logger.debug(`Recorder: resume() with frame ${this.framesCount}`);
|
|
7329
|
+
this.stopPings();
|
|
7330
|
+
this.emit("RESUMING");
|
|
7331
|
+
this.userMedia.resume();
|
|
7332
|
+
if (this.streamBackpressured) this.resumeAfterDrain = true;
|
|
7333
|
+
else loop.start();
|
|
6777
7334
|
}
|
|
6778
|
-
|
|
6779
|
-
const
|
|
6780
|
-
|
|
6781
|
-
function recorder_define_property(obj, key, value) {
|
|
6782
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
6783
|
-
value: value,
|
|
6784
|
-
enumerable: true,
|
|
6785
|
-
configurable: true,
|
|
6786
|
-
writable: true
|
|
6787
|
-
});
|
|
6788
|
-
else obj[key] = value;
|
|
6789
|
-
return obj;
|
|
6790
|
-
}
|
|
6791
|
-
const PIPE_SYMBOL = "°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸ ";
|
|
6792
|
-
class Recorder extends util_Despot {
|
|
6793
|
-
hasExceededWebSocketSendBuffer(buffer, opts) {
|
|
6794
|
-
return buffer.length > 0 && !this.pendingWrites.enqueue(buffer, opts);
|
|
7335
|
+
onFlushed(opts) {
|
|
7336
|
+
const frameNumber = opts.frameNumber;
|
|
7337
|
+
if (1 === frameNumber) this.emit("FIRST_FRAME_SENT");
|
|
6795
7338
|
}
|
|
6796
|
-
|
|
6797
|
-
if (this.
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
7339
|
+
draw(_deltaTime, elapsedTime) {
|
|
7340
|
+
if (this.recordingTransportFailed) return;
|
|
7341
|
+
if (!this.userMedia) throw new Error("No user media defined, unable to draw on canvas");
|
|
7342
|
+
try {
|
|
7343
|
+
if (!this.isPaused() && this.stream && this.ctx) {
|
|
7344
|
+
if (0 === this.framesCount) this.emit("SENDING_FIRST_FRAME");
|
|
7345
|
+
this.framesCount++;
|
|
7346
|
+
const imageSource = this.userMedia.getRawVisuals();
|
|
7347
|
+
if (this.canvas && imageSource) this.ctx.drawImage(imageSource, 0, 0, this.canvas.width, this.canvas.height);
|
|
7348
|
+
else throw new Error("Unable to draw an image without a defined canvas");
|
|
7349
|
+
this.recordingBuffer = this.frame?.toBuffer();
|
|
7350
|
+
const recordingBufferLength = this.recordingBuffer?.length;
|
|
7351
|
+
if (recordingBufferLength) {
|
|
7352
|
+
if (this.recordingBuffer) {
|
|
7353
|
+
const frameControlBuffer = capture_Buffer.from(JSON.stringify({
|
|
7354
|
+
frameNumber: this.framesCount
|
|
7355
|
+
}));
|
|
7356
|
+
const frameBuffer = capture_Buffer.concat([
|
|
7357
|
+
this.recordingBuffer,
|
|
7358
|
+
frameControlBuffer
|
|
7359
|
+
]);
|
|
7360
|
+
this.mediaTransmissionStarted = true;
|
|
7361
|
+
this.writeStream(frameBuffer, {
|
|
7362
|
+
frameNumber: this.framesCount,
|
|
7363
|
+
onFlushedCallback: this.onFlushed.bind(this)
|
|
7364
|
+
});
|
|
7365
|
+
this.visuals.checkTimer(elapsedTime);
|
|
7366
|
+
}
|
|
7367
|
+
} else throw error_createError({
|
|
7368
|
+
message: "Failed to extract webcam data.",
|
|
7369
|
+
options: this.options
|
|
7370
|
+
});
|
|
7371
|
+
}
|
|
7372
|
+
} catch (exc) {
|
|
7373
|
+
this.emit("ERROR", {
|
|
7374
|
+
exc
|
|
6803
7375
|
});
|
|
6804
|
-
|
|
6805
|
-
} else if (this.streamBackpressured) {
|
|
6806
|
-
if (this.hasExceededWebSocketSendBuffer(buffer, opts)) this.failMediaTransmission(error_createError({
|
|
6807
|
-
message: "WebSocket send buffer limit exceeded",
|
|
6808
|
-
explanation: "The connection to the server is too slow to keep up with this recording. The recording has been stopped; please start a new one.",
|
|
6809
|
-
options: this.options
|
|
6810
|
-
}));
|
|
6811
|
-
} else this.writeToStream(this.stream, buffer, opts);
|
|
7376
|
+
}
|
|
6812
7377
|
}
|
|
6813
|
-
|
|
6814
|
-
const
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
this.
|
|
6828
|
-
|
|
7378
|
+
createLoop() {
|
|
7379
|
+
const newLoop = animitter({
|
|
7380
|
+
fps: this.options.video.fps
|
|
7381
|
+
}, this.draw.bind(this));
|
|
7382
|
+
this.originalAnimationFrameObject = newLoop.getRequestAnimationFrameObject();
|
|
7383
|
+
return newLoop;
|
|
7384
|
+
}
|
|
7385
|
+
record() {
|
|
7386
|
+
if (this.unloaded || this.cameraRecovery) return;
|
|
7387
|
+
if (!this.connected) {
|
|
7388
|
+
this.options.logger.debug("Recorder: reconnecting before recording …");
|
|
7389
|
+
this.initSocket(()=>{
|
|
7390
|
+
this.once("SERVER_READY", ()=>{
|
|
7391
|
+
if (this.unloaded) return;
|
|
7392
|
+
if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
|
|
7393
|
+
else this.once("USER_MEDIA_READY", ()=>{
|
|
7394
|
+
if (!this.unloaded) this.record();
|
|
6829
7395
|
});
|
|
6830
|
-
}
|
|
7396
|
+
});
|
|
6831
7397
|
});
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
7398
|
+
return;
|
|
7399
|
+
}
|
|
7400
|
+
if (!this.userMediaLoaded) {
|
|
7401
|
+
if (this.options.loadUserMediaOnRecord) this.loadUserMedia({
|
|
7402
|
+
recordWhenReady: true
|
|
7403
|
+
});
|
|
7404
|
+
else {
|
|
7405
|
+
const err = error_createError({
|
|
7406
|
+
message: "Load and enable your camera first",
|
|
7407
|
+
options: this.options
|
|
7408
|
+
});
|
|
7409
|
+
this.emit("ERROR", {
|
|
7410
|
+
err
|
|
7411
|
+
});
|
|
6837
7412
|
}
|
|
6838
|
-
return
|
|
7413
|
+
return;
|
|
7414
|
+
}
|
|
7415
|
+
const loop = this.loop;
|
|
7416
|
+
if (!loop) throw new Error("No recording loop defined");
|
|
7417
|
+
this.recordingDimensions = void 0;
|
|
7418
|
+
try {
|
|
7419
|
+
if (!this.userMedia) throw new Error("No user media defined, unable to create canvas");
|
|
7420
|
+
this.canvas = this.userMedia.createCanvas();
|
|
6839
7421
|
} catch (exc) {
|
|
6840
7422
|
const err = error_createError({
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
options: this.options,
|
|
6844
|
-
exc
|
|
7423
|
+
exc,
|
|
7424
|
+
options: this.options
|
|
6845
7425
|
});
|
|
6846
|
-
this.
|
|
6847
|
-
|
|
7426
|
+
this.emit("ERROR", {
|
|
7427
|
+
err
|
|
7428
|
+
});
|
|
7429
|
+
return;
|
|
6848
7430
|
}
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
7431
|
+
this.ctx = this.canvas.getContext("2d");
|
|
7432
|
+
if (!this.canvas.width) {
|
|
7433
|
+
const err = error_createError({
|
|
7434
|
+
message: "Canvas has an invalid width.",
|
|
7435
|
+
options: this.options
|
|
7436
|
+
});
|
|
7437
|
+
this.emit("ERROR", {
|
|
7438
|
+
err
|
|
7439
|
+
});
|
|
7440
|
+
return;
|
|
6858
7441
|
}
|
|
6859
|
-
this.
|
|
6860
|
-
}
|
|
6861
|
-
isStaleStream(stream) {
|
|
6862
|
-
return this.stream !== stream || stream.destroyed;
|
|
6863
|
-
}
|
|
6864
|
-
canPauseForBackpressure() {
|
|
6865
|
-
return Boolean(this.recordingActive && !this.userMedia?.isPaused() && this.loop?.isRunning());
|
|
6866
|
-
}
|
|
6867
|
-
pauseForBackpressure() {
|
|
6868
|
-
if (!this.canPauseForBackpressure()) return;
|
|
6869
|
-
this.resumeAfterDrain = true;
|
|
6870
|
-
this.loop.stop();
|
|
6871
|
-
}
|
|
6872
|
-
resumeRecordingAfterDrain() {
|
|
6873
|
-
if (!this.resumeAfterDrain) return;
|
|
6874
|
-
this.resumeAfterDrain = false;
|
|
6875
|
-
if (this.canResumeAfterDrain()) this.loop?.start();
|
|
6876
|
-
}
|
|
6877
|
-
canResumeAfterDrain() {
|
|
6878
|
-
return this.recordingActive && !this.userMedia?.isPaused();
|
|
6879
|
-
}
|
|
6880
|
-
handleWriteFailure(err) {
|
|
6881
|
-
if (this.mediaTransmissionStarted) return void this.failMediaTransmission(err);
|
|
6882
|
-
this.emit("ERROR", {
|
|
6883
|
-
err
|
|
6884
|
-
});
|
|
6885
|
-
}
|
|
6886
|
-
failMediaTransmission(err) {
|
|
6887
|
-
if (this.recordingTransportFailed) return;
|
|
6888
|
-
this.recordingTransportFailed = true;
|
|
6889
|
-
this.recordingActive = false;
|
|
6890
|
-
this.mediaTransmissionStarted = false;
|
|
6891
|
-
this.streamBackpressured = false;
|
|
6892
|
-
this.resumeAfterDrain = false;
|
|
6893
|
-
this.pendingWrites.clear();
|
|
6894
|
-
this.connected = false;
|
|
6895
|
-
this.connecting = false;
|
|
6896
|
-
this.reconnecting = false;
|
|
6897
|
-
this.clearReconnectTimeout();
|
|
6898
|
-
this.clearBackpressureTimeout();
|
|
6899
|
-
this.discardSocket();
|
|
6900
|
-
this.reset();
|
|
6901
|
-
this.framesCount = this.samplesCount = 0;
|
|
6902
|
-
this.confirmedFrameNumber = this.confirmedSampleNumber = 0;
|
|
6903
|
-
delete this.recordingStats;
|
|
6904
|
-
this.recordingDimensions = void 0;
|
|
6905
|
-
this.emit("ERROR", {
|
|
6906
|
-
err
|
|
6907
|
-
});
|
|
6908
|
-
}
|
|
6909
|
-
startBackpressureTimeout() {
|
|
6910
|
-
if (!this.shouldStartBackpressureTimeout()) return;
|
|
6911
|
-
this.backpressureTimeout = window.setTimeout(()=>{
|
|
6912
|
-
this.backpressureTimeout = void 0;
|
|
6913
|
-
if (this.shouldIgnoreBackpressureTimeout()) return;
|
|
7442
|
+
if (!this.canvas.height) {
|
|
6914
7443
|
const err = error_createError({
|
|
6915
|
-
message: "
|
|
6916
|
-
explanation: "The server stopped accepting recording data. The recording has been stopped; please check your connection and start a new one.",
|
|
7444
|
+
message: "Canvas has an invalid height.",
|
|
6917
7445
|
options: this.options
|
|
6918
7446
|
});
|
|
6919
|
-
this.
|
|
6920
|
-
|
|
6921
|
-
}
|
|
6922
|
-
shouldStartBackpressureTimeout() {
|
|
6923
|
-
return void 0 === this.backpressureTimeout && this.mediaTransmissionStarted;
|
|
6924
|
-
}
|
|
6925
|
-
shouldIgnoreBackpressureTimeout() {
|
|
6926
|
-
return !this.streamBackpressured || this.recordingTransportFailed;
|
|
6927
|
-
}
|
|
6928
|
-
failInterruptedRecording() {
|
|
6929
|
-
const unconfirmedFrames = Math.max(0, this.framesCount - this.confirmedFrameNumber);
|
|
6930
|
-
const unconfirmedSamples = Math.max(0, this.samplesCount - this.confirmedSampleNumber);
|
|
6931
|
-
const err = error_createError({
|
|
6932
|
-
message: "Recording interrupted",
|
|
6933
|
-
explanation: "The server connection closed before this recording completed. It cannot be safely resumed, so it has been stopped. Please start a new recording.",
|
|
6934
|
-
options: this.options,
|
|
6935
|
-
cause: {
|
|
6936
|
-
unconfirmedFrames,
|
|
6937
|
-
unconfirmedSamples
|
|
6938
|
-
}
|
|
6939
|
-
});
|
|
6940
|
-
this.failMediaTransmission(err);
|
|
6941
|
-
}
|
|
6942
|
-
handleStreamDrain(stream) {
|
|
6943
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *drain* event emitted`);
|
|
6944
|
-
this.flushPendingWrites(stream);
|
|
6945
|
-
}
|
|
6946
|
-
canReconnectAfterStreamClose() {
|
|
6947
|
-
return Boolean(this.connected && this.userMediaLoaded);
|
|
6948
|
-
}
|
|
6949
|
-
shouldReportStreamClose() {
|
|
6950
|
-
return !this.connecting && !this.blocking;
|
|
6951
|
-
}
|
|
6952
|
-
handleStreamClose(url2Connect) {
|
|
6953
|
-
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream has closed:`, {
|
|
6954
|
-
connecting: this.connecting,
|
|
6955
|
-
connected: this.connected,
|
|
6956
|
-
userMediaLoaded: this.userMediaLoaded,
|
|
6957
|
-
blocking: this.blocking
|
|
6958
|
-
});
|
|
6959
|
-
this.options.logger.debug(debugLine);
|
|
6960
|
-
const canReconnect = this.canReconnectAfterStreamClose();
|
|
6961
|
-
this.connected = false;
|
|
6962
|
-
this.stopPings();
|
|
6963
|
-
if (this.shouldIgnoreStreamClose()) return;
|
|
6964
|
-
if (this.mediaTransmissionStarted) this.failInterruptedRecording();
|
|
6965
|
-
else if (canReconnect) {
|
|
6966
|
-
this.pendingWrites.clear();
|
|
6967
|
-
this.streamBackpressured = false;
|
|
6968
|
-
this.resumeAfterDrain = false;
|
|
6969
|
-
this.reconnecting = true;
|
|
6970
|
-
this.initSocket();
|
|
6971
|
-
} else if (this.shouldReportStreamClose()) window.setTimeout(()=>{
|
|
6972
|
-
this.handleConnectionFailure({
|
|
6973
|
-
url2Connect,
|
|
6974
|
-
cause: "closed"
|
|
7447
|
+
this.emit("ERROR", {
|
|
7448
|
+
err
|
|
6975
7449
|
});
|
|
6976
|
-
}, 0);
|
|
6977
|
-
}
|
|
6978
|
-
shouldIgnoreStreamClose() {
|
|
6979
|
-
return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
|
|
6980
|
-
}
|
|
6981
|
-
handleStreamError(url2Connect, err) {
|
|
6982
|
-
if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
|
|
6983
|
-
else this.lastSocketError = serializeError(err);
|
|
6984
|
-
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
|
|
6985
|
-
error: serializeError(err)
|
|
6986
|
-
});
|
|
6987
|
-
this.options.logger.debug(debugLine);
|
|
6988
|
-
if (this.validate()) {
|
|
6989
|
-
this.connected = false;
|
|
6990
|
-
this.stopPings();
|
|
6991
|
-
this.discardSocket();
|
|
6992
7450
|
return;
|
|
6993
7451
|
}
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
}
|
|
6998
|
-
|
|
6999
|
-
|
|
7452
|
+
this.recordingDimensions = {
|
|
7453
|
+
width: this.canvas.width,
|
|
7454
|
+
height: this.canvas.height
|
|
7455
|
+
};
|
|
7456
|
+
this.frame = new canvas_to_buffer(this.canvas, this.options.image.types, this.options.image.quality);
|
|
7457
|
+
this.options.logger.debug("Recorder: record()");
|
|
7458
|
+
this.recordingActive = true;
|
|
7459
|
+
this.recordingTransportFailed = false;
|
|
7460
|
+
this.mediaTransmissionStarted = false;
|
|
7461
|
+
this.recordingAudioSampleRate = void 0;
|
|
7462
|
+
this.userMedia.record();
|
|
7463
|
+
this.emit("RECORDING", {
|
|
7464
|
+
framesCount: this.framesCount
|
|
7000
7465
|
});
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
|
|
7006
|
-
options: this.options,
|
|
7007
|
-
exc: streamError
|
|
7008
|
-
})
|
|
7466
|
+
loop.on("update", (_deltaTime, elapsedTime)=>{
|
|
7467
|
+
let avgFPS;
|
|
7468
|
+
avgFPS = 0 !== elapsedTime ? Math.round(this.framesCount / elapsedTime * 1000) : void 0;
|
|
7469
|
+
this.options.logger.debug(`Recorder updates avgFps = ${avgFPS} at frame ${this.framesCount}`);
|
|
7009
7470
|
});
|
|
7471
|
+
loop.start();
|
|
7010
7472
|
}
|
|
7011
|
-
|
|
7012
|
-
this.
|
|
7013
|
-
|
|
7014
|
-
this.
|
|
7015
|
-
this.
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
stopPings() {
|
|
7019
|
-
if (void 0 === this.pingInterval) return;
|
|
7020
|
-
this.options.logger.debug("Recorder: stopping pings...");
|
|
7021
|
-
window.clearInterval(this.pingInterval);
|
|
7022
|
-
this.pingInterval = void 0;
|
|
7023
|
-
}
|
|
7024
|
-
onAudioSample(audioSample) {
|
|
7025
|
-
if (this.recordingTransportFailed || this.cameraRecovery) return;
|
|
7026
|
-
const sampleRate = this.userMedia?.getAudioSampleRate();
|
|
7027
|
-
if (sampleRate && sampleRate > 0) this.recordingAudioSampleRate ??= sampleRate;
|
|
7028
|
-
this.samplesCount++;
|
|
7029
|
-
this.mediaTransmissionStarted = true;
|
|
7030
|
-
const audioBuffer = audioSample.toBuffer();
|
|
7031
|
-
this.writeStream(audioBuffer);
|
|
7473
|
+
setAnimationFrameObject(newObj) {
|
|
7474
|
+
if (this.loop) {
|
|
7475
|
+
const isRecording = this.isRecording();
|
|
7476
|
+
this.loop.stop();
|
|
7477
|
+
this.loop.setRequestAnimationFrameObject(newObj);
|
|
7478
|
+
if (isRecording) this.loop.start();
|
|
7479
|
+
}
|
|
7032
7480
|
}
|
|
7033
|
-
|
|
7034
|
-
|
|
7481
|
+
restoreAnimationFrameObject() {
|
|
7482
|
+
this.options.logger.debug("Recorder: restoreAnimationFrameObject()");
|
|
7483
|
+
if (this.originalAnimationFrameObject) this.setAnimationFrameObject(this.originalAnimationFrameObject);
|
|
7035
7484
|
}
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
recovering: params?.recovering,
|
|
7053
|
-
paused: this.isPaused(),
|
|
7054
|
-
recordWhenReady: params?.recordWhenReady
|
|
7055
|
-
});
|
|
7056
|
-
if (params?.recovering) this.finishCameraRecovery(true);
|
|
7057
|
-
} catch (exc) {
|
|
7058
|
-
if (params?.recovering) this.failCameraRecovery(exc);
|
|
7059
|
-
else this.emit("ERROR", {
|
|
7060
|
-
exc
|
|
7061
|
-
});
|
|
7062
|
-
}
|
|
7485
|
+
loopWithTimeouts() {
|
|
7486
|
+
this.options.logger.debug("Recorder: loopWithTimeouts()");
|
|
7487
|
+
const wantedInterval = 1e3 / this.options.video.fps;
|
|
7488
|
+
let processingTime = 0;
|
|
7489
|
+
const raf = (fn)=>window.setTimeout(()=>{
|
|
7490
|
+
const start = Date.now();
|
|
7491
|
+
fn();
|
|
7492
|
+
processingTime = Date.now() - start;
|
|
7493
|
+
}, wantedInterval - processingTime);
|
|
7494
|
+
const cancel = (id)=>{
|
|
7495
|
+
window.clearTimeout(id);
|
|
7496
|
+
};
|
|
7497
|
+
this.setAnimationFrameObject({
|
|
7498
|
+
requestAnimationFrame: raf,
|
|
7499
|
+
cancelAnimationFrame: cancel
|
|
7500
|
+
});
|
|
7063
7501
|
}
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
this.options.logger.debug("Recorder: clearRetryTimeout()");
|
|
7067
|
-
window.clearTimeout(this.retryTimeout);
|
|
7068
|
-
this.retryTimeout = void 0;
|
|
7502
|
+
isPaused() {
|
|
7503
|
+
return this.userMedia?.isPaused() && !this.loop?.isRunning();
|
|
7069
7504
|
}
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
this.options.logger.debug("Recorder: clearConnectionTimeout()");
|
|
7073
|
-
window.clearTimeout(this.connectionTimeout);
|
|
7074
|
-
this.connectionTimeout = void 0;
|
|
7505
|
+
isUserMediaPaused() {
|
|
7506
|
+
return this.userMedia?.isPaused() ?? false;
|
|
7075
7507
|
}
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
window.clearTimeout(this.backpressureTimeout);
|
|
7079
|
-
this.backpressureTimeout = void 0;
|
|
7508
|
+
isCaptureLoopRunning() {
|
|
7509
|
+
return this.loop?.isRunning() ?? false;
|
|
7080
7510
|
}
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
this.options.logger.debug("Recorder: clearReconnectTimeout()");
|
|
7084
|
-
window.clearTimeout(this.reconnectTimeout);
|
|
7085
|
-
this.reconnectTimeout = void 0;
|
|
7511
|
+
stopCaptureLoop() {
|
|
7512
|
+
this.loop?.stop();
|
|
7086
7513
|
}
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
this.failConnection(params);
|
|
7514
|
+
startCaptureLoop() {
|
|
7515
|
+
this.loop?.start();
|
|
7090
7516
|
}
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
if (void 0 !== this.reconnectTimeout) return true;
|
|
7094
|
-
const delayMs = Math.min(1000 * 2 ** this.reconnectAttempts, 10000);
|
|
7095
|
-
this.reconnectAttempts++;
|
|
7096
|
-
this.connecting = false;
|
|
7097
|
-
this.clearConnectionTimeout();
|
|
7098
|
-
this.discardSocket();
|
|
7099
|
-
this.options.logger.debug(`Recorder: retrying WebSocket connection in ${delayMs}ms (cause ${params.cause}).`);
|
|
7100
|
-
this.reconnectTimeout = window.setTimeout(()=>{
|
|
7101
|
-
this.reconnectTimeout = void 0;
|
|
7102
|
-
if (this.shouldContinueReconnect()) this.initSocket();
|
|
7103
|
-
}, delayMs);
|
|
7104
|
-
return true;
|
|
7517
|
+
isRecording() {
|
|
7518
|
+
return Boolean(this.loop?.isRunning() && !this.isPaused() && !this.isNotifying() && this.stream && !this.stream.destroyed);
|
|
7105
7519
|
}
|
|
7106
|
-
|
|
7107
|
-
return
|
|
7520
|
+
getRecordingDimensions() {
|
|
7521
|
+
return this.recordingDimensions && {
|
|
7522
|
+
...this.recordingDimensions
|
|
7523
|
+
};
|
|
7108
7524
|
}
|
|
7109
|
-
|
|
7110
|
-
|
|
7525
|
+
constructor(visuals, replay, options){
|
|
7526
|
+
super(visuals, options), capture_define_property(this, "replay", void 0), capture_define_property(this, "loop", void 0), capture_define_property(this, "originalAnimationFrameObject", void 0), capture_define_property(this, "recordingAudioSampleRate", void 0), capture_define_property(this, "frameProgress", void 0), capture_define_property(this, "sampleProgress", void 0), capture_define_property(this, "canvas", void 0), capture_define_property(this, "ctx", void 0), capture_define_property(this, "stopTime", void 0), capture_define_property(this, "waitingTime", void 0), capture_define_property(this, "frame", void 0), capture_define_property(this, "recordingBuffer", void 0);
|
|
7527
|
+
this.replay = replay;
|
|
7111
7528
|
}
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7529
|
+
}
|
|
7530
|
+
const capture = RecorderCapture;
|
|
7531
|
+
function figureMinHeight(height, options) {
|
|
7532
|
+
let minHeight;
|
|
7533
|
+
if (options.video.height) {
|
|
7534
|
+
minHeight = height ? Math.min(options.video.height, height) : options.video.height;
|
|
7535
|
+
if (minHeight < 1) throw error_createError({
|
|
7536
|
+
message: `Got a min height less than 1 (${minHeight})!`,
|
|
7537
|
+
options
|
|
7538
|
+
});
|
|
7539
|
+
} else minHeight = height;
|
|
7540
|
+
return minHeight;
|
|
7541
|
+
}
|
|
7542
|
+
const dimensions_figureMinHeight = figureMinHeight;
|
|
7543
|
+
function getRatio(options, videoHeight, videoWidth) {
|
|
7544
|
+
let ratio = 1;
|
|
7545
|
+
const hasVideoDimensions = videoHeight && videoWidth;
|
|
7546
|
+
const desiredHeight = options.video.height;
|
|
7547
|
+
const desiredWidth = options.video.width;
|
|
7548
|
+
const hasDesiredDimensions = desiredHeight && desiredWidth;
|
|
7549
|
+
if (hasDesiredDimensions) ratio = hasVideoDimensions ? videoHeight < desiredHeight || videoWidth < desiredWidth ? videoHeight / videoWidth : desiredHeight / desiredWidth : desiredHeight / desiredWidth;
|
|
7550
|
+
else if (hasVideoDimensions) ratio = videoHeight / videoWidth;
|
|
7551
|
+
return ratio;
|
|
7552
|
+
}
|
|
7553
|
+
const dimensions_getRatio = getRatio;
|
|
7554
|
+
function calculateHeight(responsive, videoWidth, options, ratio, element) {
|
|
7555
|
+
const dimension = {
|
|
7556
|
+
unit: "px"
|
|
7557
|
+
};
|
|
7558
|
+
let width = videoWidth;
|
|
7559
|
+
if (responsive && element) {
|
|
7560
|
+
const limitedDimension = dimensions_limitWidth(element, options, width);
|
|
7561
|
+
width = limitedDimension.value;
|
|
7121
7562
|
}
|
|
7122
|
-
|
|
7123
|
-
|
|
7563
|
+
const chosenRatio = ratio ?? dimensions_getRatio(options, void 0, videoWidth);
|
|
7564
|
+
const height = width ? Math.round(width * chosenRatio) : void 0;
|
|
7565
|
+
if (height && Number.isInteger(height) && height < 1) throw error_createError({
|
|
7566
|
+
message: "Just calculated a height less than 1 which is wrong.",
|
|
7567
|
+
options
|
|
7568
|
+
});
|
|
7569
|
+
const minHeight = dimensions_figureMinHeight(height, options);
|
|
7570
|
+
dimension.value = minHeight;
|
|
7571
|
+
return dimension;
|
|
7572
|
+
}
|
|
7573
|
+
const dimensions_calculateHeight = calculateHeight;
|
|
7574
|
+
function calculateWidth(responsive, options, videoHeight, ratio) {
|
|
7575
|
+
const dimension = {
|
|
7576
|
+
unit: "px"
|
|
7577
|
+
};
|
|
7578
|
+
let height = dimensions_figureMinHeight(videoHeight, options);
|
|
7579
|
+
if (responsive) {
|
|
7580
|
+
const limitedDimension = dimensions_limitHeight(height, options, "calculateWidth");
|
|
7581
|
+
height = limitedDimension.value;
|
|
7124
7582
|
}
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7583
|
+
if (!height || height < 1) throw error_createError({
|
|
7584
|
+
message: `Height ${height} cannot be smaller than 1 when calculating width.`,
|
|
7585
|
+
options
|
|
7586
|
+
});
|
|
7587
|
+
const chosenRatio = ratio ?? dimensions_getRatio(options, videoHeight);
|
|
7588
|
+
const calculatedWidth = Math.round(height / chosenRatio);
|
|
7589
|
+
if (calculatedWidth < 1) throw error_createError({
|
|
7590
|
+
message: "Calculated width cannot be smaller than 1!",
|
|
7591
|
+
options
|
|
7592
|
+
});
|
|
7593
|
+
dimension.value = calculatedWidth;
|
|
7594
|
+
return dimension;
|
|
7595
|
+
}
|
|
7596
|
+
const dimensions_calculateWidth = calculateWidth;
|
|
7597
|
+
function dimensions_define_property(obj, key, value) {
|
|
7598
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
7599
|
+
value: value,
|
|
7600
|
+
enumerable: true,
|
|
7601
|
+
configurable: true,
|
|
7602
|
+
writable: true
|
|
7603
|
+
});
|
|
7604
|
+
else obj[key] = value;
|
|
7605
|
+
return obj;
|
|
7606
|
+
}
|
|
7607
|
+
class RecorderDimensions {
|
|
7608
|
+
correctDimensions() {
|
|
7609
|
+
const recorderElement = this.context.getRecorderElement();
|
|
7610
|
+
if (!recorderElement) return;
|
|
7611
|
+
const widthDimension = dimensions_useFullWidth(this.context.options.video.mobileBreakPoint);
|
|
7612
|
+
if (this.context.options.video.stretch || widthDimension) {
|
|
7613
|
+
recorderElement.style.width = "100%";
|
|
7614
|
+
recorderElement.style.removeProperty("height");
|
|
7615
|
+
} else {
|
|
7616
|
+
if (this.context.options.video.width) {
|
|
7617
|
+
const recorderWidth = this.getRecorderWidth(true);
|
|
7618
|
+
if (recorderWidth?.value) recorderElement.width = recorderWidth.value;
|
|
7619
|
+
else recorderElement.style.removeProperty("width");
|
|
7620
|
+
}
|
|
7621
|
+
if (this.context.options.video.height) {
|
|
7622
|
+
const recorderHeight = this.getRecorderHeight(true);
|
|
7623
|
+
if (recorderHeight?.value) recorderElement.height = recorderHeight.value;
|
|
7624
|
+
else recorderElement.style.removeProperty("height");
|
|
7625
|
+
}
|
|
7164
7626
|
}
|
|
7165
|
-
else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
|
|
7166
|
-
this.discardSocket();
|
|
7167
|
-
const err = error_createError({
|
|
7168
|
-
message: "Unable to connect to the server",
|
|
7169
|
-
explanation,
|
|
7170
|
-
options: this.options,
|
|
7171
|
-
cause: contents,
|
|
7172
|
-
exc: socketError ? new Error("WebSocket connection error", {
|
|
7173
|
-
cause: socketError
|
|
7174
|
-
}) : void 0
|
|
7175
|
-
});
|
|
7176
|
-
this.emit("ERROR", {
|
|
7177
|
-
err
|
|
7178
|
-
});
|
|
7179
7627
|
}
|
|
7180
|
-
|
|
7181
|
-
|
|
7628
|
+
getRecorderWidth(responsive) {
|
|
7629
|
+
const userMedia = this.context.getUserMedia();
|
|
7630
|
+
if (userMedia?.hasVideoWidth()) return userMedia.getRawWidth(responsive);
|
|
7631
|
+
else if (responsive && this.context.options.video.width) return this.context.visuals.limitWidth(this.context.options.video.width);
|
|
7632
|
+
const dimension = {
|
|
7633
|
+
unit: "px",
|
|
7634
|
+
value: this.context.options.video.width
|
|
7635
|
+
};
|
|
7636
|
+
return dimension;
|
|
7182
7637
|
}
|
|
7183
|
-
|
|
7184
|
-
|
|
7638
|
+
getRecorderHeight(responsive, useBoundingClientRect) {
|
|
7639
|
+
const recorderElement = this.context.getRecorderElement();
|
|
7640
|
+
const userMedia = this.context.getUserMedia();
|
|
7641
|
+
let recorderHeight;
|
|
7642
|
+
if (recorderElement && useBoundingClientRect) {
|
|
7643
|
+
const height = recorderElement.getBoundingClientRect().height;
|
|
7644
|
+
recorderHeight = {
|
|
7645
|
+
unit: "px",
|
|
7646
|
+
value: height
|
|
7647
|
+
};
|
|
7648
|
+
} else if (userMedia) {
|
|
7649
|
+
let height = userMedia.getRawHeight(responsive);
|
|
7650
|
+
if (void 0 !== height && this.context.options.video.width) {
|
|
7651
|
+
const ratio = this.getRatio();
|
|
7652
|
+
if (void 0 !== ratio) {
|
|
7653
|
+
const idealHeight = this.context.options.video.width * ratio;
|
|
7654
|
+
height = Math.min(idealHeight, height);
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
recorderHeight = {
|
|
7658
|
+
unit: "px",
|
|
7659
|
+
value: height
|
|
7660
|
+
};
|
|
7661
|
+
} else if (responsive && this.context.options.video.height) recorderHeight = this.calculateHeight(responsive);
|
|
7662
|
+
else if (this.context.options.video.height) {
|
|
7663
|
+
const height = this.context.options.video.height;
|
|
7664
|
+
recorderHeight = {
|
|
7665
|
+
unit: "px",
|
|
7666
|
+
value: height
|
|
7667
|
+
};
|
|
7668
|
+
}
|
|
7669
|
+
return recorderHeight;
|
|
7185
7670
|
}
|
|
7186
|
-
|
|
7187
|
-
|
|
7671
|
+
getRatio() {
|
|
7672
|
+
const userMedia = this.context.getUserMedia();
|
|
7673
|
+
let ratio;
|
|
7674
|
+
if (userMedia) {
|
|
7675
|
+
const userMediaVideoWidth = userMedia.getVideoWidth();
|
|
7676
|
+
const userMediaVideoHeight = userMedia.getVideoHeight();
|
|
7677
|
+
if (!userMediaVideoWidth || userMediaVideoWidth < 1) ratio = this.context.visuals.getRatio();
|
|
7678
|
+
else if (userMediaVideoHeight) ratio = userMediaVideoHeight / userMediaVideoWidth;
|
|
7679
|
+
} else ratio = dimensions_getRatio(this.context.options);
|
|
7680
|
+
return ratio;
|
|
7188
7681
|
}
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7682
|
+
calculateWidth(responsive) {
|
|
7683
|
+
const userMedia = this.context.getUserMedia();
|
|
7684
|
+
const recorderElement = this.context.getRecorderElement();
|
|
7685
|
+
let videoHeight;
|
|
7686
|
+
if (userMedia) videoHeight = userMedia.getVideoHeight();
|
|
7687
|
+
else if (recorderElement) videoHeight = recorderElement.videoHeight || recorderElement.height;
|
|
7688
|
+
return dimensions_calculateWidth(responsive, this.context.options, videoHeight, this.getRatio());
|
|
7196
7689
|
}
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7690
|
+
calculateHeight(responsive) {
|
|
7691
|
+
const userMedia = this.context.getUserMedia();
|
|
7692
|
+
const recorderElement = this.context.getRecorderElement();
|
|
7693
|
+
let videoDimension;
|
|
7694
|
+
if (userMedia) {
|
|
7695
|
+
const videoHeight = userMedia.getVideoHeight();
|
|
7696
|
+
videoDimension = {
|
|
7697
|
+
value: videoHeight,
|
|
7698
|
+
unit: "px"
|
|
7699
|
+
};
|
|
7700
|
+
} else if (recorderElement) {
|
|
7701
|
+
const videoHeight = recorderElement.videoHeight || recorderElement.height;
|
|
7702
|
+
videoDimension = {
|
|
7703
|
+
value: videoHeight,
|
|
7704
|
+
unit: "px"
|
|
7705
|
+
};
|
|
7706
|
+
} else videoDimension = dimensions_calculateHeight(responsive, void 0, this.context.options, this.getRatio(), recorderElement);
|
|
7707
|
+
return videoDimension;
|
|
7210
7708
|
}
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
message: "The audio sample number is missing",
|
|
7215
|
-
options: this.options
|
|
7216
|
-
});
|
|
7217
|
-
} else throw error_createError({
|
|
7218
|
-
message: "Arguments are missing for updating the audio sample progress",
|
|
7219
|
-
options: this.options
|
|
7220
|
-
});
|
|
7221
|
-
this.confirmedSampleNumber = args.sample;
|
|
7222
|
-
this.sampleProgress = this.calculateSampleProgress();
|
|
7223
|
-
this.updateOverallProgress();
|
|
7709
|
+
constructor(context){
|
|
7710
|
+
dimensions_define_property(this, "context", void 0);
|
|
7711
|
+
this.context = context;
|
|
7224
7712
|
}
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7713
|
+
}
|
|
7714
|
+
const recorder_dimensions = RecorderDimensions;
|
|
7715
|
+
function getFirstVideoTrack(localMediaStream) {
|
|
7716
|
+
const videoTracks = localMediaStream.getVideoTracks();
|
|
7717
|
+
let videoTrack;
|
|
7718
|
+
if (videoTracks[0]) videoTrack = videoTracks[0];
|
|
7719
|
+
return videoTrack;
|
|
7720
|
+
}
|
|
7721
|
+
const media_getFirstVideoTrack = getFirstVideoTrack;
|
|
7722
|
+
const MEDIA_EVENTS = [
|
|
7723
|
+
"loadstart",
|
|
7724
|
+
"suspend",
|
|
7725
|
+
"progress",
|
|
7726
|
+
"abort",
|
|
7727
|
+
"emptied",
|
|
7728
|
+
"stalled",
|
|
7729
|
+
"pause",
|
|
7730
|
+
"loadeddata",
|
|
7731
|
+
"waiting",
|
|
7732
|
+
"playing",
|
|
7733
|
+
"canplay",
|
|
7734
|
+
"canplaythrough",
|
|
7735
|
+
"seeking",
|
|
7736
|
+
"seeked",
|
|
7737
|
+
"ended",
|
|
7738
|
+
"ratechange",
|
|
7739
|
+
"durationchange",
|
|
7740
|
+
"volumechange"
|
|
7741
|
+
];
|
|
7742
|
+
const mediaEvents = MEDIA_EVENTS;
|
|
7743
|
+
function AudioRecorder_define_property(obj, key, value) {
|
|
7744
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
7745
|
+
value: value,
|
|
7746
|
+
enumerable: true,
|
|
7747
|
+
configurable: true,
|
|
7748
|
+
writable: true
|
|
7749
|
+
});
|
|
7750
|
+
else obj[key] = value;
|
|
7751
|
+
return obj;
|
|
7752
|
+
}
|
|
7753
|
+
const CHANNELS = 1;
|
|
7754
|
+
function getAudioContextClass() {
|
|
7755
|
+
return window.AudioContext;
|
|
7756
|
+
}
|
|
7757
|
+
class AudioRecorder {
|
|
7758
|
+
hasAudioContext() {
|
|
7759
|
+
return Boolean(getAudioContextClass()) && Boolean(this.getAudioContext());
|
|
7253
7760
|
}
|
|
7254
|
-
|
|
7255
|
-
if (this.
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
return;
|
|
7259
|
-
}
|
|
7260
|
-
if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
|
|
7261
|
-
this.clearReconnectTimeout();
|
|
7262
|
-
if (util_isAutomatedUserAgent()) {
|
|
7263
|
-
this.connecting = false;
|
|
7264
|
-
this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
|
|
7265
|
-
return;
|
|
7266
|
-
}
|
|
7267
|
-
this.connecting = true;
|
|
7268
|
-
this.connectionFailed = false;
|
|
7269
|
-
this.connectingStartedAt = Date.now();
|
|
7270
|
-
this.lastCloseEvent = void 0;
|
|
7271
|
-
this.lastSocketError = void 0;
|
|
7272
|
-
this.emit("CONNECTING");
|
|
7273
|
-
let url2Connect;
|
|
7274
|
-
try {
|
|
7275
|
-
const socketUrlObj = new URL(this.options.socketUrl);
|
|
7276
|
-
socketUrlObj.searchParams.set(constants.WHITELIST_KEY_LABEL, this.options.whitelistKey);
|
|
7277
|
-
url2Connect = socketUrlObj.toString();
|
|
7278
|
-
} catch (exc) {
|
|
7279
|
-
this.connecting = this.connected = false;
|
|
7280
|
-
const err = error_createError({
|
|
7281
|
-
message: "Invalid WebSocket URL",
|
|
7282
|
-
explanation: `The configured socketUrl "${this.options.socketUrl}" is not a valid URL. Please check your videomail-client configuration.`,
|
|
7283
|
-
options: this.options,
|
|
7284
|
-
exc
|
|
7285
|
-
});
|
|
7286
|
-
this.emit("ERROR", {
|
|
7287
|
-
err
|
|
7288
|
-
});
|
|
7289
|
-
return;
|
|
7761
|
+
getAudioContext() {
|
|
7762
|
+
if (!this.vcAudioContext) {
|
|
7763
|
+
const AudioContext = getAudioContextClass();
|
|
7764
|
+
this.vcAudioContext = new AudioContext();
|
|
7290
7765
|
}
|
|
7291
|
-
this.
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
nativeSocket.addEventListener("error", (event)=>{
|
|
7303
|
-
this.lastSocketError = error_getEventDetails(event);
|
|
7304
|
-
});
|
|
7305
|
-
} catch (exc) {
|
|
7306
|
-
this.connecting = this.connected = false;
|
|
7307
|
-
const diagnostic = error_getWebSocketDiagnostic(url2Connect);
|
|
7308
|
-
const message = diagnostic.looksAutomated ? "Automated crawler: WebSocket not supported in this environment" : `Failed to construct WebSocket to ${url2Connect}`;
|
|
7309
|
-
const explanation = diagnostic.looksAutomated ? "Headless crawlers cannot use Videomail's WebSocket. No action needed." : `Please check your connection and try again. If the problem persists, contact us. Diagnostic: ${diagnostic.text}`;
|
|
7310
|
-
const err = error_createError({
|
|
7311
|
-
message,
|
|
7312
|
-
explanation,
|
|
7313
|
-
options: this.options,
|
|
7314
|
-
exc
|
|
7315
|
-
});
|
|
7316
|
-
this.emit("ERROR", {
|
|
7317
|
-
err
|
|
7318
|
-
});
|
|
7319
|
-
return;
|
|
7766
|
+
return this.vcAudioContext;
|
|
7767
|
+
}
|
|
7768
|
+
onAudioProcess(e, cb) {
|
|
7769
|
+
if (!this.userMedia.isRecording() || this.userMedia.isPaused()) return;
|
|
7770
|
+
const float32Array = e.inputBuffer.getChannelData(0);
|
|
7771
|
+
cb(new audio_sample(float32Array));
|
|
7772
|
+
}
|
|
7773
|
+
disconnect() {
|
|
7774
|
+
if (this.scriptProcessor) {
|
|
7775
|
+
this.scriptProcessor.onaudioprocess = null;
|
|
7776
|
+
this.scriptProcessor.disconnect();
|
|
7320
7777
|
}
|
|
7778
|
+
this.audioInput?.disconnect();
|
|
7779
|
+
this.volume?.disconnect();
|
|
7780
|
+
}
|
|
7781
|
+
init(localMediaStream) {
|
|
7782
|
+
this.options.logger.debug("AudioRecorder: init()");
|
|
7783
|
+
this.disconnect();
|
|
7784
|
+
const volume = this.getAudioContext().createGain();
|
|
7785
|
+
this.volume = volume;
|
|
7321
7786
|
try {
|
|
7322
|
-
this.
|
|
7787
|
+
this.audioInput = this.getAudioContext().createMediaStreamSource(localMediaStream);
|
|
7323
7788
|
} catch (exc) {
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
options: this.options,
|
|
7329
|
-
exc
|
|
7330
|
-
});
|
|
7331
|
-
this.emit("ERROR", {
|
|
7332
|
-
err
|
|
7333
|
-
});
|
|
7334
|
-
}
|
|
7335
|
-
const stream = this.stream;
|
|
7336
|
-
if (stream) {
|
|
7337
|
-
const connectionTimeoutMs = this.options.timeouts.connection;
|
|
7338
|
-
this.connectionTimeout = window.setTimeout(()=>{
|
|
7339
|
-
if (this.isOnline() && this.isUserMediaLoaded()) {
|
|
7340
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
|
|
7341
|
-
this.reconnecting = true;
|
|
7342
|
-
this.handleConnectionFailure({
|
|
7343
|
-
url2Connect,
|
|
7344
|
-
cause: "timeout"
|
|
7345
|
-
});
|
|
7346
|
-
} else this.handleConnectionFailure({
|
|
7347
|
-
url2Connect,
|
|
7348
|
-
cause: "timeout"
|
|
7349
|
-
});
|
|
7350
|
-
}, connectionTimeoutMs);
|
|
7351
|
-
stream.on("close", ()=>{
|
|
7352
|
-
this.handleStreamClose(url2Connect);
|
|
7353
|
-
});
|
|
7354
|
-
stream.on("connect", (args)=>{
|
|
7355
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *connect* event emitted with args: ${string_pretty(args)}`);
|
|
7356
|
-
this.clearConnectionTimeout();
|
|
7357
|
-
const isClosing = this.stream?.socket.readyState === WebSocket.CLOSING;
|
|
7358
|
-
if (!this.connected && !isClosing && !this.unloaded) {
|
|
7359
|
-
this.connected = true;
|
|
7360
|
-
this.connecting = this.unloaded = false;
|
|
7361
|
-
this.reconnecting = false;
|
|
7362
|
-
this.reconnectAttempts = 0;
|
|
7363
|
-
this.clearReconnectTimeout();
|
|
7364
|
-
this.emit("CONNECTED");
|
|
7365
|
-
cb?.();
|
|
7366
|
-
}
|
|
7367
|
-
});
|
|
7368
|
-
stream.on("data", (data)=>{
|
|
7369
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *data* event emitted`);
|
|
7370
|
-
try {
|
|
7371
|
-
const command = JSON.parse(data.toString());
|
|
7372
|
-
this.executeCommand(command);
|
|
7373
|
-
} catch (exc) {
|
|
7374
|
-
this.options.logger.error(`Failed to parse command: ${exc}`);
|
|
7375
|
-
const err = error_createError({
|
|
7376
|
-
message: "Invalid server command",
|
|
7377
|
-
explanation: `Contact us. The invalid command was: ${data.toString()}.`,
|
|
7378
|
-
options: this.options,
|
|
7379
|
-
exc
|
|
7380
|
-
});
|
|
7381
|
-
this.emit("ERROR", {
|
|
7382
|
-
err
|
|
7383
|
-
});
|
|
7384
|
-
}
|
|
7385
|
-
});
|
|
7386
|
-
stream.on("error", (err)=>{
|
|
7387
|
-
this.handleStreamError(url2Connect, err);
|
|
7388
|
-
});
|
|
7389
|
-
stream.on("drain", ()=>{
|
|
7390
|
-
this.handleStreamDrain(stream);
|
|
7391
|
-
});
|
|
7392
|
-
stream.on("preend", (args)=>{
|
|
7393
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *preend* event emitted with args: ${string_pretty(args)}`);
|
|
7394
|
-
});
|
|
7395
|
-
stream.on("end", ()=>{
|
|
7396
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *end* event emitted`);
|
|
7397
|
-
});
|
|
7398
|
-
stream.on("pipe", (src)=>{
|
|
7399
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *pipe* event emitted with src: ${string_pretty(src)}`);
|
|
7400
|
-
});
|
|
7401
|
-
stream.on("unpipe", (src)=>{
|
|
7402
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *unpipe* event emitted with src: ${string_pretty(src)}`);
|
|
7403
|
-
});
|
|
7404
|
-
stream.on("resume", (args)=>{
|
|
7405
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *resume* event emitted with args: ${string_pretty(args)}`);
|
|
7406
|
-
});
|
|
7407
|
-
stream.on("uncork", (args)=>{
|
|
7408
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *uncork* event emitted with args: ${string_pretty(args)}`);
|
|
7409
|
-
});
|
|
7410
|
-
stream.on("readable", (args)=>{
|
|
7411
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *readable* event emitted with args: ${string_pretty(args)}`);
|
|
7412
|
-
});
|
|
7413
|
-
stream.on("prefinish", (args)=>{
|
|
7414
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *prefinish* event emitted with args: ${string_pretty(args)}`);
|
|
7415
|
-
});
|
|
7416
|
-
stream.on("finish", (args)=>{
|
|
7417
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *finish* event emitted with args: ${string_pretty(args)}`);
|
|
7789
|
+
throw error_createError({
|
|
7790
|
+
message: "Webcam has no audio",
|
|
7791
|
+
exc,
|
|
7792
|
+
options: this.options
|
|
7418
7793
|
});
|
|
7419
7794
|
}
|
|
7795
|
+
let { bufferSize } = this.options.audio;
|
|
7796
|
+
if ("auto" === bufferSize) bufferSize = util_getBrowser(this.options).isFirefox() ? 512 : 2048;
|
|
7797
|
+
if (void 0 === bufferSize || !is_power_of_two(bufferSize)) throw error_createError({
|
|
7798
|
+
message: `Audio buffer size must be a power of two. The current buffer size is ${bufferSize}.`,
|
|
7799
|
+
options: this.options
|
|
7800
|
+
});
|
|
7801
|
+
if (!Number.isFinite(this.options.audio.volume) || this.options.audio.volume < 0 || this.options.audio.volume > 1) throw error_createError({
|
|
7802
|
+
message: `Audio volume must be between zero and one. The current volume is ${this.options.audio.volume}.`,
|
|
7803
|
+
options: this.options
|
|
7804
|
+
});
|
|
7805
|
+
volume.gain.value = this.options.audio.volume;
|
|
7806
|
+
this.scriptProcessor = this.getAudioContext().createScriptProcessor(bufferSize, CHANNELS, CHANNELS);
|
|
7807
|
+
this.audioInput.connect(volume);
|
|
7808
|
+
volume.connect(this.scriptProcessor);
|
|
7809
|
+
this.scriptProcessor.connect(this.getAudioContext().destination);
|
|
7420
7810
|
}
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
if (notifying) return true;
|
|
7427
|
-
return this.blocking;
|
|
7811
|
+
record(cb) {
|
|
7812
|
+
this.options.logger.debug("AudioRecorder: record()");
|
|
7813
|
+
if (this.scriptProcessor) this.scriptProcessor.onaudioprocess = (e)=>{
|
|
7814
|
+
this.onAudioProcess(e, cb);
|
|
7815
|
+
};
|
|
7428
7816
|
}
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
if (
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
this.
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
usedConstraints
|
|
7442
|
-
});
|
|
7443
|
-
this.emit("ERROR", {
|
|
7444
|
-
err: videomailError
|
|
7445
|
-
});
|
|
7446
|
-
} else {
|
|
7447
|
-
this.options.logger.debug(`Recorder: ignore user media error ${string_pretty(err)}`);
|
|
7448
|
-
this.retryTimeout = window.setTimeout(()=>{
|
|
7449
|
-
this.retryTimeout = void 0;
|
|
7450
|
-
if (!this.unloaded) this.loadUserMedia();
|
|
7451
|
-
}, this.options.timeouts.userMedia);
|
|
7452
|
-
}
|
|
7453
|
-
else {
|
|
7454
|
-
this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${string_pretty(err)}`);
|
|
7455
|
-
throw error_createError({
|
|
7817
|
+
async resume() {
|
|
7818
|
+
const context = this.getAudioContext();
|
|
7819
|
+
if ("running" !== context.state) await context.resume();
|
|
7820
|
+
}
|
|
7821
|
+
stop() {
|
|
7822
|
+
this.options.logger.debug("AudioRecorder: stop()");
|
|
7823
|
+
this.disconnect();
|
|
7824
|
+
if (this.hasAudioContext()) this.getAudioContext().close().then(()=>{
|
|
7825
|
+
this.options.logger.debug("AudioRecorder: audio context is closed");
|
|
7826
|
+
this.vcAudioContext = void 0;
|
|
7827
|
+
}).catch((err)=>{
|
|
7828
|
+
if (err instanceof Error) return void this.options.logger.error(error_createError({
|
|
7456
7829
|
err,
|
|
7457
|
-
message: "Unable to process this error since there are no error listeners anymore.",
|
|
7458
7830
|
options: this.options
|
|
7459
|
-
});
|
|
7460
|
-
|
|
7831
|
+
}));
|
|
7832
|
+
this.options.logger.error(err);
|
|
7833
|
+
});
|
|
7461
7834
|
}
|
|
7462
|
-
|
|
7463
|
-
if (
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
this
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7835
|
+
getSampleRate() {
|
|
7836
|
+
if (this.hasAudioContext()) return this.getAudioContext().sampleRate;
|
|
7837
|
+
return -1;
|
|
7838
|
+
}
|
|
7839
|
+
constructor(userMedia, options){
|
|
7840
|
+
AudioRecorder_define_property(this, "scriptProcessor", void 0);
|
|
7841
|
+
AudioRecorder_define_property(this, "audioInput", void 0);
|
|
7842
|
+
AudioRecorder_define_property(this, "volume", void 0);
|
|
7843
|
+
AudioRecorder_define_property(this, "vcAudioContext", void 0);
|
|
7844
|
+
AudioRecorder_define_property(this, "userMedia", void 0);
|
|
7845
|
+
AudioRecorder_define_property(this, "options", void 0);
|
|
7846
|
+
this.options = options;
|
|
7847
|
+
this.userMedia = userMedia;
|
|
7848
|
+
}
|
|
7849
|
+
}
|
|
7850
|
+
const media_AudioRecorder = AudioRecorder;
|
|
7851
|
+
const VIRTUAL_KEYWORDS = [
|
|
7852
|
+
"obs",
|
|
7853
|
+
"virtual",
|
|
7854
|
+
"manycam",
|
|
7855
|
+
"vcam",
|
|
7856
|
+
"splitcam",
|
|
7857
|
+
"droidcam"
|
|
7858
|
+
];
|
|
7859
|
+
function isVirtualCamera(videoTrack) {
|
|
7860
|
+
const capabilities = videoTrack.getCapabilities();
|
|
7861
|
+
if (!capabilities.frameRate) return true;
|
|
7862
|
+
const isVirtual = VIRTUAL_KEYWORDS.some((keyword)=>videoTrack.label.toLowerCase().includes(keyword));
|
|
7863
|
+
return isVirtual;
|
|
7864
|
+
}
|
|
7865
|
+
const util_isVirtualCamera = isVirtualCamera;
|
|
7866
|
+
function userMedia_define_property(obj, key, value) {
|
|
7867
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
7868
|
+
value: value,
|
|
7869
|
+
enumerable: true,
|
|
7870
|
+
configurable: true,
|
|
7871
|
+
writable: true
|
|
7872
|
+
});
|
|
7873
|
+
else obj[key] = value;
|
|
7874
|
+
return obj;
|
|
7875
|
+
}
|
|
7876
|
+
const EVENT_ASCII = "|—O—|";
|
|
7877
|
+
class UserMedia extends util_Despot {
|
|
7878
|
+
attachMediaStream(stream) {
|
|
7879
|
+
if (this.rawVisualUserMedia) {
|
|
7880
|
+
this.rawVisualUserMedia.srcObject = stream;
|
|
7881
|
+
const previousStream = this.currentVisualStream;
|
|
7882
|
+
this.currentVisualStream = stream;
|
|
7883
|
+
if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
|
|
7483
7884
|
track.stop();
|
|
7484
7885
|
});
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
this
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7886
|
+
} else throw error_createError({
|
|
7887
|
+
message: "Error attaching stream to element.",
|
|
7888
|
+
explanation: "Contact the developer about this",
|
|
7889
|
+
options: this.options
|
|
7890
|
+
});
|
|
7891
|
+
}
|
|
7892
|
+
setVisualStream(localMediaStream) {
|
|
7893
|
+
if (localMediaStream) this.attachMediaStream(localMediaStream);
|
|
7491
7894
|
else {
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
this.
|
|
7497
|
-
this.cancelCameraRecovery();
|
|
7895
|
+
if (this.rawVisualUserMedia) {
|
|
7896
|
+
this.rawVisualUserMedia.srcObject = null;
|
|
7897
|
+
this.rawVisualUserMedia.removeAttribute("src");
|
|
7898
|
+
}
|
|
7899
|
+
this.currentVisualStream = void 0;
|
|
7498
7900
|
}
|
|
7499
7901
|
}
|
|
7500
|
-
|
|
7501
|
-
this.
|
|
7502
|
-
this.
|
|
7503
|
-
this.
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
this.
|
|
7516
|
-
const
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7902
|
+
async restorePlayback() {
|
|
7903
|
+
if (!this.rawVisualUserMedia) throw new Error("No camera preview is available.");
|
|
7904
|
+
await this.rawVisualUserMedia.play();
|
|
7905
|
+
await this.audioRecorder?.resume();
|
|
7906
|
+
}
|
|
7907
|
+
hasLiveStream() {
|
|
7908
|
+
const videoReady = this.currentVisualStream?.getVideoTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
|
|
7909
|
+
const audioReady = !isAudioEnabled(this.options) || this.currentVisualStream?.getAudioTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
|
|
7910
|
+
return Boolean(this.currentVisualStream?.active && videoReady && audioReady);
|
|
7911
|
+
}
|
|
7912
|
+
hasEnded() {
|
|
7913
|
+
if (this.rawVisualUserMedia?.ended) return this.rawVisualUserMedia.ended;
|
|
7914
|
+
return !this.currentVisualStream?.active;
|
|
7915
|
+
}
|
|
7916
|
+
hasInvalidDimensions() {
|
|
7917
|
+
if (!this.rawVisualUserMedia) return false;
|
|
7918
|
+
const { videoWidth, videoHeight } = this.rawVisualUserMedia;
|
|
7919
|
+
return videoWidth > 0 && videoWidth < 3 || videoHeight > 0 && videoHeight < 3;
|
|
7920
|
+
}
|
|
7921
|
+
logEvent(eventType, params) {
|
|
7922
|
+
this.options.logger.debug(`UserMedia: ... ${EVENT_ASCII} event ${eventType}: ${string_pretty(params)}`);
|
|
7923
|
+
}
|
|
7924
|
+
unloadRemainingEventListeners() {
|
|
7925
|
+
this.options.logger.debug("UserMedia: unloadRemainingEventListeners()");
|
|
7926
|
+
this.clearInitializationListeners?.();
|
|
7927
|
+
mediaEvents.forEach((eventName)=>{
|
|
7928
|
+
this.rawVisualUserMedia?.removeEventListener(eventName, this.outputEvent);
|
|
7524
7929
|
});
|
|
7930
|
+
this.rawVisualUserMedia?.removeEventListener("error", this.handleMediaError);
|
|
7931
|
+
}
|
|
7932
|
+
audioRecord(audioCallback) {
|
|
7933
|
+
this.removeListener("SENDING_FIRST_FRAME");
|
|
7934
|
+
this.audioRecorder?.record(audioCallback);
|
|
7525
7935
|
}
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7936
|
+
init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, params) {
|
|
7937
|
+
this.clearInitializationListeners?.();
|
|
7938
|
+
this.stop(localMediaStream, {
|
|
7939
|
+
aboutToInitialize: true,
|
|
7940
|
+
preserveRecordingState: Boolean(params?.switchingFacingMode || params?.recovering)
|
|
7941
|
+
});
|
|
7942
|
+
const initializationId = ++this.initializationId;
|
|
7943
|
+
this.onPlayReached = false;
|
|
7944
|
+
this.onLoadedMetaDataReached = false;
|
|
7945
|
+
this.playingPromiseReached = false;
|
|
7946
|
+
this.videoTrackLabel = void 0;
|
|
7947
|
+
if (isAudioEnabled(this.options)) this.audioRecorder ??= new media_AudioRecorder(this, this.options);
|
|
7948
|
+
const unloadAllEventListeners = ()=>{
|
|
7949
|
+
this.options.logger.debug("UserMedia: unloadAllEventListeners()");
|
|
7950
|
+
this.unloadRemainingEventListeners();
|
|
7951
|
+
this.removeListener("SENDING_FIRST_FRAME");
|
|
7952
|
+
this.rawVisualUserMedia?.removeEventListener("play", onPlay);
|
|
7953
|
+
this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
|
|
7954
|
+
};
|
|
7955
|
+
this.clearInitializationListeners = ()=>{
|
|
7956
|
+
this.rawVisualUserMedia?.removeEventListener("play", onPlay);
|
|
7957
|
+
this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
|
|
7958
|
+
this.clearInitializationListeners = void 0;
|
|
7959
|
+
};
|
|
7960
|
+
const play = ()=>{
|
|
7961
|
+
try {
|
|
7962
|
+
this.rawVisualUserMedia?.load();
|
|
7963
|
+
if (this.rawVisualUserMedia?.paused) {
|
|
7964
|
+
this.options.logger.debug(`UserMedia: play(): media.readyState=${this.rawVisualUserMedia.readyState}, media.paused=${this.rawVisualUserMedia.paused}, media.ended=${this.rawVisualUserMedia.ended}, media.played=${string_pretty(this.rawVisualUserMedia.played)}`);
|
|
7965
|
+
this.rawVisualUserMedia.play().then(()=>{
|
|
7966
|
+
if (initializationId !== this.initializationId) return;
|
|
7967
|
+
if (!this.playingPromiseReached) {
|
|
7968
|
+
this.options.logger.debug("UserMedia: play promise successful. Playing now.");
|
|
7969
|
+
this.playingPromiseReached = true;
|
|
7970
|
+
}
|
|
7971
|
+
}).catch((exc)=>{
|
|
7972
|
+
if (initializationId !== this.initializationId) return;
|
|
7973
|
+
if (exc instanceof Error && !params?.recovering) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
|
|
7974
|
+
else {
|
|
7975
|
+
unloadAllEventListeners();
|
|
7976
|
+
endedEarlyCallback(error_createError({
|
|
7977
|
+
message: "Failed to play user media upon play event.",
|
|
7978
|
+
exc,
|
|
7979
|
+
options: this.options
|
|
7980
|
+
}));
|
|
7981
|
+
}
|
|
7982
|
+
});
|
|
7983
|
+
}
|
|
7984
|
+
} catch (exc) {
|
|
7985
|
+
unloadAllEventListeners();
|
|
7986
|
+
endedEarlyCallback(exc);
|
|
7987
|
+
}
|
|
7988
|
+
};
|
|
7989
|
+
const fireCallbacks = ()=>{
|
|
7990
|
+
const readyState = this.rawVisualUserMedia?.readyState;
|
|
7991
|
+
this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
|
|
7992
|
+
if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) try {
|
|
7993
|
+
this.audioRecorder.init(localMediaStream);
|
|
7994
|
+
const onAudioReady = ()=>{
|
|
7995
|
+
if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
|
|
7996
|
+
if (this.recording) this.audioRecord(audioCallback);
|
|
7997
|
+
else this.on("SENDING_FIRST_FRAME", ()=>{
|
|
7998
|
+
this.audioRecord(audioCallback);
|
|
7999
|
+
});
|
|
8000
|
+
this.clearInitializationListeners?.();
|
|
8001
|
+
videoCallback();
|
|
8002
|
+
};
|
|
8003
|
+
if (params?.recovering) this.audioRecorder.resume().then(onAudioReady).catch(endedEarlyCallback);
|
|
8004
|
+
else onAudioReady();
|
|
8005
|
+
} catch (exc) {
|
|
8006
|
+
unloadAllEventListeners();
|
|
8007
|
+
this.stop(void 0, {
|
|
8008
|
+
preserveRecordingState: Boolean(params?.recovering)
|
|
8009
|
+
});
|
|
8010
|
+
endedEarlyCallback(exc);
|
|
8011
|
+
}
|
|
8012
|
+
else {
|
|
8013
|
+
this.clearInitializationListeners?.();
|
|
8014
|
+
videoCallback();
|
|
8015
|
+
}
|
|
8016
|
+
};
|
|
8017
|
+
const onPlay = ()=>{
|
|
8018
|
+
try {
|
|
8019
|
+
this.logEvent("play", {
|
|
8020
|
+
readyState: this.rawVisualUserMedia?.readyState,
|
|
8021
|
+
audio: isAudioEnabled(this.options),
|
|
8022
|
+
width: this.rawVisualUserMedia?.width,
|
|
8023
|
+
height: this.rawVisualUserMedia?.height,
|
|
8024
|
+
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
8025
|
+
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
8026
|
+
});
|
|
8027
|
+
this.rawVisualUserMedia?.removeEventListener("play", onPlay);
|
|
8028
|
+
if (this.hasEnded() || this.hasInvalidDimensions()) endedEarlyCallback(error_createError({
|
|
8029
|
+
message: "Already busy",
|
|
8030
|
+
explanation: "Probably another browser window is using your webcam?",
|
|
8031
|
+
options: this.options
|
|
8032
|
+
}));
|
|
8033
|
+
else {
|
|
8034
|
+
this.onPlayReached = true;
|
|
8035
|
+
fireCallbacks();
|
|
8036
|
+
}
|
|
8037
|
+
} catch (exc) {
|
|
8038
|
+
unloadAllEventListeners();
|
|
8039
|
+
endedEarlyCallback(exc);
|
|
8040
|
+
}
|
|
7532
8041
|
};
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
8042
|
+
const onLoadedMetaData = ()=>{
|
|
8043
|
+
this.logEvent("loadedmetadata", {
|
|
8044
|
+
readyState: this.rawVisualUserMedia?.readyState,
|
|
8045
|
+
paused: this.rawVisualUserMedia?.paused,
|
|
8046
|
+
width: this.rawVisualUserMedia?.width,
|
|
8047
|
+
height: this.rawVisualUserMedia?.height,
|
|
8048
|
+
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
8049
|
+
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
8050
|
+
});
|
|
8051
|
+
this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
|
|
8052
|
+
if (!this.hasEnded() && !this.hasInvalidDimensions()) {
|
|
8053
|
+
this.emit("LOADED_META_DATA");
|
|
8054
|
+
this.onLoadedMetaDataReached = true;
|
|
8055
|
+
fireCallbacks();
|
|
8056
|
+
}
|
|
7541
8057
|
};
|
|
7542
|
-
}
|
|
7543
|
-
loadUserMedia(params) {
|
|
7544
|
-
if (this.userMediaLoaded) {
|
|
7545
|
-
this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already loaded");
|
|
7546
|
-
this.onUserMediaReady(params);
|
|
7547
|
-
return;
|
|
7548
|
-
} else if (this.userMediaLoading) return void this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already asking for permission");
|
|
7549
|
-
this.options.logger.debug(`Recorder: loadUserMedia(${params ? string_pretty(params) : ""})`);
|
|
7550
|
-
this.emit("LOADING_USER_MEDIA");
|
|
7551
8058
|
try {
|
|
7552
|
-
|
|
7553
|
-
this.
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
audio: isAudioEnabled(this.options)
|
|
7559
|
-
});
|
|
7560
|
-
}
|
|
7561
|
-
}, this.options.timeouts.userMedia);
|
|
7562
|
-
this.userMediaLoading = true;
|
|
7563
|
-
this.loadGenuineUserMedia(params);
|
|
7564
|
-
} catch (exc) {
|
|
7565
|
-
this.clearUserMediaTimeout();
|
|
7566
|
-
this.options.logger.debug("Recorder: failed to load genuine user media");
|
|
7567
|
-
this.userMediaLoading = false;
|
|
7568
|
-
if (this.cameraRecovery) return void this.failCameraRecovery(exc);
|
|
7569
|
-
const errorListeners = this.getListeners("ERROR");
|
|
7570
|
-
if (errorListeners?.length) this.emit("ERROR", {
|
|
7571
|
-
exc
|
|
8059
|
+
const videoTrack = media_getFirstVideoTrack(localMediaStream);
|
|
8060
|
+
this.videoTrackLabel = videoTrack?.label;
|
|
8061
|
+
if (videoTrack) if (videoTrack.enabled) if (util_isVirtualCamera(videoTrack)) throw error_createError({
|
|
8062
|
+
message: "Virtual cameras are not allowed.",
|
|
8063
|
+
explanation: `Please use a real camera, not "${videoTrack.label}".`,
|
|
8064
|
+
options: this.options
|
|
7572
8065
|
});
|
|
7573
8066
|
else {
|
|
7574
|
-
|
|
7575
|
-
|
|
8067
|
+
let description = "";
|
|
8068
|
+
if (videoTrack.label.length > 0) description = description.concat(videoTrack.label);
|
|
8069
|
+
description = description.concat(` with enabled=${videoTrack.enabled}, muted=${videoTrack.muted}, readyState=${videoTrack.readyState}`);
|
|
8070
|
+
this.options.logger.debug(`UserMedia: ${videoTrack.kind} detected. ${description}`);
|
|
7576
8071
|
}
|
|
8072
|
+
else throw error_createError({
|
|
8073
|
+
message: "Webcam is disabled",
|
|
8074
|
+
explanation: "The video track seems to be disabled. Enable it in your system.",
|
|
8075
|
+
options: this.options
|
|
8076
|
+
});
|
|
8077
|
+
else this.options.logger.debug("UserMedia: detected (but no video tracks exist");
|
|
8078
|
+
this.rawVisualUserMedia?.addEventListener("loadedmetadata", onLoadedMetaData);
|
|
8079
|
+
this.rawVisualUserMedia?.addEventListener("play", onPlay);
|
|
8080
|
+
this.rawVisualUserMedia?.removeEventListener("error", this.handleMediaError);
|
|
8081
|
+
this.rawVisualUserMedia?.addEventListener("error", this.handleMediaError);
|
|
8082
|
+
this.setVisualStream(localMediaStream);
|
|
8083
|
+
play();
|
|
8084
|
+
} catch (exc) {
|
|
8085
|
+
unloadAllEventListeners();
|
|
8086
|
+
endedEarlyCallback(exc);
|
|
7577
8087
|
}
|
|
7578
8088
|
}
|
|
7579
|
-
|
|
7580
|
-
|
|
8089
|
+
getVideoTrackLabel() {
|
|
8090
|
+
return this.videoTrackLabel;
|
|
8091
|
+
}
|
|
8092
|
+
isReady() {
|
|
8093
|
+
return Boolean(this.rawVisualUserMedia?.srcObject || this.rawVisualUserMedia?.currentSrc);
|
|
8094
|
+
}
|
|
8095
|
+
stop(visualStream, params) {
|
|
7581
8096
|
try {
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
this.
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
break;
|
|
7595
|
-
case "error":
|
|
7596
|
-
{
|
|
7597
|
-
let explanation = "(No explanation given)";
|
|
7598
|
-
if (command.args?.err?.message) explanation = command.args.err.message;
|
|
7599
|
-
const err = error_createError({
|
|
7600
|
-
message: "Websocket error from Videomail server",
|
|
7601
|
-
explanation,
|
|
7602
|
-
err: deserializeError(command.args?.err),
|
|
7603
|
-
options: this.options
|
|
7604
|
-
});
|
|
7605
|
-
this.emit("ERROR", {
|
|
7606
|
-
err
|
|
7607
|
-
});
|
|
7608
|
-
break;
|
|
7609
|
-
}
|
|
7610
|
-
case "confirmFrame":
|
|
7611
|
-
this.updateFrameProgress(command.args);
|
|
7612
|
-
break;
|
|
7613
|
-
case "confirmSample":
|
|
7614
|
-
this.updateSampleProgress(command.args);
|
|
7615
|
-
break;
|
|
7616
|
-
case "beginAudioEncoding":
|
|
7617
|
-
this.emit("BEGIN_AUDIO_ENCODING");
|
|
7618
|
-
break;
|
|
7619
|
-
case "beginVideoEncoding":
|
|
7620
|
-
this.emit("BEGIN_VIDEO_ENCODING");
|
|
7621
|
-
break;
|
|
7622
|
-
default:
|
|
7623
|
-
{
|
|
7624
|
-
const err = error_createError({
|
|
7625
|
-
message: `Unknown server command: ${command.command}`,
|
|
7626
|
-
options: this.options
|
|
7627
|
-
});
|
|
7628
|
-
this.emit("ERROR", {
|
|
7629
|
-
err
|
|
7630
|
-
});
|
|
7631
|
-
break;
|
|
7632
|
-
}
|
|
8097
|
+
let chosenStream = visualStream;
|
|
8098
|
+
if (!params?.aboutToInitialize) {
|
|
8099
|
+
this.initializationId++;
|
|
8100
|
+
this.clearInitializationListeners?.();
|
|
8101
|
+
chosenStream ??= this.currentVisualStream;
|
|
8102
|
+
const tracks = chosenStream?.getTracks();
|
|
8103
|
+
if (tracks) tracks.forEach((track)=>{
|
|
8104
|
+
track.stop();
|
|
8105
|
+
});
|
|
8106
|
+
this.setVisualStream();
|
|
8107
|
+
this.audioRecorder?.stop();
|
|
8108
|
+
this.audioRecorder = void 0;
|
|
7633
8109
|
}
|
|
8110
|
+
if (!params?.preserveRecordingState) this.paused = this.recording = false;
|
|
7634
8111
|
} catch (exc) {
|
|
7635
8112
|
this.emit("ERROR", {
|
|
7636
8113
|
exc
|
|
7637
8114
|
});
|
|
7638
8115
|
}
|
|
7639
8116
|
}
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
if (this.connected) {
|
|
7648
|
-
if (this.stream) {
|
|
7649
|
-
if (args) this.options.logger.debug(`$ ${command} with ${string_pretty(args)}`);
|
|
7650
|
-
else this.options.logger.debug(`$ ${command}`);
|
|
7651
|
-
const commandObj = {
|
|
7652
|
-
command,
|
|
7653
|
-
args
|
|
7654
|
-
};
|
|
7655
|
-
this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
|
|
7656
|
-
onFlushedCallback: ()=>{
|
|
7657
|
-
queueMicrotask(cb);
|
|
7658
|
-
}
|
|
7659
|
-
} : void 0);
|
|
7660
|
-
}
|
|
7661
|
-
} else {
|
|
7662
|
-
this.options.logger.debug(`Reconnecting for the command ${command} …`);
|
|
7663
|
-
this.initSocket(()=>{
|
|
7664
|
-
this.writeCommand(command, args, cb);
|
|
7665
|
-
});
|
|
7666
|
-
}
|
|
7667
|
-
}
|
|
7668
|
-
cancelAnimationFrame() {
|
|
7669
|
-
this.loop?.dispose();
|
|
7670
|
-
}
|
|
7671
|
-
getIntervalSum() {
|
|
7672
|
-
if (!this.loop) return;
|
|
7673
|
-
return this.loop.getElapsedTime();
|
|
7674
|
-
}
|
|
7675
|
-
getAvgInterval() {
|
|
7676
|
-
const intervalSum = this.getIntervalSum() ?? 0;
|
|
7677
|
-
if (!this.hasValidInterval(intervalSum)) return;
|
|
7678
|
-
return intervalSum / this.framesCount;
|
|
7679
|
-
}
|
|
7680
|
-
getAvgFps() {
|
|
7681
|
-
const intervalSum = this.getIntervalSum() ?? 0;
|
|
7682
|
-
if (!this.hasValidInterval(intervalSum)) return;
|
|
7683
|
-
return this.framesCount / intervalSum * 1000;
|
|
7684
|
-
}
|
|
7685
|
-
hasValidInterval(intervalSum) {
|
|
7686
|
-
return intervalSum > 0;
|
|
7687
|
-
}
|
|
7688
|
-
getRecordingStats() {
|
|
7689
|
-
return this.recordingStats;
|
|
7690
|
-
}
|
|
7691
|
-
getAudioSampleRate() {
|
|
7692
|
-
return this.recordingAudioSampleRate ?? this.userMedia?.getAudioSampleRate();
|
|
7693
|
-
}
|
|
7694
|
-
stop(params) {
|
|
7695
|
-
this.cancelCameraRecovery();
|
|
7696
|
-
if (!this.userMedia) throw new Error("No user media defined, unable to stop");
|
|
7697
|
-
this.options.logger.debug(`Recorder: stop(${params ? string_pretty(params) : ""})`);
|
|
7698
|
-
this.recordingActive = false;
|
|
7699
|
-
const limitReached = params?.limitReached;
|
|
7700
|
-
this.emit("STOPPING", {
|
|
7701
|
-
limitReached
|
|
7702
|
-
});
|
|
7703
|
-
this.loop?.complete();
|
|
7704
|
-
this.stopTimeout = window.setTimeout(()=>{
|
|
7705
|
-
this.stopTimeout = void 0;
|
|
7706
|
-
this.stopTime = Date.now();
|
|
7707
|
-
const videoType = this.replay.getVideoType();
|
|
7708
|
-
if (!videoType) throw new Error("Unable to video record when no video type is defined.");
|
|
7709
|
-
this.recordingStats = {
|
|
7710
|
-
avgFps: this.getAvgFps(),
|
|
7711
|
-
wantedFps: this.options.video.fps,
|
|
7712
|
-
avgInterval: this.getAvgInterval(),
|
|
7713
|
-
wantedInterval: 1e3 / this.options.video.fps,
|
|
7714
|
-
intervalSum: this.getIntervalSum(),
|
|
7715
|
-
framesCount: this.framesCount,
|
|
7716
|
-
videoType
|
|
7717
|
-
};
|
|
7718
|
-
if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
|
|
7719
|
-
if (isAudioEnabled(this.options) && this.userMedia) {
|
|
7720
|
-
this.recordingStats.samplesCount = this.samplesCount;
|
|
7721
|
-
this.recordingStats.sampleRate = this.recordingAudioSampleRate ?? this.userMedia.getAudioSampleRate();
|
|
7722
|
-
}
|
|
7723
|
-
this.writeCommand("stop", this.recordingStats, ()=>{
|
|
7724
|
-
this.emit("STOPPED", {
|
|
7725
|
-
recordingStats: this.recordingStats
|
|
7726
|
-
});
|
|
7727
|
-
});
|
|
7728
|
-
this.reset();
|
|
7729
|
-
}, 60);
|
|
8117
|
+
createCanvas() {
|
|
8118
|
+
const canvas = document.createElement("canvas");
|
|
8119
|
+
const rawWidthDimension = this.getRawWidth(true);
|
|
8120
|
+
if (rawWidthDimension.value) canvas.width = rawWidthDimension.value;
|
|
8121
|
+
const rawHeight = this.getRawHeight(true);
|
|
8122
|
+
if (rawHeight) canvas.height = rawHeight;
|
|
8123
|
+
return canvas;
|
|
7730
8124
|
}
|
|
7731
|
-
|
|
7732
|
-
this.
|
|
7733
|
-
this.
|
|
7734
|
-
this.recordingDimensions = void 0;
|
|
7735
|
-
this.unloaded = false;
|
|
7736
|
-
this.show();
|
|
7737
|
-
this.writeCommand("back", void 0, cb);
|
|
8125
|
+
getVideoHeight() {
|
|
8126
|
+
if (!this.rawVisualUserMedia) return;
|
|
8127
|
+
return this.rawVisualUserMedia.videoHeight || this.rawVisualUserMedia.height;
|
|
7738
8128
|
}
|
|
7739
|
-
|
|
7740
|
-
this.
|
|
7741
|
-
this.
|
|
7742
|
-
this.userMediaLoading = false;
|
|
7743
|
-
this.userMedia?.stop();
|
|
7744
|
-
this.recordingDimensions = void 0;
|
|
7745
|
-
this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
|
|
7746
|
-
this.loadUserMedia();
|
|
8129
|
+
getVideoWidth() {
|
|
8130
|
+
if (!this.rawVisualUserMedia) return;
|
|
8131
|
+
return this.rawVisualUserMedia.videoWidth || this.rawVisualUserMedia.width;
|
|
7747
8132
|
}
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
let cause;
|
|
7752
|
-
if (e) cause = e.type;
|
|
7753
|
-
const prettyCause = cause ? `, cause: ${cause}` : "";
|
|
7754
|
-
this.options.logger.debug(`Recorder: unload()${prettyCause}`);
|
|
7755
|
-
this.recordingActive = false;
|
|
7756
|
-
this.mediaTransmissionStarted = false;
|
|
7757
|
-
this.reset();
|
|
7758
|
-
this.recordingDimensions = void 0;
|
|
7759
|
-
this.clearUserMediaTimeout();
|
|
7760
|
-
this.clearConnectionTimeout();
|
|
7761
|
-
this.clearReconnectTimeout();
|
|
7762
|
-
this.clearRetryTimeout();
|
|
7763
|
-
this.clearStopTimeout();
|
|
7764
|
-
this.stopPings();
|
|
7765
|
-
this.userMediaLoading = false;
|
|
7766
|
-
this.connecting = false;
|
|
7767
|
-
this.reconnecting = false;
|
|
7768
|
-
this.reconnectAttempts = 0;
|
|
7769
|
-
if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
|
|
7770
|
-
if (this.submitting) ;
|
|
7771
|
-
else if (this.stream) this.discardSocket();
|
|
7772
|
-
this.unloaded = true;
|
|
7773
|
-
this.built = this.connecting = this.connected = false;
|
|
8133
|
+
hasVideoWidth() {
|
|
8134
|
+
const videoWidth = this.getVideoWidth();
|
|
8135
|
+
return videoWidth && videoWidth > 0;
|
|
7774
8136
|
}
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
this.
|
|
7783
|
-
|
|
8137
|
+
getRawWidth(responsive) {
|
|
8138
|
+
let rawWidth;
|
|
8139
|
+
const widthDimension = {
|
|
8140
|
+
unit: "px"
|
|
8141
|
+
};
|
|
8142
|
+
rawWidth = responsive ? this.getVideoWidth() : this.options.video.width ? this.options.video.width : this.getVideoWidth();
|
|
8143
|
+
if (responsive) {
|
|
8144
|
+
const widthDimension = this.recorder.limitWidth(rawWidth);
|
|
8145
|
+
rawWidth = widthDimension?.value;
|
|
7784
8146
|
}
|
|
8147
|
+
widthDimension.value = rawWidth;
|
|
8148
|
+
return widthDimension;
|
|
7785
8149
|
}
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
8150
|
+
getRawHeight(responsive) {
|
|
8151
|
+
let rawHeight;
|
|
8152
|
+
if (responsive) {
|
|
8153
|
+
rawHeight = this.options.video.height || this.getVideoHeight();
|
|
8154
|
+
const ratio = this.recorder.getRatio();
|
|
8155
|
+
const rawWidth = this.getRawWidth(responsive);
|
|
8156
|
+
if (void 0 !== ratio && rawWidth.value) rawHeight = rawWidth.value * ratio;
|
|
8157
|
+
} else rawHeight = this.options.video.height ? this.options.video.height : this.getVideoHeight();
|
|
8158
|
+
if (responsive) {
|
|
8159
|
+
const heightDimension = this.recorder.limitHeight(rawHeight);
|
|
8160
|
+
rawHeight = heightDimension.value;
|
|
7791
8161
|
}
|
|
8162
|
+
return rawHeight;
|
|
7792
8163
|
}
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
window.clearTimeout(this.stopTimeout);
|
|
7796
|
-
this.stopTimeout = void 0;
|
|
7797
|
-
}
|
|
7798
|
-
validate() {
|
|
7799
|
-
return Boolean(this.key) && void 0 === this.canvas;
|
|
8164
|
+
getRawVisuals() {
|
|
8165
|
+
return this.rawVisualUserMedia;
|
|
7800
8166
|
}
|
|
7801
|
-
|
|
7802
|
-
|
|
8167
|
+
pause() {
|
|
8168
|
+
this.paused = true;
|
|
7803
8169
|
}
|
|
7804
|
-
|
|
7805
|
-
this.
|
|
7806
|
-
const resolve = this.resolveCameraRecovery;
|
|
7807
|
-
const resumeRequested = restored && this.resumeAfterCameraRecovery;
|
|
7808
|
-
this.cameraRecovery = void 0;
|
|
7809
|
-
this.resolveCameraRecovery = void 0;
|
|
7810
|
-
this.resumeAfterCameraRecovery = false;
|
|
7811
|
-
resolve?.(restored);
|
|
7812
|
-
if (resumeRequested && this.isPaused()) this.resume();
|
|
8170
|
+
isPaused() {
|
|
8171
|
+
return this.paused;
|
|
7813
8172
|
}
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
this.mediaRequestId++;
|
|
7817
|
-
this.userMediaLoading = false;
|
|
7818
|
-
this.userMediaLoaded = false;
|
|
7819
|
-
this.clearUserMediaTimeout();
|
|
7820
|
-
this.userMedia?.stop(void 0, {
|
|
7821
|
-
preserveRecordingState: true
|
|
7822
|
-
});
|
|
7823
|
-
this.finishCameraRecovery(false);
|
|
8173
|
+
resume() {
|
|
8174
|
+
this.paused = false;
|
|
7824
8175
|
}
|
|
7825
|
-
|
|
7826
|
-
this.
|
|
7827
|
-
const err = error_createError({
|
|
7828
|
-
message: "Camera recovery failed",
|
|
7829
|
-
explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
|
|
7830
|
-
exc,
|
|
7831
|
-
options: this.options
|
|
7832
|
-
});
|
|
7833
|
-
this.emit("CAMERA_RECOVERY_FAILED", {
|
|
7834
|
-
err
|
|
7835
|
-
});
|
|
8176
|
+
record() {
|
|
8177
|
+
this.recording = true;
|
|
7836
8178
|
}
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
|
|
7840
|
-
const liveStream = this.userMedia.hasLiveStream();
|
|
7841
|
-
if (liveStream && this.isRecording()) return Promise.resolve(true);
|
|
7842
|
-
if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
|
|
7843
|
-
const recovery = new Promise((resolve)=>{
|
|
7844
|
-
this.resolveCameraRecovery = resolve;
|
|
7845
|
-
});
|
|
7846
|
-
this.cameraRecovery = recovery;
|
|
7847
|
-
this.emit("CAMERA_RECOVERING");
|
|
7848
|
-
if (liveStream) {
|
|
7849
|
-
this.userMediaTimeout = window.setTimeout(()=>{
|
|
7850
|
-
this.failCameraRecovery(new Error("Camera playback recovery timed out."));
|
|
7851
|
-
}, this.options.timeouts.userMedia);
|
|
7852
|
-
this.userMedia.restorePlayback().then(()=>{
|
|
7853
|
-
if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
|
|
7854
|
-
}).catch((exc)=>{
|
|
7855
|
-
if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
|
|
7856
|
-
});
|
|
7857
|
-
} else {
|
|
7858
|
-
this.userMediaLoaded = false;
|
|
7859
|
-
this.loadUserMedia({
|
|
7860
|
-
recovering: true
|
|
7861
|
-
});
|
|
7862
|
-
}
|
|
7863
|
-
return recovery;
|
|
8179
|
+
isRecording() {
|
|
8180
|
+
return this.recording;
|
|
7864
8181
|
}
|
|
7865
|
-
|
|
7866
|
-
this.
|
|
7867
|
-
|
|
7868
|
-
if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
|
|
7869
|
-
else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
|
|
7870
|
-
this.userMedia?.pause();
|
|
7871
|
-
this.loop.stop();
|
|
7872
|
-
this.emit("PAUSED");
|
|
7873
|
-
this.sendPings();
|
|
8182
|
+
getAudioSampleRate() {
|
|
8183
|
+
if (this.audioRecorder) return this.audioRecorder.getSampleRate();
|
|
8184
|
+
return -1;
|
|
7874
8185
|
}
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
this.
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
this.userMedia.resume();
|
|
7885
|
-
if (this.streamBackpressured) this.resumeAfterDrain = true;
|
|
7886
|
-
else this.loop.start();
|
|
8186
|
+
getCharacteristics() {
|
|
8187
|
+
return {
|
|
8188
|
+
audioSampleRate: this.getAudioSampleRate(),
|
|
8189
|
+
muted: this.rawVisualUserMedia?.muted,
|
|
8190
|
+
width: this.rawVisualUserMedia?.width,
|
|
8191
|
+
height: this.rawVisualUserMedia?.height,
|
|
8192
|
+
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
8193
|
+
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
8194
|
+
};
|
|
7887
8195
|
}
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
8196
|
+
constructor(recorder, options){
|
|
8197
|
+
super("UserMedia", options), userMedia_define_property(this, "recorder", void 0), userMedia_define_property(this, "rawVisualUserMedia", void 0), userMedia_define_property(this, "paused", false), userMedia_define_property(this, "recording", false), userMedia_define_property(this, "audioRecorder", void 0), userMedia_define_property(this, "currentVisualStream", void 0), userMedia_define_property(this, "onPlayReached", false), userMedia_define_property(this, "onLoadedMetaDataReached", false), userMedia_define_property(this, "playingPromiseReached", false), userMedia_define_property(this, "videoTrackLabel", void 0), userMedia_define_property(this, "clearInitializationListeners", void 0), userMedia_define_property(this, "initializationId", 0), userMedia_define_property(this, "outputEvent", (e)=>{
|
|
8198
|
+
this.logEvent(e.type, {
|
|
8199
|
+
readyState: this.rawVisualUserMedia?.readyState
|
|
8200
|
+
});
|
|
8201
|
+
this.rawVisualUserMedia?.removeEventListener(e.type, this.outputEvent);
|
|
8202
|
+
}), userMedia_define_property(this, "handleMediaError", (event)=>{
|
|
8203
|
+
this.options.logger.warn(`Caught video element error event: ${string_pretty(event)}`);
|
|
8204
|
+
});
|
|
8205
|
+
this.recorder = recorder;
|
|
8206
|
+
this.rawVisualUserMedia = recorder.getRawVisualUserMedia();
|
|
8207
|
+
mediaEvents.forEach((eventName)=>{
|
|
8208
|
+
this.rawVisualUserMedia?.addEventListener(eventName, this.outputEvent, false);
|
|
8209
|
+
});
|
|
7891
8210
|
}
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
8211
|
+
}
|
|
8212
|
+
const visuals_userMedia = UserMedia;
|
|
8213
|
+
function recorder_define_property(obj, key, value) {
|
|
8214
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
8215
|
+
value: value,
|
|
8216
|
+
enumerable: true,
|
|
8217
|
+
configurable: true,
|
|
8218
|
+
writable: true
|
|
8219
|
+
});
|
|
8220
|
+
else obj[key] = value;
|
|
8221
|
+
return obj;
|
|
8222
|
+
}
|
|
8223
|
+
class Recorder extends capture {
|
|
8224
|
+
executeCommand(command) {
|
|
8225
|
+
if (this.unloaded) return;
|
|
7895
8226
|
try {
|
|
7896
|
-
if (
|
|
7897
|
-
|
|
7898
|
-
this.
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
8227
|
+
if (command.args) {
|
|
8228
|
+
const debugLine = string_summarize(`Server commanded: ${command.command} with:`, command.args);
|
|
8229
|
+
this.options.logger.debug(debugLine);
|
|
8230
|
+
} else this.options.logger.debug(`Server commanded: ${command.command}`);
|
|
8231
|
+
switch(command.command){
|
|
8232
|
+
case "ready":
|
|
8233
|
+
this.emit("SERVER_READY");
|
|
8234
|
+
if (!this.userMediaTimeout) if (this.options.loadUserMediaOnRecord) this.show();
|
|
8235
|
+
else this.loadUserMedia();
|
|
8236
|
+
break;
|
|
8237
|
+
case "preview":
|
|
8238
|
+
this.preview(command.args);
|
|
8239
|
+
break;
|
|
8240
|
+
case "error":
|
|
8241
|
+
{
|
|
8242
|
+
let explanation = "(No explanation given)";
|
|
8243
|
+
if (command.args?.err?.message) explanation = command.args.err.message;
|
|
8244
|
+
const err = error_createError({
|
|
8245
|
+
message: "Websocket error from Videomail server",
|
|
8246
|
+
explanation,
|
|
8247
|
+
err: deserializeError(command.args?.err),
|
|
8248
|
+
options: this.options
|
|
7917
8249
|
});
|
|
7918
|
-
this.
|
|
8250
|
+
this.emit("ERROR", {
|
|
8251
|
+
err
|
|
8252
|
+
});
|
|
8253
|
+
break;
|
|
8254
|
+
}
|
|
8255
|
+
case "confirmFrame":
|
|
8256
|
+
this.updateFrameProgress(command.args);
|
|
8257
|
+
break;
|
|
8258
|
+
case "confirmSample":
|
|
8259
|
+
this.updateSampleProgress(command.args);
|
|
8260
|
+
break;
|
|
8261
|
+
case "beginAudioEncoding":
|
|
8262
|
+
this.emit("BEGIN_AUDIO_ENCODING");
|
|
8263
|
+
break;
|
|
8264
|
+
case "beginVideoEncoding":
|
|
8265
|
+
this.emit("BEGIN_VIDEO_ENCODING");
|
|
8266
|
+
break;
|
|
8267
|
+
default:
|
|
8268
|
+
{
|
|
8269
|
+
const err = error_createError({
|
|
8270
|
+
message: `Unknown server command: ${command.command}`,
|
|
8271
|
+
options: this.options
|
|
8272
|
+
});
|
|
8273
|
+
this.emit("ERROR", {
|
|
8274
|
+
err
|
|
8275
|
+
});
|
|
8276
|
+
break;
|
|
7919
8277
|
}
|
|
7920
|
-
} else throw error_createError({
|
|
7921
|
-
message: "Failed to extract webcam data.",
|
|
7922
|
-
options: this.options
|
|
7923
|
-
});
|
|
7924
8278
|
}
|
|
7925
8279
|
} catch (exc) {
|
|
7926
8280
|
this.emit("ERROR", {
|
|
@@ -7928,156 +8282,34 @@ class Recorder extends util_Despot {
|
|
|
7928
8282
|
});
|
|
7929
8283
|
}
|
|
7930
8284
|
}
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
if (this.unloaded || this.cameraRecovery) return;
|
|
7940
|
-
if (!this.connected) {
|
|
7941
|
-
this.options.logger.debug("Recorder: reconnecting before recording …");
|
|
7942
|
-
this.initSocket(()=>{
|
|
7943
|
-
this.once("SERVER_READY", ()=>{
|
|
7944
|
-
if (this.unloaded) return;
|
|
7945
|
-
if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
|
|
7946
|
-
else this.once("USER_MEDIA_READY", ()=>{
|
|
7947
|
-
if (!this.unloaded) this.record();
|
|
7948
|
-
});
|
|
7949
|
-
});
|
|
7950
|
-
});
|
|
7951
|
-
return;
|
|
7952
|
-
}
|
|
7953
|
-
if (!this.userMediaLoaded) {
|
|
7954
|
-
if (this.options.loadUserMediaOnRecord) this.loadUserMedia({
|
|
7955
|
-
recordWhenReady: true
|
|
7956
|
-
});
|
|
7957
|
-
else {
|
|
7958
|
-
const err = error_createError({
|
|
7959
|
-
message: "Load and enable your camera first",
|
|
7960
|
-
options: this.options
|
|
7961
|
-
});
|
|
7962
|
-
this.emit("ERROR", {
|
|
7963
|
-
err
|
|
7964
|
-
});
|
|
7965
|
-
}
|
|
7966
|
-
return;
|
|
7967
|
-
}
|
|
7968
|
-
this.recordingDimensions = void 0;
|
|
7969
|
-
try {
|
|
7970
|
-
if (!this.userMedia) throw new Error("No user media defined, unable to create canvas");
|
|
7971
|
-
this.canvas = this.userMedia.createCanvas();
|
|
7972
|
-
} catch (exc) {
|
|
7973
|
-
const err = error_createError({
|
|
7974
|
-
exc,
|
|
7975
|
-
options: this.options
|
|
7976
|
-
});
|
|
7977
|
-
this.emit("ERROR", {
|
|
7978
|
-
err
|
|
7979
|
-
});
|
|
7980
|
-
return;
|
|
7981
|
-
}
|
|
7982
|
-
this.ctx = this.canvas.getContext("2d");
|
|
7983
|
-
if (!this.canvas.width) {
|
|
7984
|
-
const err = error_createError({
|
|
7985
|
-
message: "Canvas has an invalid width.",
|
|
7986
|
-
options: this.options
|
|
7987
|
-
});
|
|
7988
|
-
this.emit("ERROR", {
|
|
7989
|
-
err
|
|
7990
|
-
});
|
|
7991
|
-
return;
|
|
7992
|
-
}
|
|
7993
|
-
if (!this.canvas.height) {
|
|
7994
|
-
const err = error_createError({
|
|
7995
|
-
message: "Canvas has an invalid height.",
|
|
7996
|
-
options: this.options
|
|
7997
|
-
});
|
|
7998
|
-
this.emit("ERROR", {
|
|
7999
|
-
err
|
|
8000
|
-
});
|
|
8001
|
-
return;
|
|
8002
|
-
}
|
|
8003
|
-
this.recordingDimensions = {
|
|
8004
|
-
width: this.canvas.width,
|
|
8005
|
-
height: this.canvas.height
|
|
8006
|
-
};
|
|
8007
|
-
this.frame = new canvas_to_buffer(this.canvas, this.options.image.types, this.options.image.quality);
|
|
8008
|
-
this.options.logger.debug("Recorder: record()");
|
|
8009
|
-
this.recordingActive = true;
|
|
8010
|
-
this.recordingTransportFailed = false;
|
|
8285
|
+
unload(params) {
|
|
8286
|
+
if (this.unloaded || !this.built) return;
|
|
8287
|
+
const e = params?.e;
|
|
8288
|
+
let cause;
|
|
8289
|
+
if (e) cause = e.type;
|
|
8290
|
+
const prettyCause = cause ? `, cause: ${cause}` : "";
|
|
8291
|
+
this.options.logger.debug(`Recorder: unload()${prettyCause}`);
|
|
8292
|
+
this.recordingActive = false;
|
|
8011
8293
|
this.mediaTransmissionStarted = false;
|
|
8012
|
-
this.
|
|
8013
|
-
this.
|
|
8014
|
-
this.
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
this.
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
this.
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
this.loop.setRequestAnimationFrameObject(newObj);
|
|
8029
|
-
if (isRecording) this.loop.start();
|
|
8030
|
-
}
|
|
8031
|
-
}
|
|
8032
|
-
restoreAnimationFrameObject() {
|
|
8033
|
-
this.options.logger.debug("Recorder: restoreAnimationFrameObject()");
|
|
8034
|
-
this.setAnimationFrameObject(this.originalAnimationFrameObject);
|
|
8035
|
-
}
|
|
8036
|
-
loopWithTimeouts() {
|
|
8037
|
-
this.options.logger.debug("Recorder: loopWithTimeouts()");
|
|
8038
|
-
const wantedInterval = 1e3 / this.options.video.fps;
|
|
8039
|
-
let processingTime = 0;
|
|
8040
|
-
let start;
|
|
8041
|
-
const raf = (fn)=>setTimeout(()=>{
|
|
8042
|
-
start = Date.now();
|
|
8043
|
-
fn();
|
|
8044
|
-
processingTime = Date.now() - start;
|
|
8045
|
-
}, wantedInterval - processingTime);
|
|
8046
|
-
const cancel = (id)=>{
|
|
8047
|
-
window.clearTimeout(id);
|
|
8048
|
-
};
|
|
8049
|
-
this.setAnimationFrameObject({
|
|
8050
|
-
requestAnimationFrame: raf,
|
|
8051
|
-
cancelAnimationFrame: cancel
|
|
8052
|
-
});
|
|
8294
|
+
this.reset();
|
|
8295
|
+
this.recordingDimensions = void 0;
|
|
8296
|
+
this.clearUserMediaTimeout();
|
|
8297
|
+
this.clearConnectionTimeout();
|
|
8298
|
+
this.clearReconnectTimeout();
|
|
8299
|
+
this.clearRetryTimeout();
|
|
8300
|
+
this.clearStopTimeout();
|
|
8301
|
+
this.stopPings();
|
|
8302
|
+
this.userMediaLoading = false;
|
|
8303
|
+
this.connecting = false;
|
|
8304
|
+
this.reconnecting = false;
|
|
8305
|
+
this.reconnectAttempts = 0;
|
|
8306
|
+
if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
|
|
8307
|
+
this.unloaded = true;
|
|
8308
|
+
this.discardSocket();
|
|
8309
|
+
this.built = this.connecting = this.connected = false;
|
|
8053
8310
|
}
|
|
8054
8311
|
correctDimensions() {
|
|
8055
|
-
|
|
8056
|
-
const widthDimension = dimensions_useFullWidth(this.options.video.mobileBreakPoint);
|
|
8057
|
-
if (this.options.video.stretch || widthDimension) {
|
|
8058
|
-
this.recorderElement.style.width = "100%";
|
|
8059
|
-
this.recorderElement.style.removeProperty("height");
|
|
8060
|
-
} else {
|
|
8061
|
-
if (this.options.video.width) {
|
|
8062
|
-
const recorderWidth = this.getRecorderWidth(true);
|
|
8063
|
-
if (recorderWidth?.value) this.recorderElement.width = recorderWidth.value;
|
|
8064
|
-
else this.recorderElement.style.removeProperty("width");
|
|
8065
|
-
}
|
|
8066
|
-
if (this.options.video.height) {
|
|
8067
|
-
const recorderHeight = this.getRecorderHeight(true);
|
|
8068
|
-
if (recorderHeight?.value) this.recorderElement.height = recorderHeight.value;
|
|
8069
|
-
else this.recorderElement.style.removeProperty("height");
|
|
8070
|
-
}
|
|
8071
|
-
}
|
|
8072
|
-
}
|
|
8073
|
-
switchFacingMode() {
|
|
8074
|
-
if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
|
|
8075
|
-
if ("user" === this.facingMode) this.facingMode = "environment";
|
|
8076
|
-
else if ("environment" === this.facingMode) this.facingMode = "user";
|
|
8077
|
-
else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
|
|
8078
|
-
this.loadGenuineUserMedia({
|
|
8079
|
-
switchingFacingMode: this.facingMode
|
|
8080
|
-
});
|
|
8312
|
+
this.dimensions.correctDimensions();
|
|
8081
8313
|
}
|
|
8082
8314
|
initEvents() {
|
|
8083
8315
|
this.options.logger.debug("Recorder: initEvents()");
|
|
@@ -8149,100 +8381,20 @@ class Recorder extends util_Despot {
|
|
|
8149
8381
|
}
|
|
8150
8382
|
this.built = true;
|
|
8151
8383
|
}
|
|
8152
|
-
isPaused() {
|
|
8153
|
-
return this.userMedia?.isPaused() && !this.loop.isRunning();
|
|
8154
|
-
}
|
|
8155
|
-
isRecording() {
|
|
8156
|
-
return Boolean(this.loop?.isRunning() && !this.isPaused() && !this.isNotifying() && this.stream && !this.stream.destroyed);
|
|
8157
|
-
}
|
|
8158
|
-
hide() {
|
|
8159
|
-
if (!this.isHidden()) {
|
|
8160
|
-
if (this.recorderElement) html_hideElement(this.recorderElement);
|
|
8161
|
-
this.clearUserMediaTimeout();
|
|
8162
|
-
this.clearRetryTimeout();
|
|
8163
|
-
}
|
|
8164
|
-
}
|
|
8165
|
-
isUnloaded() {
|
|
8166
|
-
return this.unloaded;
|
|
8167
|
-
}
|
|
8168
8384
|
getRecorderWidth(responsive) {
|
|
8169
|
-
|
|
8170
|
-
else if (responsive && this.options.video.width) return this.limitWidth(this.options.video.width);
|
|
8171
|
-
const dimension = {
|
|
8172
|
-
unit: "px",
|
|
8173
|
-
value: this.options.video.width
|
|
8174
|
-
};
|
|
8175
|
-
return dimension;
|
|
8176
|
-
}
|
|
8177
|
-
getRecordingDimensions() {
|
|
8178
|
-
return this.recordingDimensions && {
|
|
8179
|
-
...this.recordingDimensions
|
|
8180
|
-
};
|
|
8385
|
+
return this.dimensions.getRecorderWidth(responsive);
|
|
8181
8386
|
}
|
|
8182
8387
|
getRecorderHeight(responsive, useBoundingClientRect) {
|
|
8183
|
-
|
|
8184
|
-
if (this.recorderElement && useBoundingClientRect) {
|
|
8185
|
-
const height = this.recorderElement.getBoundingClientRect().height;
|
|
8186
|
-
recorderHeight = {
|
|
8187
|
-
unit: "px",
|
|
8188
|
-
value: height
|
|
8189
|
-
};
|
|
8190
|
-
} else if (this.userMedia) {
|
|
8191
|
-
let height = this.userMedia.getRawHeight(responsive);
|
|
8192
|
-
if (void 0 !== height && this.options.video.width) {
|
|
8193
|
-
const ratio = this.getRatio();
|
|
8194
|
-
if (void 0 !== ratio) {
|
|
8195
|
-
const idealHeight = this.options.video.width * ratio;
|
|
8196
|
-
height = Math.min(idealHeight, height);
|
|
8197
|
-
}
|
|
8198
|
-
}
|
|
8199
|
-
recorderHeight = {
|
|
8200
|
-
unit: "px",
|
|
8201
|
-
value: height
|
|
8202
|
-
};
|
|
8203
|
-
} else if (responsive && this.options.video.height) recorderHeight = this.calculateHeight(responsive);
|
|
8204
|
-
else if (this.options.video.height) {
|
|
8205
|
-
const height = this.options.video.height;
|
|
8206
|
-
recorderHeight = {
|
|
8207
|
-
unit: "px",
|
|
8208
|
-
value: height
|
|
8209
|
-
};
|
|
8210
|
-
}
|
|
8211
|
-
return recorderHeight;
|
|
8388
|
+
return this.dimensions.getRecorderHeight(responsive, useBoundingClientRect);
|
|
8212
8389
|
}
|
|
8213
8390
|
getRatio() {
|
|
8214
|
-
|
|
8215
|
-
if (this.userMedia) {
|
|
8216
|
-
const userMediaVideoWidth = this.userMedia.getVideoWidth();
|
|
8217
|
-
const userMediaVideoHeight = this.userMedia.getVideoHeight();
|
|
8218
|
-
if (!userMediaVideoWidth || userMediaVideoWidth < 1) ratio = this.visuals.getRatio();
|
|
8219
|
-
else if (userMediaVideoHeight) ratio = userMediaVideoHeight / userMediaVideoWidth;
|
|
8220
|
-
} else ratio = dimensions_getRatio(this.options);
|
|
8221
|
-
return ratio;
|
|
8391
|
+
return this.dimensions.getRatio();
|
|
8222
8392
|
}
|
|
8223
8393
|
calculateWidth(responsive) {
|
|
8224
|
-
|
|
8225
|
-
if (this.userMedia) videoHeight = this.userMedia.getVideoHeight();
|
|
8226
|
-
else if (this.recorderElement) videoHeight = this.recorderElement.videoHeight || this.recorderElement.height;
|
|
8227
|
-
const ratio = this.getRatio();
|
|
8228
|
-
return dimensions_calculateWidth(responsive, this.options, videoHeight, ratio);
|
|
8394
|
+
return this.dimensions.calculateWidth(responsive);
|
|
8229
8395
|
}
|
|
8230
8396
|
calculateHeight(responsive) {
|
|
8231
|
-
|
|
8232
|
-
if (this.userMedia) {
|
|
8233
|
-
const videoHeight = this.userMedia.getVideoHeight();
|
|
8234
|
-
videoDimension = {
|
|
8235
|
-
value: videoHeight,
|
|
8236
|
-
unit: "px"
|
|
8237
|
-
};
|
|
8238
|
-
} else if (this.recorderElement) {
|
|
8239
|
-
const videoHeight = this.recorderElement.videoHeight || this.recorderElement.height;
|
|
8240
|
-
videoDimension = {
|
|
8241
|
-
value: videoHeight,
|
|
8242
|
-
unit: "px"
|
|
8243
|
-
};
|
|
8244
|
-
} else videoDimension = dimensions_calculateHeight(responsive, void 0, this.options, this.getRatio(), this.recorderElement);
|
|
8245
|
-
return videoDimension;
|
|
8397
|
+
return this.dimensions.calculateHeight(responsive);
|
|
8246
8398
|
}
|
|
8247
8399
|
getRawVisualUserMedia() {
|
|
8248
8400
|
return this.recorderElement;
|
|
@@ -8259,14 +8411,14 @@ class Recorder extends util_Despot {
|
|
|
8259
8411
|
limitHeight(height) {
|
|
8260
8412
|
return this.visuals.limitHeight(height);
|
|
8261
8413
|
}
|
|
8262
|
-
isUserMediaLoaded() {
|
|
8263
|
-
return this.userMediaLoaded;
|
|
8264
|
-
}
|
|
8265
8414
|
constructor(visuals, replay, options){
|
|
8266
|
-
super(
|
|
8267
|
-
this.
|
|
8268
|
-
|
|
8269
|
-
|
|
8415
|
+
super(visuals, replay, options), recorder_define_property(this, "dimensions", void 0);
|
|
8416
|
+
this.dimensions = new recorder_dimensions({
|
|
8417
|
+
options,
|
|
8418
|
+
visuals,
|
|
8419
|
+
getRecorderElement: ()=>this.recorderElement,
|
|
8420
|
+
getUserMedia: ()=>this.userMedia
|
|
8421
|
+
});
|
|
8270
8422
|
}
|
|
8271
8423
|
}
|
|
8272
8424
|
const visuals_recorder = Recorder;
|
|
@@ -9158,7 +9310,8 @@ class Container extends util_Despot {
|
|
|
9158
9310
|
}
|
|
9159
9311
|
else runValidation = false;
|
|
9160
9312
|
if (runValidation) {
|
|
9161
|
-
const
|
|
9313
|
+
const target = event?.target;
|
|
9314
|
+
const targetName = target instanceof Element ? target.getAttribute("name") : void 0;
|
|
9162
9315
|
if (targetName) this.emit("VALIDATING", {
|
|
9163
9316
|
targetName
|
|
9164
9317
|
});
|
|
@@ -9193,27 +9346,10 @@ class Container extends util_Despot {
|
|
|
9193
9346
|
}
|
|
9194
9347
|
}
|
|
9195
9348
|
if (valid) {
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
const bccIsConfigured = "bcc" in recipients;
|
|
9200
|
-
const hasTo = recipients.to && recipients.to.length > 0;
|
|
9201
|
-
const hasCc = recipients.cc && recipients.cc.length > 0;
|
|
9202
|
-
const hasBcc = recipients.bcc && recipients.bcc.length > 0;
|
|
9203
|
-
if (toIsConfigured) {
|
|
9204
|
-
if (!hasTo) if (ccIsConfigured && bccIsConfigured) {
|
|
9205
|
-
if (!hasCc && !hasBcc) valid = false;
|
|
9206
|
-
} else if (ccIsConfigured) {
|
|
9207
|
-
if (!hasCc) valid = false;
|
|
9208
|
-
} else if (bccIsConfigured) {
|
|
9209
|
-
if (!hasBcc) valid = false;
|
|
9210
|
-
} else valid = false;
|
|
9211
|
-
} else if (ccIsConfigured) {
|
|
9212
|
-
if (!hasCc) {
|
|
9213
|
-
if (bccIsConfigured && !hasBcc) valid = false;
|
|
9214
|
-
}
|
|
9349
|
+
if (!email_isRecipientSelectionValid(this.form.getRecipients())) {
|
|
9350
|
+
valid = false;
|
|
9351
|
+
whyInvalid = "At least one recipient is required";
|
|
9215
9352
|
}
|
|
9216
|
-
if (!valid) whyInvalid = "At least one recipient is required";
|
|
9217
9353
|
}
|
|
9218
9354
|
} else {
|
|
9219
9355
|
valid = visualsValid;
|