videomail-client 16.1.1 → 16.4.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 +4 -6
- package/dist/cjs/index.cjs +2260 -1954
- package/dist/esm/index.js +2110 -1824
- package/dist/esm/types/events/params.d.ts +4 -3
- package/dist/esm/util/email/isRecipientSelectionValid.d.ts +4 -0
- package/dist/esm/util/html/media/AudioRecorder.d.ts +19 -9
- package/dist/esm/util/html/media/AudioRecorderWorklet.d.ts +17 -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 +2173 -1914
- package/package.json +12 -12
- /package/dist/esm/util/{trimEmail.d.ts → email/trimEmail.d.ts} +0 -0
package/dist/umd/index.js
CHANGED
|
@@ -5954,7 +5954,8 @@
|
|
|
5954
5954
|
var percentTwenties = /%20/g;
|
|
5955
5955
|
var Format = {
|
|
5956
5956
|
RFC1738: 'RFC1738',
|
|
5957
|
-
RFC3986: 'RFC3986'
|
|
5957
|
+
RFC3986: 'RFC3986',
|
|
5958
|
+
WHATWG: 'WHATWG'
|
|
5958
5959
|
};
|
|
5959
5960
|
module1.exports = {
|
|
5960
5961
|
default: Format.RFC3986,
|
|
@@ -5964,10 +5965,14 @@
|
|
|
5964
5965
|
},
|
|
5965
5966
|
RFC3986: function(value) {
|
|
5966
5967
|
return String(value);
|
|
5968
|
+
},
|
|
5969
|
+
WHATWG: function(value) {
|
|
5970
|
+
return replace.call(value, percentTwenties, '+');
|
|
5967
5971
|
}
|
|
5968
5972
|
},
|
|
5969
5973
|
RFC1738: Format.RFC1738,
|
|
5970
|
-
RFC3986: Format.RFC3986
|
|
5974
|
+
RFC3986: Format.RFC3986,
|
|
5975
|
+
WHATWG: Format.WHATWG
|
|
5971
5976
|
};
|
|
5972
5977
|
},
|
|
5973
5978
|
b (module1, __unused_rspack_exports, __webpack_require__) {
|
|
@@ -6028,8 +6033,8 @@
|
|
|
6028
6033
|
target: copy
|
|
6029
6034
|
};
|
|
6030
6035
|
}
|
|
6031
|
-
target
|
|
6032
|
-
} else target
|
|
6036
|
+
utils.setProperty(target, keys[j], copies.get(value));
|
|
6037
|
+
} else utils.setProperty(target, keys[j], value);
|
|
6033
6038
|
}
|
|
6034
6039
|
}
|
|
6035
6040
|
return result;
|
|
@@ -6076,7 +6081,7 @@
|
|
|
6076
6081
|
}
|
|
6077
6082
|
return val.split(',');
|
|
6078
6083
|
}
|
|
6079
|
-
if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (1 === options.arrayLimit ? '' : 's') + ' allowed in an array.');
|
|
6084
|
+
if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit && currentArrayLength > 0) throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (1 === options.arrayLimit ? '' : 's') + ' allowed in an array.');
|
|
6080
6085
|
return val;
|
|
6081
6086
|
};
|
|
6082
6087
|
var isoSentinel = 'utf8=%26%2310003%3B';
|
|
@@ -6113,11 +6118,13 @@
|
|
|
6113
6118
|
val = options.strictNullHandling ? null : '';
|
|
6114
6119
|
} else {
|
|
6115
6120
|
key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
|
|
6116
|
-
if (null !== key) val = utils.maybeMap(parseArrayValue(part.slice(pos + 1), options, isArray(obj[key]) ? obj[key].length : 0), function(encodedVal) {
|
|
6121
|
+
if (null !== key) val = utils.maybeMap(parseArrayValue(part.slice(pos + 1), options, isArray(obj[key]) && ('combine' === options.duplicates || isEmptyBracketKey) ? obj[key].length : 0), function(encodedVal) {
|
|
6117
6122
|
return options.decoder(encodedVal, defaults.decoder, charset, 'value');
|
|
6118
6123
|
});
|
|
6119
6124
|
}
|
|
6120
|
-
if (val && options.interpretNumericEntities && 'iso-8859-1' === charset) val =
|
|
6125
|
+
if (val && options.interpretNumericEntities && 'iso-8859-1' === charset) val = utils.maybeMap(val, function(value) {
|
|
6126
|
+
return value ? interpretNumericEntities(String(value)) : value;
|
|
6127
|
+
});
|
|
6121
6128
|
if (isEmptyBracketKey) val = isArray(val) ? [
|
|
6122
6129
|
val
|
|
6123
6130
|
] : val;
|
|
@@ -6146,7 +6153,7 @@
|
|
|
6146
6153
|
__proto__: null
|
|
6147
6154
|
} : {};
|
|
6148
6155
|
var cleanRoot = '[' === root.charAt(0) && ']' === root.charAt(root.length - 1) ? root.slice(1, -1) : root;
|
|
6149
|
-
var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/
|
|
6156
|
+
var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/gi, '.') : cleanRoot;
|
|
6150
6157
|
var index = parseInt(decodedRoot, 10);
|
|
6151
6158
|
var isValidArrayIndex = !isNaN(index) && root !== decodedRoot && String(index) === decodedRoot && index >= 0 && options.parseArrays;
|
|
6152
6159
|
if (options.parseArrays || '' !== decodedRoot) {
|
|
@@ -6408,7 +6415,7 @@
|
|
|
6408
6415
|
}
|
|
6409
6416
|
var encodedPrefix = String(prefix);
|
|
6410
6417
|
var adjustedPrefix = commaRoundTrip && isArray(obj) && 1 === obj.length ? encodedPrefix + '[]' : encodedPrefix;
|
|
6411
|
-
if (allowEmptyArrays && isArray(obj) && 0 === obj.length && 0 === Object.keys(obj).length) return adjustedPrefix + '[]';
|
|
6418
|
+
if (allowEmptyArrays && isArray(obj) && 0 === obj.length && 0 === Object.keys(obj).length) return formatter(encoder && !encodeValuesOnly ? encoder(adjustedPrefix, defaults.encoder, charset, 'key', format) : adjustedPrefix) + '[]';
|
|
6412
6419
|
for(var j = 0; j < objKeys.length; ++j){
|
|
6413
6420
|
var key = objKeys[j];
|
|
6414
6421
|
var value = 'object' == typeof key && key && void 0 !== key.value ? key.value : obj[key];
|
|
@@ -6474,7 +6481,7 @@
|
|
|
6474
6481
|
filter = options.filter;
|
|
6475
6482
|
obj = filter('', obj);
|
|
6476
6483
|
} else if (isArray(options.filter)) {
|
|
6477
|
-
filter = options.filter;
|
|
6484
|
+
filter = options.filter.slice();
|
|
6478
6485
|
objKeys = filter;
|
|
6479
6486
|
}
|
|
6480
6487
|
var keys = [];
|
|
@@ -6490,7 +6497,7 @@
|
|
|
6490
6497
|
var value = obj[key];
|
|
6491
6498
|
if (!options.skipNulls || null !== value) {
|
|
6492
6499
|
var encodedKey = options.encodeDotInKeys ? String(key).replace(/\./g, '%2E') : String(key);
|
|
6493
|
-
pushToArray(keys, stringify(value, encodedKey, generateArrayPrefix, commaRoundTrip, options.allowEmptyArrays, options.strictNullHandling, options.skipNulls, options.encodeDotInKeys, options.encode ? options.encoder : null,
|
|
6500
|
+
pushToArray(keys, stringify(value, encodedKey, generateArrayPrefix, commaRoundTrip, options.allowEmptyArrays, options.strictNullHandling, options.skipNulls, options.encodeDotInKeys, options.encode ? options.encoder : null, filter, options.sort, options.allowDots, options.serializeDate, options.format, options.formatter, options.encodeValuesOnly, options.charset, sideChannel, options.depth, 0));
|
|
6494
6501
|
}
|
|
6495
6502
|
}
|
|
6496
6503
|
}
|
|
@@ -6739,7 +6746,7 @@
|
|
|
6739
6746
|
var arr = [];
|
|
6740
6747
|
for(var i = 0; i < segment.length; ++i){
|
|
6741
6748
|
var c = segment.charCodeAt(i);
|
|
6742
|
-
if (0x2D === c || 0x2E === c || 0x5F === c || 0x7E === c || c >= 0x30 && c <= 0x39 || c >= 0x41 && c <= 0x5A || c >= 0x61 && c <= 0x7A || format === formats.RFC1738 && (0x28 === c || 0x29 === c)) {
|
|
6749
|
+
if (0x2D === c || 0x2E === c || 0x5F === c || 0x7E === c && format !== formats.WHATWG || c >= 0x30 && c <= 0x39 || c >= 0x41 && c <= 0x5A || c >= 0x61 && c <= 0x7A || format === formats.RFC1738 && (0x28 === c || 0x29 === c) || format === formats.WHATWG && 0x2A === c) {
|
|
6743
6750
|
arr[arr.length] = segment.charAt(i);
|
|
6744
6751
|
continue;
|
|
6745
6752
|
}
|
|
@@ -6755,9 +6762,12 @@
|
|
|
6755
6762
|
arr[arr.length] = hexTable[0xE0 | c >> 12] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | 0x3F & c];
|
|
6756
6763
|
continue;
|
|
6757
6764
|
}
|
|
6758
|
-
i
|
|
6759
|
-
c
|
|
6760
|
-
|
|
6765
|
+
var next = segment.charCodeAt(i + 1);
|
|
6766
|
+
if (c <= 0xDBFF && next >= 0xDC00 && next <= 0xDFFF) {
|
|
6767
|
+
i += 1;
|
|
6768
|
+
c = 0x10000 + ((0x3FF & c) << 10 | 0x3FF & next);
|
|
6769
|
+
arr[arr.length] = hexTable[0xF0 | c >> 18] + hexTable[0x80 | c >> 12 & 0x3F] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | 0x3F & c];
|
|
6770
|
+
} else arr[arr.length] = '%EF%BF%BD';
|
|
6761
6771
|
}
|
|
6762
6772
|
out += arr.join('');
|
|
6763
6773
|
}
|
|
@@ -6843,6 +6853,7 @@
|
|
|
6843
6853
|
markOverflow: markOverflow,
|
|
6844
6854
|
maybeMap: maybeMap,
|
|
6845
6855
|
merge: merge,
|
|
6856
|
+
setProperty: setProperty,
|
|
6846
6857
|
toCoercible: toCoercible
|
|
6847
6858
|
};
|
|
6848
6859
|
},
|
|
@@ -7195,7 +7206,7 @@
|
|
|
7195
7206
|
const qs = __webpack_require__("b");
|
|
7196
7207
|
const RequestBase = __webpack_require__("d");
|
|
7197
7208
|
const { isObject, mixin, hasOwn, isSafeKey } = __webpack_require__("Dp");
|
|
7198
|
-
const ResponseBase = __webpack_require__("
|
|
7209
|
+
const ResponseBase = __webpack_require__("es");
|
|
7199
7210
|
const Agent = __webpack_require__("pf");
|
|
7200
7211
|
function noop() {}
|
|
7201
7212
|
module1.exports = function(method, url) {
|
|
@@ -7909,7 +7920,7 @@
|
|
|
7909
7920
|
}, this._responseTimeout);
|
|
7910
7921
|
};
|
|
7911
7922
|
},
|
|
7912
|
-
|
|
7923
|
+
es (module1, __unused_rspack_exports, __webpack_require__) {
|
|
7913
7924
|
"use strict";
|
|
7914
7925
|
const utils = __webpack_require__("Dp");
|
|
7915
7926
|
module1.exports = ResponseBase;
|
|
@@ -8254,7 +8265,7 @@
|
|
|
8254
8265
|
});
|
|
8255
8266
|
});
|
|
8256
8267
|
},
|
|
8257
|
-
|
|
8268
|
+
S (__unused_rspack_module, exports1, __webpack_require__) {
|
|
8258
8269
|
var process = __webpack_require__("zT");
|
|
8259
8270
|
var getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function(obj) {
|
|
8260
8271
|
var keys = Object.keys(obj);
|
|
@@ -11121,7 +11132,7 @@
|
|
|
11121
11132
|
var client = __webpack_require__("Q");
|
|
11122
11133
|
var client_default = /*#__PURE__*/ __webpack_require__.n(client);
|
|
11123
11134
|
var package_namespaceObject = {
|
|
11124
|
-
rE: "16.
|
|
11135
|
+
rE: "16.4.0"
|
|
11125
11136
|
};
|
|
11126
11137
|
function isAudioEnabled(options) {
|
|
11127
11138
|
return Boolean(options.audio.enabled);
|
|
@@ -11133,7 +11144,7 @@
|
|
|
11133
11144
|
function isAutoPauseEnabled(options) {
|
|
11134
11145
|
return options.enableAutoPause && options.enablePause;
|
|
11135
11146
|
}
|
|
11136
|
-
var util = __webpack_require__("
|
|
11147
|
+
var util = __webpack_require__("S");
|
|
11137
11148
|
var util_default = /*#__PURE__*/ __webpack_require__.n(util);
|
|
11138
11149
|
function getEventDetails(event) {
|
|
11139
11150
|
const eventWithErrorDetails = event;
|
|
@@ -14996,6 +15007,14 @@
|
|
|
14996
15007
|
}
|
|
14997
15008
|
}
|
|
14998
15009
|
const util_Despot = Despot;
|
|
15010
|
+
const EMAIL_SEPARATOR_REGEX = /[ ,]+/gu;
|
|
15011
|
+
function trimEmail(email) {
|
|
15012
|
+
return email.replace(EMAIL_SEPARATOR_REGEX, "");
|
|
15013
|
+
}
|
|
15014
|
+
function trimEmails(emails) {
|
|
15015
|
+
const trimmedEmails = emails.split(EMAIL_SEPARATOR_REGEX).map((item)=>item.trim()).filter(Boolean);
|
|
15016
|
+
return trimmedEmails;
|
|
15017
|
+
}
|
|
14999
15018
|
function getValidity(element) {
|
|
15000
15019
|
const validity = "validity" in element ? element.validity : void 0;
|
|
15001
15020
|
return validity;
|
|
@@ -15015,14 +15034,6 @@
|
|
|
15015
15034
|
element.style.removeProperty("display");
|
|
15016
15035
|
}
|
|
15017
15036
|
const html_showElement = showElement;
|
|
15018
|
-
const EMAIL_SEPARATOR_REGEX = /[ ,]+/gu;
|
|
15019
|
-
function trimEmail(email) {
|
|
15020
|
-
return email.replace(EMAIL_SEPARATOR_REGEX, "");
|
|
15021
|
-
}
|
|
15022
|
-
function trimEmails(emails) {
|
|
15023
|
-
const trimmedEmails = emails.split(EMAIL_SEPARATOR_REGEX).map((item)=>item.trim()).filter(Boolean);
|
|
15024
|
-
return trimmedEmails;
|
|
15025
|
-
}
|
|
15026
15037
|
const FormMethod = {
|
|
15027
15038
|
POST: "post",
|
|
15028
15039
|
PUT: "put",
|
|
@@ -16705,11 +16716,11 @@
|
|
|
16705
16716
|
const originalDocument = document1;
|
|
16706
16717
|
const currentScript = originalDocument.currentScript;
|
|
16707
16718
|
window1.DocumentFragment;
|
|
16708
|
-
const HTMLTemplateElement = window1.HTMLTemplateElement, Node = window1.Node,
|
|
16719
|
+
const HTMLTemplateElement = window1.HTMLTemplateElement, Node = window1.Node, Element1 = window1.Element, NodeFilter = window1.NodeFilter;
|
|
16709
16720
|
void 0 === window1.NamedNodeMap && (window1.NamedNodeMap || window1.MozNamedAttrMap);
|
|
16710
16721
|
window1.HTMLFormElement;
|
|
16711
16722
|
const DOMParser = window1.DOMParser, trustedTypes = window1.trustedTypes;
|
|
16712
|
-
const ElementPrototype =
|
|
16723
|
+
const ElementPrototype = Element1.prototype;
|
|
16713
16724
|
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
16714
16725
|
const remove = lookupGetter(ElementPrototype, "remove");
|
|
16715
16726
|
const removeAttributeNode = lookupGetter(ElementPrototype, "removeAttributeNode");
|
|
@@ -17792,6 +17803,19 @@
|
|
|
17792
17803
|
return DOMPurify;
|
|
17793
17804
|
}
|
|
17794
17805
|
var purify_default = createDOMPurify();
|
|
17806
|
+
function isRecipientSelectionValid(recipients) {
|
|
17807
|
+
const hasTo = Boolean(recipients.to?.length);
|
|
17808
|
+
const hasCc = Boolean(recipients.cc?.length);
|
|
17809
|
+
const hasBcc = Boolean(recipients.bcc?.length);
|
|
17810
|
+
const hasConfiguredTo = "to" in recipients;
|
|
17811
|
+
const hasConfiguredCc = "cc" in recipients;
|
|
17812
|
+
const hasConfiguredBcc = "bcc" in recipients;
|
|
17813
|
+
let isValid = true;
|
|
17814
|
+
if (hasConfiguredTo) isValid = hasTo || hasConfiguredCc && hasCc || hasConfiguredBcc && hasBcc;
|
|
17815
|
+
else if (hasConfiguredCc) isValid = hasCc || !hasConfiguredBcc || hasBcc;
|
|
17816
|
+
return isValid;
|
|
17817
|
+
}
|
|
17818
|
+
const email_isRecipientSelectionValid = isRecipientSelectionValid;
|
|
17795
17819
|
function limitHeight(height, options, calledFrom) {
|
|
17796
17820
|
const dimension = {
|
|
17797
17821
|
unit: "px"
|
|
@@ -18388,7 +18412,7 @@
|
|
|
18388
18412
|
html_showElement(this.facingModeElement);
|
|
18389
18413
|
}
|
|
18390
18414
|
}
|
|
18391
|
-
const
|
|
18415
|
+
const recorder_facingMode = FacingMode;
|
|
18392
18416
|
class PausedNote {
|
|
18393
18417
|
visuals;
|
|
18394
18418
|
options;
|
|
@@ -18609,7 +18633,7 @@
|
|
|
18609
18633
|
this.recordTimer = new recordTimer(visuals, this.recordNote, options);
|
|
18610
18634
|
const browser = util_getBrowser(options);
|
|
18611
18635
|
if (options.video.countdown) this.countdown = new countdown(visuals, options);
|
|
18612
|
-
if (options.video.facingModeButton && browser.isMobile()) this.facingMode = new
|
|
18636
|
+
if (options.video.facingModeButton && browser.isMobile()) this.facingMode = new recorder_facingMode(visuals, options);
|
|
18613
18637
|
if (options.enablePause) this.pausedNote = new pausedNote(visuals, options);
|
|
18614
18638
|
}
|
|
18615
18639
|
startRecording() {
|
|
@@ -18942,6 +18966,18 @@
|
|
|
18942
18966
|
}
|
|
18943
18967
|
}
|
|
18944
18968
|
const notifier = Notifier;
|
|
18969
|
+
function summarize(title, contents) {
|
|
18970
|
+
const lines = Object.entries(contents).sort(([keyA], [keyB])=>keyA.localeCompare(keyB)).map(([key, value])=>{
|
|
18971
|
+
const formattedValue = void 0 === value ? "undefined" : "object" == typeof value ? string_pretty(value) : value;
|
|
18972
|
+
return ` • ${key}: ${formattedValue}`;
|
|
18973
|
+
});
|
|
18974
|
+
const line = [
|
|
18975
|
+
`🔎 ${title}`,
|
|
18976
|
+
...lines
|
|
18977
|
+
].join("\n");
|
|
18978
|
+
return line;
|
|
18979
|
+
}
|
|
18980
|
+
const string_summarize = summarize;
|
|
18945
18981
|
var animitter = __webpack_require__("b6");
|
|
18946
18982
|
var animitter_default = /*#__PURE__*/ __webpack_require__.n(animitter);
|
|
18947
18983
|
var typedarray_to_buffer = __webpack_require__("E");
|
|
@@ -19005,34 +19041,6 @@
|
|
|
19005
19041
|
canvas_to_buffer_modern_r.atob = void 0;
|
|
19006
19042
|
var websocket_stream_stream = __webpack_require__("U");
|
|
19007
19043
|
var stream_default = /*#__PURE__*/ __webpack_require__.n(websocket_stream_stream);
|
|
19008
|
-
const DEFAULT_MAX_QUEUED_BYTES = 1048576;
|
|
19009
|
-
class BoundedWriteQueue {
|
|
19010
|
-
maxBytes;
|
|
19011
|
-
writes = [];
|
|
19012
|
-
queuedBytes = 0;
|
|
19013
|
-
constructor(maxBytes = DEFAULT_MAX_QUEUED_BYTES){
|
|
19014
|
-
this.maxBytes = maxBytes;
|
|
19015
|
-
}
|
|
19016
|
-
enqueue(buffer, params) {
|
|
19017
|
-
if (this.queuedBytes + buffer.length > this.maxBytes) return false;
|
|
19018
|
-
this.writes.push({
|
|
19019
|
-
buffer,
|
|
19020
|
-
params
|
|
19021
|
-
});
|
|
19022
|
-
this.queuedBytes += buffer.length;
|
|
19023
|
-
return true;
|
|
19024
|
-
}
|
|
19025
|
-
dequeue() {
|
|
19026
|
-
const write = this.writes.shift();
|
|
19027
|
-
if (write) this.queuedBytes -= write.buffer.length;
|
|
19028
|
-
return write;
|
|
19029
|
-
}
|
|
19030
|
-
clear() {
|
|
19031
|
-
this.writes.length = 0;
|
|
19032
|
-
this.queuedBytes = 0;
|
|
19033
|
-
}
|
|
19034
|
-
}
|
|
19035
|
-
const boundedWriteQueue = BoundedWriteQueue;
|
|
19036
19044
|
function getEdgeCodes(value, amount, fromEnd) {
|
|
19037
19045
|
const chars = fromEnd ? value.slice(-amount) : value.slice(0, amount);
|
|
19038
19046
|
const codes = Array.from(chars, (char)=>String(char.codePointAt(0))).join(".");
|
|
@@ -19112,1804 +19120,2276 @@
|
|
|
19112
19120
|
};
|
|
19113
19121
|
}
|
|
19114
19122
|
const error_getWebSocketDiagnostic = getWebSocketDiagnostic;
|
|
19115
|
-
function figureMinHeight(height, options) {
|
|
19116
|
-
let minHeight;
|
|
19117
|
-
if (options.video.height) {
|
|
19118
|
-
minHeight = height ? Math.min(options.video.height, height) : options.video.height;
|
|
19119
|
-
if (minHeight < 1) throw error_createError({
|
|
19120
|
-
message: `Got a min height less than 1 (${minHeight})!`,
|
|
19121
|
-
options
|
|
19122
|
-
});
|
|
19123
|
-
} else minHeight = height;
|
|
19124
|
-
return minHeight;
|
|
19125
|
-
}
|
|
19126
|
-
const dimensions_figureMinHeight = figureMinHeight;
|
|
19127
|
-
function getRatio(options, videoHeight, videoWidth) {
|
|
19128
|
-
let ratio = 1;
|
|
19129
|
-
const hasVideoDimensions = videoHeight && videoWidth;
|
|
19130
|
-
const desiredHeight = options.video.height;
|
|
19131
|
-
const desiredWidth = options.video.width;
|
|
19132
|
-
const hasDesiredDimensions = desiredHeight && desiredWidth;
|
|
19133
|
-
if (hasDesiredDimensions) ratio = hasVideoDimensions ? videoHeight < desiredHeight || videoWidth < desiredWidth ? videoHeight / videoWidth : desiredHeight / desiredWidth : desiredHeight / desiredWidth;
|
|
19134
|
-
else if (hasVideoDimensions) ratio = videoHeight / videoWidth;
|
|
19135
|
-
return ratio;
|
|
19136
|
-
}
|
|
19137
|
-
const dimensions_getRatio = getRatio;
|
|
19138
|
-
function calculateHeight(responsive, videoWidth, options, ratio, element) {
|
|
19139
|
-
const dimension = {
|
|
19140
|
-
unit: "px"
|
|
19141
|
-
};
|
|
19142
|
-
let width = videoWidth;
|
|
19143
|
-
if (responsive && element) {
|
|
19144
|
-
const limitedDimension = dimensions_limitWidth(element, options, width);
|
|
19145
|
-
width = limitedDimension.value;
|
|
19146
|
-
}
|
|
19147
|
-
const chosenRatio = ratio ?? dimensions_getRatio(options, void 0, videoWidth);
|
|
19148
|
-
const height = width ? Math.round(width * chosenRatio) : void 0;
|
|
19149
|
-
if (height && Number.isInteger(height) && height < 1) throw error_createError({
|
|
19150
|
-
message: "Just calculated a height less than 1 which is wrong.",
|
|
19151
|
-
options
|
|
19152
|
-
});
|
|
19153
|
-
const minHeight = dimensions_figureMinHeight(height, options);
|
|
19154
|
-
dimension.value = minHeight;
|
|
19155
|
-
return dimension;
|
|
19156
|
-
}
|
|
19157
|
-
const dimensions_calculateHeight = calculateHeight;
|
|
19158
|
-
function calculateWidth(responsive, options, videoHeight, ratio) {
|
|
19159
|
-
const dimension = {
|
|
19160
|
-
unit: "px"
|
|
19161
|
-
};
|
|
19162
|
-
let height = dimensions_figureMinHeight(videoHeight, options);
|
|
19163
|
-
if (responsive) {
|
|
19164
|
-
const limitedDimension = dimensions_limitHeight(height, options, "calculateWidth");
|
|
19165
|
-
height = limitedDimension.value;
|
|
19166
|
-
}
|
|
19167
|
-
if (!height || height < 1) throw error_createError({
|
|
19168
|
-
message: `Height ${height} cannot be smaller than 1 when calculating width.`,
|
|
19169
|
-
options
|
|
19170
|
-
});
|
|
19171
|
-
const chosenRatio = ratio ?? dimensions_getRatio(options, videoHeight);
|
|
19172
|
-
const calculatedWidth = Math.round(height / chosenRatio);
|
|
19173
|
-
if (calculatedWidth < 1) throw error_createError({
|
|
19174
|
-
message: "Calculated width cannot be smaller than 1!",
|
|
19175
|
-
options
|
|
19176
|
-
});
|
|
19177
|
-
dimension.value = calculatedWidth;
|
|
19178
|
-
return dimension;
|
|
19179
|
-
}
|
|
19180
|
-
const dimensions_calculateWidth = calculateWidth;
|
|
19181
19123
|
function isAutomatedUserAgent(userAgent = navigator.userAgent) {
|
|
19182
19124
|
return /bot|crawler|spider|headlesschrome|phantomjs|puppeteer|playwright/iu.test(userAgent);
|
|
19183
19125
|
}
|
|
19184
19126
|
const util_isAutomatedUserAgent = isAutomatedUserAgent;
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
...lines
|
|
19193
|
-
].join("\n");
|
|
19194
|
-
return line;
|
|
19195
|
-
}
|
|
19196
|
-
const string_summarize = summarize;
|
|
19197
|
-
function getFirstVideoTrack(localMediaStream) {
|
|
19198
|
-
const videoTracks = localMediaStream.getVideoTracks();
|
|
19199
|
-
let videoTrack;
|
|
19200
|
-
if (videoTracks[0]) videoTrack = videoTracks[0];
|
|
19201
|
-
return videoTrack;
|
|
19202
|
-
}
|
|
19203
|
-
const media_getFirstVideoTrack = getFirstVideoTrack;
|
|
19204
|
-
const MEDIA_EVENTS = [
|
|
19205
|
-
"loadstart",
|
|
19206
|
-
"suspend",
|
|
19207
|
-
"progress",
|
|
19208
|
-
"abort",
|
|
19209
|
-
"emptied",
|
|
19210
|
-
"stalled",
|
|
19211
|
-
"pause",
|
|
19212
|
-
"loadeddata",
|
|
19213
|
-
"waiting",
|
|
19214
|
-
"playing",
|
|
19215
|
-
"canplay",
|
|
19216
|
-
"canplaythrough",
|
|
19217
|
-
"seeking",
|
|
19218
|
-
"seeked",
|
|
19219
|
-
"ended",
|
|
19220
|
-
"ratechange",
|
|
19221
|
-
"durationchange",
|
|
19222
|
-
"volumechange"
|
|
19223
|
-
];
|
|
19224
|
-
const mediaEvents = MEDIA_EVENTS;
|
|
19225
|
-
function _define_property(obj, key, value) {
|
|
19226
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
19227
|
-
value: value,
|
|
19228
|
-
enumerable: true,
|
|
19229
|
-
configurable: true,
|
|
19230
|
-
writable: true
|
|
19231
|
-
});
|
|
19232
|
-
else obj[key] = value;
|
|
19233
|
-
return obj;
|
|
19234
|
-
}
|
|
19235
|
-
class AudioSample {
|
|
19236
|
-
toBuffer() {
|
|
19237
|
-
const l = this.float32Array.length;
|
|
19238
|
-
const arr = new Int16Array(l);
|
|
19239
|
-
this.float32Array.forEach((sample, i)=>{
|
|
19240
|
-
const clampedSample = Number.isFinite(sample) ? Math.max(-1, sample) : sample;
|
|
19241
|
-
arr[i] = 0x7fff * Math.min(1, clampedSample);
|
|
19242
|
-
});
|
|
19243
|
-
return typedarray_to_buffer(arr);
|
|
19127
|
+
const DEFAULT_MAX_QUEUED_BYTES = 1048576;
|
|
19128
|
+
class BoundedWriteQueue {
|
|
19129
|
+
maxBytes;
|
|
19130
|
+
writes = [];
|
|
19131
|
+
queuedBytes = 0;
|
|
19132
|
+
constructor(maxBytes = DEFAULT_MAX_QUEUED_BYTES){
|
|
19133
|
+
this.maxBytes = maxBytes;
|
|
19244
19134
|
}
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
this.
|
|
19135
|
+
enqueue(buffer, params) {
|
|
19136
|
+
if (this.queuedBytes + buffer.length > this.maxBytes) return false;
|
|
19137
|
+
this.writes.push({
|
|
19138
|
+
buffer,
|
|
19139
|
+
params
|
|
19140
|
+
});
|
|
19141
|
+
this.queuedBytes += buffer.length;
|
|
19142
|
+
return true;
|
|
19248
19143
|
}
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
|
|
19253
|
-
var is_power_of_two_default = /*#__PURE__*/ __webpack_require__.n(is_power_of_two);
|
|
19254
|
-
const CHANNELS = 1;
|
|
19255
|
-
function getAudioContextClass() {
|
|
19256
|
-
return window.AudioContext;
|
|
19257
|
-
}
|
|
19258
|
-
class AudioRecorder {
|
|
19259
|
-
scriptProcessor;
|
|
19260
|
-
audioInput;
|
|
19261
|
-
volume;
|
|
19262
|
-
vcAudioContext;
|
|
19263
|
-
userMedia;
|
|
19264
|
-
options;
|
|
19265
|
-
constructor(userMedia, options){
|
|
19266
|
-
this.options = options;
|
|
19267
|
-
this.userMedia = userMedia;
|
|
19144
|
+
dequeue() {
|
|
19145
|
+
const write = this.writes.shift();
|
|
19146
|
+
if (write) this.queuedBytes -= write.buffer.length;
|
|
19147
|
+
return write;
|
|
19268
19148
|
}
|
|
19269
|
-
|
|
19270
|
-
|
|
19149
|
+
clear() {
|
|
19150
|
+
this.writes.length = 0;
|
|
19151
|
+
this.queuedBytes = 0;
|
|
19271
19152
|
}
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
19275
|
-
|
|
19276
|
-
|
|
19277
|
-
|
|
19153
|
+
}
|
|
19154
|
+
const boundedWriteQueue = BoundedWriteQueue;
|
|
19155
|
+
const PIPE_SYMBOL = "°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸ ";
|
|
19156
|
+
class RecorderStreamWriter extends util_Despot {
|
|
19157
|
+
pendingWrites = new boundedWriteQueue();
|
|
19158
|
+
samplesCount = 0;
|
|
19159
|
+
framesCount = 0;
|
|
19160
|
+
recordingStats;
|
|
19161
|
+
confirmedFrameNumber = 0;
|
|
19162
|
+
confirmedSampleNumber = 0;
|
|
19163
|
+
backpressureTimeout;
|
|
19164
|
+
recordingDimensions;
|
|
19165
|
+
stream;
|
|
19166
|
+
streamBackpressured = false;
|
|
19167
|
+
resumeAfterDrain = false;
|
|
19168
|
+
mediaTransmissionStarted = false;
|
|
19169
|
+
recordingActive = false;
|
|
19170
|
+
recordingTransportFailed = false;
|
|
19171
|
+
connecting = false;
|
|
19172
|
+
connected = false;
|
|
19173
|
+
reconnecting = false;
|
|
19174
|
+
constructor(options){
|
|
19175
|
+
super("Recorder", options);
|
|
19278
19176
|
}
|
|
19279
|
-
|
|
19280
|
-
|
|
19281
|
-
const float32Array = e.inputBuffer.getChannelData(0);
|
|
19282
|
-
cb(new esm(float32Array));
|
|
19177
|
+
hasExceededWebSocketSendBuffer(buffer, opts) {
|
|
19178
|
+
return buffer.length > 0 && !this.pendingWrites.enqueue(buffer, opts);
|
|
19283
19179
|
}
|
|
19284
|
-
|
|
19285
|
-
if (this.
|
|
19286
|
-
this.
|
|
19287
|
-
|
|
19288
|
-
|
|
19289
|
-
|
|
19290
|
-
|
|
19180
|
+
writeStream(buffer, opts) {
|
|
19181
|
+
if (this.stream) if (this.stream.destroyed) {
|
|
19182
|
+
this.stopPings();
|
|
19183
|
+
const err = error_createError({
|
|
19184
|
+
message: "Already disconnected",
|
|
19185
|
+
explanation: "Sorry, connection to the server has been destroyed. Please reload.",
|
|
19186
|
+
options: this.options
|
|
19187
|
+
});
|
|
19188
|
+
this.handleWriteFailure(err);
|
|
19189
|
+
} else if (this.streamBackpressured) {
|
|
19190
|
+
if (this.hasExceededWebSocketSendBuffer(buffer, opts)) this.failMediaTransmission(error_createError({
|
|
19191
|
+
message: "WebSocket send buffer limit exceeded",
|
|
19192
|
+
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.",
|
|
19193
|
+
options: this.options
|
|
19194
|
+
}));
|
|
19195
|
+
} else this.writeToStream(this.stream, buffer, opts);
|
|
19291
19196
|
}
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
this.disconnect();
|
|
19295
|
-
const volume = this.getAudioContext().createGain();
|
|
19296
|
-
this.volume = volume;
|
|
19197
|
+
writeToStream(stream, buffer, opts) {
|
|
19198
|
+
const onFlushedCallback = opts?.onFlushedCallback;
|
|
19297
19199
|
try {
|
|
19298
|
-
|
|
19200
|
+
const canContinue = stream.write(buffer, ()=>{
|
|
19201
|
+
if (!onFlushedCallback) return;
|
|
19202
|
+
try {
|
|
19203
|
+
onFlushedCallback(opts);
|
|
19204
|
+
} catch (exc) {
|
|
19205
|
+
const err = error_createError({
|
|
19206
|
+
message: "Failed to write stream buffer",
|
|
19207
|
+
explanation: `stream.write() failed because of ${string_pretty(exc)}`,
|
|
19208
|
+
options: this.options,
|
|
19209
|
+
exc
|
|
19210
|
+
});
|
|
19211
|
+
this.emit("ERROR", {
|
|
19212
|
+
err
|
|
19213
|
+
});
|
|
19214
|
+
}
|
|
19215
|
+
});
|
|
19216
|
+
if (!canContinue) {
|
|
19217
|
+
this.streamBackpressured = true;
|
|
19218
|
+
this.startBackpressureTimeout();
|
|
19219
|
+
this.pauseForBackpressure();
|
|
19220
|
+
return false;
|
|
19221
|
+
}
|
|
19222
|
+
return true;
|
|
19299
19223
|
} catch (exc) {
|
|
19300
|
-
|
|
19301
|
-
message: "
|
|
19302
|
-
exc
|
|
19303
|
-
options: this.options
|
|
19224
|
+
const err = error_createError({
|
|
19225
|
+
message: "Failed writing to server",
|
|
19226
|
+
explanation: `stream.write() failed because of ${string_pretty(exc)}`,
|
|
19227
|
+
options: this.options,
|
|
19228
|
+
exc
|
|
19304
19229
|
});
|
|
19230
|
+
this.handleWriteFailure(err);
|
|
19231
|
+
return false;
|
|
19305
19232
|
}
|
|
19306
|
-
let { bufferSize } = this.options.audio;
|
|
19307
|
-
if ("auto" === bufferSize) bufferSize = util_getBrowser(this.options).isFirefox() ? 512 : 2048;
|
|
19308
|
-
if (void 0 === bufferSize || !is_power_of_two_default()(bufferSize)) throw error_createError({
|
|
19309
|
-
message: `Audio buffer size must be a power of two. The current buffer size is ${bufferSize}.`,
|
|
19310
|
-
options: this.options
|
|
19311
|
-
});
|
|
19312
|
-
if (!Number.isFinite(this.options.audio.volume) || this.options.audio.volume < 0 || this.options.audio.volume > 1) throw error_createError({
|
|
19313
|
-
message: `Audio volume must be between zero and one. The current volume is ${this.options.audio.volume}.`,
|
|
19314
|
-
options: this.options
|
|
19315
|
-
});
|
|
19316
|
-
volume.gain.value = this.options.audio.volume;
|
|
19317
|
-
this.scriptProcessor = this.getAudioContext().createScriptProcessor(bufferSize, CHANNELS, CHANNELS);
|
|
19318
|
-
this.audioInput.connect(volume);
|
|
19319
|
-
volume.connect(this.scriptProcessor);
|
|
19320
|
-
this.scriptProcessor.connect(this.getAudioContext().destination);
|
|
19321
19233
|
}
|
|
19322
|
-
|
|
19323
|
-
this.
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19234
|
+
flushPendingWrites(stream) {
|
|
19235
|
+
if (this.isStaleStream(stream)) return;
|
|
19236
|
+
this.streamBackpressured = false;
|
|
19237
|
+
this.clearBackpressureTimeout();
|
|
19238
|
+
let pendingWrite = this.pendingWrites.dequeue();
|
|
19239
|
+
while(pendingWrite){
|
|
19240
|
+
if (!this.writeToStream(stream, pendingWrite.buffer, pendingWrite.params)) return;
|
|
19241
|
+
pendingWrite = this.pendingWrites.dequeue();
|
|
19242
|
+
}
|
|
19243
|
+
this.resumeRecordingAfterDrain();
|
|
19327
19244
|
}
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
if ("running" !== context.state) await context.resume();
|
|
19245
|
+
isStaleStream(stream) {
|
|
19246
|
+
return this.stream !== stream || stream.destroyed;
|
|
19331
19247
|
}
|
|
19332
|
-
|
|
19333
|
-
this.
|
|
19334
|
-
this.disconnect();
|
|
19335
|
-
if (this.hasAudioContext()) this.getAudioContext().close().then(()=>{
|
|
19336
|
-
this.options.logger.debug("AudioRecorder: audio context is closed");
|
|
19337
|
-
this.vcAudioContext = void 0;
|
|
19338
|
-
}).catch((err)=>{
|
|
19339
|
-
if (err instanceof Error) return void this.options.logger.error(error_createError({
|
|
19340
|
-
err,
|
|
19341
|
-
options: this.options
|
|
19342
|
-
}));
|
|
19343
|
-
this.options.logger.error(err);
|
|
19344
|
-
});
|
|
19248
|
+
canPauseForBackpressure() {
|
|
19249
|
+
return this.recordingActive && !this.isUserMediaPaused() && this.isCaptureLoopRunning();
|
|
19345
19250
|
}
|
|
19346
|
-
|
|
19347
|
-
if (this.
|
|
19348
|
-
|
|
19251
|
+
pauseForBackpressure() {
|
|
19252
|
+
if (!this.canPauseForBackpressure()) return;
|
|
19253
|
+
this.resumeAfterDrain = true;
|
|
19254
|
+
this.stopCaptureLoop();
|
|
19349
19255
|
}
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
if (!capabilities.frameRate) return true;
|
|
19363
|
-
const isVirtual = VIRTUAL_KEYWORDS.some((keyword)=>videoTrack.label.toLowerCase().includes(keyword));
|
|
19364
|
-
return isVirtual;
|
|
19365
|
-
}
|
|
19366
|
-
const util_isVirtualCamera = isVirtualCamera;
|
|
19367
|
-
const EVENT_ASCII = "|—O—|";
|
|
19368
|
-
class UserMedia extends util_Despot {
|
|
19369
|
-
recorder;
|
|
19370
|
-
rawVisualUserMedia;
|
|
19371
|
-
paused = false;
|
|
19372
|
-
recording = false;
|
|
19373
|
-
audioRecorder;
|
|
19374
|
-
currentVisualStream;
|
|
19375
|
-
onPlayReached = false;
|
|
19376
|
-
onLoadedMetaDataReached = false;
|
|
19377
|
-
playingPromiseReached = false;
|
|
19378
|
-
videoTrackLabel;
|
|
19379
|
-
clearInitializationListeners;
|
|
19380
|
-
initializationId = 0;
|
|
19381
|
-
outputEvent = (e)=>{
|
|
19382
|
-
this.logEvent(e.type, {
|
|
19383
|
-
readyState: this.rawVisualUserMedia?.readyState
|
|
19256
|
+
resumeRecordingAfterDrain() {
|
|
19257
|
+
if (!this.resumeAfterDrain) return;
|
|
19258
|
+
this.resumeAfterDrain = false;
|
|
19259
|
+
if (this.canResumeAfterDrain()) this.startCaptureLoop();
|
|
19260
|
+
}
|
|
19261
|
+
canResumeAfterDrain() {
|
|
19262
|
+
return this.recordingActive && !this.isUserMediaPaused();
|
|
19263
|
+
}
|
|
19264
|
+
handleWriteFailure(err) {
|
|
19265
|
+
if (this.mediaTransmissionStarted) return void this.failMediaTransmission(err);
|
|
19266
|
+
this.emit("ERROR", {
|
|
19267
|
+
err
|
|
19384
19268
|
});
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
this.
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
this.
|
|
19393
|
-
this.
|
|
19394
|
-
|
|
19395
|
-
|
|
19269
|
+
}
|
|
19270
|
+
failMediaTransmission(err) {
|
|
19271
|
+
if (this.recordingTransportFailed) return;
|
|
19272
|
+
this.recordingTransportFailed = true;
|
|
19273
|
+
this.recordingActive = false;
|
|
19274
|
+
this.mediaTransmissionStarted = false;
|
|
19275
|
+
this.streamBackpressured = false;
|
|
19276
|
+
this.resumeAfterDrain = false;
|
|
19277
|
+
this.pendingWrites.clear();
|
|
19278
|
+
this.connected = false;
|
|
19279
|
+
this.connecting = false;
|
|
19280
|
+
this.reconnecting = false;
|
|
19281
|
+
this.clearReconnectTimeout();
|
|
19282
|
+
this.clearBackpressureTimeout();
|
|
19283
|
+
this.discardSocket();
|
|
19284
|
+
this.reset();
|
|
19285
|
+
this.framesCount = this.samplesCount = 0;
|
|
19286
|
+
this.confirmedFrameNumber = this.confirmedSampleNumber = 0;
|
|
19287
|
+
delete this.recordingStats;
|
|
19288
|
+
this.recordingDimensions = void 0;
|
|
19289
|
+
this.emit("ERROR", {
|
|
19290
|
+
err
|
|
19396
19291
|
});
|
|
19397
19292
|
}
|
|
19398
|
-
|
|
19399
|
-
if (this.
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
this.
|
|
19403
|
-
|
|
19404
|
-
|
|
19293
|
+
startBackpressureTimeout() {
|
|
19294
|
+
if (!this.shouldStartBackpressureTimeout()) return;
|
|
19295
|
+
this.backpressureTimeout = window.setTimeout(()=>{
|
|
19296
|
+
this.backpressureTimeout = void 0;
|
|
19297
|
+
if (this.shouldIgnoreBackpressureTimeout()) return;
|
|
19298
|
+
const err = error_createError({
|
|
19299
|
+
message: "WebSocket write timed out",
|
|
19300
|
+
explanation: "The server stopped accepting recording data. The recording has been stopped; please check your connection and start a new one.",
|
|
19301
|
+
options: this.options
|
|
19405
19302
|
});
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
explanation: "Contact the developer about this",
|
|
19409
|
-
options: this.options
|
|
19410
|
-
});
|
|
19303
|
+
this.failMediaTransmission(err);
|
|
19304
|
+
}, this.options.timeouts.connection);
|
|
19411
19305
|
}
|
|
19412
|
-
|
|
19413
|
-
|
|
19414
|
-
else {
|
|
19415
|
-
if (this.rawVisualUserMedia) {
|
|
19416
|
-
this.rawVisualUserMedia.srcObject = null;
|
|
19417
|
-
this.rawVisualUserMedia.removeAttribute("src");
|
|
19418
|
-
}
|
|
19419
|
-
this.currentVisualStream = void 0;
|
|
19420
|
-
}
|
|
19306
|
+
shouldStartBackpressureTimeout() {
|
|
19307
|
+
return void 0 === this.backpressureTimeout && this.mediaTransmissionStarted;
|
|
19421
19308
|
}
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
await this.rawVisualUserMedia.play();
|
|
19425
|
-
await this.audioRecorder?.resume();
|
|
19309
|
+
shouldIgnoreBackpressureTimeout() {
|
|
19310
|
+
return !this.streamBackpressured || this.recordingTransportFailed;
|
|
19426
19311
|
}
|
|
19427
|
-
|
|
19428
|
-
const
|
|
19429
|
-
const
|
|
19430
|
-
|
|
19312
|
+
failInterruptedRecording() {
|
|
19313
|
+
const unconfirmedFrames = Math.max(0, this.framesCount - this.confirmedFrameNumber);
|
|
19314
|
+
const unconfirmedSamples = Math.max(0, this.samplesCount - this.confirmedSampleNumber);
|
|
19315
|
+
const err = error_createError({
|
|
19316
|
+
message: "Recording interrupted",
|
|
19317
|
+
explanation: "The server connection closed before this recording completed. It cannot be safely resumed, so it has been stopped. Please start a new recording.",
|
|
19318
|
+
options: this.options,
|
|
19319
|
+
cause: {
|
|
19320
|
+
unconfirmedFrames,
|
|
19321
|
+
unconfirmedSamples
|
|
19322
|
+
}
|
|
19323
|
+
});
|
|
19324
|
+
this.failMediaTransmission(err);
|
|
19431
19325
|
}
|
|
19432
|
-
|
|
19433
|
-
|
|
19434
|
-
|
|
19326
|
+
handleStreamDrain(stream) {
|
|
19327
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *drain* event emitted`);
|
|
19328
|
+
this.flushPendingWrites(stream);
|
|
19435
19329
|
}
|
|
19436
|
-
|
|
19437
|
-
if (
|
|
19438
|
-
|
|
19439
|
-
|
|
19330
|
+
clearBackpressureTimeout() {
|
|
19331
|
+
if (void 0 === this.backpressureTimeout) return;
|
|
19332
|
+
window.clearTimeout(this.backpressureTimeout);
|
|
19333
|
+
this.backpressureTimeout = void 0;
|
|
19440
19334
|
}
|
|
19441
|
-
|
|
19442
|
-
this.options.logger.debug(
|
|
19335
|
+
discardSocket() {
|
|
19336
|
+
this.options.logger.debug("Recorder: discarding socket stream ...");
|
|
19337
|
+
this.clearBackpressureTimeout();
|
|
19338
|
+
this.pendingWrites.clear();
|
|
19339
|
+
this.streamBackpressured = false;
|
|
19340
|
+
this.resumeAfterDrain = false;
|
|
19341
|
+
const stream = this.stream;
|
|
19342
|
+
this.stream = void 0;
|
|
19343
|
+
if (stream && !stream.destroyed) stream.destroy();
|
|
19443
19344
|
}
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19345
|
+
}
|
|
19346
|
+
const streamWriter = RecorderStreamWriter;
|
|
19347
|
+
var Buffer = __webpack_require__("dY").Buffer;
|
|
19348
|
+
class RecorderConnection extends streamWriter {
|
|
19349
|
+
reconnectTimeout;
|
|
19350
|
+
connectionTimeout;
|
|
19351
|
+
userMediaLoaded;
|
|
19352
|
+
userMediaLoading = false;
|
|
19353
|
+
unloaded;
|
|
19354
|
+
connectionFailed = false;
|
|
19355
|
+
reconnectAttempts = 0;
|
|
19356
|
+
blocking = false;
|
|
19357
|
+
connectingStartedAt;
|
|
19358
|
+
lastCloseEvent;
|
|
19359
|
+
lastSocketError;
|
|
19360
|
+
pingInterval;
|
|
19361
|
+
constructor(options){
|
|
19362
|
+
super(options);
|
|
19363
|
+
}
|
|
19364
|
+
canReconnectAfterStreamClose() {
|
|
19365
|
+
return Boolean(this.connected && this.userMediaLoaded);
|
|
19366
|
+
}
|
|
19367
|
+
shouldReportStreamClose() {
|
|
19368
|
+
return !this.connecting && !this.blocking;
|
|
19369
|
+
}
|
|
19370
|
+
handleStreamClose(url2Connect) {
|
|
19371
|
+
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream has closed:`, {
|
|
19372
|
+
connecting: this.connecting,
|
|
19373
|
+
connected: this.connected,
|
|
19374
|
+
userMediaLoaded: this.userMediaLoaded,
|
|
19375
|
+
blocking: this.blocking
|
|
19449
19376
|
});
|
|
19450
|
-
this.
|
|
19377
|
+
this.options.logger.debug(debugLine);
|
|
19378
|
+
const canReconnect = this.canReconnectAfterStreamClose();
|
|
19379
|
+
this.connected = false;
|
|
19380
|
+
this.stopPings();
|
|
19381
|
+
if (this.shouldIgnoreStreamClose()) return;
|
|
19382
|
+
if (this.mediaTransmissionStarted) this.failInterruptedRecording();
|
|
19383
|
+
else if (canReconnect) {
|
|
19384
|
+
this.pendingWrites.clear();
|
|
19385
|
+
this.streamBackpressured = false;
|
|
19386
|
+
this.resumeAfterDrain = false;
|
|
19387
|
+
this.reconnecting = true;
|
|
19388
|
+
this.initSocket();
|
|
19389
|
+
} else if (this.shouldReportStreamClose()) window.setTimeout(()=>{
|
|
19390
|
+
this.handleConnectionFailure({
|
|
19391
|
+
url2Connect,
|
|
19392
|
+
cause: "closed"
|
|
19393
|
+
});
|
|
19394
|
+
}, 0);
|
|
19451
19395
|
}
|
|
19452
|
-
|
|
19453
|
-
this.
|
|
19454
|
-
this.audioRecorder?.record(audioCallback);
|
|
19396
|
+
shouldIgnoreStreamClose() {
|
|
19397
|
+
return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
|
|
19455
19398
|
}
|
|
19456
|
-
|
|
19457
|
-
this.
|
|
19458
|
-
this.
|
|
19459
|
-
|
|
19460
|
-
|
|
19399
|
+
handleStreamError(url2Connect, err) {
|
|
19400
|
+
if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
|
|
19401
|
+
else this.lastSocketError = serializeError(err);
|
|
19402
|
+
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
|
|
19403
|
+
error: serializeError(err)
|
|
19461
19404
|
});
|
|
19462
|
-
|
|
19463
|
-
this.
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
|
|
19478
|
-
|
|
19405
|
+
this.options.logger.debug(debugLine);
|
|
19406
|
+
if (this.validate()) {
|
|
19407
|
+
this.connected = false;
|
|
19408
|
+
this.stopPings();
|
|
19409
|
+
this.discardSocket();
|
|
19410
|
+
return;
|
|
19411
|
+
}
|
|
19412
|
+
if (!this.connected) return void this.handleConnectionFailure({
|
|
19413
|
+
url2Connect,
|
|
19414
|
+
cause: "error"
|
|
19415
|
+
});
|
|
19416
|
+
const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
|
|
19417
|
+
cause: this.lastSocketError ?? err
|
|
19418
|
+
});
|
|
19419
|
+
if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
|
|
19420
|
+
this.emit("ERROR", {
|
|
19421
|
+
err: error_createError({
|
|
19422
|
+
message: "WebSocket stream error",
|
|
19423
|
+
explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
|
|
19424
|
+
options: this.options,
|
|
19425
|
+
exc: streamError
|
|
19426
|
+
})
|
|
19427
|
+
});
|
|
19428
|
+
}
|
|
19429
|
+
sendPings() {
|
|
19430
|
+
this.stopPings();
|
|
19431
|
+
this.pingInterval = window.setInterval(()=>{
|
|
19432
|
+
this.options.logger.debug("Recorder: pinging...");
|
|
19433
|
+
this.writeStream(Buffer.from(""));
|
|
19434
|
+
}, this.options.timeouts.pingInterval);
|
|
19435
|
+
}
|
|
19436
|
+
stopPings() {
|
|
19437
|
+
if (void 0 === this.pingInterval) return;
|
|
19438
|
+
this.options.logger.debug("Recorder: stopping pings...");
|
|
19439
|
+
window.clearInterval(this.pingInterval);
|
|
19440
|
+
this.pingInterval = void 0;
|
|
19441
|
+
}
|
|
19442
|
+
clearConnectionTimeout() {
|
|
19443
|
+
if (void 0 === this.connectionTimeout) return;
|
|
19444
|
+
this.options.logger.debug("Recorder: clearConnectionTimeout()");
|
|
19445
|
+
window.clearTimeout(this.connectionTimeout);
|
|
19446
|
+
this.connectionTimeout = void 0;
|
|
19447
|
+
}
|
|
19448
|
+
clearReconnectTimeout() {
|
|
19449
|
+
if (void 0 === this.reconnectTimeout) return;
|
|
19450
|
+
this.options.logger.debug("Recorder: clearReconnectTimeout()");
|
|
19451
|
+
window.clearTimeout(this.reconnectTimeout);
|
|
19452
|
+
this.reconnectTimeout = void 0;
|
|
19453
|
+
}
|
|
19454
|
+
handleConnectionFailure(params) {
|
|
19455
|
+
if (this.retryConnection(params)) return;
|
|
19456
|
+
this.failConnection(params);
|
|
19457
|
+
}
|
|
19458
|
+
retryConnection(params) {
|
|
19459
|
+
if (!this.canRetryConnection()) return false;
|
|
19460
|
+
if (void 0 !== this.reconnectTimeout) return true;
|
|
19461
|
+
const delayMs = Math.min(1000 * 2 ** this.reconnectAttempts, 10000);
|
|
19462
|
+
this.reconnectAttempts++;
|
|
19463
|
+
this.connecting = false;
|
|
19464
|
+
this.clearConnectionTimeout();
|
|
19465
|
+
this.discardSocket();
|
|
19466
|
+
this.options.logger.debug(`Recorder: retrying WebSocket connection in ${delayMs}ms (cause ${params.cause}).`);
|
|
19467
|
+
this.reconnectTimeout = window.setTimeout(()=>{
|
|
19468
|
+
this.reconnectTimeout = void 0;
|
|
19469
|
+
if (this.shouldContinueReconnect()) this.initSocket();
|
|
19470
|
+
}, delayMs);
|
|
19471
|
+
return true;
|
|
19472
|
+
}
|
|
19473
|
+
canRetryConnection() {
|
|
19474
|
+
return Boolean(this.reconnecting && this.userMediaLoaded && this.isOnline() && !this.unloaded && !this.blocking);
|
|
19475
|
+
}
|
|
19476
|
+
shouldContinueReconnect() {
|
|
19477
|
+
return this.reconnecting && !this.unloaded && !this.blocking;
|
|
19478
|
+
}
|
|
19479
|
+
isOnline() {
|
|
19480
|
+
return navigator.onLine;
|
|
19481
|
+
}
|
|
19482
|
+
failConnection(params) {
|
|
19483
|
+
if (this.shouldIgnoreConnectionFailure()) return;
|
|
19484
|
+
this.connectionFailed = true;
|
|
19485
|
+
this.connecting = false;
|
|
19486
|
+
this.reconnecting = false;
|
|
19487
|
+
this.reconnectAttempts = 0;
|
|
19488
|
+
this.clearReconnectTimeout();
|
|
19489
|
+
this.clearConnectionTimeout();
|
|
19490
|
+
const { url2Connect, cause } = params;
|
|
19491
|
+
const online = this.isOnline();
|
|
19492
|
+
const elapsedMs = this.connectingStartedAt ? Date.now() - this.connectingStartedAt : void 0;
|
|
19493
|
+
const closeEvent = this.lastCloseEvent;
|
|
19494
|
+
const socketError = this.lastSocketError;
|
|
19495
|
+
const contents = {
|
|
19496
|
+
cause,
|
|
19497
|
+
closeCode: closeEvent?.code,
|
|
19498
|
+
closeReason: closeEvent?.reason,
|
|
19499
|
+
elapsedMs,
|
|
19500
|
+
online,
|
|
19501
|
+
secureContext: globalThis.isSecureContext,
|
|
19502
|
+
socketReadyState: this.stream?.socket.readyState,
|
|
19503
|
+
timeoutMs: this.options.timeouts.connection,
|
|
19504
|
+
url: url2Connect,
|
|
19505
|
+
unloaded: this.unloaded,
|
|
19506
|
+
userMediaLoaded: this.userMediaLoaded,
|
|
19507
|
+
userMediaLoading: this.userMediaLoading,
|
|
19508
|
+
wasClean: closeEvent?.wasClean,
|
|
19509
|
+
socketError,
|
|
19510
|
+
blocking: this.blocking,
|
|
19511
|
+
visibilityState: document.visibilityState
|
|
19479
19512
|
};
|
|
19480
|
-
const
|
|
19481
|
-
|
|
19482
|
-
|
|
19483
|
-
|
|
19484
|
-
|
|
19485
|
-
|
|
19486
|
-
|
|
19487
|
-
|
|
19488
|
-
|
|
19489
|
-
|
|
19490
|
-
|
|
19491
|
-
|
|
19492
|
-
|
|
19493
|
-
|
|
19494
|
-
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19513
|
+
const debugLine = string_summarize("Recorder: failConnection() diagnostic", contents);
|
|
19514
|
+
this.options.logger.debug(debugLine);
|
|
19515
|
+
let explanation;
|
|
19516
|
+
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.`;
|
|
19517
|
+
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.`;
|
|
19518
|
+
else {
|
|
19519
|
+
const closeSuffix = closeEvent ? ` (code ${closeEvent.code})` : "";
|
|
19520
|
+
explanation = `Connection to ${url2Connect} is closed${closeSuffix}. Please check your internet connection and try again. If the problem persists, contact us.`;
|
|
19521
|
+
}
|
|
19522
|
+
else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
|
|
19523
|
+
this.discardSocket();
|
|
19524
|
+
const err = error_createError({
|
|
19525
|
+
message: "Unable to connect to the server",
|
|
19526
|
+
explanation,
|
|
19527
|
+
options: this.options,
|
|
19528
|
+
cause: contents,
|
|
19529
|
+
exc: socketError ? new Error("WebSocket connection error", {
|
|
19530
|
+
cause: socketError
|
|
19531
|
+
}) : void 0
|
|
19532
|
+
});
|
|
19533
|
+
this.emit("ERROR", {
|
|
19534
|
+
err
|
|
19535
|
+
});
|
|
19536
|
+
}
|
|
19537
|
+
shouldIgnoreConnectionFailure() {
|
|
19538
|
+
return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
|
|
19539
|
+
}
|
|
19540
|
+
initSocket(cb) {
|
|
19541
|
+
if (this.connected) {
|
|
19542
|
+
this.options.logger.debug("Recorder: already **connected**. Not going to initialize a new WebSocket connection.");
|
|
19543
|
+
cb?.();
|
|
19544
|
+
return;
|
|
19545
|
+
}
|
|
19546
|
+
if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
|
|
19547
|
+
this.clearReconnectTimeout();
|
|
19548
|
+
if (util_isAutomatedUserAgent()) {
|
|
19549
|
+
this.connecting = false;
|
|
19550
|
+
this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
|
|
19551
|
+
return;
|
|
19552
|
+
}
|
|
19553
|
+
this.connecting = true;
|
|
19554
|
+
this.connectionFailed = false;
|
|
19555
|
+
this.connectingStartedAt = Date.now();
|
|
19556
|
+
this.lastCloseEvent = void 0;
|
|
19557
|
+
this.lastSocketError = void 0;
|
|
19558
|
+
this.emit("CONNECTING");
|
|
19559
|
+
let url2Connect;
|
|
19560
|
+
try {
|
|
19561
|
+
const socketUrlObj = new URL(this.options.socketUrl);
|
|
19562
|
+
socketUrlObj.searchParams.set(constants.WHITELIST_KEY_LABEL, this.options.whitelistKey);
|
|
19563
|
+
url2Connect = socketUrlObj.toString();
|
|
19564
|
+
} catch (exc) {
|
|
19565
|
+
this.connecting = this.connected = false;
|
|
19566
|
+
const err = error_createError({
|
|
19567
|
+
message: "Invalid WebSocket URL",
|
|
19568
|
+
explanation: `The configured socketUrl "${this.options.socketUrl}" is not a valid URL. Please check your videomail-client configuration.`,
|
|
19569
|
+
options: this.options,
|
|
19570
|
+
exc
|
|
19571
|
+
});
|
|
19572
|
+
this.emit("ERROR", {
|
|
19573
|
+
err
|
|
19574
|
+
});
|
|
19575
|
+
return;
|
|
19576
|
+
}
|
|
19577
|
+
this.options.logger.debug(`Recorder: initializing web socket stream to ${url2Connect}`);
|
|
19578
|
+
let nativeSocket;
|
|
19579
|
+
try {
|
|
19580
|
+
nativeSocket = new WebSocket(url2Connect);
|
|
19581
|
+
nativeSocket.addEventListener("close", (event)=>{
|
|
19582
|
+
this.lastCloseEvent = {
|
|
19583
|
+
code: event.code,
|
|
19584
|
+
reason: event.reason,
|
|
19585
|
+
wasClean: event.wasClean
|
|
19586
|
+
};
|
|
19587
|
+
});
|
|
19588
|
+
nativeSocket.addEventListener("error", (event)=>{
|
|
19589
|
+
this.lastSocketError = error_getEventDetails(event);
|
|
19590
|
+
});
|
|
19591
|
+
} catch (exc) {
|
|
19592
|
+
this.connecting = this.connected = false;
|
|
19593
|
+
const diagnostic = error_getWebSocketDiagnostic(url2Connect);
|
|
19594
|
+
const message = diagnostic.looksAutomated ? "Automated crawler: WebSocket not supported in this environment" : `Failed to construct WebSocket to ${url2Connect}`;
|
|
19595
|
+
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}`;
|
|
19596
|
+
const err = error_createError({
|
|
19597
|
+
message,
|
|
19598
|
+
explanation,
|
|
19599
|
+
options: this.options,
|
|
19600
|
+
exc
|
|
19601
|
+
});
|
|
19602
|
+
this.emit("ERROR", {
|
|
19603
|
+
err
|
|
19604
|
+
});
|
|
19605
|
+
return;
|
|
19606
|
+
}
|
|
19607
|
+
try {
|
|
19608
|
+
this.stream = stream_default()(nativeSocket);
|
|
19609
|
+
} catch (exc) {
|
|
19610
|
+
this.connecting = this.connected = false;
|
|
19611
|
+
const err = error_createError({
|
|
19612
|
+
message: `Failed to create a stream to ${url2Connect}`,
|
|
19613
|
+
explanation: "Please check your connection and try again. If the problem persists, contact us.",
|
|
19614
|
+
options: this.options,
|
|
19615
|
+
exc
|
|
19616
|
+
});
|
|
19617
|
+
this.emit("ERROR", {
|
|
19618
|
+
err
|
|
19619
|
+
});
|
|
19620
|
+
}
|
|
19621
|
+
const stream = this.stream;
|
|
19622
|
+
if (stream) {
|
|
19623
|
+
const connectionTimeoutMs = this.options.timeouts.connection;
|
|
19624
|
+
this.connectionTimeout = window.setTimeout(()=>{
|
|
19625
|
+
if (this.isOnline() && this.isUserMediaLoaded()) {
|
|
19626
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
|
|
19627
|
+
this.reconnecting = true;
|
|
19628
|
+
this.handleConnectionFailure({
|
|
19629
|
+
url2Connect,
|
|
19630
|
+
cause: "timeout"
|
|
19502
19631
|
});
|
|
19632
|
+
} else this.handleConnectionFailure({
|
|
19633
|
+
url2Connect,
|
|
19634
|
+
cause: "timeout"
|
|
19635
|
+
});
|
|
19636
|
+
}, connectionTimeoutMs);
|
|
19637
|
+
stream.on("close", ()=>{
|
|
19638
|
+
this.handleStreamClose(url2Connect);
|
|
19639
|
+
});
|
|
19640
|
+
stream.on("connect", (args)=>{
|
|
19641
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *connect* event emitted with args: ${string_pretty(args)}`);
|
|
19642
|
+
this.clearConnectionTimeout();
|
|
19643
|
+
const isClosing = this.stream?.socket.readyState === WebSocket.CLOSING;
|
|
19644
|
+
if (!this.connected && !isClosing && !this.unloaded) {
|
|
19645
|
+
this.connected = true;
|
|
19646
|
+
this.connecting = this.unloaded = false;
|
|
19647
|
+
this.reconnecting = false;
|
|
19648
|
+
this.reconnectAttempts = 0;
|
|
19649
|
+
this.clearReconnectTimeout();
|
|
19650
|
+
this.emit("CONNECTED");
|
|
19651
|
+
cb?.();
|
|
19503
19652
|
}
|
|
19504
|
-
}
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
19511
|
-
|
|
19512
|
-
|
|
19513
|
-
|
|
19514
|
-
|
|
19515
|
-
|
|
19516
|
-
|
|
19517
|
-
|
|
19518
|
-
|
|
19653
|
+
});
|
|
19654
|
+
stream.on("data", (data)=>{
|
|
19655
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *data* event emitted`);
|
|
19656
|
+
try {
|
|
19657
|
+
const command = JSON.parse(data.toString());
|
|
19658
|
+
this.executeCommand(command);
|
|
19659
|
+
} catch (exc) {
|
|
19660
|
+
this.options.logger.error(`Failed to parse command: ${exc}`);
|
|
19661
|
+
const err = error_createError({
|
|
19662
|
+
message: "Invalid server command",
|
|
19663
|
+
explanation: `Contact us. The invalid command was: ${data.toString()}.`,
|
|
19664
|
+
options: this.options,
|
|
19665
|
+
exc
|
|
19666
|
+
});
|
|
19667
|
+
this.emit("ERROR", {
|
|
19668
|
+
err
|
|
19519
19669
|
});
|
|
19520
|
-
this.clearInitializationListeners?.();
|
|
19521
|
-
videoCallback();
|
|
19522
|
-
};
|
|
19523
|
-
if (params?.recovering) this.audioRecorder.resume().then(onAudioReady).catch(endedEarlyCallback);
|
|
19524
|
-
else onAudioReady();
|
|
19525
|
-
} catch (exc) {
|
|
19526
|
-
unloadAllEventListeners();
|
|
19527
|
-
this.stop(void 0, {
|
|
19528
|
-
preserveRecordingState: Boolean(params?.recovering)
|
|
19529
|
-
});
|
|
19530
|
-
endedEarlyCallback(exc);
|
|
19531
|
-
}
|
|
19532
|
-
else {
|
|
19533
|
-
this.clearInitializationListeners?.();
|
|
19534
|
-
videoCallback();
|
|
19535
|
-
}
|
|
19536
|
-
};
|
|
19537
|
-
const onPlay = ()=>{
|
|
19538
|
-
try {
|
|
19539
|
-
this.logEvent("play", {
|
|
19540
|
-
readyState: this.rawVisualUserMedia?.readyState,
|
|
19541
|
-
audio: isAudioEnabled(this.options),
|
|
19542
|
-
width: this.rawVisualUserMedia?.width,
|
|
19543
|
-
height: this.rawVisualUserMedia?.height,
|
|
19544
|
-
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
19545
|
-
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
19546
|
-
});
|
|
19547
|
-
this.rawVisualUserMedia?.removeEventListener("play", onPlay);
|
|
19548
|
-
if (this.hasEnded() || this.hasInvalidDimensions()) endedEarlyCallback(error_createError({
|
|
19549
|
-
message: "Already busy",
|
|
19550
|
-
explanation: "Probably another browser window is using your webcam?",
|
|
19551
|
-
options: this.options
|
|
19552
|
-
}));
|
|
19553
|
-
else {
|
|
19554
|
-
this.onPlayReached = true;
|
|
19555
|
-
fireCallbacks();
|
|
19556
19670
|
}
|
|
19557
|
-
}
|
|
19558
|
-
|
|
19559
|
-
|
|
19671
|
+
});
|
|
19672
|
+
stream.on("error", (err)=>{
|
|
19673
|
+
this.handleStreamError(url2Connect, err);
|
|
19674
|
+
});
|
|
19675
|
+
stream.on("drain", ()=>{
|
|
19676
|
+
this.handleStreamDrain(stream);
|
|
19677
|
+
});
|
|
19678
|
+
stream.on("preend", (args)=>{
|
|
19679
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *preend* event emitted with args: ${string_pretty(args)}`);
|
|
19680
|
+
});
|
|
19681
|
+
stream.on("end", ()=>{
|
|
19682
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *end* event emitted`);
|
|
19683
|
+
});
|
|
19684
|
+
stream.on("pipe", (src)=>{
|
|
19685
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *pipe* event emitted with src: ${string_pretty(src)}`);
|
|
19686
|
+
});
|
|
19687
|
+
stream.on("unpipe", (src)=>{
|
|
19688
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *unpipe* event emitted with src: ${string_pretty(src)}`);
|
|
19689
|
+
});
|
|
19690
|
+
stream.on("resume", (args)=>{
|
|
19691
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *resume* event emitted with args: ${string_pretty(args)}`);
|
|
19692
|
+
});
|
|
19693
|
+
stream.on("uncork", (args)=>{
|
|
19694
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *uncork* event emitted with args: ${string_pretty(args)}`);
|
|
19695
|
+
});
|
|
19696
|
+
stream.on("readable", (args)=>{
|
|
19697
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *readable* event emitted with args: ${string_pretty(args)}`);
|
|
19698
|
+
});
|
|
19699
|
+
stream.on("prefinish", (args)=>{
|
|
19700
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *prefinish* event emitted with args: ${string_pretty(args)}`);
|
|
19701
|
+
});
|
|
19702
|
+
stream.on("finish", (args)=>{
|
|
19703
|
+
this.options.logger.debug(`${PIPE_SYMBOL}Stream *finish* event emitted with args: ${string_pretty(args)}`);
|
|
19704
|
+
});
|
|
19705
|
+
}
|
|
19706
|
+
}
|
|
19707
|
+
writeCommand(command, args, cb) {
|
|
19708
|
+
if (this.connected) {
|
|
19709
|
+
if (this.stream) {
|
|
19710
|
+
if (args) this.options.logger.debug(`$ ${command} with ${string_pretty(args)}`);
|
|
19711
|
+
else this.options.logger.debug(`$ ${command}`);
|
|
19712
|
+
const commandObj = {
|
|
19713
|
+
command,
|
|
19714
|
+
args
|
|
19715
|
+
};
|
|
19716
|
+
this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
|
|
19717
|
+
onFlushedCallback: ()=>{
|
|
19718
|
+
queueMicrotask(cb);
|
|
19719
|
+
}
|
|
19720
|
+
} : void 0);
|
|
19560
19721
|
}
|
|
19561
|
-
}
|
|
19562
|
-
|
|
19563
|
-
this.
|
|
19564
|
-
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19722
|
+
} else {
|
|
19723
|
+
this.options.logger.debug(`Reconnecting for the command ${command} …`);
|
|
19724
|
+
this.initSocket(()=>{
|
|
19725
|
+
this.writeCommand(command, args, cb);
|
|
19726
|
+
});
|
|
19727
|
+
}
|
|
19728
|
+
}
|
|
19729
|
+
}
|
|
19730
|
+
const connection = RecorderConnection;
|
|
19731
|
+
function createUserMediaConstraints({ options, facingMode, limitedWidth, userMediaParams }) {
|
|
19732
|
+
const video = {
|
|
19733
|
+
frameRate: {
|
|
19734
|
+
ideal: options.video.fps
|
|
19735
|
+
}
|
|
19736
|
+
};
|
|
19737
|
+
if (userMediaParams?.switchingFacingMode || userMediaParams?.recovering) video.facingMode = userMediaParams.switchingFacingMode || facingMode;
|
|
19738
|
+
if (options.video.width) video.width = {
|
|
19739
|
+
ideal: options.video.width
|
|
19740
|
+
};
|
|
19741
|
+
else if (limitedWidth?.value) video.width = {
|
|
19742
|
+
ideal: limitedWidth.value
|
|
19743
|
+
};
|
|
19744
|
+
if (options.video.height) video.height = {
|
|
19745
|
+
ideal: options.video.height
|
|
19746
|
+
};
|
|
19747
|
+
return {
|
|
19748
|
+
audio: isAudioEnabled(options),
|
|
19749
|
+
video
|
|
19750
|
+
};
|
|
19751
|
+
}
|
|
19752
|
+
const mediaConstraints = createUserMediaConstraints;
|
|
19753
|
+
class RecorderMedia extends connection {
|
|
19754
|
+
visuals;
|
|
19755
|
+
recorderElement;
|
|
19756
|
+
userMedia;
|
|
19757
|
+
userMediaTimeout;
|
|
19758
|
+
retryTimeout;
|
|
19759
|
+
stopTimeout;
|
|
19760
|
+
mediaRequestId = 0;
|
|
19761
|
+
cameraRecovery;
|
|
19762
|
+
resolveCameraRecovery;
|
|
19763
|
+
resumeAfterCameraRecovery = false;
|
|
19764
|
+
submitting = false;
|
|
19765
|
+
built = false;
|
|
19766
|
+
key;
|
|
19767
|
+
facingMode;
|
|
19768
|
+
constructor(visuals, options){
|
|
19769
|
+
super(options);
|
|
19770
|
+
this.visuals = visuals;
|
|
19771
|
+
this.facingMode = options.video.facingMode;
|
|
19772
|
+
}
|
|
19773
|
+
show() {
|
|
19774
|
+
html_showElement(this.recorderElement);
|
|
19775
|
+
}
|
|
19776
|
+
clearRetryTimeout() {
|
|
19777
|
+
if (void 0 === this.retryTimeout) return;
|
|
19778
|
+
this.options.logger.debug("Recorder: clearRetryTimeout()");
|
|
19779
|
+
window.clearTimeout(this.retryTimeout);
|
|
19780
|
+
this.retryTimeout = void 0;
|
|
19781
|
+
}
|
|
19782
|
+
showUserMedia() {
|
|
19783
|
+
if (!this.connected) return false;
|
|
19784
|
+
const hidden = this.isHidden();
|
|
19785
|
+
if (!hidden) return true;
|
|
19786
|
+
const notifying = this.isNotifying();
|
|
19787
|
+
if (notifying) return true;
|
|
19788
|
+
return this.blocking;
|
|
19789
|
+
}
|
|
19790
|
+
userMediaErrorCallback(err, usedConstraints) {
|
|
19791
|
+
if (this.cameraRecovery) return void this.failCameraRecovery(err);
|
|
19792
|
+
if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${string_pretty(err)}`);
|
|
19793
|
+
this.userMediaLoading = false;
|
|
19794
|
+
this.clearUserMediaTimeout();
|
|
19795
|
+
const characteristics = this.userMedia?.getCharacteristics();
|
|
19796
|
+
this.options.logger.debug(`Recorder: userMediaErrorCallback(), name: ${err.name}, message: ${err.message} and Webcam characteristics: ${characteristics ? string_pretty(characteristics) : "none"}`);
|
|
19797
|
+
const errorListeners = this.getListeners("ERROR");
|
|
19798
|
+
if (errorListeners?.length) if (err.name !== error_VideomailError.MEDIA_DEVICE_NOT_SUPPORTED) {
|
|
19799
|
+
const videomailError = error_createError({
|
|
19800
|
+
err,
|
|
19801
|
+
options: this.options,
|
|
19802
|
+
usedConstraints
|
|
19570
19803
|
});
|
|
19571
|
-
this.
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19583
|
-
|
|
19804
|
+
this.emit("ERROR", {
|
|
19805
|
+
err: videomailError
|
|
19806
|
+
});
|
|
19807
|
+
} else {
|
|
19808
|
+
this.options.logger.debug(`Recorder: ignore user media error ${string_pretty(err)}`);
|
|
19809
|
+
this.retryTimeout = window.setTimeout(()=>{
|
|
19810
|
+
this.retryTimeout = void 0;
|
|
19811
|
+
if (!this.unloaded) this.loadUserMedia();
|
|
19812
|
+
}, this.options.timeouts.userMedia);
|
|
19813
|
+
}
|
|
19814
|
+
else {
|
|
19815
|
+
this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${string_pretty(err)}`);
|
|
19816
|
+
throw error_createError({
|
|
19817
|
+
err,
|
|
19818
|
+
message: "Unable to process this error since there are no error listeners anymore.",
|
|
19584
19819
|
options: this.options
|
|
19585
19820
|
});
|
|
19586
|
-
|
|
19587
|
-
|
|
19588
|
-
|
|
19589
|
-
|
|
19590
|
-
|
|
19591
|
-
|
|
19592
|
-
|
|
19593
|
-
|
|
19594
|
-
|
|
19821
|
+
}
|
|
19822
|
+
}
|
|
19823
|
+
getUserMediaCallback(localStream, params, requestId = this.mediaRequestId) {
|
|
19824
|
+
if (!this.userMedia) {
|
|
19825
|
+
localStream.getTracks().forEach((track)=>{
|
|
19826
|
+
track.stop();
|
|
19827
|
+
});
|
|
19828
|
+
throw error_createError({
|
|
19829
|
+
message: "No user media is defined",
|
|
19595
19830
|
options: this.options
|
|
19596
19831
|
});
|
|
19597
|
-
|
|
19598
|
-
|
|
19599
|
-
|
|
19600
|
-
this.
|
|
19601
|
-
|
|
19602
|
-
this.
|
|
19603
|
-
|
|
19832
|
+
}
|
|
19833
|
+
this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? string_pretty(params) : ""})`);
|
|
19834
|
+
if (this.showUserMedia()) try {
|
|
19835
|
+
this.userMedia.init(localStream, ()=>{
|
|
19836
|
+
if (!this.unloaded && requestId === this.mediaRequestId) this.onUserMediaReady(params);
|
|
19837
|
+
}, this.onAudioSample.bind(this), (err)=>{
|
|
19838
|
+
if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(err, {
|
|
19839
|
+
audio: isAudioEnabled(this.options)
|
|
19840
|
+
});
|
|
19841
|
+
}, params);
|
|
19604
19842
|
} catch (exc) {
|
|
19605
|
-
|
|
19606
|
-
|
|
19843
|
+
localStream.getTracks().forEach((track)=>{
|
|
19844
|
+
track.stop();
|
|
19845
|
+
});
|
|
19846
|
+
this.userMediaLoading = false;
|
|
19847
|
+
this.clearUserMediaTimeout();
|
|
19848
|
+
this.userMediaErrorCallback(exc, {
|
|
19849
|
+
audio: isAudioEnabled(this.options)
|
|
19850
|
+
});
|
|
19851
|
+
}
|
|
19852
|
+
else {
|
|
19853
|
+
localStream.getTracks().forEach((track)=>{
|
|
19854
|
+
track.stop();
|
|
19855
|
+
});
|
|
19856
|
+
this.userMediaLoading = false;
|
|
19857
|
+
this.clearUserMediaTimeout();
|
|
19858
|
+
this.cancelCameraRecovery();
|
|
19607
19859
|
}
|
|
19608
19860
|
}
|
|
19609
|
-
|
|
19610
|
-
|
|
19611
|
-
|
|
19612
|
-
|
|
19613
|
-
|
|
19861
|
+
loadGenuineUserMedia(params) {
|
|
19862
|
+
this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? string_pretty(params) : ""})`);
|
|
19863
|
+
this.emit("ASKING_WEBCAM_PERMISSION");
|
|
19864
|
+
this.userMediaLoading = true;
|
|
19865
|
+
const requestId = ++this.mediaRequestId;
|
|
19866
|
+
const limitedWidth = this.options.video.width ? void 0 : this.visuals.limitWidth();
|
|
19867
|
+
const constraints = mediaConstraints({
|
|
19868
|
+
facingMode: this.facingMode,
|
|
19869
|
+
limitedWidth,
|
|
19870
|
+
options: this.options,
|
|
19871
|
+
userMediaParams: params
|
|
19872
|
+
});
|
|
19873
|
+
this.options.logger.debug(string_summarize("Recorder: our webcam constraints are:", constraints));
|
|
19874
|
+
this.options.logger.debug(string_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
|
|
19875
|
+
const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
|
|
19876
|
+
streamPromise.then((localStream)=>{
|
|
19877
|
+
if (this.unloaded || requestId !== this.mediaRequestId) return void localStream.getTracks().forEach((track)=>{
|
|
19878
|
+
track.stop();
|
|
19879
|
+
});
|
|
19880
|
+
this.getUserMediaCallback(localStream, params, requestId);
|
|
19881
|
+
}).catch((reason)=>{
|
|
19882
|
+
if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(reason, constraints);
|
|
19883
|
+
});
|
|
19614
19884
|
}
|
|
19615
|
-
|
|
19885
|
+
loadUserMedia(params) {
|
|
19886
|
+
if (this.userMediaLoaded) {
|
|
19887
|
+
this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already loaded");
|
|
19888
|
+
this.onUserMediaReady(params);
|
|
19889
|
+
return;
|
|
19890
|
+
} else if (this.userMediaLoading) return void this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already asking for permission");
|
|
19891
|
+
this.options.logger.debug(`Recorder: loadUserMedia(${params ? string_pretty(params) : ""})`);
|
|
19892
|
+
this.emit("LOADING_USER_MEDIA");
|
|
19616
19893
|
try {
|
|
19617
|
-
|
|
19618
|
-
|
|
19619
|
-
this.
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
}
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19629
|
-
}
|
|
19630
|
-
if (!params?.preserveRecordingState) this.paused = this.recording = false;
|
|
19894
|
+
if (!navigator.mediaDevices) throw new Error("No media devices are available.");
|
|
19895
|
+
this.userMediaTimeout = window.setTimeout(()=>{
|
|
19896
|
+
if (!this.userMediaLoaded) {
|
|
19897
|
+
this.userMediaLoading = false;
|
|
19898
|
+
const err = util_getBrowser(this.options).getNoAccessIssue();
|
|
19899
|
+
this.userMediaErrorCallback(err, {
|
|
19900
|
+
audio: isAudioEnabled(this.options)
|
|
19901
|
+
});
|
|
19902
|
+
}
|
|
19903
|
+
}, this.options.timeouts.userMedia);
|
|
19904
|
+
this.userMediaLoading = true;
|
|
19905
|
+
this.loadGenuineUserMedia(params);
|
|
19631
19906
|
} catch (exc) {
|
|
19632
|
-
this.
|
|
19907
|
+
this.clearUserMediaTimeout();
|
|
19908
|
+
this.options.logger.debug("Recorder: failed to load genuine user media");
|
|
19909
|
+
this.userMediaLoading = false;
|
|
19910
|
+
if (this.cameraRecovery) return void this.failCameraRecovery(exc);
|
|
19911
|
+
const errorListeners = this.getListeners("ERROR");
|
|
19912
|
+
if (errorListeners?.length) this.emit("ERROR", {
|
|
19633
19913
|
exc
|
|
19634
19914
|
});
|
|
19915
|
+
else {
|
|
19916
|
+
this.options.logger.debug("Recorder: no error listeners attached but throwing exception further");
|
|
19917
|
+
throw exc;
|
|
19918
|
+
}
|
|
19635
19919
|
}
|
|
19636
19920
|
}
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
const rawWidthDimension = this.getRawWidth(true);
|
|
19640
|
-
if (rawWidthDimension.value) canvas.width = rawWidthDimension.value;
|
|
19641
|
-
const rawHeight = this.getRawHeight(true);
|
|
19642
|
-
if (rawHeight) canvas.height = rawHeight;
|
|
19643
|
-
return canvas;
|
|
19644
|
-
}
|
|
19645
|
-
getVideoHeight() {
|
|
19646
|
-
if (!this.rawVisualUserMedia) return;
|
|
19647
|
-
return this.rawVisualUserMedia.videoHeight || this.rawVisualUserMedia.height;
|
|
19648
|
-
}
|
|
19649
|
-
getVideoWidth() {
|
|
19650
|
-
if (!this.rawVisualUserMedia) return;
|
|
19651
|
-
return this.rawVisualUserMedia.videoWidth || this.rawVisualUserMedia.width;
|
|
19921
|
+
isNotifying() {
|
|
19922
|
+
return this.visuals.isNotifying();
|
|
19652
19923
|
}
|
|
19653
|
-
|
|
19654
|
-
|
|
19655
|
-
return videoWidth && videoWidth > 0;
|
|
19924
|
+
isHidden() {
|
|
19925
|
+
return !this.recorderElement || html_isHidden(this.recorderElement);
|
|
19656
19926
|
}
|
|
19657
|
-
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
rawWidth = responsive ? this.getVideoWidth() : this.options.video.width ? this.options.video.width : this.getVideoWidth();
|
|
19663
|
-
if (responsive) {
|
|
19664
|
-
const widthDimension = this.recorder.limitWidth(rawWidth);
|
|
19665
|
-
rawWidth = widthDimension?.value;
|
|
19927
|
+
clearUserMediaTimeout() {
|
|
19928
|
+
if (void 0 !== this.userMediaTimeout) {
|
|
19929
|
+
this.options.logger.debug("Recorder: clearUserMediaTimeout()");
|
|
19930
|
+
window.clearTimeout(this.userMediaTimeout);
|
|
19931
|
+
this.userMediaTimeout = void 0;
|
|
19666
19932
|
}
|
|
19667
|
-
widthDimension.value = rawWidth;
|
|
19668
|
-
return widthDimension;
|
|
19669
19933
|
}
|
|
19670
|
-
|
|
19671
|
-
|
|
19672
|
-
|
|
19673
|
-
|
|
19674
|
-
const ratio = this.recorder.getRatio();
|
|
19675
|
-
const rawWidth = this.getRawWidth(responsive);
|
|
19676
|
-
if (void 0 !== ratio && rawWidth.value) rawHeight = rawWidth.value * ratio;
|
|
19677
|
-
} else rawHeight = this.options.video.height ? this.options.video.height : this.getVideoHeight();
|
|
19678
|
-
if (responsive) {
|
|
19679
|
-
const heightDimension = this.recorder.limitHeight(rawHeight);
|
|
19680
|
-
rawHeight = heightDimension.value;
|
|
19681
|
-
}
|
|
19682
|
-
return rawHeight;
|
|
19934
|
+
clearStopTimeout() {
|
|
19935
|
+
if (void 0 === this.stopTimeout) return;
|
|
19936
|
+
window.clearTimeout(this.stopTimeout);
|
|
19937
|
+
this.stopTimeout = void 0;
|
|
19683
19938
|
}
|
|
19684
|
-
|
|
19685
|
-
return this.
|
|
19939
|
+
isReady() {
|
|
19940
|
+
return this.userMedia?.isReady();
|
|
19686
19941
|
}
|
|
19687
|
-
|
|
19688
|
-
this.
|
|
19942
|
+
finishCameraRecovery(restored) {
|
|
19943
|
+
this.clearUserMediaTimeout();
|
|
19944
|
+
const resolve = this.resolveCameraRecovery;
|
|
19945
|
+
const resumeRequested = restored && this.resumeAfterCameraRecovery;
|
|
19946
|
+
this.cameraRecovery = void 0;
|
|
19947
|
+
this.resolveCameraRecovery = void 0;
|
|
19948
|
+
this.resumeAfterCameraRecovery = false;
|
|
19949
|
+
resolve?.(restored);
|
|
19950
|
+
if (resumeRequested && this.isPaused()) this.resume();
|
|
19689
19951
|
}
|
|
19690
|
-
|
|
19691
|
-
|
|
19952
|
+
cancelCameraRecovery() {
|
|
19953
|
+
if (!this.cameraRecovery) return;
|
|
19954
|
+
this.mediaRequestId++;
|
|
19955
|
+
this.userMediaLoading = false;
|
|
19956
|
+
this.userMediaLoaded = false;
|
|
19957
|
+
this.clearUserMediaTimeout();
|
|
19958
|
+
this.userMedia?.stop(void 0, {
|
|
19959
|
+
preserveRecordingState: true
|
|
19960
|
+
});
|
|
19961
|
+
this.finishCameraRecovery(false);
|
|
19692
19962
|
}
|
|
19693
|
-
|
|
19694
|
-
this.
|
|
19963
|
+
failCameraRecovery(exc) {
|
|
19964
|
+
this.cancelCameraRecovery();
|
|
19965
|
+
const err = error_createError({
|
|
19966
|
+
message: "Camera recovery failed",
|
|
19967
|
+
explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
|
|
19968
|
+
exc,
|
|
19969
|
+
options: this.options
|
|
19970
|
+
});
|
|
19971
|
+
this.emit("CAMERA_RECOVERY_FAILED", {
|
|
19972
|
+
err
|
|
19973
|
+
});
|
|
19695
19974
|
}
|
|
19696
|
-
|
|
19697
|
-
this.
|
|
19975
|
+
recoverCamera() {
|
|
19976
|
+
if (this.cameraRecovery) return this.cameraRecovery;
|
|
19977
|
+
if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
|
|
19978
|
+
const liveStream = this.userMedia.hasLiveStream();
|
|
19979
|
+
if (liveStream && this.isRecording()) return Promise.resolve(true);
|
|
19980
|
+
if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
|
|
19981
|
+
const recovery = new Promise((resolve)=>{
|
|
19982
|
+
this.resolveCameraRecovery = resolve;
|
|
19983
|
+
});
|
|
19984
|
+
this.cameraRecovery = recovery;
|
|
19985
|
+
this.emit("CAMERA_RECOVERING");
|
|
19986
|
+
if (liveStream) {
|
|
19987
|
+
this.userMediaTimeout = window.setTimeout(()=>{
|
|
19988
|
+
this.failCameraRecovery(new Error("Camera playback recovery timed out."));
|
|
19989
|
+
}, this.options.timeouts.userMedia);
|
|
19990
|
+
this.userMedia.restorePlayback().then(()=>{
|
|
19991
|
+
if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
|
|
19992
|
+
}).catch((exc)=>{
|
|
19993
|
+
if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
|
|
19994
|
+
});
|
|
19995
|
+
} else {
|
|
19996
|
+
this.userMediaLoaded = false;
|
|
19997
|
+
this.loadUserMedia({
|
|
19998
|
+
recovering: true
|
|
19999
|
+
});
|
|
20000
|
+
}
|
|
20001
|
+
return recovery;
|
|
19698
20002
|
}
|
|
19699
|
-
|
|
19700
|
-
|
|
20003
|
+
switchFacingMode() {
|
|
20004
|
+
if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
|
|
20005
|
+
if ("user" === this.facingMode) this.facingMode = "environment";
|
|
20006
|
+
else if ("environment" === this.facingMode) this.facingMode = "user";
|
|
20007
|
+
else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
|
|
20008
|
+
this.loadGenuineUserMedia({
|
|
20009
|
+
switchingFacingMode: this.facingMode
|
|
20010
|
+
});
|
|
19701
20011
|
}
|
|
19702
|
-
|
|
19703
|
-
if (this.
|
|
19704
|
-
|
|
20012
|
+
hide() {
|
|
20013
|
+
if (!this.isHidden()) {
|
|
20014
|
+
if (this.recorderElement) html_hideElement(this.recorderElement);
|
|
20015
|
+
this.clearUserMediaTimeout();
|
|
20016
|
+
this.clearRetryTimeout();
|
|
20017
|
+
}
|
|
19705
20018
|
}
|
|
19706
|
-
|
|
19707
|
-
return
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
height: this.rawVisualUserMedia?.height,
|
|
19712
|
-
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
19713
|
-
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
19714
|
-
};
|
|
20019
|
+
isUnloaded() {
|
|
20020
|
+
return this.unloaded;
|
|
20021
|
+
}
|
|
20022
|
+
isUserMediaLoaded() {
|
|
20023
|
+
return this.userMediaLoaded;
|
|
19715
20024
|
}
|
|
19716
20025
|
}
|
|
19717
|
-
const
|
|
19718
|
-
|
|
19719
|
-
|
|
19720
|
-
|
|
19721
|
-
|
|
20026
|
+
const media = RecorderMedia;
|
|
20027
|
+
function calculateProgress(confirmedCount, totalCount) {
|
|
20028
|
+
return `${(confirmedCount / (totalCount || 1) * 100).toFixed(2)}%`;
|
|
20029
|
+
}
|
|
20030
|
+
function getConfirmedProgressNumber({ args, kind, options }) {
|
|
20031
|
+
const label = "frame" === kind ? "frame" : "audio sample";
|
|
20032
|
+
if (!args) throw error_createError({
|
|
20033
|
+
message: `Arguments are missing for updating the ${label} progress`,
|
|
20034
|
+
options
|
|
20035
|
+
});
|
|
20036
|
+
const confirmedNumber = args[kind];
|
|
20037
|
+
if (!confirmedNumber) throw error_createError({
|
|
20038
|
+
message: `The ${label} number is missing`,
|
|
20039
|
+
options
|
|
20040
|
+
});
|
|
20041
|
+
return confirmedNumber;
|
|
20042
|
+
}
|
|
20043
|
+
var capture_Buffer = __webpack_require__("dY").Buffer;
|
|
20044
|
+
class RecorderCapture extends media {
|
|
19722
20045
|
replay;
|
|
19723
|
-
pendingWrites = new boundedWriteQueue();
|
|
19724
20046
|
loop;
|
|
19725
20047
|
originalAnimationFrameObject;
|
|
19726
|
-
samplesCount = 0;
|
|
19727
|
-
framesCount = 0;
|
|
19728
20048
|
recordingAudioSampleRate;
|
|
19729
|
-
recordingStats;
|
|
19730
|
-
confirmedFrameNumber = 0;
|
|
19731
|
-
confirmedSampleNumber = 0;
|
|
19732
|
-
recorderElement;
|
|
19733
|
-
userMedia;
|
|
19734
|
-
userMediaTimeout;
|
|
19735
|
-
retryTimeout;
|
|
19736
|
-
reconnectTimeout;
|
|
19737
|
-
connectionTimeout;
|
|
19738
|
-
backpressureTimeout;
|
|
19739
|
-
stopTimeout;
|
|
19740
20049
|
frameProgress;
|
|
19741
20050
|
sampleProgress;
|
|
19742
20051
|
canvas;
|
|
19743
|
-
recordingDimensions;
|
|
19744
20052
|
ctx;
|
|
19745
|
-
userMediaLoaded;
|
|
19746
|
-
userMediaLoading = false;
|
|
19747
|
-
mediaRequestId = 0;
|
|
19748
|
-
cameraRecovery;
|
|
19749
|
-
resolveCameraRecovery;
|
|
19750
|
-
resumeAfterCameraRecovery = false;
|
|
19751
|
-
submitting = false;
|
|
19752
|
-
unloaded;
|
|
19753
20053
|
stopTime;
|
|
19754
|
-
stream;
|
|
19755
|
-
streamBackpressured = false;
|
|
19756
|
-
resumeAfterDrain = false;
|
|
19757
|
-
mediaTransmissionStarted = false;
|
|
19758
|
-
recordingActive = false;
|
|
19759
|
-
recordingTransportFailed = false;
|
|
19760
|
-
connecting = false;
|
|
19761
|
-
connected = false;
|
|
19762
|
-
connectionFailed = false;
|
|
19763
|
-
reconnecting = false;
|
|
19764
|
-
reconnectAttempts = 0;
|
|
19765
|
-
blocking = false;
|
|
19766
|
-
built = false;
|
|
19767
|
-
key;
|
|
19768
20054
|
waitingTime;
|
|
19769
|
-
connectingStartedAt;
|
|
19770
|
-
lastCloseEvent;
|
|
19771
|
-
lastSocketError;
|
|
19772
|
-
pingInterval;
|
|
19773
20055
|
frame;
|
|
19774
20056
|
recordingBuffer;
|
|
19775
|
-
facingMode;
|
|
19776
20057
|
constructor(visuals, replay, options){
|
|
19777
|
-
super(
|
|
19778
|
-
this.visuals = visuals;
|
|
20058
|
+
super(visuals, options);
|
|
19779
20059
|
this.replay = replay;
|
|
19780
|
-
this.facingMode = options.video.facingMode;
|
|
19781
20060
|
}
|
|
19782
|
-
|
|
19783
|
-
|
|
20061
|
+
onAudioSample(audioSample) {
|
|
20062
|
+
if (this.recordingTransportFailed || this.cameraRecovery) return;
|
|
20063
|
+
const sampleRate = this.userMedia?.getAudioSampleRate();
|
|
20064
|
+
if (sampleRate && sampleRate > 0) this.recordingAudioSampleRate ??= sampleRate;
|
|
20065
|
+
this.samplesCount++;
|
|
20066
|
+
this.mediaTransmissionStarted = true;
|
|
20067
|
+
const audioBuffer = audioSample.toBuffer();
|
|
20068
|
+
this.writeStream(audioBuffer);
|
|
20069
|
+
}
|
|
20070
|
+
onUserMediaReady(params) {
|
|
20071
|
+
try {
|
|
20072
|
+
this.options.logger.debug(`Recorder: onUserMediaReady(${params ? string_pretty(params) : ""})`);
|
|
20073
|
+
const switchingFacingMode = params?.switchingFacingMode;
|
|
20074
|
+
if (params?.recovering) {
|
|
20075
|
+
if (!this.userMedia?.hasLiveStream()) return void this.failCameraRecovery(new Error("The restored camera has no live tracks."));
|
|
20076
|
+
if (void 0 !== this.recordingAudioSampleRate && this.userMedia.getAudioSampleRate() !== this.recordingAudioSampleRate) return void this.failCameraRecovery(new Error("The audio sample rate changed during recovery."));
|
|
20077
|
+
}
|
|
20078
|
+
this.userMediaLoading = this.blocking = this.unloaded = this.submitting = false;
|
|
20079
|
+
this.userMediaLoaded = true;
|
|
20080
|
+
this.clearUserMediaTimeout();
|
|
20081
|
+
if (!switchingFacingMode && !params?.recovering) this.loop = this.createLoop();
|
|
20082
|
+
this.show();
|
|
20083
|
+
if (params?.recordWhenReady) this.record();
|
|
20084
|
+
this.emit("USER_MEDIA_READY", {
|
|
20085
|
+
switchingFacingMode: params?.switchingFacingMode,
|
|
20086
|
+
recovering: params?.recovering,
|
|
20087
|
+
paused: this.isPaused(),
|
|
20088
|
+
recordWhenReady: params?.recordWhenReady
|
|
20089
|
+
});
|
|
20090
|
+
if (params?.recovering) this.finishCameraRecovery(true);
|
|
20091
|
+
} catch (exc) {
|
|
20092
|
+
if (params?.recovering) this.failCameraRecovery(exc);
|
|
20093
|
+
else this.emit("ERROR", {
|
|
20094
|
+
exc
|
|
20095
|
+
});
|
|
20096
|
+
}
|
|
20097
|
+
}
|
|
20098
|
+
calculateFrameProgress() {
|
|
20099
|
+
return calculateProgress(this.confirmedFrameNumber, this.framesCount);
|
|
20100
|
+
}
|
|
20101
|
+
calculateSampleProgress() {
|
|
20102
|
+
return calculateProgress(this.confirmedSampleNumber, this.samplesCount);
|
|
20103
|
+
}
|
|
20104
|
+
updateOverallProgress() {
|
|
20105
|
+
this.frameProgress = this.calculateFrameProgress();
|
|
20106
|
+
if (isAudioEnabled(this.options)) this.sampleProgress = this.calculateSampleProgress();
|
|
20107
|
+
this.emit("PROGRESS", {
|
|
20108
|
+
frameProgress: this.frameProgress,
|
|
20109
|
+
sampleProgress: this.sampleProgress
|
|
20110
|
+
});
|
|
20111
|
+
}
|
|
20112
|
+
updateFrameProgress(args) {
|
|
20113
|
+
this.confirmedFrameNumber = getConfirmedProgressNumber({
|
|
20114
|
+
args,
|
|
20115
|
+
kind: "frame",
|
|
20116
|
+
options: this.options
|
|
20117
|
+
});
|
|
20118
|
+
this.frameProgress = this.calculateFrameProgress();
|
|
20119
|
+
this.updateOverallProgress();
|
|
20120
|
+
}
|
|
20121
|
+
updateSampleProgress(args) {
|
|
20122
|
+
this.confirmedSampleNumber = getConfirmedProgressNumber({
|
|
20123
|
+
args,
|
|
20124
|
+
kind: "sample",
|
|
20125
|
+
options: this.options
|
|
20126
|
+
});
|
|
20127
|
+
this.sampleProgress = this.calculateSampleProgress();
|
|
20128
|
+
this.updateOverallProgress();
|
|
20129
|
+
}
|
|
20130
|
+
preview(args) {
|
|
20131
|
+
if (!args) throw error_createError({
|
|
20132
|
+
message: "Preview arguments are missing.",
|
|
20133
|
+
options: this.options
|
|
20134
|
+
});
|
|
20135
|
+
const hasAudio = this.samplesCount > 0;
|
|
20136
|
+
this.mediaTransmissionStarted = false;
|
|
20137
|
+
this.recordingActive = false;
|
|
20138
|
+
this.recordingTransportFailed = false;
|
|
20139
|
+
this.confirmedFrameNumber = this.confirmedSampleNumber = this.samplesCount = this.framesCount = 0;
|
|
20140
|
+
this.sampleProgress = this.frameProgress = void 0;
|
|
20141
|
+
this.key = args.key;
|
|
20142
|
+
if (args.mp4) this.replay.setMp4Source(`${args.mp4 + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.mp4`, true);
|
|
20143
|
+
if (args.webm) this.replay.setWebMSource(`${args.webm + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.webm`, true);
|
|
20144
|
+
this.hide();
|
|
20145
|
+
const widthDimension = this.getRecorderWidth(true);
|
|
20146
|
+
const heightDimension = this.getRecorderHeight(true);
|
|
20147
|
+
const duration = args.duration ?? -1;
|
|
20148
|
+
this.emit("PREVIEW", {
|
|
20149
|
+
key: this.key,
|
|
20150
|
+
width: widthDimension?.value,
|
|
20151
|
+
height: heightDimension?.value,
|
|
20152
|
+
hasAudio,
|
|
20153
|
+
duration
|
|
20154
|
+
});
|
|
20155
|
+
if (this.stopTime) this.waitingTime = Date.now() - this.stopTime;
|
|
20156
|
+
this.recordingStats ??= {};
|
|
20157
|
+
this.recordingStats.waitingTime = this.waitingTime;
|
|
20158
|
+
}
|
|
20159
|
+
cancelAnimationFrame() {
|
|
20160
|
+
this.loop?.dispose();
|
|
20161
|
+
}
|
|
20162
|
+
getIntervalSum() {
|
|
20163
|
+
if (!this.loop) return;
|
|
20164
|
+
return this.loop.getElapsedTime();
|
|
20165
|
+
}
|
|
20166
|
+
getAvgInterval() {
|
|
20167
|
+
const intervalSum = this.getIntervalSum() ?? 0;
|
|
20168
|
+
if (!this.hasValidInterval(intervalSum)) return;
|
|
20169
|
+
return intervalSum / this.framesCount;
|
|
20170
|
+
}
|
|
20171
|
+
getAvgFps() {
|
|
20172
|
+
const intervalSum = this.getIntervalSum() ?? 0;
|
|
20173
|
+
if (!this.hasValidInterval(intervalSum)) return;
|
|
20174
|
+
return this.framesCount / intervalSum * 1000;
|
|
19784
20175
|
}
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
this.stopPings();
|
|
19788
|
-
const err = error_createError({
|
|
19789
|
-
message: "Already disconnected",
|
|
19790
|
-
explanation: "Sorry, connection to the server has been destroyed. Please reload.",
|
|
19791
|
-
options: this.options
|
|
19792
|
-
});
|
|
19793
|
-
this.handleWriteFailure(err);
|
|
19794
|
-
} else if (this.streamBackpressured) {
|
|
19795
|
-
if (this.hasExceededWebSocketSendBuffer(buffer, opts)) this.failMediaTransmission(error_createError({
|
|
19796
|
-
message: "WebSocket send buffer limit exceeded",
|
|
19797
|
-
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.",
|
|
19798
|
-
options: this.options
|
|
19799
|
-
}));
|
|
19800
|
-
} else this.writeToStream(this.stream, buffer, opts);
|
|
20176
|
+
hasValidInterval(intervalSum) {
|
|
20177
|
+
return intervalSum > 0;
|
|
19801
20178
|
}
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
19806
|
-
|
|
19807
|
-
|
|
19808
|
-
|
|
19809
|
-
|
|
19810
|
-
|
|
19811
|
-
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
this.
|
|
19825
|
-
|
|
20179
|
+
getRecordingStats() {
|
|
20180
|
+
return this.recordingStats;
|
|
20181
|
+
}
|
|
20182
|
+
getAudioSampleRate() {
|
|
20183
|
+
return this.recordingAudioSampleRate ?? this.userMedia?.getAudioSampleRate();
|
|
20184
|
+
}
|
|
20185
|
+
stop(params) {
|
|
20186
|
+
this.cancelCameraRecovery();
|
|
20187
|
+
if (!this.userMedia) throw new Error("No user media defined, unable to stop");
|
|
20188
|
+
this.options.logger.debug(`Recorder: stop(${params ? string_pretty(params) : ""})`);
|
|
20189
|
+
this.recordingActive = false;
|
|
20190
|
+
const limitReached = params?.limitReached;
|
|
20191
|
+
this.emit("STOPPING", {
|
|
20192
|
+
limitReached
|
|
20193
|
+
});
|
|
20194
|
+
this.loop?.complete?.();
|
|
20195
|
+
this.stopTimeout = window.setTimeout(()=>{
|
|
20196
|
+
this.stopTimeout = void 0;
|
|
20197
|
+
this.stopTime = Date.now();
|
|
20198
|
+
const videoType = this.replay.getVideoType();
|
|
20199
|
+
if (!videoType) throw new Error("Unable to video record when no video type is defined.");
|
|
20200
|
+
this.recordingStats = {
|
|
20201
|
+
avgFps: this.getAvgFps(),
|
|
20202
|
+
wantedFps: this.options.video.fps,
|
|
20203
|
+
avgInterval: this.getAvgInterval(),
|
|
20204
|
+
wantedInterval: 1e3 / this.options.video.fps,
|
|
20205
|
+
intervalSum: this.getIntervalSum(),
|
|
20206
|
+
framesCount: this.framesCount,
|
|
20207
|
+
videoType
|
|
20208
|
+
};
|
|
20209
|
+
if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
|
|
20210
|
+
if (isAudioEnabled(this.options) && this.userMedia) {
|
|
20211
|
+
this.recordingStats.samplesCount = this.samplesCount;
|
|
20212
|
+
this.recordingStats.sampleRate = this.recordingAudioSampleRate ?? this.userMedia.getAudioSampleRate();
|
|
19826
20213
|
}
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
19830
|
-
|
|
19831
|
-
explanation: `stream.write() failed because of ${string_pretty(exc)}`,
|
|
19832
|
-
options: this.options,
|
|
19833
|
-
exc
|
|
20214
|
+
this.writeCommand("stop", this.recordingStats, ()=>{
|
|
20215
|
+
this.emit("STOPPED", {
|
|
20216
|
+
recordingStats: this.recordingStats
|
|
20217
|
+
});
|
|
19834
20218
|
});
|
|
19835
|
-
this.
|
|
19836
|
-
|
|
19837
|
-
}
|
|
20219
|
+
this.reset();
|
|
20220
|
+
}, 60);
|
|
19838
20221
|
}
|
|
19839
|
-
|
|
19840
|
-
|
|
19841
|
-
this.
|
|
19842
|
-
this.
|
|
19843
|
-
|
|
19844
|
-
|
|
19845
|
-
|
|
19846
|
-
pendingWrite = this.pendingWrites.dequeue();
|
|
19847
|
-
}
|
|
19848
|
-
this.resumeRecordingAfterDrain();
|
|
20222
|
+
back(cb) {
|
|
20223
|
+
this.emit("GOING_BACK");
|
|
20224
|
+
this.key = void 0;
|
|
20225
|
+
this.recordingDimensions = void 0;
|
|
20226
|
+
this.unloaded = false;
|
|
20227
|
+
this.show();
|
|
20228
|
+
this.writeCommand("back", void 0, cb);
|
|
19849
20229
|
}
|
|
19850
|
-
|
|
19851
|
-
|
|
20230
|
+
reInitializeAudio() {
|
|
20231
|
+
this.options.logger.debug("Recorder: reInitializeAudio()");
|
|
20232
|
+
this.clearUserMediaTimeout();
|
|
20233
|
+
this.userMediaLoading = false;
|
|
20234
|
+
this.userMedia?.stop();
|
|
20235
|
+
this.recordingDimensions = void 0;
|
|
20236
|
+
this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
|
|
20237
|
+
this.loadUserMedia();
|
|
19852
20238
|
}
|
|
19853
|
-
|
|
19854
|
-
|
|
20239
|
+
reset() {
|
|
20240
|
+
this.cancelCameraRecovery();
|
|
20241
|
+
if (!this.unloaded) {
|
|
20242
|
+
this.options.logger.debug("Recorder: reset()");
|
|
20243
|
+
this.emit("RESETTING");
|
|
20244
|
+
this.cancelAnimationFrame();
|
|
20245
|
+
this.userMedia?.stop();
|
|
20246
|
+
this.replay.reset();
|
|
20247
|
+
this.userMediaLoaded = this.key = this.canvas = this.ctx = this.recordingBuffer = void 0;
|
|
20248
|
+
}
|
|
19855
20249
|
}
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
this.resumeAfterDrain = true;
|
|
19859
|
-
this.loop.stop();
|
|
20250
|
+
validate() {
|
|
20251
|
+
return Boolean(this.key) && void 0 === this.canvas;
|
|
19860
20252
|
}
|
|
19861
|
-
|
|
19862
|
-
|
|
20253
|
+
pause(params) {
|
|
20254
|
+
this.cancelCameraRecovery();
|
|
19863
20255
|
this.resumeAfterDrain = false;
|
|
19864
|
-
|
|
20256
|
+
const loop = this.loop;
|
|
20257
|
+
if (!loop) throw new Error("No recording loop defined");
|
|
20258
|
+
if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
|
|
20259
|
+
else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
|
|
20260
|
+
this.userMedia?.pause();
|
|
20261
|
+
loop.stop();
|
|
20262
|
+
this.emit("PAUSED");
|
|
20263
|
+
this.sendPings();
|
|
19865
20264
|
}
|
|
19866
|
-
|
|
19867
|
-
|
|
20265
|
+
resume() {
|
|
20266
|
+
if (this.cameraRecovery || !this.userMedia?.hasLiveStream()) {
|
|
20267
|
+
this.recoverCamera();
|
|
20268
|
+
this.resumeAfterCameraRecovery = Boolean(this.cameraRecovery);
|
|
20269
|
+
return;
|
|
20270
|
+
}
|
|
20271
|
+
const loop = this.loop;
|
|
20272
|
+
if (!loop) throw new Error("No recording loop defined");
|
|
20273
|
+
this.options.logger.debug(`Recorder: resume() with frame ${this.framesCount}`);
|
|
20274
|
+
this.stopPings();
|
|
20275
|
+
this.emit("RESUMING");
|
|
20276
|
+
this.userMedia.resume();
|
|
20277
|
+
if (this.streamBackpressured) this.resumeAfterDrain = true;
|
|
20278
|
+
else loop.start();
|
|
19868
20279
|
}
|
|
19869
|
-
|
|
19870
|
-
|
|
19871
|
-
this.emit("
|
|
19872
|
-
err
|
|
19873
|
-
});
|
|
20280
|
+
onFlushed(opts) {
|
|
20281
|
+
const frameNumber = opts.frameNumber;
|
|
20282
|
+
if (1 === frameNumber) this.emit("FIRST_FRAME_SENT");
|
|
19874
20283
|
}
|
|
19875
|
-
|
|
20284
|
+
draw(_deltaTime, elapsedTime) {
|
|
19876
20285
|
if (this.recordingTransportFailed) return;
|
|
19877
|
-
this.
|
|
19878
|
-
|
|
19879
|
-
|
|
19880
|
-
|
|
19881
|
-
|
|
19882
|
-
|
|
19883
|
-
|
|
19884
|
-
|
|
19885
|
-
|
|
19886
|
-
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
|
|
20286
|
+
if (!this.userMedia) throw new Error("No user media defined, unable to draw on canvas");
|
|
20287
|
+
try {
|
|
20288
|
+
if (!this.isPaused() && this.stream && this.ctx) {
|
|
20289
|
+
if (0 === this.framesCount) this.emit("SENDING_FIRST_FRAME");
|
|
20290
|
+
this.framesCount++;
|
|
20291
|
+
const imageSource = this.userMedia.getRawVisuals();
|
|
20292
|
+
if (this.canvas && imageSource) this.ctx.drawImage(imageSource, 0, 0, this.canvas.width, this.canvas.height);
|
|
20293
|
+
else throw new Error("Unable to draw an image without a defined canvas");
|
|
20294
|
+
this.recordingBuffer = this.frame?.toBuffer();
|
|
20295
|
+
const recordingBufferLength = this.recordingBuffer?.length;
|
|
20296
|
+
if (recordingBufferLength) {
|
|
20297
|
+
if (this.recordingBuffer) {
|
|
20298
|
+
const frameControlBuffer = capture_Buffer.from(JSON.stringify({
|
|
20299
|
+
frameNumber: this.framesCount
|
|
20300
|
+
}));
|
|
20301
|
+
const frameBuffer = capture_Buffer.concat([
|
|
20302
|
+
this.recordingBuffer,
|
|
20303
|
+
frameControlBuffer
|
|
20304
|
+
]);
|
|
20305
|
+
this.mediaTransmissionStarted = true;
|
|
20306
|
+
this.writeStream(frameBuffer, {
|
|
20307
|
+
frameNumber: this.framesCount,
|
|
20308
|
+
onFlushedCallback: this.onFlushed.bind(this)
|
|
20309
|
+
});
|
|
20310
|
+
this.visuals.checkTimer(elapsedTime);
|
|
20311
|
+
}
|
|
20312
|
+
} else throw error_createError({
|
|
20313
|
+
message: "Failed to extract webcam data.",
|
|
20314
|
+
options: this.options
|
|
20315
|
+
});
|
|
20316
|
+
}
|
|
20317
|
+
} catch (exc) {
|
|
20318
|
+
this.emit("ERROR", {
|
|
20319
|
+
exc
|
|
20320
|
+
});
|
|
20321
|
+
}
|
|
20322
|
+
}
|
|
20323
|
+
createLoop() {
|
|
20324
|
+
const newLoop = animitter_default()({
|
|
20325
|
+
fps: this.options.video.fps
|
|
20326
|
+
}, this.draw.bind(this));
|
|
20327
|
+
this.originalAnimationFrameObject = newLoop.getRequestAnimationFrameObject();
|
|
20328
|
+
return newLoop;
|
|
20329
|
+
}
|
|
20330
|
+
record() {
|
|
20331
|
+
if (this.unloaded || this.cameraRecovery) return;
|
|
20332
|
+
if (!this.connected) {
|
|
20333
|
+
this.options.logger.debug("Recorder: reconnecting before recording …");
|
|
20334
|
+
this.initSocket(()=>{
|
|
20335
|
+
this.once("SERVER_READY", ()=>{
|
|
20336
|
+
if (this.unloaded) return;
|
|
20337
|
+
if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
|
|
20338
|
+
else this.once("USER_MEDIA_READY", ()=>{
|
|
20339
|
+
if (!this.unloaded) this.record();
|
|
20340
|
+
});
|
|
20341
|
+
});
|
|
20342
|
+
});
|
|
20343
|
+
return;
|
|
20344
|
+
}
|
|
20345
|
+
if (!this.userMediaLoaded) {
|
|
20346
|
+
if (this.options.loadUserMediaOnRecord) this.loadUserMedia({
|
|
20347
|
+
recordWhenReady: true
|
|
20348
|
+
});
|
|
20349
|
+
else {
|
|
20350
|
+
const err = error_createError({
|
|
20351
|
+
message: "Load and enable your camera first",
|
|
20352
|
+
options: this.options
|
|
20353
|
+
});
|
|
20354
|
+
this.emit("ERROR", {
|
|
20355
|
+
err
|
|
20356
|
+
});
|
|
20357
|
+
}
|
|
20358
|
+
return;
|
|
20359
|
+
}
|
|
20360
|
+
const loop = this.loop;
|
|
20361
|
+
if (!loop) throw new Error("No recording loop defined");
|
|
19893
20362
|
this.recordingDimensions = void 0;
|
|
19894
|
-
|
|
19895
|
-
|
|
19896
|
-
|
|
19897
|
-
|
|
19898
|
-
startBackpressureTimeout() {
|
|
19899
|
-
if (!this.shouldStartBackpressureTimeout()) return;
|
|
19900
|
-
this.backpressureTimeout = window.setTimeout(()=>{
|
|
19901
|
-
this.backpressureTimeout = void 0;
|
|
19902
|
-
if (this.shouldIgnoreBackpressureTimeout()) return;
|
|
20363
|
+
try {
|
|
20364
|
+
if (!this.userMedia) throw new Error("No user media defined, unable to create canvas");
|
|
20365
|
+
this.canvas = this.userMedia.createCanvas();
|
|
20366
|
+
} catch (exc) {
|
|
19903
20367
|
const err = error_createError({
|
|
19904
|
-
|
|
19905
|
-
explanation: "The server stopped accepting recording data. The recording has been stopped; please check your connection and start a new one.",
|
|
20368
|
+
exc,
|
|
19906
20369
|
options: this.options
|
|
19907
20370
|
});
|
|
19908
|
-
this.
|
|
19909
|
-
|
|
19910
|
-
}
|
|
19911
|
-
shouldStartBackpressureTimeout() {
|
|
19912
|
-
return void 0 === this.backpressureTimeout && this.mediaTransmissionStarted;
|
|
19913
|
-
}
|
|
19914
|
-
shouldIgnoreBackpressureTimeout() {
|
|
19915
|
-
return !this.streamBackpressured || this.recordingTransportFailed;
|
|
19916
|
-
}
|
|
19917
|
-
failInterruptedRecording() {
|
|
19918
|
-
const unconfirmedFrames = Math.max(0, this.framesCount - this.confirmedFrameNumber);
|
|
19919
|
-
const unconfirmedSamples = Math.max(0, this.samplesCount - this.confirmedSampleNumber);
|
|
19920
|
-
const err = error_createError({
|
|
19921
|
-
message: "Recording interrupted",
|
|
19922
|
-
explanation: "The server connection closed before this recording completed. It cannot be safely resumed, so it has been stopped. Please start a new recording.",
|
|
19923
|
-
options: this.options,
|
|
19924
|
-
cause: {
|
|
19925
|
-
unconfirmedFrames,
|
|
19926
|
-
unconfirmedSamples
|
|
19927
|
-
}
|
|
19928
|
-
});
|
|
19929
|
-
this.failMediaTransmission(err);
|
|
19930
|
-
}
|
|
19931
|
-
handleStreamDrain(stream) {
|
|
19932
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *drain* event emitted`);
|
|
19933
|
-
this.flushPendingWrites(stream);
|
|
19934
|
-
}
|
|
19935
|
-
canReconnectAfterStreamClose() {
|
|
19936
|
-
return Boolean(this.connected && this.userMediaLoaded);
|
|
19937
|
-
}
|
|
19938
|
-
shouldReportStreamClose() {
|
|
19939
|
-
return !this.connecting && !this.blocking;
|
|
19940
|
-
}
|
|
19941
|
-
handleStreamClose(url2Connect) {
|
|
19942
|
-
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream has closed:`, {
|
|
19943
|
-
connecting: this.connecting,
|
|
19944
|
-
connected: this.connected,
|
|
19945
|
-
userMediaLoaded: this.userMediaLoaded,
|
|
19946
|
-
blocking: this.blocking
|
|
19947
|
-
});
|
|
19948
|
-
this.options.logger.debug(debugLine);
|
|
19949
|
-
const canReconnect = this.canReconnectAfterStreamClose();
|
|
19950
|
-
this.connected = false;
|
|
19951
|
-
this.stopPings();
|
|
19952
|
-
if (this.shouldIgnoreStreamClose()) return;
|
|
19953
|
-
if (this.mediaTransmissionStarted) this.failInterruptedRecording();
|
|
19954
|
-
else if (canReconnect) {
|
|
19955
|
-
this.pendingWrites.clear();
|
|
19956
|
-
this.streamBackpressured = false;
|
|
19957
|
-
this.resumeAfterDrain = false;
|
|
19958
|
-
this.reconnecting = true;
|
|
19959
|
-
this.initSocket();
|
|
19960
|
-
} else if (this.shouldReportStreamClose()) window.setTimeout(()=>{
|
|
19961
|
-
this.handleConnectionFailure({
|
|
19962
|
-
url2Connect,
|
|
19963
|
-
cause: "closed"
|
|
20371
|
+
this.emit("ERROR", {
|
|
20372
|
+
err
|
|
19964
20373
|
});
|
|
19965
|
-
}, 0);
|
|
19966
|
-
}
|
|
19967
|
-
shouldIgnoreStreamClose() {
|
|
19968
|
-
return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
|
|
19969
|
-
}
|
|
19970
|
-
handleStreamError(url2Connect, err) {
|
|
19971
|
-
if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
|
|
19972
|
-
else this.lastSocketError = serializeError(err);
|
|
19973
|
-
const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
|
|
19974
|
-
error: serializeError(err)
|
|
19975
|
-
});
|
|
19976
|
-
this.options.logger.debug(debugLine);
|
|
19977
|
-
if (this.validate()) {
|
|
19978
|
-
this.connected = false;
|
|
19979
|
-
this.stopPings();
|
|
19980
|
-
this.discardSocket();
|
|
19981
20374
|
return;
|
|
19982
20375
|
}
|
|
19983
|
-
|
|
19984
|
-
|
|
19985
|
-
|
|
20376
|
+
this.ctx = this.canvas.getContext("2d");
|
|
20377
|
+
if (!this.canvas.width) {
|
|
20378
|
+
const err = error_createError({
|
|
20379
|
+
message: "Canvas has an invalid width.",
|
|
20380
|
+
options: this.options
|
|
20381
|
+
});
|
|
20382
|
+
this.emit("ERROR", {
|
|
20383
|
+
err
|
|
20384
|
+
});
|
|
20385
|
+
return;
|
|
20386
|
+
}
|
|
20387
|
+
if (!this.canvas.height) {
|
|
20388
|
+
const err = error_createError({
|
|
20389
|
+
message: "Canvas has an invalid height.",
|
|
20390
|
+
options: this.options
|
|
20391
|
+
});
|
|
20392
|
+
this.emit("ERROR", {
|
|
20393
|
+
err
|
|
20394
|
+
});
|
|
20395
|
+
return;
|
|
20396
|
+
}
|
|
20397
|
+
this.recordingDimensions = {
|
|
20398
|
+
width: this.canvas.width,
|
|
20399
|
+
height: this.canvas.height
|
|
20400
|
+
};
|
|
20401
|
+
this.frame = new canvas_to_buffer_modern_r(this.canvas, this.options.image.types, this.options.image.quality);
|
|
20402
|
+
this.options.logger.debug("Recorder: record()");
|
|
20403
|
+
this.recordingActive = true;
|
|
20404
|
+
this.recordingTransportFailed = false;
|
|
20405
|
+
this.mediaTransmissionStarted = false;
|
|
20406
|
+
this.recordingAudioSampleRate = void 0;
|
|
20407
|
+
this.userMedia.record();
|
|
20408
|
+
this.emit("RECORDING", {
|
|
20409
|
+
framesCount: this.framesCount
|
|
19986
20410
|
});
|
|
19987
|
-
|
|
19988
|
-
|
|
20411
|
+
loop.on("update", (_deltaTime, elapsedTime)=>{
|
|
20412
|
+
let avgFPS;
|
|
20413
|
+
avgFPS = 0 !== elapsedTime ? Math.round(this.framesCount / elapsedTime * 1000) : void 0;
|
|
20414
|
+
this.options.logger.debug(`Recorder updates avgFps = ${avgFPS} at frame ${this.framesCount}`);
|
|
19989
20415
|
});
|
|
19990
|
-
|
|
19991
|
-
|
|
19992
|
-
|
|
19993
|
-
|
|
19994
|
-
|
|
19995
|
-
|
|
19996
|
-
|
|
19997
|
-
|
|
20416
|
+
loop.start();
|
|
20417
|
+
}
|
|
20418
|
+
setAnimationFrameObject(newObj) {
|
|
20419
|
+
if (this.loop) {
|
|
20420
|
+
const isRecording = this.isRecording();
|
|
20421
|
+
this.loop.stop();
|
|
20422
|
+
this.loop.setRequestAnimationFrameObject(newObj);
|
|
20423
|
+
if (isRecording) this.loop.start();
|
|
20424
|
+
}
|
|
20425
|
+
}
|
|
20426
|
+
restoreAnimationFrameObject() {
|
|
20427
|
+
this.options.logger.debug("Recorder: restoreAnimationFrameObject()");
|
|
20428
|
+
if (this.originalAnimationFrameObject) this.setAnimationFrameObject(this.originalAnimationFrameObject);
|
|
20429
|
+
}
|
|
20430
|
+
loopWithTimeouts() {
|
|
20431
|
+
this.options.logger.debug("Recorder: loopWithTimeouts()");
|
|
20432
|
+
const wantedInterval = 1e3 / this.options.video.fps;
|
|
20433
|
+
let processingTime = 0;
|
|
20434
|
+
const raf = (fn)=>window.setTimeout(()=>{
|
|
20435
|
+
const start = Date.now();
|
|
20436
|
+
fn();
|
|
20437
|
+
processingTime = Date.now() - start;
|
|
20438
|
+
}, wantedInterval - processingTime);
|
|
20439
|
+
const cancel = (id)=>{
|
|
20440
|
+
window.clearTimeout(id);
|
|
20441
|
+
};
|
|
20442
|
+
this.setAnimationFrameObject({
|
|
20443
|
+
requestAnimationFrame: raf,
|
|
20444
|
+
cancelAnimationFrame: cancel
|
|
19998
20445
|
});
|
|
19999
20446
|
}
|
|
20000
|
-
|
|
20001
|
-
this.
|
|
20002
|
-
this.pingInterval = window.setInterval(()=>{
|
|
20003
|
-
this.options.logger.debug("Recorder: pinging...");
|
|
20004
|
-
this.writeStream(Buffer.from(""));
|
|
20005
|
-
}, this.options.timeouts.pingInterval);
|
|
20447
|
+
isPaused() {
|
|
20448
|
+
return this.userMedia?.isPaused() && !this.loop?.isRunning();
|
|
20006
20449
|
}
|
|
20007
|
-
|
|
20008
|
-
|
|
20009
|
-
this.options.logger.debug("Recorder: stopping pings...");
|
|
20010
|
-
window.clearInterval(this.pingInterval);
|
|
20011
|
-
this.pingInterval = void 0;
|
|
20450
|
+
isUserMediaPaused() {
|
|
20451
|
+
return this.userMedia?.isPaused() ?? false;
|
|
20012
20452
|
}
|
|
20013
|
-
|
|
20014
|
-
|
|
20015
|
-
const sampleRate = this.userMedia?.getAudioSampleRate();
|
|
20016
|
-
if (sampleRate && sampleRate > 0) this.recordingAudioSampleRate ??= sampleRate;
|
|
20017
|
-
this.samplesCount++;
|
|
20018
|
-
this.mediaTransmissionStarted = true;
|
|
20019
|
-
const audioBuffer = audioSample.toBuffer();
|
|
20020
|
-
this.writeStream(audioBuffer);
|
|
20453
|
+
isCaptureLoopRunning() {
|
|
20454
|
+
return this.loop?.isRunning() ?? false;
|
|
20021
20455
|
}
|
|
20022
|
-
|
|
20023
|
-
|
|
20456
|
+
stopCaptureLoop() {
|
|
20457
|
+
this.loop?.stop();
|
|
20024
20458
|
}
|
|
20025
|
-
|
|
20026
|
-
|
|
20027
|
-
this.options.logger.debug(`Recorder: onUserMediaReady(${params ? string_pretty(params) : ""})`);
|
|
20028
|
-
const switchingFacingMode = params?.switchingFacingMode;
|
|
20029
|
-
if (params?.recovering) {
|
|
20030
|
-
if (!this.userMedia?.hasLiveStream()) return void this.failCameraRecovery(new Error("The restored camera has no live tracks."));
|
|
20031
|
-
if (void 0 !== this.recordingAudioSampleRate && this.userMedia.getAudioSampleRate() !== this.recordingAudioSampleRate) return void this.failCameraRecovery(new Error("The audio sample rate changed during recovery."));
|
|
20032
|
-
}
|
|
20033
|
-
this.userMediaLoading = this.blocking = this.unloaded = this.submitting = false;
|
|
20034
|
-
this.userMediaLoaded = true;
|
|
20035
|
-
this.clearUserMediaTimeout();
|
|
20036
|
-
if (!switchingFacingMode && !params?.recovering) this.loop = this.createLoop();
|
|
20037
|
-
this.show();
|
|
20038
|
-
if (params?.recordWhenReady) this.record();
|
|
20039
|
-
this.emit("USER_MEDIA_READY", {
|
|
20040
|
-
switchingFacingMode: params?.switchingFacingMode,
|
|
20041
|
-
recovering: params?.recovering,
|
|
20042
|
-
paused: this.isPaused(),
|
|
20043
|
-
recordWhenReady: params?.recordWhenReady
|
|
20044
|
-
});
|
|
20045
|
-
if (params?.recovering) this.finishCameraRecovery(true);
|
|
20046
|
-
} catch (exc) {
|
|
20047
|
-
if (params?.recovering) this.failCameraRecovery(exc);
|
|
20048
|
-
else this.emit("ERROR", {
|
|
20049
|
-
exc
|
|
20050
|
-
});
|
|
20051
|
-
}
|
|
20459
|
+
startCaptureLoop() {
|
|
20460
|
+
this.loop?.start();
|
|
20052
20461
|
}
|
|
20053
|
-
|
|
20054
|
-
|
|
20055
|
-
this.options.logger.debug("Recorder: clearRetryTimeout()");
|
|
20056
|
-
window.clearTimeout(this.retryTimeout);
|
|
20057
|
-
this.retryTimeout = void 0;
|
|
20462
|
+
isRecording() {
|
|
20463
|
+
return Boolean(this.loop?.isRunning() && !this.isPaused() && !this.isNotifying() && this.stream && !this.stream.destroyed);
|
|
20058
20464
|
}
|
|
20059
|
-
|
|
20060
|
-
|
|
20061
|
-
|
|
20062
|
-
|
|
20063
|
-
|
|
20465
|
+
getRecordingDimensions() {
|
|
20466
|
+
return this.recordingDimensions && {
|
|
20467
|
+
...this.recordingDimensions
|
|
20468
|
+
};
|
|
20469
|
+
}
|
|
20470
|
+
}
|
|
20471
|
+
const capture = RecorderCapture;
|
|
20472
|
+
function figureMinHeight(height, options) {
|
|
20473
|
+
let minHeight;
|
|
20474
|
+
if (options.video.height) {
|
|
20475
|
+
minHeight = height ? Math.min(options.video.height, height) : options.video.height;
|
|
20476
|
+
if (minHeight < 1) throw error_createError({
|
|
20477
|
+
message: `Got a min height less than 1 (${minHeight})!`,
|
|
20478
|
+
options
|
|
20479
|
+
});
|
|
20480
|
+
} else minHeight = height;
|
|
20481
|
+
return minHeight;
|
|
20482
|
+
}
|
|
20483
|
+
const dimensions_figureMinHeight = figureMinHeight;
|
|
20484
|
+
function getRatio(options, videoHeight, videoWidth) {
|
|
20485
|
+
let ratio = 1;
|
|
20486
|
+
const hasVideoDimensions = videoHeight && videoWidth;
|
|
20487
|
+
const desiredHeight = options.video.height;
|
|
20488
|
+
const desiredWidth = options.video.width;
|
|
20489
|
+
const hasDesiredDimensions = desiredHeight && desiredWidth;
|
|
20490
|
+
if (hasDesiredDimensions) ratio = hasVideoDimensions ? videoHeight < desiredHeight || videoWidth < desiredWidth ? videoHeight / videoWidth : desiredHeight / desiredWidth : desiredHeight / desiredWidth;
|
|
20491
|
+
else if (hasVideoDimensions) ratio = videoHeight / videoWidth;
|
|
20492
|
+
return ratio;
|
|
20493
|
+
}
|
|
20494
|
+
const dimensions_getRatio = getRatio;
|
|
20495
|
+
function calculateHeight(responsive, videoWidth, options, ratio, element) {
|
|
20496
|
+
const dimension = {
|
|
20497
|
+
unit: "px"
|
|
20498
|
+
};
|
|
20499
|
+
let width = videoWidth;
|
|
20500
|
+
if (responsive && element) {
|
|
20501
|
+
const limitedDimension = dimensions_limitWidth(element, options, width);
|
|
20502
|
+
width = limitedDimension.value;
|
|
20064
20503
|
}
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
|
|
20068
|
-
|
|
20504
|
+
const chosenRatio = ratio ?? dimensions_getRatio(options, void 0, videoWidth);
|
|
20505
|
+
const height = width ? Math.round(width * chosenRatio) : void 0;
|
|
20506
|
+
if (height && Number.isInteger(height) && height < 1) throw error_createError({
|
|
20507
|
+
message: "Just calculated a height less than 1 which is wrong.",
|
|
20508
|
+
options
|
|
20509
|
+
});
|
|
20510
|
+
const minHeight = dimensions_figureMinHeight(height, options);
|
|
20511
|
+
dimension.value = minHeight;
|
|
20512
|
+
return dimension;
|
|
20513
|
+
}
|
|
20514
|
+
const dimensions_calculateHeight = calculateHeight;
|
|
20515
|
+
function calculateWidth(responsive, options, videoHeight, ratio) {
|
|
20516
|
+
const dimension = {
|
|
20517
|
+
unit: "px"
|
|
20518
|
+
};
|
|
20519
|
+
let height = dimensions_figureMinHeight(videoHeight, options);
|
|
20520
|
+
if (responsive) {
|
|
20521
|
+
const limitedDimension = dimensions_limitHeight(height, options, "calculateWidth");
|
|
20522
|
+
height = limitedDimension.value;
|
|
20069
20523
|
}
|
|
20070
|
-
|
|
20071
|
-
|
|
20072
|
-
|
|
20073
|
-
|
|
20074
|
-
|
|
20524
|
+
if (!height || height < 1) throw error_createError({
|
|
20525
|
+
message: `Height ${height} cannot be smaller than 1 when calculating width.`,
|
|
20526
|
+
options
|
|
20527
|
+
});
|
|
20528
|
+
const chosenRatio = ratio ?? dimensions_getRatio(options, videoHeight);
|
|
20529
|
+
const calculatedWidth = Math.round(height / chosenRatio);
|
|
20530
|
+
if (calculatedWidth < 1) throw error_createError({
|
|
20531
|
+
message: "Calculated width cannot be smaller than 1!",
|
|
20532
|
+
options
|
|
20533
|
+
});
|
|
20534
|
+
dimension.value = calculatedWidth;
|
|
20535
|
+
return dimension;
|
|
20536
|
+
}
|
|
20537
|
+
const dimensions_calculateWidth = calculateWidth;
|
|
20538
|
+
class RecorderDimensions {
|
|
20539
|
+
context;
|
|
20540
|
+
constructor(context){
|
|
20541
|
+
this.context = context;
|
|
20075
20542
|
}
|
|
20076
|
-
|
|
20077
|
-
|
|
20078
|
-
|
|
20543
|
+
correctDimensions() {
|
|
20544
|
+
const recorderElement = this.context.getRecorderElement();
|
|
20545
|
+
if (!recorderElement) return;
|
|
20546
|
+
const widthDimension = dimensions_useFullWidth(this.context.options.video.mobileBreakPoint);
|
|
20547
|
+
if (this.context.options.video.stretch || widthDimension) {
|
|
20548
|
+
recorderElement.style.width = "100%";
|
|
20549
|
+
recorderElement.style.removeProperty("height");
|
|
20550
|
+
} else {
|
|
20551
|
+
if (this.context.options.video.width) {
|
|
20552
|
+
const recorderWidth = this.getRecorderWidth(true);
|
|
20553
|
+
if (recorderWidth?.value) recorderElement.width = recorderWidth.value;
|
|
20554
|
+
else recorderElement.style.removeProperty("width");
|
|
20555
|
+
}
|
|
20556
|
+
if (this.context.options.video.height) {
|
|
20557
|
+
const recorderHeight = this.getRecorderHeight(true);
|
|
20558
|
+
if (recorderHeight?.value) recorderElement.height = recorderHeight.value;
|
|
20559
|
+
else recorderElement.style.removeProperty("height");
|
|
20560
|
+
}
|
|
20561
|
+
}
|
|
20079
20562
|
}
|
|
20080
|
-
|
|
20081
|
-
|
|
20082
|
-
if (
|
|
20083
|
-
|
|
20084
|
-
|
|
20085
|
-
|
|
20086
|
-
|
|
20087
|
-
|
|
20088
|
-
|
|
20089
|
-
this.reconnectTimeout = window.setTimeout(()=>{
|
|
20090
|
-
this.reconnectTimeout = void 0;
|
|
20091
|
-
if (this.shouldContinueReconnect()) this.initSocket();
|
|
20092
|
-
}, delayMs);
|
|
20093
|
-
return true;
|
|
20563
|
+
getRecorderWidth(responsive) {
|
|
20564
|
+
const userMedia = this.context.getUserMedia();
|
|
20565
|
+
if (userMedia?.hasVideoWidth()) return userMedia.getRawWidth(responsive);
|
|
20566
|
+
else if (responsive && this.context.options.video.width) return this.context.visuals.limitWidth(this.context.options.video.width);
|
|
20567
|
+
const dimension = {
|
|
20568
|
+
unit: "px",
|
|
20569
|
+
value: this.context.options.video.width
|
|
20570
|
+
};
|
|
20571
|
+
return dimension;
|
|
20094
20572
|
}
|
|
20095
|
-
|
|
20096
|
-
|
|
20573
|
+
getRecorderHeight(responsive, useBoundingClientRect) {
|
|
20574
|
+
const recorderElement = this.context.getRecorderElement();
|
|
20575
|
+
const userMedia = this.context.getUserMedia();
|
|
20576
|
+
let recorderHeight;
|
|
20577
|
+
if (recorderElement && useBoundingClientRect) {
|
|
20578
|
+
const height = recorderElement.getBoundingClientRect().height;
|
|
20579
|
+
recorderHeight = {
|
|
20580
|
+
unit: "px",
|
|
20581
|
+
value: height
|
|
20582
|
+
};
|
|
20583
|
+
} else if (userMedia) {
|
|
20584
|
+
let height = userMedia.getRawHeight(responsive);
|
|
20585
|
+
if (void 0 !== height && this.context.options.video.width) {
|
|
20586
|
+
const ratio = this.getRatio();
|
|
20587
|
+
if (void 0 !== ratio) {
|
|
20588
|
+
const idealHeight = this.context.options.video.width * ratio;
|
|
20589
|
+
height = Math.min(idealHeight, height);
|
|
20590
|
+
}
|
|
20591
|
+
}
|
|
20592
|
+
recorderHeight = {
|
|
20593
|
+
unit: "px",
|
|
20594
|
+
value: height
|
|
20595
|
+
};
|
|
20596
|
+
} else if (responsive && this.context.options.video.height) recorderHeight = this.calculateHeight(responsive);
|
|
20597
|
+
else if (this.context.options.video.height) {
|
|
20598
|
+
const height = this.context.options.video.height;
|
|
20599
|
+
recorderHeight = {
|
|
20600
|
+
unit: "px",
|
|
20601
|
+
value: height
|
|
20602
|
+
};
|
|
20603
|
+
}
|
|
20604
|
+
return recorderHeight;
|
|
20097
20605
|
}
|
|
20098
|
-
|
|
20099
|
-
|
|
20606
|
+
getRatio() {
|
|
20607
|
+
const userMedia = this.context.getUserMedia();
|
|
20608
|
+
let ratio;
|
|
20609
|
+
if (userMedia) {
|
|
20610
|
+
const userMediaVideoWidth = userMedia.getVideoWidth();
|
|
20611
|
+
const userMediaVideoHeight = userMedia.getVideoHeight();
|
|
20612
|
+
if (!userMediaVideoWidth || userMediaVideoWidth < 1) ratio = this.context.visuals.getRatio();
|
|
20613
|
+
else if (userMediaVideoHeight) ratio = userMediaVideoHeight / userMediaVideoWidth;
|
|
20614
|
+
} else ratio = dimensions_getRatio(this.context.options);
|
|
20615
|
+
return ratio;
|
|
20100
20616
|
}
|
|
20101
|
-
|
|
20102
|
-
this.
|
|
20103
|
-
this.
|
|
20104
|
-
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20108
|
-
this.stream = void 0;
|
|
20109
|
-
if (stream && !stream.destroyed) stream.destroy();
|
|
20617
|
+
calculateWidth(responsive) {
|
|
20618
|
+
const userMedia = this.context.getUserMedia();
|
|
20619
|
+
const recorderElement = this.context.getRecorderElement();
|
|
20620
|
+
let videoHeight;
|
|
20621
|
+
if (userMedia) videoHeight = userMedia.getVideoHeight();
|
|
20622
|
+
else if (recorderElement) videoHeight = recorderElement.videoHeight || recorderElement.height;
|
|
20623
|
+
return dimensions_calculateWidth(responsive, this.context.options, videoHeight, this.getRatio());
|
|
20110
20624
|
}
|
|
20111
|
-
|
|
20112
|
-
|
|
20625
|
+
calculateHeight(responsive) {
|
|
20626
|
+
const userMedia = this.context.getUserMedia();
|
|
20627
|
+
const recorderElement = this.context.getRecorderElement();
|
|
20628
|
+
let videoDimension;
|
|
20629
|
+
if (userMedia) {
|
|
20630
|
+
const videoHeight = userMedia.getVideoHeight();
|
|
20631
|
+
videoDimension = {
|
|
20632
|
+
value: videoHeight,
|
|
20633
|
+
unit: "px"
|
|
20634
|
+
};
|
|
20635
|
+
} else if (recorderElement) {
|
|
20636
|
+
const videoHeight = recorderElement.videoHeight || recorderElement.height;
|
|
20637
|
+
videoDimension = {
|
|
20638
|
+
value: videoHeight,
|
|
20639
|
+
unit: "px"
|
|
20640
|
+
};
|
|
20641
|
+
} else videoDimension = dimensions_calculateHeight(responsive, void 0, this.context.options, this.getRatio(), recorderElement);
|
|
20642
|
+
return videoDimension;
|
|
20113
20643
|
}
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20119
|
-
|
|
20120
|
-
|
|
20121
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20131
|
-
|
|
20132
|
-
|
|
20133
|
-
|
|
20134
|
-
|
|
20135
|
-
|
|
20136
|
-
|
|
20137
|
-
|
|
20138
|
-
|
|
20139
|
-
|
|
20140
|
-
|
|
20141
|
-
|
|
20142
|
-
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
20152
|
-
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
|
|
20156
|
-
const
|
|
20157
|
-
|
|
20158
|
-
|
|
20159
|
-
|
|
20160
|
-
|
|
20161
|
-
exc: socketError ? new Error("WebSocket connection error", {
|
|
20162
|
-
cause: socketError
|
|
20163
|
-
}) : void 0
|
|
20164
|
-
});
|
|
20165
|
-
this.emit("ERROR", {
|
|
20166
|
-
err
|
|
20644
|
+
}
|
|
20645
|
+
const recorder_dimensions = RecorderDimensions;
|
|
20646
|
+
function getFirstVideoTrack(localMediaStream) {
|
|
20647
|
+
const videoTracks = localMediaStream.getVideoTracks();
|
|
20648
|
+
let videoTrack;
|
|
20649
|
+
if (videoTracks[0]) videoTrack = videoTracks[0];
|
|
20650
|
+
return videoTrack;
|
|
20651
|
+
}
|
|
20652
|
+
const media_getFirstVideoTrack = getFirstVideoTrack;
|
|
20653
|
+
const MEDIA_EVENTS = [
|
|
20654
|
+
"loadstart",
|
|
20655
|
+
"suspend",
|
|
20656
|
+
"progress",
|
|
20657
|
+
"abort",
|
|
20658
|
+
"emptied",
|
|
20659
|
+
"stalled",
|
|
20660
|
+
"pause",
|
|
20661
|
+
"loadeddata",
|
|
20662
|
+
"waiting",
|
|
20663
|
+
"playing",
|
|
20664
|
+
"canplay",
|
|
20665
|
+
"canplaythrough",
|
|
20666
|
+
"seeking",
|
|
20667
|
+
"seeked",
|
|
20668
|
+
"ended",
|
|
20669
|
+
"ratechange",
|
|
20670
|
+
"durationchange",
|
|
20671
|
+
"volumechange"
|
|
20672
|
+
];
|
|
20673
|
+
const mediaEvents = MEDIA_EVENTS;
|
|
20674
|
+
function _define_property(obj, key, value) {
|
|
20675
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
20676
|
+
value: value,
|
|
20677
|
+
enumerable: true,
|
|
20678
|
+
configurable: true,
|
|
20679
|
+
writable: true
|
|
20680
|
+
});
|
|
20681
|
+
else obj[key] = value;
|
|
20682
|
+
return obj;
|
|
20683
|
+
}
|
|
20684
|
+
class AudioSample {
|
|
20685
|
+
toBuffer() {
|
|
20686
|
+
const l = this.float32Array.length;
|
|
20687
|
+
const arr = new Int16Array(l);
|
|
20688
|
+
this.float32Array.forEach((sample, i)=>{
|
|
20689
|
+
const clampedSample = Number.isFinite(sample) ? Math.max(-1, sample) : sample;
|
|
20690
|
+
arr[i] = 0x7fff * Math.min(1, clampedSample);
|
|
20167
20691
|
});
|
|
20692
|
+
return typedarray_to_buffer(arr);
|
|
20168
20693
|
}
|
|
20169
|
-
|
|
20170
|
-
|
|
20694
|
+
constructor(float32Array){
|
|
20695
|
+
_define_property(this, "float32Array", void 0);
|
|
20696
|
+
this.float32Array = float32Array;
|
|
20171
20697
|
}
|
|
20172
|
-
|
|
20173
|
-
|
|
20698
|
+
}
|
|
20699
|
+
const esm_src = AudioSample;
|
|
20700
|
+
const esm = esm_src;
|
|
20701
|
+
var is_power_of_two = __webpack_require__("dZ");
|
|
20702
|
+
var is_power_of_two_default = /*#__PURE__*/ __webpack_require__.n(is_power_of_two);
|
|
20703
|
+
const AUDIO_RECORDER_PROCESSOR_NAME = "videomail-pcm-recorder";
|
|
20704
|
+
function registerAudioRecorderProcessor(name, Processor, register) {
|
|
20705
|
+
class PcmRecorderProcessor extends Processor {
|
|
20706
|
+
constructor(options){
|
|
20707
|
+
super();
|
|
20708
|
+
this.samples = new Float32Array(options.processorOptions.bufferSize);
|
|
20709
|
+
this.offset = 0;
|
|
20710
|
+
this.recordingId = 0;
|
|
20711
|
+
this.port.onmessage = (event)=>{
|
|
20712
|
+
const recordingId = event.data;
|
|
20713
|
+
if ("number" != typeof recordingId) return;
|
|
20714
|
+
if (!Number.isSafeInteger(recordingId) || recordingId < -1) return;
|
|
20715
|
+
this.recordingId = recordingId;
|
|
20716
|
+
this.offset = 0;
|
|
20717
|
+
};
|
|
20718
|
+
}
|
|
20719
|
+
process(inputs) {
|
|
20720
|
+
if (this.recordingId < 0) return false;
|
|
20721
|
+
const channel = inputs[0]?.[0];
|
|
20722
|
+
if (!channel || 0 === this.recordingId) return true;
|
|
20723
|
+
let inputOffset = 0;
|
|
20724
|
+
while(inputOffset < channel.length){
|
|
20725
|
+
const count = Math.min(this.samples.length - this.offset, channel.length - inputOffset);
|
|
20726
|
+
this.samples.set(channel.subarray(inputOffset, inputOffset + count), this.offset);
|
|
20727
|
+
this.offset += count;
|
|
20728
|
+
inputOffset += count;
|
|
20729
|
+
if (this.offset === this.samples.length) {
|
|
20730
|
+
const samples = this.samples;
|
|
20731
|
+
this.port.postMessage({
|
|
20732
|
+
recordingId: this.recordingId,
|
|
20733
|
+
samples
|
|
20734
|
+
}, [
|
|
20735
|
+
samples.buffer
|
|
20736
|
+
]);
|
|
20737
|
+
this.samples = new Float32Array(this.offset);
|
|
20738
|
+
this.offset = 0;
|
|
20739
|
+
}
|
|
20740
|
+
}
|
|
20741
|
+
return true;
|
|
20742
|
+
}
|
|
20174
20743
|
}
|
|
20175
|
-
|
|
20176
|
-
|
|
20744
|
+
register(name, PcmRecorderProcessor);
|
|
20745
|
+
}
|
|
20746
|
+
const CHANNELS = 1;
|
|
20747
|
+
const DEFAULT_BUFFER_SIZE = 2048;
|
|
20748
|
+
const MIN_BUFFER_SIZE = 256;
|
|
20749
|
+
const MAX_BUFFER_SIZE = 16384;
|
|
20750
|
+
class AudioRecorder extends util_Despot {
|
|
20751
|
+
userMedia;
|
|
20752
|
+
worklet;
|
|
20753
|
+
audioInput;
|
|
20754
|
+
volume;
|
|
20755
|
+
vcAudioContext;
|
|
20756
|
+
workletReady;
|
|
20757
|
+
moduleUrl;
|
|
20758
|
+
audioCallback;
|
|
20759
|
+
initializationId = 0;
|
|
20760
|
+
recordingSequence = 0;
|
|
20761
|
+
recordingId = 0;
|
|
20762
|
+
constructor(userMedia, options){
|
|
20763
|
+
super("AudioRecorder", options);
|
|
20764
|
+
this.userMedia = userMedia;
|
|
20177
20765
|
}
|
|
20178
|
-
|
|
20179
|
-
this.
|
|
20180
|
-
|
|
20181
|
-
this.emit("PROGRESS", {
|
|
20182
|
-
frameProgress: this.frameProgress,
|
|
20183
|
-
sampleProgress: this.sampleProgress
|
|
20184
|
-
});
|
|
20766
|
+
getAudioContext() {
|
|
20767
|
+
if (!this.vcAudioContext) this.vcAudioContext = new window.AudioContext();
|
|
20768
|
+
return this.vcAudioContext;
|
|
20185
20769
|
}
|
|
20186
|
-
|
|
20187
|
-
|
|
20188
|
-
|
|
20189
|
-
|
|
20190
|
-
|
|
20191
|
-
});
|
|
20192
|
-
} else throw error_createError({
|
|
20193
|
-
message: "Arguments are missing for updating the frame progress",
|
|
20770
|
+
getBufferSize() {
|
|
20771
|
+
const configuredSize = this.options.audio.bufferSize;
|
|
20772
|
+
const bufferSize = "auto" === configuredSize ? DEFAULT_BUFFER_SIZE : configuredSize;
|
|
20773
|
+
if (void 0 === bufferSize) throw error_createError({
|
|
20774
|
+
message: "Audio buffer size is required.",
|
|
20194
20775
|
options: this.options
|
|
20195
20776
|
});
|
|
20196
|
-
|
|
20197
|
-
|
|
20198
|
-
|
|
20199
|
-
}
|
|
20200
|
-
updateSampleProgress(args) {
|
|
20201
|
-
if (args) {
|
|
20202
|
-
if (!args.sample) throw error_createError({
|
|
20203
|
-
message: "The audio sample number is missing",
|
|
20204
|
-
options: this.options
|
|
20205
|
-
});
|
|
20206
|
-
} else throw error_createError({
|
|
20207
|
-
message: "Arguments are missing for updating the audio sample progress",
|
|
20777
|
+
const validBufferSize = Number.isInteger(bufferSize) && bufferSize >= MIN_BUFFER_SIZE && bufferSize <= MAX_BUFFER_SIZE && is_power_of_two_default()(bufferSize);
|
|
20778
|
+
if (!validBufferSize) throw error_createError({
|
|
20779
|
+
message: `Audio buffer size must be a power of two between ${MIN_BUFFER_SIZE} and ${MAX_BUFFER_SIZE}. The current buffer size is ${bufferSize}.`,
|
|
20208
20780
|
options: this.options
|
|
20209
20781
|
});
|
|
20210
|
-
|
|
20211
|
-
this.sampleProgress = this.calculateSampleProgress();
|
|
20212
|
-
this.updateOverallProgress();
|
|
20782
|
+
return bufferSize;
|
|
20213
20783
|
}
|
|
20214
|
-
|
|
20215
|
-
|
|
20216
|
-
|
|
20784
|
+
validateVolume() {
|
|
20785
|
+
const { volume } = this.options.audio;
|
|
20786
|
+
const validVolume = Number.isFinite(volume) && volume >= 0 && volume <= 1;
|
|
20787
|
+
if (!validVolume) throw error_createError({
|
|
20788
|
+
message: `Audio volume must be between zero and one. The current volume is ${volume}.`,
|
|
20217
20789
|
options: this.options
|
|
20218
20790
|
});
|
|
20219
|
-
const hasAudio = this.samplesCount > 0;
|
|
20220
|
-
this.mediaTransmissionStarted = false;
|
|
20221
|
-
this.recordingActive = false;
|
|
20222
|
-
this.recordingTransportFailed = false;
|
|
20223
|
-
this.confirmedFrameNumber = this.confirmedSampleNumber = this.samplesCount = this.framesCount = 0;
|
|
20224
|
-
this.sampleProgress = this.frameProgress = void 0;
|
|
20225
|
-
this.key = args.key;
|
|
20226
|
-
if (args.mp4) this.replay.setMp4Source(`${args.mp4 + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.mp4`, true);
|
|
20227
|
-
if (args.webm) this.replay.setWebMSource(`${args.webm + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.webm`, true);
|
|
20228
|
-
this.hide();
|
|
20229
|
-
const widthDimension = this.getRecorderWidth(true);
|
|
20230
|
-
const heightDimension = this.getRecorderHeight(true);
|
|
20231
|
-
const duration = args.duration ?? -1;
|
|
20232
|
-
this.emit("PREVIEW", {
|
|
20233
|
-
key: this.key,
|
|
20234
|
-
width: widthDimension?.value,
|
|
20235
|
-
height: heightDimension?.value,
|
|
20236
|
-
hasAudio,
|
|
20237
|
-
duration
|
|
20238
|
-
});
|
|
20239
|
-
if (this.stopTime) this.waitingTime = Date.now() - this.stopTime;
|
|
20240
|
-
this.recordingStats ??= {};
|
|
20241
|
-
this.recordingStats.waitingTime = this.waitingTime;
|
|
20242
20791
|
}
|
|
20243
|
-
|
|
20244
|
-
if (this.
|
|
20245
|
-
|
|
20246
|
-
|
|
20247
|
-
return;
|
|
20248
|
-
}
|
|
20249
|
-
if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
|
|
20250
|
-
this.clearReconnectTimeout();
|
|
20251
|
-
if (util_isAutomatedUserAgent()) {
|
|
20252
|
-
this.connecting = false;
|
|
20253
|
-
this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
|
|
20254
|
-
return;
|
|
20255
|
-
}
|
|
20256
|
-
this.connecting = true;
|
|
20257
|
-
this.connectionFailed = false;
|
|
20258
|
-
this.connectingStartedAt = Date.now();
|
|
20259
|
-
this.lastCloseEvent = void 0;
|
|
20260
|
-
this.lastSocketError = void 0;
|
|
20261
|
-
this.emit("CONNECTING");
|
|
20262
|
-
let url2Connect;
|
|
20263
|
-
try {
|
|
20264
|
-
const socketUrlObj = new URL(this.options.socketUrl);
|
|
20265
|
-
socketUrlObj.searchParams.set(constants.WHITELIST_KEY_LABEL, this.options.whitelistKey);
|
|
20266
|
-
url2Connect = socketUrlObj.toString();
|
|
20267
|
-
} catch (exc) {
|
|
20268
|
-
this.connecting = this.connected = false;
|
|
20269
|
-
const err = error_createError({
|
|
20270
|
-
message: "Invalid WebSocket URL",
|
|
20271
|
-
explanation: `The configured socketUrl "${this.options.socketUrl}" is not a valid URL. Please check your videomail-client configuration.`,
|
|
20272
|
-
options: this.options,
|
|
20273
|
-
exc
|
|
20274
|
-
});
|
|
20275
|
-
this.emit("ERROR", {
|
|
20276
|
-
err
|
|
20277
|
-
});
|
|
20278
|
-
return;
|
|
20279
|
-
}
|
|
20280
|
-
this.options.logger.debug(`Recorder: initializing web socket stream to ${url2Connect}`);
|
|
20281
|
-
let nativeSocket;
|
|
20282
|
-
try {
|
|
20283
|
-
nativeSocket = new WebSocket(url2Connect);
|
|
20284
|
-
nativeSocket.addEventListener("close", (event)=>{
|
|
20285
|
-
this.lastCloseEvent = {
|
|
20286
|
-
code: event.code,
|
|
20287
|
-
reason: event.reason,
|
|
20288
|
-
wasClean: event.wasClean
|
|
20289
|
-
};
|
|
20290
|
-
});
|
|
20291
|
-
nativeSocket.addEventListener("error", (event)=>{
|
|
20292
|
-
this.lastSocketError = error_getEventDetails(event);
|
|
20293
|
-
});
|
|
20294
|
-
} catch (exc) {
|
|
20295
|
-
this.connecting = this.connected = false;
|
|
20296
|
-
const diagnostic = error_getWebSocketDiagnostic(url2Connect);
|
|
20297
|
-
const message = diagnostic.looksAutomated ? "Automated crawler: WebSocket not supported in this environment" : `Failed to construct WebSocket to ${url2Connect}`;
|
|
20298
|
-
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}`;
|
|
20299
|
-
const err = error_createError({
|
|
20300
|
-
message,
|
|
20301
|
-
explanation,
|
|
20302
|
-
options: this.options,
|
|
20303
|
-
exc
|
|
20304
|
-
});
|
|
20305
|
-
this.emit("ERROR", {
|
|
20306
|
-
err
|
|
20307
|
-
});
|
|
20308
|
-
return;
|
|
20792
|
+
revokeModuleUrl() {
|
|
20793
|
+
if (this.moduleUrl) {
|
|
20794
|
+
URL.revokeObjectURL(this.moduleUrl);
|
|
20795
|
+
this.moduleUrl = void 0;
|
|
20309
20796
|
}
|
|
20797
|
+
}
|
|
20798
|
+
async loadWorklet(context) {
|
|
20799
|
+
const source = `(${registerAudioRecorderProcessor.toString()})(${JSON.stringify(AUDIO_RECORDER_PROCESSOR_NAME)}, AudioWorkletProcessor, registerProcessor);`;
|
|
20800
|
+
const moduleUrl = URL.createObjectURL(new Blob([
|
|
20801
|
+
source
|
|
20802
|
+
], {
|
|
20803
|
+
type: "text/javascript"
|
|
20804
|
+
}));
|
|
20805
|
+
this.moduleUrl = moduleUrl;
|
|
20310
20806
|
try {
|
|
20311
|
-
|
|
20312
|
-
}
|
|
20313
|
-
this.
|
|
20314
|
-
const err = error_createError({
|
|
20315
|
-
message: `Failed to create a stream to ${url2Connect}`,
|
|
20316
|
-
explanation: "Please check your connection and try again. If the problem persists, contact us.",
|
|
20317
|
-
options: this.options,
|
|
20318
|
-
exc
|
|
20319
|
-
});
|
|
20320
|
-
this.emit("ERROR", {
|
|
20321
|
-
err
|
|
20322
|
-
});
|
|
20323
|
-
}
|
|
20324
|
-
const stream = this.stream;
|
|
20325
|
-
if (stream) {
|
|
20326
|
-
const connectionTimeoutMs = this.options.timeouts.connection;
|
|
20327
|
-
this.connectionTimeout = window.setTimeout(()=>{
|
|
20328
|
-
if (this.isOnline() && this.isUserMediaLoaded()) {
|
|
20329
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
|
|
20330
|
-
this.reconnecting = true;
|
|
20331
|
-
this.handleConnectionFailure({
|
|
20332
|
-
url2Connect,
|
|
20333
|
-
cause: "timeout"
|
|
20334
|
-
});
|
|
20335
|
-
} else this.handleConnectionFailure({
|
|
20336
|
-
url2Connect,
|
|
20337
|
-
cause: "timeout"
|
|
20338
|
-
});
|
|
20339
|
-
}, connectionTimeoutMs);
|
|
20340
|
-
stream.on("close", ()=>{
|
|
20341
|
-
this.handleStreamClose(url2Connect);
|
|
20342
|
-
});
|
|
20343
|
-
stream.on("connect", (args)=>{
|
|
20344
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *connect* event emitted with args: ${string_pretty(args)}`);
|
|
20345
|
-
this.clearConnectionTimeout();
|
|
20346
|
-
const isClosing = this.stream?.socket.readyState === WebSocket.CLOSING;
|
|
20347
|
-
if (!this.connected && !isClosing && !this.unloaded) {
|
|
20348
|
-
this.connected = true;
|
|
20349
|
-
this.connecting = this.unloaded = false;
|
|
20350
|
-
this.reconnecting = false;
|
|
20351
|
-
this.reconnectAttempts = 0;
|
|
20352
|
-
this.clearReconnectTimeout();
|
|
20353
|
-
this.emit("CONNECTED");
|
|
20354
|
-
cb?.();
|
|
20355
|
-
}
|
|
20356
|
-
});
|
|
20357
|
-
stream.on("data", (data)=>{
|
|
20358
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *data* event emitted`);
|
|
20359
|
-
try {
|
|
20360
|
-
const command = JSON.parse(data.toString());
|
|
20361
|
-
this.executeCommand(command);
|
|
20362
|
-
} catch (exc) {
|
|
20363
|
-
this.options.logger.error(`Failed to parse command: ${exc}`);
|
|
20364
|
-
const err = error_createError({
|
|
20365
|
-
message: "Invalid server command",
|
|
20366
|
-
explanation: `Contact us. The invalid command was: ${data.toString()}.`,
|
|
20367
|
-
options: this.options,
|
|
20368
|
-
exc
|
|
20369
|
-
});
|
|
20370
|
-
this.emit("ERROR", {
|
|
20371
|
-
err
|
|
20372
|
-
});
|
|
20373
|
-
}
|
|
20374
|
-
});
|
|
20375
|
-
stream.on("error", (err)=>{
|
|
20376
|
-
this.handleStreamError(url2Connect, err);
|
|
20377
|
-
});
|
|
20378
|
-
stream.on("drain", ()=>{
|
|
20379
|
-
this.handleStreamDrain(stream);
|
|
20380
|
-
});
|
|
20381
|
-
stream.on("preend", (args)=>{
|
|
20382
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *preend* event emitted with args: ${string_pretty(args)}`);
|
|
20383
|
-
});
|
|
20384
|
-
stream.on("end", ()=>{
|
|
20385
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *end* event emitted`);
|
|
20386
|
-
});
|
|
20387
|
-
stream.on("pipe", (src)=>{
|
|
20388
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *pipe* event emitted with src: ${string_pretty(src)}`);
|
|
20389
|
-
});
|
|
20390
|
-
stream.on("unpipe", (src)=>{
|
|
20391
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *unpipe* event emitted with src: ${string_pretty(src)}`);
|
|
20392
|
-
});
|
|
20393
|
-
stream.on("resume", (args)=>{
|
|
20394
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *resume* event emitted with args: ${string_pretty(args)}`);
|
|
20395
|
-
});
|
|
20396
|
-
stream.on("uncork", (args)=>{
|
|
20397
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *uncork* event emitted with args: ${string_pretty(args)}`);
|
|
20398
|
-
});
|
|
20399
|
-
stream.on("readable", (args)=>{
|
|
20400
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *readable* event emitted with args: ${string_pretty(args)}`);
|
|
20401
|
-
});
|
|
20402
|
-
stream.on("prefinish", (args)=>{
|
|
20403
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *prefinish* event emitted with args: ${string_pretty(args)}`);
|
|
20404
|
-
});
|
|
20405
|
-
stream.on("finish", (args)=>{
|
|
20406
|
-
this.options.logger.debug(`${PIPE_SYMBOL}Stream *finish* event emitted with args: ${string_pretty(args)}`);
|
|
20407
|
-
});
|
|
20807
|
+
await context.audioWorklet.addModule(moduleUrl);
|
|
20808
|
+
} finally{
|
|
20809
|
+
if (this.moduleUrl === moduleUrl) this.revokeModuleUrl();
|
|
20408
20810
|
}
|
|
20409
20811
|
}
|
|
20410
|
-
|
|
20411
|
-
|
|
20412
|
-
|
|
20413
|
-
if (!
|
|
20414
|
-
const
|
|
20415
|
-
if (
|
|
20416
|
-
|
|
20812
|
+
onAudioProcess(event) {
|
|
20813
|
+
const callback = this.audioCallback;
|
|
20814
|
+
if (!callback) return;
|
|
20815
|
+
if (!this.userMedia.isRecording() || this.userMedia.isPaused()) return;
|
|
20816
|
+
const message = event.data;
|
|
20817
|
+
if ("object" != typeof message || null === message) return;
|
|
20818
|
+
if (!("recordingId" in message) || message.recordingId !== this.recordingId) return;
|
|
20819
|
+
if (!("samples" in message) || !(message.samples instanceof Float32Array)) return;
|
|
20820
|
+
if (0 === message.samples.length) return;
|
|
20821
|
+
callback(new esm(message.samples));
|
|
20417
20822
|
}
|
|
20418
|
-
|
|
20419
|
-
if (this.
|
|
20420
|
-
|
|
20421
|
-
|
|
20422
|
-
|
|
20423
|
-
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
if (errorListeners?.length) if (err.name !== error_VideomailError.MEDIA_DEVICE_NOT_SUPPORTED) {
|
|
20427
|
-
const videomailError = error_createError({
|
|
20428
|
-
err,
|
|
20429
|
-
options: this.options,
|
|
20430
|
-
usedConstraints
|
|
20431
|
-
});
|
|
20432
|
-
this.emit("ERROR", {
|
|
20433
|
-
err: videomailError
|
|
20434
|
-
});
|
|
20435
|
-
} else {
|
|
20436
|
-
this.options.logger.debug(`Recorder: ignore user media error ${string_pretty(err)}`);
|
|
20437
|
-
this.retryTimeout = window.setTimeout(()=>{
|
|
20438
|
-
this.retryTimeout = void 0;
|
|
20439
|
-
if (!this.unloaded) this.loadUserMedia();
|
|
20440
|
-
}, this.options.timeouts.userMedia);
|
|
20823
|
+
disconnect() {
|
|
20824
|
+
if (this.worklet) {
|
|
20825
|
+
this.worklet.port.onmessage = null;
|
|
20826
|
+
this.worklet.onprocessorerror = null;
|
|
20827
|
+
this.worklet.port.postMessage(-1);
|
|
20828
|
+
this.worklet.port.close();
|
|
20829
|
+
this.worklet.disconnect();
|
|
20830
|
+
this.worklet = void 0;
|
|
20441
20831
|
}
|
|
20442
|
-
|
|
20443
|
-
|
|
20832
|
+
this.audioInput?.disconnect();
|
|
20833
|
+
this.volume?.disconnect();
|
|
20834
|
+
this.audioInput = void 0;
|
|
20835
|
+
this.volume = void 0;
|
|
20836
|
+
this.audioCallback = void 0;
|
|
20837
|
+
this.recordingId = 0;
|
|
20838
|
+
}
|
|
20839
|
+
async init(localMediaStream) {
|
|
20840
|
+
this.options.logger.debug("AudioRecorder: init()");
|
|
20841
|
+
const initializationId = ++this.initializationId;
|
|
20842
|
+
this.disconnect();
|
|
20843
|
+
const bufferSize = this.getBufferSize();
|
|
20844
|
+
this.validateVolume();
|
|
20845
|
+
if ("function" != typeof window.AudioContext) throw error_createError({
|
|
20846
|
+
message: "Audio recording requires Web Audio support.",
|
|
20847
|
+
options: this.options
|
|
20848
|
+
});
|
|
20849
|
+
const context = this.getAudioContext();
|
|
20850
|
+
if (void 0 === context.audioWorklet || "function" != typeof window.AudioWorkletNode) throw error_createError({
|
|
20851
|
+
message: "Audio recording requires AudioWorklet support in a secure context.",
|
|
20852
|
+
options: this.options
|
|
20853
|
+
});
|
|
20854
|
+
const workletReady = this.workletReady ??= this.loadWorklet(context);
|
|
20855
|
+
try {
|
|
20856
|
+
await workletReady;
|
|
20857
|
+
} catch (exc) {
|
|
20858
|
+
if (this.workletReady === workletReady) this.workletReady = void 0;
|
|
20859
|
+
if (initializationId !== this.initializationId) return;
|
|
20444
20860
|
throw error_createError({
|
|
20445
|
-
|
|
20446
|
-
|
|
20861
|
+
message: "Unable to load the audio recorder worklet. Check AudioWorklet and Content Security Policy support for blob: scripts.",
|
|
20862
|
+
cause: exc,
|
|
20447
20863
|
options: this.options
|
|
20448
20864
|
});
|
|
20449
20865
|
}
|
|
20450
|
-
|
|
20451
|
-
|
|
20452
|
-
|
|
20453
|
-
|
|
20454
|
-
track.stop();
|
|
20455
|
-
});
|
|
20866
|
+
if (initializationId !== this.initializationId) return;
|
|
20867
|
+
try {
|
|
20868
|
+
this.audioInput = context.createMediaStreamSource(localMediaStream);
|
|
20869
|
+
} catch (exc) {
|
|
20456
20870
|
throw error_createError({
|
|
20457
|
-
message: "
|
|
20871
|
+
message: "Webcam has no audio",
|
|
20872
|
+
exc,
|
|
20458
20873
|
options: this.options
|
|
20459
20874
|
});
|
|
20460
20875
|
}
|
|
20461
|
-
|
|
20462
|
-
|
|
20463
|
-
this.
|
|
20464
|
-
|
|
20465
|
-
|
|
20466
|
-
|
|
20467
|
-
|
|
20876
|
+
try {
|
|
20877
|
+
this.volume = context.createGain();
|
|
20878
|
+
this.volume.gain.value = this.options.audio.volume;
|
|
20879
|
+
this.worklet = new window.AudioWorkletNode(context, AUDIO_RECORDER_PROCESSOR_NAME, {
|
|
20880
|
+
channelCount: CHANNELS,
|
|
20881
|
+
channelCountMode: "explicit",
|
|
20882
|
+
outputChannelCount: [
|
|
20883
|
+
CHANNELS
|
|
20884
|
+
],
|
|
20885
|
+
processorOptions: {
|
|
20886
|
+
bufferSize
|
|
20887
|
+
}
|
|
20888
|
+
});
|
|
20889
|
+
this.worklet.port.onmessage = (event)=>{
|
|
20890
|
+
this.onAudioProcess(event);
|
|
20891
|
+
};
|
|
20892
|
+
this.worklet.onprocessorerror = ()=>{
|
|
20893
|
+
this.stop();
|
|
20894
|
+
this.emit("ERROR", {
|
|
20895
|
+
err: error_createError({
|
|
20896
|
+
message: "Audio recording processor failed.",
|
|
20897
|
+
options: this.options
|
|
20898
|
+
})
|
|
20468
20899
|
});
|
|
20469
|
-
}
|
|
20900
|
+
};
|
|
20901
|
+
this.audioInput.connect(this.volume);
|
|
20902
|
+
this.volume.connect(this.worklet);
|
|
20903
|
+
this.worklet.connect(context.destination);
|
|
20470
20904
|
} catch (exc) {
|
|
20471
|
-
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
this.userMediaErrorCallback(exc, {
|
|
20477
|
-
audio: isAudioEnabled(this.options)
|
|
20478
|
-
});
|
|
20479
|
-
}
|
|
20480
|
-
else {
|
|
20481
|
-
localStream.getTracks().forEach((track)=>{
|
|
20482
|
-
track.stop();
|
|
20905
|
+
this.disconnect();
|
|
20906
|
+
throw error_createError({
|
|
20907
|
+
message: "Failed to connect the audio recorder.",
|
|
20908
|
+
exc,
|
|
20909
|
+
options: this.options
|
|
20483
20910
|
});
|
|
20484
|
-
this.userMediaLoading = false;
|
|
20485
|
-
this.clearUserMediaTimeout();
|
|
20486
|
-
this.cancelCameraRecovery();
|
|
20487
20911
|
}
|
|
20488
20912
|
}
|
|
20489
|
-
|
|
20490
|
-
this.options.logger.debug(
|
|
20491
|
-
this.
|
|
20492
|
-
this.
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20913
|
+
record(cb) {
|
|
20914
|
+
this.options.logger.debug("AudioRecorder: record()");
|
|
20915
|
+
this.audioCallback = cb;
|
|
20916
|
+
this.updateRecordingState();
|
|
20917
|
+
}
|
|
20918
|
+
updateRecordingState() {
|
|
20919
|
+
if (!this.worklet) return;
|
|
20920
|
+
const shouldRecord = Boolean(this.audioCallback) && this.userMedia.isRecording() && !this.userMedia.isPaused();
|
|
20921
|
+
this.recordingId = shouldRecord ? ++this.recordingSequence : 0;
|
|
20922
|
+
this.worklet.port.postMessage(this.recordingId);
|
|
20923
|
+
}
|
|
20924
|
+
async resume() {
|
|
20925
|
+
const context = this.vcAudioContext;
|
|
20926
|
+
if (!context) return;
|
|
20927
|
+
if ("running" !== context.state) await context.resume();
|
|
20928
|
+
}
|
|
20929
|
+
stop() {
|
|
20930
|
+
this.options.logger.debug("AudioRecorder: stop()");
|
|
20931
|
+
this.initializationId++;
|
|
20932
|
+
this.disconnect();
|
|
20933
|
+
this.revokeModuleUrl();
|
|
20934
|
+
const context = this.vcAudioContext;
|
|
20935
|
+
this.vcAudioContext = void 0;
|
|
20936
|
+
this.workletReady = void 0;
|
|
20937
|
+
if (context && "closed" !== context.state) context.close().then(()=>{
|
|
20938
|
+
this.options.logger.debug("AudioRecorder: audio context is closed");
|
|
20939
|
+
}).catch((err)=>{
|
|
20940
|
+
if (err instanceof Error) return void this.options.logger.error(error_createError({
|
|
20941
|
+
err,
|
|
20942
|
+
options: this.options
|
|
20943
|
+
}));
|
|
20944
|
+
this.options.logger.error(err);
|
|
20945
|
+
});
|
|
20946
|
+
}
|
|
20947
|
+
getSampleRate() {
|
|
20948
|
+
if ("function" == typeof window.AudioContext) return this.getAudioContext().sampleRate;
|
|
20949
|
+
return -1;
|
|
20950
|
+
}
|
|
20951
|
+
}
|
|
20952
|
+
const media_AudioRecorder = AudioRecorder;
|
|
20953
|
+
const VIRTUAL_KEYWORDS = [
|
|
20954
|
+
"obs",
|
|
20955
|
+
"virtual",
|
|
20956
|
+
"manycam",
|
|
20957
|
+
"vcam",
|
|
20958
|
+
"splitcam",
|
|
20959
|
+
"droidcam"
|
|
20960
|
+
];
|
|
20961
|
+
function isVirtualCamera(videoTrack) {
|
|
20962
|
+
const capabilities = videoTrack.getCapabilities();
|
|
20963
|
+
if (!capabilities.frameRate) return true;
|
|
20964
|
+
const isVirtual = VIRTUAL_KEYWORDS.some((keyword)=>videoTrack.label.toLowerCase().includes(keyword));
|
|
20965
|
+
return isVirtual;
|
|
20966
|
+
}
|
|
20967
|
+
const util_isVirtualCamera = isVirtualCamera;
|
|
20968
|
+
const EVENT_ASCII = "|—O—|";
|
|
20969
|
+
class UserMedia extends util_Despot {
|
|
20970
|
+
recorder;
|
|
20971
|
+
rawVisualUserMedia;
|
|
20972
|
+
paused = false;
|
|
20973
|
+
recording = false;
|
|
20974
|
+
audioRecorder;
|
|
20975
|
+
currentVisualStream;
|
|
20976
|
+
onPlayReached = false;
|
|
20977
|
+
onLoadedMetaDataReached = false;
|
|
20978
|
+
playingPromiseReached = false;
|
|
20979
|
+
videoTrackLabel;
|
|
20980
|
+
clearInitializationListeners;
|
|
20981
|
+
initializationId = 0;
|
|
20982
|
+
outputEvent = (e)=>{
|
|
20983
|
+
this.logEvent(e.type, {
|
|
20984
|
+
readyState: this.rawVisualUserMedia?.readyState
|
|
20985
|
+
});
|
|
20986
|
+
this.rawVisualUserMedia?.removeEventListener(e.type, this.outputEvent);
|
|
20987
|
+
};
|
|
20988
|
+
handleMediaError = (event)=>{
|
|
20989
|
+
this.options.logger.warn(`Caught video element error event: ${string_pretty(event)}`);
|
|
20990
|
+
};
|
|
20991
|
+
constructor(recorder, options){
|
|
20992
|
+
super("UserMedia", options);
|
|
20993
|
+
this.recorder = recorder;
|
|
20994
|
+
this.rawVisualUserMedia = recorder.getRawVisualUserMedia();
|
|
20995
|
+
mediaEvents.forEach((eventName)=>{
|
|
20996
|
+
this.rawVisualUserMedia?.addEventListener(eventName, this.outputEvent, false);
|
|
20997
|
+
});
|
|
20998
|
+
}
|
|
20999
|
+
attachMediaStream(stream) {
|
|
21000
|
+
if (this.rawVisualUserMedia) {
|
|
21001
|
+
this.rawVisualUserMedia.srcObject = stream;
|
|
21002
|
+
const previousStream = this.currentVisualStream;
|
|
21003
|
+
this.currentVisualStream = stream;
|
|
21004
|
+
if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
|
|
20508
21005
|
track.stop();
|
|
20509
21006
|
});
|
|
20510
|
-
|
|
20511
|
-
|
|
20512
|
-
|
|
21007
|
+
} else throw error_createError({
|
|
21008
|
+
message: "Error attaching stream to element.",
|
|
21009
|
+
explanation: "Contact the developer about this",
|
|
21010
|
+
options: this.options
|
|
20513
21011
|
});
|
|
20514
21012
|
}
|
|
20515
|
-
|
|
20516
|
-
|
|
20517
|
-
if (!videoConstraints) return;
|
|
20518
|
-
if (params?.switchingFacingMode || params?.recovering) videoConstraints.facingMode = params.switchingFacingMode || this.facingMode;
|
|
20519
|
-
if (this.options.video.width) videoConstraints.width = {
|
|
20520
|
-
ideal: this.options.video.width
|
|
20521
|
-
};
|
|
21013
|
+
setVisualStream(localMediaStream) {
|
|
21014
|
+
if (localMediaStream) this.attachMediaStream(localMediaStream);
|
|
20522
21015
|
else {
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
}
|
|
21016
|
+
if (this.rawVisualUserMedia) {
|
|
21017
|
+
this.rawVisualUserMedia.srcObject = null;
|
|
21018
|
+
this.rawVisualUserMedia.removeAttribute("src");
|
|
21019
|
+
}
|
|
21020
|
+
this.currentVisualStream = void 0;
|
|
20527
21021
|
}
|
|
20528
|
-
if (this.options.video.height) videoConstraints.height = {
|
|
20529
|
-
ideal: this.options.video.height
|
|
20530
|
-
};
|
|
20531
21022
|
}
|
|
20532
|
-
|
|
20533
|
-
if (this.
|
|
20534
|
-
|
|
20535
|
-
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
this.
|
|
20539
|
-
this.
|
|
20540
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
20543
|
-
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
|
|
21023
|
+
async restorePlayback() {
|
|
21024
|
+
if (!this.rawVisualUserMedia) throw new Error("No camera preview is available.");
|
|
21025
|
+
await this.rawVisualUserMedia.play();
|
|
21026
|
+
await this.audioRecorder?.resume();
|
|
21027
|
+
}
|
|
21028
|
+
hasLiveStream() {
|
|
21029
|
+
const videoReady = this.currentVisualStream?.getVideoTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
|
|
21030
|
+
const audioReady = !isAudioEnabled(this.options) || this.currentVisualStream?.getAudioTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
|
|
21031
|
+
return Boolean(this.currentVisualStream?.active && videoReady && audioReady);
|
|
21032
|
+
}
|
|
21033
|
+
hasEnded() {
|
|
21034
|
+
if (this.rawVisualUserMedia?.ended) return this.rawVisualUserMedia.ended;
|
|
21035
|
+
return !this.currentVisualStream?.active;
|
|
21036
|
+
}
|
|
21037
|
+
hasInvalidDimensions() {
|
|
21038
|
+
if (!this.rawVisualUserMedia) return false;
|
|
21039
|
+
const { videoWidth, videoHeight } = this.rawVisualUserMedia;
|
|
21040
|
+
return videoWidth > 0 && videoWidth < 3 || videoHeight > 0 && videoHeight < 3;
|
|
21041
|
+
}
|
|
21042
|
+
logEvent(eventType, params) {
|
|
21043
|
+
this.options.logger.debug(`UserMedia: ... ${EVENT_ASCII} event ${eventType}: ${string_pretty(params)}`);
|
|
21044
|
+
}
|
|
21045
|
+
unloadRemainingEventListeners() {
|
|
21046
|
+
this.options.logger.debug("UserMedia: unloadRemainingEventListeners()");
|
|
21047
|
+
this.clearInitializationListeners?.();
|
|
21048
|
+
mediaEvents.forEach((eventName)=>{
|
|
21049
|
+
this.rawVisualUserMedia?.removeEventListener(eventName, this.outputEvent);
|
|
21050
|
+
});
|
|
21051
|
+
this.rawVisualUserMedia?.removeEventListener("error", this.handleMediaError);
|
|
21052
|
+
}
|
|
21053
|
+
audioRecord(audioCallback) {
|
|
21054
|
+
this.removeListener("SENDING_FIRST_FRAME");
|
|
21055
|
+
this.audioRecorder?.record(audioCallback);
|
|
21056
|
+
}
|
|
21057
|
+
init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, params) {
|
|
21058
|
+
this.clearInitializationListeners?.();
|
|
21059
|
+
this.stop(localMediaStream, {
|
|
21060
|
+
aboutToInitialize: true,
|
|
21061
|
+
preserveRecordingState: Boolean(params?.switchingFacingMode || params?.recovering)
|
|
21062
|
+
});
|
|
21063
|
+
const initializationId = ++this.initializationId;
|
|
21064
|
+
this.onPlayReached = false;
|
|
21065
|
+
this.onLoadedMetaDataReached = false;
|
|
21066
|
+
this.playingPromiseReached = false;
|
|
21067
|
+
this.videoTrackLabel = void 0;
|
|
21068
|
+
if (isAudioEnabled(this.options)) this.audioRecorder ??= new media_AudioRecorder(this, this.options);
|
|
21069
|
+
const unloadAllEventListeners = ()=>{
|
|
21070
|
+
this.options.logger.debug("UserMedia: unloadAllEventListeners()");
|
|
21071
|
+
this.unloadRemainingEventListeners();
|
|
21072
|
+
this.removeListener("SENDING_FIRST_FRAME");
|
|
21073
|
+
this.rawVisualUserMedia?.removeEventListener("play", onPlay);
|
|
21074
|
+
this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
|
|
21075
|
+
};
|
|
21076
|
+
this.clearInitializationListeners = ()=>{
|
|
21077
|
+
this.rawVisualUserMedia?.removeEventListener("play", onPlay);
|
|
21078
|
+
this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
|
|
21079
|
+
this.clearInitializationListeners = void 0;
|
|
21080
|
+
};
|
|
21081
|
+
const play = ()=>{
|
|
21082
|
+
try {
|
|
21083
|
+
this.rawVisualUserMedia?.load();
|
|
21084
|
+
if (this.rawVisualUserMedia?.paused) {
|
|
21085
|
+
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)}`);
|
|
21086
|
+
this.rawVisualUserMedia.play().then(()=>{
|
|
21087
|
+
if (initializationId !== this.initializationId) return;
|
|
21088
|
+
if (!this.playingPromiseReached) {
|
|
21089
|
+
this.options.logger.debug("UserMedia: play promise successful. Playing now.");
|
|
21090
|
+
this.playingPromiseReached = true;
|
|
21091
|
+
}
|
|
21092
|
+
}).catch((exc)=>{
|
|
21093
|
+
if (initializationId !== this.initializationId) return;
|
|
21094
|
+
if (exc instanceof Error && !params?.recovering) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
|
|
21095
|
+
else {
|
|
21096
|
+
unloadAllEventListeners();
|
|
21097
|
+
endedEarlyCallback(error_createError({
|
|
21098
|
+
message: "Failed to play user media upon play event.",
|
|
21099
|
+
exc,
|
|
21100
|
+
options: this.options
|
|
21101
|
+
}));
|
|
21102
|
+
}
|
|
20548
21103
|
});
|
|
20549
21104
|
}
|
|
20550
|
-
}
|
|
20551
|
-
|
|
20552
|
-
|
|
20553
|
-
} catch (exc) {
|
|
20554
|
-
this.clearUserMediaTimeout();
|
|
20555
|
-
this.options.logger.debug("Recorder: failed to load genuine user media");
|
|
20556
|
-
this.userMediaLoading = false;
|
|
20557
|
-
if (this.cameraRecovery) return void this.failCameraRecovery(exc);
|
|
20558
|
-
const errorListeners = this.getListeners("ERROR");
|
|
20559
|
-
if (errorListeners?.length) this.emit("ERROR", {
|
|
20560
|
-
exc
|
|
20561
|
-
});
|
|
20562
|
-
else {
|
|
20563
|
-
this.options.logger.debug("Recorder: no error listeners attached but throwing exception further");
|
|
20564
|
-
throw exc;
|
|
21105
|
+
} catch (exc) {
|
|
21106
|
+
unloadAllEventListeners();
|
|
21107
|
+
endedEarlyCallback(exc);
|
|
20565
21108
|
}
|
|
20566
|
-
}
|
|
20567
|
-
|
|
20568
|
-
|
|
20569
|
-
|
|
20570
|
-
|
|
20571
|
-
|
|
20572
|
-
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
|
|
20580
|
-
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20584
|
-
|
|
20585
|
-
|
|
20586
|
-
|
|
20587
|
-
if (
|
|
20588
|
-
|
|
20589
|
-
|
|
20590
|
-
|
|
20591
|
-
err: deserializeError(command.args?.err),
|
|
20592
|
-
options: this.options
|
|
20593
|
-
});
|
|
20594
|
-
this.emit("ERROR", {
|
|
20595
|
-
err
|
|
20596
|
-
});
|
|
20597
|
-
break;
|
|
20598
|
-
}
|
|
20599
|
-
case "confirmFrame":
|
|
20600
|
-
this.updateFrameProgress(command.args);
|
|
20601
|
-
break;
|
|
20602
|
-
case "confirmSample":
|
|
20603
|
-
this.updateSampleProgress(command.args);
|
|
20604
|
-
break;
|
|
20605
|
-
case "beginAudioEncoding":
|
|
20606
|
-
this.emit("BEGIN_AUDIO_ENCODING");
|
|
20607
|
-
break;
|
|
20608
|
-
case "beginVideoEncoding":
|
|
20609
|
-
this.emit("BEGIN_VIDEO_ENCODING");
|
|
20610
|
-
break;
|
|
20611
|
-
default:
|
|
20612
|
-
{
|
|
20613
|
-
const err = error_createError({
|
|
20614
|
-
message: `Unknown server command: ${command.command}`,
|
|
20615
|
-
options: this.options
|
|
20616
|
-
});
|
|
20617
|
-
this.emit("ERROR", {
|
|
20618
|
-
err
|
|
21109
|
+
};
|
|
21110
|
+
const fireCallbacks = ()=>{
|
|
21111
|
+
const readyState = this.rawVisualUserMedia?.readyState;
|
|
21112
|
+
this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
|
|
21113
|
+
if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) {
|
|
21114
|
+
const initializeAudio = async (audioRecorder)=>{
|
|
21115
|
+
try {
|
|
21116
|
+
await audioRecorder.init(localMediaStream);
|
|
21117
|
+
if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
|
|
21118
|
+
const onAudioReady = ()=>{
|
|
21119
|
+
if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
|
|
21120
|
+
if (this.recording) this.audioRecord(audioCallback);
|
|
21121
|
+
else this.on("SENDING_FIRST_FRAME", ()=>{
|
|
21122
|
+
this.audioRecord(audioCallback);
|
|
21123
|
+
});
|
|
21124
|
+
this.clearInitializationListeners?.();
|
|
21125
|
+
videoCallback();
|
|
21126
|
+
};
|
|
21127
|
+
if (params?.recovering) await audioRecorder.resume();
|
|
21128
|
+
onAudioReady();
|
|
21129
|
+
} catch (exc) {
|
|
21130
|
+
if (initializationId !== this.initializationId) return;
|
|
21131
|
+
unloadAllEventListeners();
|
|
21132
|
+
this.stop(void 0, {
|
|
21133
|
+
preserveRecordingState: Boolean(params?.recovering)
|
|
20619
21134
|
});
|
|
20620
|
-
|
|
21135
|
+
endedEarlyCallback(exc);
|
|
20621
21136
|
}
|
|
21137
|
+
};
|
|
21138
|
+
initializeAudio(this.audioRecorder);
|
|
21139
|
+
} else {
|
|
21140
|
+
this.clearInitializationListeners?.();
|
|
21141
|
+
videoCallback();
|
|
20622
21142
|
}
|
|
20623
|
-
}
|
|
20624
|
-
|
|
20625
|
-
|
|
21143
|
+
};
|
|
21144
|
+
const onPlay = ()=>{
|
|
21145
|
+
try {
|
|
21146
|
+
this.logEvent("play", {
|
|
21147
|
+
readyState: this.rawVisualUserMedia?.readyState,
|
|
21148
|
+
audio: isAudioEnabled(this.options),
|
|
21149
|
+
width: this.rawVisualUserMedia?.width,
|
|
21150
|
+
height: this.rawVisualUserMedia?.height,
|
|
21151
|
+
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
21152
|
+
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
21153
|
+
});
|
|
21154
|
+
this.rawVisualUserMedia?.removeEventListener("play", onPlay);
|
|
21155
|
+
if (this.hasEnded() || this.hasInvalidDimensions()) endedEarlyCallback(error_createError({
|
|
21156
|
+
message: "Already busy",
|
|
21157
|
+
explanation: "Probably another browser window is using your webcam?",
|
|
21158
|
+
options: this.options
|
|
21159
|
+
}));
|
|
21160
|
+
else {
|
|
21161
|
+
this.onPlayReached = true;
|
|
21162
|
+
fireCallbacks();
|
|
21163
|
+
}
|
|
21164
|
+
} catch (exc) {
|
|
21165
|
+
unloadAllEventListeners();
|
|
21166
|
+
endedEarlyCallback(exc);
|
|
21167
|
+
}
|
|
21168
|
+
};
|
|
21169
|
+
const onLoadedMetaData = ()=>{
|
|
21170
|
+
this.logEvent("loadedmetadata", {
|
|
21171
|
+
readyState: this.rawVisualUserMedia?.readyState,
|
|
21172
|
+
paused: this.rawVisualUserMedia?.paused,
|
|
21173
|
+
width: this.rawVisualUserMedia?.width,
|
|
21174
|
+
height: this.rawVisualUserMedia?.height,
|
|
21175
|
+
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
21176
|
+
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
20626
21177
|
});
|
|
20627
|
-
|
|
20628
|
-
|
|
20629
|
-
|
|
20630
|
-
|
|
20631
|
-
|
|
20632
|
-
isHidden() {
|
|
20633
|
-
return !this.recorderElement || html_isHidden(this.recorderElement);
|
|
20634
|
-
}
|
|
20635
|
-
writeCommand(command, args, cb) {
|
|
20636
|
-
if (this.connected) {
|
|
20637
|
-
if (this.stream) {
|
|
20638
|
-
if (args) this.options.logger.debug(`$ ${command} with ${string_pretty(args)}`);
|
|
20639
|
-
else this.options.logger.debug(`$ ${command}`);
|
|
20640
|
-
const commandObj = {
|
|
20641
|
-
command,
|
|
20642
|
-
args
|
|
20643
|
-
};
|
|
20644
|
-
this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
|
|
20645
|
-
onFlushedCallback: ()=>{
|
|
20646
|
-
queueMicrotask(cb);
|
|
20647
|
-
}
|
|
20648
|
-
} : void 0);
|
|
21178
|
+
this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
|
|
21179
|
+
if (!this.hasEnded() && !this.hasInvalidDimensions()) {
|
|
21180
|
+
this.emit("LOADED_META_DATA");
|
|
21181
|
+
this.onLoadedMetaDataReached = true;
|
|
21182
|
+
fireCallbacks();
|
|
20649
21183
|
}
|
|
20650
|
-
}
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
21184
|
+
};
|
|
21185
|
+
try {
|
|
21186
|
+
const videoTrack = media_getFirstVideoTrack(localMediaStream);
|
|
21187
|
+
this.videoTrackLabel = videoTrack?.label;
|
|
21188
|
+
if (videoTrack) if (videoTrack.enabled) if (util_isVirtualCamera(videoTrack)) throw error_createError({
|
|
21189
|
+
message: "Virtual cameras are not allowed.",
|
|
21190
|
+
explanation: `Please use a real camera, not "${videoTrack.label}".`,
|
|
21191
|
+
options: this.options
|
|
21192
|
+
});
|
|
21193
|
+
else {
|
|
21194
|
+
let description = "";
|
|
21195
|
+
if (videoTrack.label.length > 0) description = description.concat(videoTrack.label);
|
|
21196
|
+
description = description.concat(` with enabled=${videoTrack.enabled}, muted=${videoTrack.muted}, readyState=${videoTrack.readyState}`);
|
|
21197
|
+
this.options.logger.debug(`UserMedia: ${videoTrack.kind} detected. ${description}`);
|
|
21198
|
+
}
|
|
21199
|
+
else throw error_createError({
|
|
21200
|
+
message: "Webcam is disabled",
|
|
21201
|
+
explanation: "The video track seems to be disabled. Enable it in your system.",
|
|
21202
|
+
options: this.options
|
|
20654
21203
|
});
|
|
21204
|
+
else this.options.logger.debug("UserMedia: detected (but no video tracks exist");
|
|
21205
|
+
this.rawVisualUserMedia?.addEventListener("loadedmetadata", onLoadedMetaData);
|
|
21206
|
+
this.rawVisualUserMedia?.addEventListener("play", onPlay);
|
|
21207
|
+
this.rawVisualUserMedia?.removeEventListener("error", this.handleMediaError);
|
|
21208
|
+
this.rawVisualUserMedia?.addEventListener("error", this.handleMediaError);
|
|
21209
|
+
this.setVisualStream(localMediaStream);
|
|
21210
|
+
play();
|
|
21211
|
+
} catch (exc) {
|
|
21212
|
+
unloadAllEventListeners();
|
|
21213
|
+
endedEarlyCallback(exc);
|
|
20655
21214
|
}
|
|
20656
21215
|
}
|
|
20657
|
-
|
|
20658
|
-
this.
|
|
20659
|
-
}
|
|
20660
|
-
getIntervalSum() {
|
|
20661
|
-
if (!this.loop) return;
|
|
20662
|
-
return this.loop.getElapsedTime();
|
|
20663
|
-
}
|
|
20664
|
-
getAvgInterval() {
|
|
20665
|
-
const intervalSum = this.getIntervalSum() ?? 0;
|
|
20666
|
-
if (!this.hasValidInterval(intervalSum)) return;
|
|
20667
|
-
return intervalSum / this.framesCount;
|
|
20668
|
-
}
|
|
20669
|
-
getAvgFps() {
|
|
20670
|
-
const intervalSum = this.getIntervalSum() ?? 0;
|
|
20671
|
-
if (!this.hasValidInterval(intervalSum)) return;
|
|
20672
|
-
return this.framesCount / intervalSum * 1000;
|
|
20673
|
-
}
|
|
20674
|
-
hasValidInterval(intervalSum) {
|
|
20675
|
-
return intervalSum > 0;
|
|
20676
|
-
}
|
|
20677
|
-
getRecordingStats() {
|
|
20678
|
-
return this.recordingStats;
|
|
21216
|
+
getVideoTrackLabel() {
|
|
21217
|
+
return this.videoTrackLabel;
|
|
20679
21218
|
}
|
|
20680
|
-
|
|
20681
|
-
return this.
|
|
21219
|
+
isReady() {
|
|
21220
|
+
return Boolean(this.rawVisualUserMedia?.srcObject || this.rawVisualUserMedia?.currentSrc);
|
|
20682
21221
|
}
|
|
20683
|
-
stop(params) {
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
this.stopTimeout = window.setTimeout(()=>{
|
|
20694
|
-
this.stopTimeout = void 0;
|
|
20695
|
-
this.stopTime = Date.now();
|
|
20696
|
-
const videoType = this.replay.getVideoType();
|
|
20697
|
-
if (!videoType) throw new Error("Unable to video record when no video type is defined.");
|
|
20698
|
-
this.recordingStats = {
|
|
20699
|
-
avgFps: this.getAvgFps(),
|
|
20700
|
-
wantedFps: this.options.video.fps,
|
|
20701
|
-
avgInterval: this.getAvgInterval(),
|
|
20702
|
-
wantedInterval: 1e3 / this.options.video.fps,
|
|
20703
|
-
intervalSum: this.getIntervalSum(),
|
|
20704
|
-
framesCount: this.framesCount,
|
|
20705
|
-
videoType
|
|
20706
|
-
};
|
|
20707
|
-
if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
|
|
20708
|
-
if (isAudioEnabled(this.options) && this.userMedia) {
|
|
20709
|
-
this.recordingStats.samplesCount = this.samplesCount;
|
|
20710
|
-
this.recordingStats.sampleRate = this.recordingAudioSampleRate ?? this.userMedia.getAudioSampleRate();
|
|
20711
|
-
}
|
|
20712
|
-
this.writeCommand("stop", this.recordingStats, ()=>{
|
|
20713
|
-
this.emit("STOPPED", {
|
|
20714
|
-
recordingStats: this.recordingStats
|
|
21222
|
+
stop(visualStream, params) {
|
|
21223
|
+
try {
|
|
21224
|
+
let chosenStream = visualStream;
|
|
21225
|
+
if (!params?.aboutToInitialize) {
|
|
21226
|
+
this.initializationId++;
|
|
21227
|
+
this.clearInitializationListeners?.();
|
|
21228
|
+
chosenStream ??= this.currentVisualStream;
|
|
21229
|
+
const tracks = chosenStream?.getTracks();
|
|
21230
|
+
if (tracks) tracks.forEach((track)=>{
|
|
21231
|
+
track.stop();
|
|
20715
21232
|
});
|
|
21233
|
+
this.setVisualStream();
|
|
21234
|
+
this.audioRecorder?.stop();
|
|
21235
|
+
this.audioRecorder = void 0;
|
|
21236
|
+
}
|
|
21237
|
+
if (!params?.preserveRecordingState) this.paused = this.recording = false;
|
|
21238
|
+
} catch (exc) {
|
|
21239
|
+
this.emit("ERROR", {
|
|
21240
|
+
exc
|
|
20716
21241
|
});
|
|
20717
|
-
|
|
20718
|
-
}, 60);
|
|
21242
|
+
}
|
|
20719
21243
|
}
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20725
|
-
|
|
20726
|
-
|
|
21244
|
+
createCanvas() {
|
|
21245
|
+
const canvas = document.createElement("canvas");
|
|
21246
|
+
const rawWidthDimension = this.getRawWidth(true);
|
|
21247
|
+
if (rawWidthDimension.value) canvas.width = rawWidthDimension.value;
|
|
21248
|
+
const rawHeight = this.getRawHeight(true);
|
|
21249
|
+
if (rawHeight) canvas.height = rawHeight;
|
|
21250
|
+
return canvas;
|
|
20727
21251
|
}
|
|
20728
|
-
|
|
20729
|
-
this.
|
|
20730
|
-
this.
|
|
20731
|
-
this.userMediaLoading = false;
|
|
20732
|
-
this.userMedia?.stop();
|
|
20733
|
-
this.recordingDimensions = void 0;
|
|
20734
|
-
this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
|
|
20735
|
-
this.loadUserMedia();
|
|
21252
|
+
getVideoHeight() {
|
|
21253
|
+
if (!this.rawVisualUserMedia) return;
|
|
21254
|
+
return this.rawVisualUserMedia.videoHeight || this.rawVisualUserMedia.height;
|
|
20736
21255
|
}
|
|
20737
|
-
|
|
20738
|
-
if (
|
|
20739
|
-
|
|
20740
|
-
let cause;
|
|
20741
|
-
if (e) cause = e.type;
|
|
20742
|
-
const prettyCause = cause ? `, cause: ${cause}` : "";
|
|
20743
|
-
this.options.logger.debug(`Recorder: unload()${prettyCause}`);
|
|
20744
|
-
this.recordingActive = false;
|
|
20745
|
-
this.mediaTransmissionStarted = false;
|
|
20746
|
-
this.reset();
|
|
20747
|
-
this.recordingDimensions = void 0;
|
|
20748
|
-
this.clearUserMediaTimeout();
|
|
20749
|
-
this.clearConnectionTimeout();
|
|
20750
|
-
this.clearReconnectTimeout();
|
|
20751
|
-
this.clearRetryTimeout();
|
|
20752
|
-
this.clearStopTimeout();
|
|
20753
|
-
this.stopPings();
|
|
20754
|
-
this.userMediaLoading = false;
|
|
20755
|
-
this.connecting = false;
|
|
20756
|
-
this.reconnecting = false;
|
|
20757
|
-
this.reconnectAttempts = 0;
|
|
20758
|
-
if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
|
|
20759
|
-
if (this.submitting) ;
|
|
20760
|
-
else if (this.stream) this.discardSocket();
|
|
20761
|
-
this.unloaded = true;
|
|
20762
|
-
this.built = this.connecting = this.connected = false;
|
|
21256
|
+
getVideoWidth() {
|
|
21257
|
+
if (!this.rawVisualUserMedia) return;
|
|
21258
|
+
return this.rawVisualUserMedia.videoWidth || this.rawVisualUserMedia.width;
|
|
20763
21259
|
}
|
|
20764
|
-
|
|
20765
|
-
this.
|
|
20766
|
-
|
|
20767
|
-
this.options.logger.debug("Recorder: reset()");
|
|
20768
|
-
this.emit("RESETTING");
|
|
20769
|
-
this.cancelAnimationFrame();
|
|
20770
|
-
this.userMedia?.stop();
|
|
20771
|
-
this.replay.reset();
|
|
20772
|
-
this.userMediaLoaded = this.key = this.canvas = this.ctx = this.recordingBuffer = void 0;
|
|
20773
|
-
}
|
|
21260
|
+
hasVideoWidth() {
|
|
21261
|
+
const videoWidth = this.getVideoWidth();
|
|
21262
|
+
return videoWidth && videoWidth > 0;
|
|
20774
21263
|
}
|
|
20775
|
-
|
|
20776
|
-
|
|
20777
|
-
|
|
20778
|
-
|
|
20779
|
-
|
|
21264
|
+
getRawWidth(responsive) {
|
|
21265
|
+
let rawWidth;
|
|
21266
|
+
const widthDimension = {
|
|
21267
|
+
unit: "px"
|
|
21268
|
+
};
|
|
21269
|
+
rawWidth = responsive ? this.getVideoWidth() : this.options.video.width ? this.options.video.width : this.getVideoWidth();
|
|
21270
|
+
if (responsive) {
|
|
21271
|
+
const widthDimension = this.recorder.limitWidth(rawWidth);
|
|
21272
|
+
rawWidth = widthDimension?.value;
|
|
20780
21273
|
}
|
|
21274
|
+
widthDimension.value = rawWidth;
|
|
21275
|
+
return widthDimension;
|
|
20781
21276
|
}
|
|
20782
|
-
|
|
20783
|
-
|
|
20784
|
-
|
|
20785
|
-
|
|
21277
|
+
getRawHeight(responsive) {
|
|
21278
|
+
let rawHeight;
|
|
21279
|
+
if (responsive) {
|
|
21280
|
+
rawHeight = this.options.video.height || this.getVideoHeight();
|
|
21281
|
+
const ratio = this.recorder.getRatio();
|
|
21282
|
+
const rawWidth = this.getRawWidth(responsive);
|
|
21283
|
+
if (void 0 !== ratio && rawWidth.value) rawHeight = rawWidth.value * ratio;
|
|
21284
|
+
} else rawHeight = this.options.video.height ? this.options.video.height : this.getVideoHeight();
|
|
21285
|
+
if (responsive) {
|
|
21286
|
+
const heightDimension = this.recorder.limitHeight(rawHeight);
|
|
21287
|
+
rawHeight = heightDimension.value;
|
|
21288
|
+
}
|
|
21289
|
+
return rawHeight;
|
|
20786
21290
|
}
|
|
20787
|
-
|
|
20788
|
-
return
|
|
21291
|
+
getRawVisuals() {
|
|
21292
|
+
return this.rawVisualUserMedia;
|
|
20789
21293
|
}
|
|
20790
|
-
|
|
20791
|
-
|
|
21294
|
+
pause() {
|
|
21295
|
+
this.paused = true;
|
|
21296
|
+
this.audioRecorder?.updateRecordingState();
|
|
20792
21297
|
}
|
|
20793
|
-
|
|
20794
|
-
this.
|
|
20795
|
-
const resolve = this.resolveCameraRecovery;
|
|
20796
|
-
const resumeRequested = restored && this.resumeAfterCameraRecovery;
|
|
20797
|
-
this.cameraRecovery = void 0;
|
|
20798
|
-
this.resolveCameraRecovery = void 0;
|
|
20799
|
-
this.resumeAfterCameraRecovery = false;
|
|
20800
|
-
resolve?.(restored);
|
|
20801
|
-
if (resumeRequested && this.isPaused()) this.resume();
|
|
21298
|
+
isPaused() {
|
|
21299
|
+
return this.paused;
|
|
20802
21300
|
}
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
this.
|
|
20806
|
-
this.userMediaLoading = false;
|
|
20807
|
-
this.userMediaLoaded = false;
|
|
20808
|
-
this.clearUserMediaTimeout();
|
|
20809
|
-
this.userMedia?.stop(void 0, {
|
|
20810
|
-
preserveRecordingState: true
|
|
20811
|
-
});
|
|
20812
|
-
this.finishCameraRecovery(false);
|
|
21301
|
+
resume() {
|
|
21302
|
+
this.paused = false;
|
|
21303
|
+
this.audioRecorder?.updateRecordingState();
|
|
20813
21304
|
}
|
|
20814
|
-
|
|
20815
|
-
this.
|
|
20816
|
-
|
|
20817
|
-
message: "Camera recovery failed",
|
|
20818
|
-
explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
|
|
20819
|
-
exc,
|
|
20820
|
-
options: this.options
|
|
20821
|
-
});
|
|
20822
|
-
this.emit("CAMERA_RECOVERY_FAILED", {
|
|
20823
|
-
err
|
|
20824
|
-
});
|
|
21305
|
+
record() {
|
|
21306
|
+
this.recording = true;
|
|
21307
|
+
this.audioRecorder?.updateRecordingState();
|
|
20825
21308
|
}
|
|
20826
|
-
|
|
20827
|
-
|
|
20828
|
-
if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
|
|
20829
|
-
const liveStream = this.userMedia.hasLiveStream();
|
|
20830
|
-
if (liveStream && this.isRecording()) return Promise.resolve(true);
|
|
20831
|
-
if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
|
|
20832
|
-
const recovery = new Promise((resolve)=>{
|
|
20833
|
-
this.resolveCameraRecovery = resolve;
|
|
20834
|
-
});
|
|
20835
|
-
this.cameraRecovery = recovery;
|
|
20836
|
-
this.emit("CAMERA_RECOVERING");
|
|
20837
|
-
if (liveStream) {
|
|
20838
|
-
this.userMediaTimeout = window.setTimeout(()=>{
|
|
20839
|
-
this.failCameraRecovery(new Error("Camera playback recovery timed out."));
|
|
20840
|
-
}, this.options.timeouts.userMedia);
|
|
20841
|
-
this.userMedia.restorePlayback().then(()=>{
|
|
20842
|
-
if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
|
|
20843
|
-
}).catch((exc)=>{
|
|
20844
|
-
if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
|
|
20845
|
-
});
|
|
20846
|
-
} else {
|
|
20847
|
-
this.userMediaLoaded = false;
|
|
20848
|
-
this.loadUserMedia({
|
|
20849
|
-
recovering: true
|
|
20850
|
-
});
|
|
20851
|
-
}
|
|
20852
|
-
return recovery;
|
|
21309
|
+
isRecording() {
|
|
21310
|
+
return this.recording;
|
|
20853
21311
|
}
|
|
20854
|
-
|
|
20855
|
-
this.
|
|
20856
|
-
|
|
20857
|
-
if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
|
|
20858
|
-
else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
|
|
20859
|
-
this.userMedia?.pause();
|
|
20860
|
-
this.loop.stop();
|
|
20861
|
-
this.emit("PAUSED");
|
|
20862
|
-
this.sendPings();
|
|
21312
|
+
getAudioSampleRate() {
|
|
21313
|
+
if (this.audioRecorder) return this.audioRecorder.getSampleRate();
|
|
21314
|
+
return -1;
|
|
20863
21315
|
}
|
|
20864
|
-
|
|
20865
|
-
|
|
20866
|
-
this.
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
this.userMedia.resume();
|
|
20874
|
-
if (this.streamBackpressured) this.resumeAfterDrain = true;
|
|
20875
|
-
else this.loop.start();
|
|
21316
|
+
getCharacteristics() {
|
|
21317
|
+
return {
|
|
21318
|
+
audioSampleRate: this.getAudioSampleRate(),
|
|
21319
|
+
muted: this.rawVisualUserMedia?.muted,
|
|
21320
|
+
width: this.rawVisualUserMedia?.width,
|
|
21321
|
+
height: this.rawVisualUserMedia?.height,
|
|
21322
|
+
videoWidth: this.rawVisualUserMedia?.videoWidth,
|
|
21323
|
+
videoHeight: this.rawVisualUserMedia?.videoHeight
|
|
21324
|
+
};
|
|
20876
21325
|
}
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
|
|
21326
|
+
}
|
|
21327
|
+
const visuals_userMedia = UserMedia;
|
|
21328
|
+
class Recorder extends capture {
|
|
21329
|
+
dimensions;
|
|
21330
|
+
constructor(visuals, replay, options){
|
|
21331
|
+
super(visuals, replay, options);
|
|
21332
|
+
this.dimensions = new recorder_dimensions({
|
|
21333
|
+
options,
|
|
21334
|
+
visuals,
|
|
21335
|
+
getRecorderElement: ()=>this.recorderElement,
|
|
21336
|
+
getUserMedia: ()=>this.userMedia
|
|
21337
|
+
});
|
|
20880
21338
|
}
|
|
20881
|
-
|
|
20882
|
-
if (this.
|
|
20883
|
-
if (!this.userMedia) throw new Error("No user media defined, unable to draw on canvas");
|
|
21339
|
+
executeCommand(command) {
|
|
21340
|
+
if (this.unloaded) return;
|
|
20884
21341
|
try {
|
|
20885
|
-
if (
|
|
20886
|
-
|
|
20887
|
-
this.
|
|
20888
|
-
|
|
20889
|
-
|
|
20890
|
-
|
|
20891
|
-
|
|
20892
|
-
|
|
20893
|
-
|
|
20894
|
-
|
|
20895
|
-
|
|
20896
|
-
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
|
|
20902
|
-
|
|
20903
|
-
|
|
20904
|
-
|
|
20905
|
-
|
|
21342
|
+
if (command.args) {
|
|
21343
|
+
const debugLine = string_summarize(`Server commanded: ${command.command} with:`, command.args);
|
|
21344
|
+
this.options.logger.debug(debugLine);
|
|
21345
|
+
} else this.options.logger.debug(`Server commanded: ${command.command}`);
|
|
21346
|
+
switch(command.command){
|
|
21347
|
+
case "ready":
|
|
21348
|
+
this.emit("SERVER_READY");
|
|
21349
|
+
if (!this.userMediaTimeout) if (this.options.loadUserMediaOnRecord) this.show();
|
|
21350
|
+
else this.loadUserMedia();
|
|
21351
|
+
break;
|
|
21352
|
+
case "preview":
|
|
21353
|
+
this.preview(command.args);
|
|
21354
|
+
break;
|
|
21355
|
+
case "error":
|
|
21356
|
+
{
|
|
21357
|
+
let explanation = "(No explanation given)";
|
|
21358
|
+
if (command.args?.err?.message) explanation = command.args.err.message;
|
|
21359
|
+
const err = error_createError({
|
|
21360
|
+
message: "Websocket error from Videomail server",
|
|
21361
|
+
explanation,
|
|
21362
|
+
err: deserializeError(command.args?.err),
|
|
21363
|
+
options: this.options
|
|
20906
21364
|
});
|
|
20907
|
-
this.
|
|
21365
|
+
this.emit("ERROR", {
|
|
21366
|
+
err
|
|
21367
|
+
});
|
|
21368
|
+
break;
|
|
21369
|
+
}
|
|
21370
|
+
case "confirmFrame":
|
|
21371
|
+
this.updateFrameProgress(command.args);
|
|
21372
|
+
break;
|
|
21373
|
+
case "confirmSample":
|
|
21374
|
+
this.updateSampleProgress(command.args);
|
|
21375
|
+
break;
|
|
21376
|
+
case "beginAudioEncoding":
|
|
21377
|
+
this.emit("BEGIN_AUDIO_ENCODING");
|
|
21378
|
+
break;
|
|
21379
|
+
case "beginVideoEncoding":
|
|
21380
|
+
this.emit("BEGIN_VIDEO_ENCODING");
|
|
21381
|
+
break;
|
|
21382
|
+
default:
|
|
21383
|
+
{
|
|
21384
|
+
const err = error_createError({
|
|
21385
|
+
message: `Unknown server command: ${command.command}`,
|
|
21386
|
+
options: this.options
|
|
21387
|
+
});
|
|
21388
|
+
this.emit("ERROR", {
|
|
21389
|
+
err
|
|
21390
|
+
});
|
|
21391
|
+
break;
|
|
20908
21392
|
}
|
|
20909
|
-
} else throw error_createError({
|
|
20910
|
-
message: "Failed to extract webcam data.",
|
|
20911
|
-
options: this.options
|
|
20912
|
-
});
|
|
20913
21393
|
}
|
|
20914
21394
|
} catch (exc) {
|
|
20915
21395
|
this.emit("ERROR", {
|
|
@@ -20917,156 +21397,34 @@
|
|
|
20917
21397
|
});
|
|
20918
21398
|
}
|
|
20919
21399
|
}
|
|
20920
|
-
|
|
20921
|
-
|
|
20922
|
-
|
|
20923
|
-
|
|
20924
|
-
|
|
20925
|
-
|
|
20926
|
-
|
|
20927
|
-
|
|
20928
|
-
if (this.unloaded || this.cameraRecovery) return;
|
|
20929
|
-
if (!this.connected) {
|
|
20930
|
-
this.options.logger.debug("Recorder: reconnecting before recording …");
|
|
20931
|
-
this.initSocket(()=>{
|
|
20932
|
-
this.once("SERVER_READY", ()=>{
|
|
20933
|
-
if (this.unloaded) return;
|
|
20934
|
-
if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
|
|
20935
|
-
else this.once("USER_MEDIA_READY", ()=>{
|
|
20936
|
-
if (!this.unloaded) this.record();
|
|
20937
|
-
});
|
|
20938
|
-
});
|
|
20939
|
-
});
|
|
20940
|
-
return;
|
|
20941
|
-
}
|
|
20942
|
-
if (!this.userMediaLoaded) {
|
|
20943
|
-
if (this.options.loadUserMediaOnRecord) this.loadUserMedia({
|
|
20944
|
-
recordWhenReady: true
|
|
20945
|
-
});
|
|
20946
|
-
else {
|
|
20947
|
-
const err = error_createError({
|
|
20948
|
-
message: "Load and enable your camera first",
|
|
20949
|
-
options: this.options
|
|
20950
|
-
});
|
|
20951
|
-
this.emit("ERROR", {
|
|
20952
|
-
err
|
|
20953
|
-
});
|
|
20954
|
-
}
|
|
20955
|
-
return;
|
|
20956
|
-
}
|
|
20957
|
-
this.recordingDimensions = void 0;
|
|
20958
|
-
try {
|
|
20959
|
-
if (!this.userMedia) throw new Error("No user media defined, unable to create canvas");
|
|
20960
|
-
this.canvas = this.userMedia.createCanvas();
|
|
20961
|
-
} catch (exc) {
|
|
20962
|
-
const err = error_createError({
|
|
20963
|
-
exc,
|
|
20964
|
-
options: this.options
|
|
20965
|
-
});
|
|
20966
|
-
this.emit("ERROR", {
|
|
20967
|
-
err
|
|
20968
|
-
});
|
|
20969
|
-
return;
|
|
20970
|
-
}
|
|
20971
|
-
this.ctx = this.canvas.getContext("2d");
|
|
20972
|
-
if (!this.canvas.width) {
|
|
20973
|
-
const err = error_createError({
|
|
20974
|
-
message: "Canvas has an invalid width.",
|
|
20975
|
-
options: this.options
|
|
20976
|
-
});
|
|
20977
|
-
this.emit("ERROR", {
|
|
20978
|
-
err
|
|
20979
|
-
});
|
|
20980
|
-
return;
|
|
20981
|
-
}
|
|
20982
|
-
if (!this.canvas.height) {
|
|
20983
|
-
const err = error_createError({
|
|
20984
|
-
message: "Canvas has an invalid height.",
|
|
20985
|
-
options: this.options
|
|
20986
|
-
});
|
|
20987
|
-
this.emit("ERROR", {
|
|
20988
|
-
err
|
|
20989
|
-
});
|
|
20990
|
-
return;
|
|
20991
|
-
}
|
|
20992
|
-
this.recordingDimensions = {
|
|
20993
|
-
width: this.canvas.width,
|
|
20994
|
-
height: this.canvas.height
|
|
20995
|
-
};
|
|
20996
|
-
this.frame = new canvas_to_buffer_modern_r(this.canvas, this.options.image.types, this.options.image.quality);
|
|
20997
|
-
this.options.logger.debug("Recorder: record()");
|
|
20998
|
-
this.recordingActive = true;
|
|
20999
|
-
this.recordingTransportFailed = false;
|
|
21400
|
+
unload(params) {
|
|
21401
|
+
if (this.unloaded || !this.built) return;
|
|
21402
|
+
const e = params?.e;
|
|
21403
|
+
let cause;
|
|
21404
|
+
if (e) cause = e.type;
|
|
21405
|
+
const prettyCause = cause ? `, cause: ${cause}` : "";
|
|
21406
|
+
this.options.logger.debug(`Recorder: unload()${prettyCause}`);
|
|
21407
|
+
this.recordingActive = false;
|
|
21000
21408
|
this.mediaTransmissionStarted = false;
|
|
21001
|
-
this.
|
|
21002
|
-
this.
|
|
21003
|
-
this.
|
|
21004
|
-
|
|
21005
|
-
|
|
21006
|
-
this.
|
|
21007
|
-
|
|
21008
|
-
|
|
21009
|
-
|
|
21010
|
-
|
|
21011
|
-
this.
|
|
21012
|
-
|
|
21013
|
-
|
|
21014
|
-
|
|
21015
|
-
|
|
21016
|
-
|
|
21017
|
-
this.loop.setRequestAnimationFrameObject(newObj);
|
|
21018
|
-
if (isRecording) this.loop.start();
|
|
21019
|
-
}
|
|
21020
|
-
}
|
|
21021
|
-
restoreAnimationFrameObject() {
|
|
21022
|
-
this.options.logger.debug("Recorder: restoreAnimationFrameObject()");
|
|
21023
|
-
this.setAnimationFrameObject(this.originalAnimationFrameObject);
|
|
21024
|
-
}
|
|
21025
|
-
loopWithTimeouts() {
|
|
21026
|
-
this.options.logger.debug("Recorder: loopWithTimeouts()");
|
|
21027
|
-
const wantedInterval = 1e3 / this.options.video.fps;
|
|
21028
|
-
let processingTime = 0;
|
|
21029
|
-
let start;
|
|
21030
|
-
const raf = (fn)=>setTimeout(()=>{
|
|
21031
|
-
start = Date.now();
|
|
21032
|
-
fn();
|
|
21033
|
-
processingTime = Date.now() - start;
|
|
21034
|
-
}, wantedInterval - processingTime);
|
|
21035
|
-
const cancel = (id)=>{
|
|
21036
|
-
window.clearTimeout(id);
|
|
21037
|
-
};
|
|
21038
|
-
this.setAnimationFrameObject({
|
|
21039
|
-
requestAnimationFrame: raf,
|
|
21040
|
-
cancelAnimationFrame: cancel
|
|
21041
|
-
});
|
|
21409
|
+
this.reset();
|
|
21410
|
+
this.recordingDimensions = void 0;
|
|
21411
|
+
this.clearUserMediaTimeout();
|
|
21412
|
+
this.clearConnectionTimeout();
|
|
21413
|
+
this.clearReconnectTimeout();
|
|
21414
|
+
this.clearRetryTimeout();
|
|
21415
|
+
this.clearStopTimeout();
|
|
21416
|
+
this.stopPings();
|
|
21417
|
+
this.userMediaLoading = false;
|
|
21418
|
+
this.connecting = false;
|
|
21419
|
+
this.reconnecting = false;
|
|
21420
|
+
this.reconnectAttempts = 0;
|
|
21421
|
+
if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
|
|
21422
|
+
this.unloaded = true;
|
|
21423
|
+
this.discardSocket();
|
|
21424
|
+
this.built = this.connecting = this.connected = false;
|
|
21042
21425
|
}
|
|
21043
21426
|
correctDimensions() {
|
|
21044
|
-
|
|
21045
|
-
const widthDimension = dimensions_useFullWidth(this.options.video.mobileBreakPoint);
|
|
21046
|
-
if (this.options.video.stretch || widthDimension) {
|
|
21047
|
-
this.recorderElement.style.width = "100%";
|
|
21048
|
-
this.recorderElement.style.removeProperty("height");
|
|
21049
|
-
} else {
|
|
21050
|
-
if (this.options.video.width) {
|
|
21051
|
-
const recorderWidth = this.getRecorderWidth(true);
|
|
21052
|
-
if (recorderWidth?.value) this.recorderElement.width = recorderWidth.value;
|
|
21053
|
-
else this.recorderElement.style.removeProperty("width");
|
|
21054
|
-
}
|
|
21055
|
-
if (this.options.video.height) {
|
|
21056
|
-
const recorderHeight = this.getRecorderHeight(true);
|
|
21057
|
-
if (recorderHeight?.value) this.recorderElement.height = recorderHeight.value;
|
|
21058
|
-
else this.recorderElement.style.removeProperty("height");
|
|
21059
|
-
}
|
|
21060
|
-
}
|
|
21061
|
-
}
|
|
21062
|
-
switchFacingMode() {
|
|
21063
|
-
if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
|
|
21064
|
-
if ("user" === this.facingMode) this.facingMode = "environment";
|
|
21065
|
-
else if ("environment" === this.facingMode) this.facingMode = "user";
|
|
21066
|
-
else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
|
|
21067
|
-
this.loadGenuineUserMedia({
|
|
21068
|
-
switchingFacingMode: this.facingMode
|
|
21069
|
-
});
|
|
21427
|
+
this.dimensions.correctDimensions();
|
|
21070
21428
|
}
|
|
21071
21429
|
initEvents() {
|
|
21072
21430
|
this.options.logger.debug("Recorder: initEvents()");
|
|
@@ -21138,100 +21496,20 @@
|
|
|
21138
21496
|
}
|
|
21139
21497
|
this.built = true;
|
|
21140
21498
|
}
|
|
21141
|
-
isPaused() {
|
|
21142
|
-
return this.userMedia?.isPaused() && !this.loop.isRunning();
|
|
21143
|
-
}
|
|
21144
|
-
isRecording() {
|
|
21145
|
-
return Boolean(this.loop?.isRunning() && !this.isPaused() && !this.isNotifying() && this.stream && !this.stream.destroyed);
|
|
21146
|
-
}
|
|
21147
|
-
hide() {
|
|
21148
|
-
if (!this.isHidden()) {
|
|
21149
|
-
if (this.recorderElement) html_hideElement(this.recorderElement);
|
|
21150
|
-
this.clearUserMediaTimeout();
|
|
21151
|
-
this.clearRetryTimeout();
|
|
21152
|
-
}
|
|
21153
|
-
}
|
|
21154
|
-
isUnloaded() {
|
|
21155
|
-
return this.unloaded;
|
|
21156
|
-
}
|
|
21157
21499
|
getRecorderWidth(responsive) {
|
|
21158
|
-
|
|
21159
|
-
else if (responsive && this.options.video.width) return this.limitWidth(this.options.video.width);
|
|
21160
|
-
const dimension = {
|
|
21161
|
-
unit: "px",
|
|
21162
|
-
value: this.options.video.width
|
|
21163
|
-
};
|
|
21164
|
-
return dimension;
|
|
21165
|
-
}
|
|
21166
|
-
getRecordingDimensions() {
|
|
21167
|
-
return this.recordingDimensions && {
|
|
21168
|
-
...this.recordingDimensions
|
|
21169
|
-
};
|
|
21500
|
+
return this.dimensions.getRecorderWidth(responsive);
|
|
21170
21501
|
}
|
|
21171
21502
|
getRecorderHeight(responsive, useBoundingClientRect) {
|
|
21172
|
-
|
|
21173
|
-
if (this.recorderElement && useBoundingClientRect) {
|
|
21174
|
-
const height = this.recorderElement.getBoundingClientRect().height;
|
|
21175
|
-
recorderHeight = {
|
|
21176
|
-
unit: "px",
|
|
21177
|
-
value: height
|
|
21178
|
-
};
|
|
21179
|
-
} else if (this.userMedia) {
|
|
21180
|
-
let height = this.userMedia.getRawHeight(responsive);
|
|
21181
|
-
if (void 0 !== height && this.options.video.width) {
|
|
21182
|
-
const ratio = this.getRatio();
|
|
21183
|
-
if (void 0 !== ratio) {
|
|
21184
|
-
const idealHeight = this.options.video.width * ratio;
|
|
21185
|
-
height = Math.min(idealHeight, height);
|
|
21186
|
-
}
|
|
21187
|
-
}
|
|
21188
|
-
recorderHeight = {
|
|
21189
|
-
unit: "px",
|
|
21190
|
-
value: height
|
|
21191
|
-
};
|
|
21192
|
-
} else if (responsive && this.options.video.height) recorderHeight = this.calculateHeight(responsive);
|
|
21193
|
-
else if (this.options.video.height) {
|
|
21194
|
-
const height = this.options.video.height;
|
|
21195
|
-
recorderHeight = {
|
|
21196
|
-
unit: "px",
|
|
21197
|
-
value: height
|
|
21198
|
-
};
|
|
21199
|
-
}
|
|
21200
|
-
return recorderHeight;
|
|
21503
|
+
return this.dimensions.getRecorderHeight(responsive, useBoundingClientRect);
|
|
21201
21504
|
}
|
|
21202
21505
|
getRatio() {
|
|
21203
|
-
|
|
21204
|
-
if (this.userMedia) {
|
|
21205
|
-
const userMediaVideoWidth = this.userMedia.getVideoWidth();
|
|
21206
|
-
const userMediaVideoHeight = this.userMedia.getVideoHeight();
|
|
21207
|
-
if (!userMediaVideoWidth || userMediaVideoWidth < 1) ratio = this.visuals.getRatio();
|
|
21208
|
-
else if (userMediaVideoHeight) ratio = userMediaVideoHeight / userMediaVideoWidth;
|
|
21209
|
-
} else ratio = dimensions_getRatio(this.options);
|
|
21210
|
-
return ratio;
|
|
21506
|
+
return this.dimensions.getRatio();
|
|
21211
21507
|
}
|
|
21212
21508
|
calculateWidth(responsive) {
|
|
21213
|
-
|
|
21214
|
-
if (this.userMedia) videoHeight = this.userMedia.getVideoHeight();
|
|
21215
|
-
else if (this.recorderElement) videoHeight = this.recorderElement.videoHeight || this.recorderElement.height;
|
|
21216
|
-
const ratio = this.getRatio();
|
|
21217
|
-
return dimensions_calculateWidth(responsive, this.options, videoHeight, ratio);
|
|
21509
|
+
return this.dimensions.calculateWidth(responsive);
|
|
21218
21510
|
}
|
|
21219
21511
|
calculateHeight(responsive) {
|
|
21220
|
-
|
|
21221
|
-
if (this.userMedia) {
|
|
21222
|
-
const videoHeight = this.userMedia.getVideoHeight();
|
|
21223
|
-
videoDimension = {
|
|
21224
|
-
value: videoHeight,
|
|
21225
|
-
unit: "px"
|
|
21226
|
-
};
|
|
21227
|
-
} else if (this.recorderElement) {
|
|
21228
|
-
const videoHeight = this.recorderElement.videoHeight || this.recorderElement.height;
|
|
21229
|
-
videoDimension = {
|
|
21230
|
-
value: videoHeight,
|
|
21231
|
-
unit: "px"
|
|
21232
|
-
};
|
|
21233
|
-
} else videoDimension = dimensions_calculateHeight(responsive, void 0, this.options, this.getRatio(), this.recorderElement);
|
|
21234
|
-
return videoDimension;
|
|
21512
|
+
return this.dimensions.calculateHeight(responsive);
|
|
21235
21513
|
}
|
|
21236
21514
|
getRawVisualUserMedia() {
|
|
21237
21515
|
return this.recorderElement;
|
|
@@ -21248,9 +21526,6 @@
|
|
|
21248
21526
|
limitHeight(height) {
|
|
21249
21527
|
return this.visuals.limitHeight(height);
|
|
21250
21528
|
}
|
|
21251
|
-
isUserMediaLoaded() {
|
|
21252
|
-
return this.userMediaLoaded;
|
|
21253
|
-
}
|
|
21254
21529
|
}
|
|
21255
21530
|
const visuals_recorder = Recorder;
|
|
21256
21531
|
class Replay extends util_Despot {
|
|
@@ -22162,7 +22437,8 @@
|
|
|
22162
22437
|
}
|
|
22163
22438
|
else runValidation = false;
|
|
22164
22439
|
if (runValidation) {
|
|
22165
|
-
const
|
|
22440
|
+
const target = event?.target;
|
|
22441
|
+
const targetName = target instanceof Element ? target.getAttribute("name") : void 0;
|
|
22166
22442
|
if (targetName) this.emit("VALIDATING", {
|
|
22167
22443
|
targetName
|
|
22168
22444
|
});
|
|
@@ -22197,27 +22473,10 @@
|
|
|
22197
22473
|
}
|
|
22198
22474
|
}
|
|
22199
22475
|
if (valid) {
|
|
22200
|
-
|
|
22201
|
-
|
|
22202
|
-
|
|
22203
|
-
const bccIsConfigured = "bcc" in recipients;
|
|
22204
|
-
const hasTo = recipients.to && recipients.to.length > 0;
|
|
22205
|
-
const hasCc = recipients.cc && recipients.cc.length > 0;
|
|
22206
|
-
const hasBcc = recipients.bcc && recipients.bcc.length > 0;
|
|
22207
|
-
if (toIsConfigured) {
|
|
22208
|
-
if (!hasTo) if (ccIsConfigured && bccIsConfigured) {
|
|
22209
|
-
if (!hasCc && !hasBcc) valid = false;
|
|
22210
|
-
} else if (ccIsConfigured) {
|
|
22211
|
-
if (!hasCc) valid = false;
|
|
22212
|
-
} else if (bccIsConfigured) {
|
|
22213
|
-
if (!hasBcc) valid = false;
|
|
22214
|
-
} else valid = false;
|
|
22215
|
-
} else if (ccIsConfigured) {
|
|
22216
|
-
if (!hasCc) {
|
|
22217
|
-
if (bccIsConfigured && !hasBcc) valid = false;
|
|
22218
|
-
}
|
|
22476
|
+
if (!email_isRecipientSelectionValid(this.form.getRecipients())) {
|
|
22477
|
+
valid = false;
|
|
22478
|
+
whyInvalid = "At least one recipient is required";
|
|
22219
22479
|
}
|
|
22220
|
-
if (!valid) whyInvalid = "At least one recipient is required";
|
|
22221
22480
|
}
|
|
22222
22481
|
} else {
|
|
22223
22482
|
valid = visualsValid;
|