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.
@@ -5948,7 +5948,8 @@ var __webpack_modules__ = {
5948
5948
  var percentTwenties = /%20/g;
5949
5949
  var Format = {
5950
5950
  RFC1738: 'RFC1738',
5951
- RFC3986: 'RFC3986'
5951
+ RFC3986: 'RFC3986',
5952
+ WHATWG: 'WHATWG'
5952
5953
  };
5953
5954
  module.exports = {
5954
5955
  default: Format.RFC3986,
@@ -5958,10 +5959,14 @@ var __webpack_modules__ = {
5958
5959
  },
5959
5960
  RFC3986: function(value) {
5960
5961
  return String(value);
5962
+ },
5963
+ WHATWG: function(value) {
5964
+ return replace.call(value, percentTwenties, '+');
5961
5965
  }
5962
5966
  },
5963
5967
  RFC1738: Format.RFC1738,
5964
- RFC3986: Format.RFC3986
5968
+ RFC3986: Format.RFC3986,
5969
+ WHATWG: Format.WHATWG
5965
5970
  };
5966
5971
  },
5967
5972
  b (module, __unused_rspack_exports, __webpack_require__) {
@@ -6022,8 +6027,8 @@ var __webpack_modules__ = {
6022
6027
  target: copy
6023
6028
  };
6024
6029
  }
6025
- target[keys[j]] = copies.get(value);
6026
- } else target[keys[j]] = value;
6030
+ utils.setProperty(target, keys[j], copies.get(value));
6031
+ } else utils.setProperty(target, keys[j], value);
6027
6032
  }
6028
6033
  }
6029
6034
  return result;
@@ -6070,7 +6075,7 @@ var __webpack_modules__ = {
6070
6075
  }
6071
6076
  return val.split(',');
6072
6077
  }
6073
- if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (1 === options.arrayLimit ? '' : 's') + ' allowed in an array.');
6078
+ 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.');
6074
6079
  return val;
6075
6080
  };
6076
6081
  var isoSentinel = 'utf8=%26%2310003%3B';
@@ -6107,11 +6112,13 @@ var __webpack_modules__ = {
6107
6112
  val = options.strictNullHandling ? null : '';
6108
6113
  } else {
6109
6114
  key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
6110
- if (null !== key) val = utils.maybeMap(parseArrayValue(part.slice(pos + 1), options, isArray(obj[key]) ? obj[key].length : 0), function(encodedVal) {
6115
+ 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) {
6111
6116
  return options.decoder(encodedVal, defaults.decoder, charset, 'value');
6112
6117
  });
6113
6118
  }
6114
- if (val && options.interpretNumericEntities && 'iso-8859-1' === charset) val = interpretNumericEntities(String(val));
6119
+ if (val && options.interpretNumericEntities && 'iso-8859-1' === charset) val = utils.maybeMap(val, function(value) {
6120
+ return value ? interpretNumericEntities(String(value)) : value;
6121
+ });
6115
6122
  if (isEmptyBracketKey) val = isArray(val) ? [
6116
6123
  val
6117
6124
  ] : val;
@@ -6140,7 +6147,7 @@ var __webpack_modules__ = {
6140
6147
  __proto__: null
6141
6148
  } : {};
6142
6149
  var cleanRoot = '[' === root.charAt(0) && ']' === root.charAt(root.length - 1) ? root.slice(1, -1) : root;
6143
- var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/g, '.') : cleanRoot;
6150
+ var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/gi, '.') : cleanRoot;
6144
6151
  var index = parseInt(decodedRoot, 10);
6145
6152
  var isValidArrayIndex = !isNaN(index) && root !== decodedRoot && String(index) === decodedRoot && index >= 0 && options.parseArrays;
6146
6153
  if (options.parseArrays || '' !== decodedRoot) {
@@ -6402,7 +6409,7 @@ var __webpack_modules__ = {
6402
6409
  }
6403
6410
  var encodedPrefix = String(prefix);
6404
6411
  var adjustedPrefix = commaRoundTrip && isArray(obj) && 1 === obj.length ? encodedPrefix + '[]' : encodedPrefix;
6405
- if (allowEmptyArrays && isArray(obj) && 0 === obj.length && 0 === Object.keys(obj).length) return adjustedPrefix + '[]';
6412
+ if (allowEmptyArrays && isArray(obj) && 0 === obj.length && 0 === Object.keys(obj).length) return formatter(encoder && !encodeValuesOnly ? encoder(adjustedPrefix, defaults.encoder, charset, 'key', format) : adjustedPrefix) + '[]';
6406
6413
  for(var j = 0; j < objKeys.length; ++j){
6407
6414
  var key = objKeys[j];
6408
6415
  var value = 'object' == typeof key && key && void 0 !== key.value ? key.value : obj[key];
@@ -6468,7 +6475,7 @@ var __webpack_modules__ = {
6468
6475
  filter = options.filter;
6469
6476
  obj = filter('', obj);
6470
6477
  } else if (isArray(options.filter)) {
6471
- filter = options.filter;
6478
+ filter = options.filter.slice();
6472
6479
  objKeys = filter;
6473
6480
  }
6474
6481
  var keys = [];
@@ -6484,7 +6491,7 @@ var __webpack_modules__ = {
6484
6491
  var value = obj[key];
6485
6492
  if (!options.skipNulls || null !== value) {
6486
6493
  var encodedKey = options.encodeDotInKeys ? String(key).replace(/\./g, '%2E') : String(key);
6487
- pushToArray(keys, stringify(value, encodedKey, generateArrayPrefix, commaRoundTrip, options.allowEmptyArrays, options.strictNullHandling, options.skipNulls, options.encodeDotInKeys, options.encode ? options.encoder : null, options.filter, options.sort, options.allowDots, options.serializeDate, options.format, options.formatter, options.encodeValuesOnly, options.charset, sideChannel, options.depth, 0));
6494
+ 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));
6488
6495
  }
6489
6496
  }
6490
6497
  }
@@ -6733,7 +6740,7 @@ var __webpack_modules__ = {
6733
6740
  var arr = [];
6734
6741
  for(var i = 0; i < segment.length; ++i){
6735
6742
  var c = segment.charCodeAt(i);
6736
- 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)) {
6743
+ 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) {
6737
6744
  arr[arr.length] = segment.charAt(i);
6738
6745
  continue;
6739
6746
  }
@@ -6749,9 +6756,12 @@ var __webpack_modules__ = {
6749
6756
  arr[arr.length] = hexTable[0xE0 | c >> 12] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | 0x3F & c];
6750
6757
  continue;
6751
6758
  }
6752
- i += 1;
6753
- c = 0x10000 + ((0x3FF & c) << 10 | 0x3FF & segment.charCodeAt(i));
6754
- arr[arr.length] = hexTable[0xF0 | c >> 18] + hexTable[0x80 | c >> 12 & 0x3F] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | 0x3F & c];
6759
+ var next = segment.charCodeAt(i + 1);
6760
+ if (c <= 0xDBFF && next >= 0xDC00 && next <= 0xDFFF) {
6761
+ i += 1;
6762
+ c = 0x10000 + ((0x3FF & c) << 10 | 0x3FF & next);
6763
+ arr[arr.length] = hexTable[0xF0 | c >> 18] + hexTable[0x80 | c >> 12 & 0x3F] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | 0x3F & c];
6764
+ } else arr[arr.length] = '%EF%BF%BD';
6755
6765
  }
6756
6766
  out += arr.join('');
6757
6767
  }
@@ -6837,6 +6847,7 @@ var __webpack_modules__ = {
6837
6847
  markOverflow: markOverflow,
6838
6848
  maybeMap: maybeMap,
6839
6849
  merge: merge,
6850
+ setProperty: setProperty,
6840
6851
  toCoercible: toCoercible
6841
6852
  };
6842
6853
  },
@@ -7189,7 +7200,7 @@ var __webpack_modules__ = {
7189
7200
  const qs = __webpack_require__("b");
7190
7201
  const RequestBase = __webpack_require__("d");
7191
7202
  const { isObject, mixin, hasOwn, isSafeKey } = __webpack_require__("Dp");
7192
- const ResponseBase = __webpack_require__("e");
7203
+ const ResponseBase = __webpack_require__("es");
7193
7204
  const Agent = __webpack_require__("pf");
7194
7205
  function noop() {}
7195
7206
  module.exports = function(method, url) {
@@ -7903,7 +7914,7 @@ var __webpack_modules__ = {
7903
7914
  }, this._responseTimeout);
7904
7915
  };
7905
7916
  },
7906
- e (module, __unused_rspack_exports, __webpack_require__) {
7917
+ es (module, __unused_rspack_exports, __webpack_require__) {
7907
7918
  "use strict";
7908
7919
  const utils = __webpack_require__("Dp");
7909
7920
  module.exports = ResponseBase;
@@ -8248,7 +8259,7 @@ var __webpack_modules__ = {
8248
8259
  });
8249
8260
  });
8250
8261
  },
8251
- Sy (__unused_rspack_module, exports1, __webpack_require__) {
8262
+ S (__unused_rspack_module, exports1, __webpack_require__) {
8252
8263
  var process = __webpack_require__("zT");
8253
8264
  var getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function(obj) {
8254
8265
  var keys = Object.keys(obj);
@@ -11115,7 +11126,7 @@ var __webpack_exports__ = {};
11115
11126
  var client = __webpack_require__("Q");
11116
11127
  var client_default = /*#__PURE__*/ __webpack_require__.n(client);
11117
11128
  var package_namespaceObject = {
11118
- rE: "16.1.1"
11129
+ rE: "16.4.0"
11119
11130
  };
11120
11131
  function isAudioEnabled(options) {
11121
11132
  return Boolean(options.audio.enabled);
@@ -11127,7 +11138,7 @@ var __webpack_exports__ = {};
11127
11138
  function isAutoPauseEnabled(options) {
11128
11139
  return options.enableAutoPause && options.enablePause;
11129
11140
  }
11130
- var util = __webpack_require__("Sy");
11141
+ var util = __webpack_require__("S");
11131
11142
  var util_default = /*#__PURE__*/ __webpack_require__.n(util);
11132
11143
  function getEventDetails(event) {
11133
11144
  const eventWithErrorDetails = event;
@@ -15017,6 +15028,14 @@ var __webpack_exports__ = {};
15017
15028
  }
15018
15029
  Despot_define_property(Despot, "emitters", new WeakMap());
15019
15030
  const util_Despot = Despot;
15031
+ const EMAIL_SEPARATOR_REGEX = /[ ,]+/gu;
15032
+ function trimEmail(email) {
15033
+ return email.replace(EMAIL_SEPARATOR_REGEX, "");
15034
+ }
15035
+ function trimEmails(emails) {
15036
+ const trimmedEmails = emails.split(EMAIL_SEPARATOR_REGEX).map((item)=>item.trim()).filter(Boolean);
15037
+ return trimmedEmails;
15038
+ }
15020
15039
  function getValidity(element) {
15021
15040
  const validity = "validity" in element ? element.validity : void 0;
15022
15041
  return validity;
@@ -15036,14 +15055,6 @@ var __webpack_exports__ = {};
15036
15055
  element.style.removeProperty("display");
15037
15056
  }
15038
15057
  const html_showElement = showElement;
15039
- const EMAIL_SEPARATOR_REGEX = /[ ,]+/gu;
15040
- function trimEmail(email) {
15041
- return email.replace(EMAIL_SEPARATOR_REGEX, "");
15042
- }
15043
- function trimEmails(emails) {
15044
- const trimmedEmails = emails.split(EMAIL_SEPARATOR_REGEX).map((item)=>item.trim()).filter(Boolean);
15045
- return trimmedEmails;
15046
- }
15047
15058
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
15048
15059
  try {
15049
15060
  var info = gen[key](arg);
@@ -16837,11 +16848,11 @@ var __webpack_exports__ = {};
16837
16848
  const originalDocument = document1;
16838
16849
  const currentScript = originalDocument.currentScript;
16839
16850
  window1.DocumentFragment;
16840
- const HTMLTemplateElement = window1.HTMLTemplateElement, Node = window1.Node, Element = window1.Element, NodeFilter = window1.NodeFilter;
16851
+ const HTMLTemplateElement = window1.HTMLTemplateElement, Node = window1.Node, Element1 = window1.Element, NodeFilter = window1.NodeFilter;
16841
16852
  void 0 === window1.NamedNodeMap && (window1.NamedNodeMap || window1.MozNamedAttrMap);
16842
16853
  window1.HTMLFormElement;
16843
16854
  const DOMParser = window1.DOMParser, trustedTypes = window1.trustedTypes;
16844
- const ElementPrototype = Element.prototype;
16855
+ const ElementPrototype = Element1.prototype;
16845
16856
  const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
16846
16857
  const remove = lookupGetter(ElementPrototype, "remove");
16847
16858
  const removeAttributeNode = lookupGetter(ElementPrototype, "removeAttributeNode");
@@ -17924,6 +17935,20 @@ var __webpack_exports__ = {};
17924
17935
  return DOMPurify;
17925
17936
  }
17926
17937
  var purify_default = createDOMPurify();
17938
+ function isRecipientSelectionValid(recipients) {
17939
+ var _recipients_to, _recipients_cc, _recipients_bcc;
17940
+ const hasTo = Boolean(null == (_recipients_to = recipients.to) ? void 0 : _recipients_to.length);
17941
+ const hasCc = Boolean(null == (_recipients_cc = recipients.cc) ? void 0 : _recipients_cc.length);
17942
+ const hasBcc = Boolean(null == (_recipients_bcc = recipients.bcc) ? void 0 : _recipients_bcc.length);
17943
+ const hasConfiguredTo = "to" in recipients;
17944
+ const hasConfiguredCc = "cc" in recipients;
17945
+ const hasConfiguredBcc = "bcc" in recipients;
17946
+ let isValid = true;
17947
+ if (hasConfiguredTo) isValid = hasTo || hasConfiguredCc && hasCc || hasConfiguredBcc && hasBcc;
17948
+ else if (hasConfiguredCc) isValid = hasCc || !hasConfiguredBcc || hasBcc;
17949
+ return isValid;
17950
+ }
17951
+ const email_isRecipientSelectionValid = isRecipientSelectionValid;
17927
17952
  function limitHeight(height, options, calledFrom) {
17928
17953
  const dimension = {
17929
17954
  unit: "px"
@@ -18557,7 +18582,7 @@ var __webpack_exports__ = {};
18557
18582
  this.visuals = visuals;
18558
18583
  }
18559
18584
  }
18560
- const facingMode = FacingMode;
18585
+ const recorder_facingMode = FacingMode;
18561
18586
  function pausedNote_define_property(obj, key, value) {
18562
18587
  if (key in obj) Object.defineProperty(obj, key, {
18563
18588
  value: value,
@@ -18924,7 +18949,7 @@ var __webpack_exports__ = {};
18924
18949
  this.recordTimer = new recordTimer(visuals, this.recordNote, options);
18925
18950
  const browser = util_getBrowser(options);
18926
18951
  if (options.video.countdown) this.countdown = new countdown(visuals, options);
18927
- if (options.video.facingModeButton && browser.isMobile()) this.facingMode = new facingMode(visuals, options);
18952
+ if (options.video.facingModeButton && browser.isMobile()) this.facingMode = new recorder_facingMode(visuals, options);
18928
18953
  if (options.enablePause) this.pausedNote = new pausedNote(visuals, options);
18929
18954
  }
18930
18955
  }
@@ -19175,6 +19200,18 @@ var __webpack_exports__ = {};
19175
19200
  }
19176
19201
  }
19177
19202
  const notifier = Notifier;
19203
+ function summarize(title, contents) {
19204
+ const lines = Object.entries(contents).sort(([keyA], [keyB])=>keyA.localeCompare(keyB)).map(([key, value])=>{
19205
+ const formattedValue = void 0 === value ? "undefined" : "object" == typeof value ? string_pretty(value) : value;
19206
+ return ` • ${key}: ${formattedValue}`;
19207
+ });
19208
+ const line = [
19209
+ `🔎 ${title}`,
19210
+ ...lines
19211
+ ].join("\n");
19212
+ return line;
19213
+ }
19214
+ const string_summarize = summarize;
19178
19215
  var animitter = __webpack_require__("b6");
19179
19216
  var animitter_default = /*#__PURE__*/ __webpack_require__.n(animitter);
19180
19217
  var typedarray_to_buffer = __webpack_require__("E");
@@ -19238,46 +19275,6 @@ var __webpack_exports__ = {};
19238
19275
  canvas_to_buffer_modern_r.atob = void 0;
19239
19276
  var websocket_stream_stream = __webpack_require__("U");
19240
19277
  var stream_default = /*#__PURE__*/ __webpack_require__.n(websocket_stream_stream);
19241
- function boundedWriteQueue_define_property(obj, key, value) {
19242
- if (key in obj) Object.defineProperty(obj, key, {
19243
- value: value,
19244
- enumerable: true,
19245
- configurable: true,
19246
- writable: true
19247
- });
19248
- else obj[key] = value;
19249
- return obj;
19250
- }
19251
- const DEFAULT_MAX_QUEUED_BYTES = 1048576;
19252
- class BoundedWriteQueue {
19253
- enqueue(buffer, params) {
19254
- if (this.queuedBytes + buffer.length > this.maxBytes) return false;
19255
- this.writes.push({
19256
- buffer,
19257
- params
19258
- });
19259
- this.queuedBytes += buffer.length;
19260
- return true;
19261
- }
19262
- dequeue() {
19263
- const write = this.writes.shift();
19264
- if (write) this.queuedBytes -= write.buffer.length;
19265
- return write;
19266
- }
19267
- clear() {
19268
- this.writes.length = 0;
19269
- this.queuedBytes = 0;
19270
- }
19271
- constructor(maxBytes = DEFAULT_MAX_QUEUED_BYTES){
19272
- boundedWriteQueue_define_property(this, "maxBytes", void 0);
19273
- boundedWriteQueue_define_property(this, "writes", void 0);
19274
- boundedWriteQueue_define_property(this, "queuedBytes", void 0);
19275
- this.maxBytes = maxBytes;
19276
- this.writes = [];
19277
- this.queuedBytes = 0;
19278
- }
19279
- }
19280
- const boundedWriteQueue = BoundedWriteQueue;
19281
19278
  function getEdgeCodes(value, amount, fromEnd) {
19282
19279
  const chars = fromEnd ? value.slice(-amount) : value.slice(0, amount);
19283
19280
  const codes = Array.from(chars, (char)=>String(char.codePointAt(0))).join(".");
@@ -19357,117 +19354,11 @@ var __webpack_exports__ = {};
19357
19354
  };
19358
19355
  }
19359
19356
  const error_getWebSocketDiagnostic = getWebSocketDiagnostic;
19360
- function figureMinHeight(height, options) {
19361
- let minHeight;
19362
- if (options.video.height) {
19363
- minHeight = height ? Math.min(options.video.height, height) : options.video.height;
19364
- if (minHeight < 1) throw error_createError({
19365
- message: `Got a min height less than 1 (${minHeight})!`,
19366
- options
19367
- });
19368
- } else minHeight = height;
19369
- return minHeight;
19370
- }
19371
- const dimensions_figureMinHeight = figureMinHeight;
19372
- function getRatio(options, videoHeight, videoWidth) {
19373
- let ratio = 1;
19374
- const hasVideoDimensions = videoHeight && videoWidth;
19375
- const desiredHeight = options.video.height;
19376
- const desiredWidth = options.video.width;
19377
- const hasDesiredDimensions = desiredHeight && desiredWidth;
19378
- if (hasDesiredDimensions) ratio = hasVideoDimensions ? videoHeight < desiredHeight || videoWidth < desiredWidth ? videoHeight / videoWidth : desiredHeight / desiredWidth : desiredHeight / desiredWidth;
19379
- else if (hasVideoDimensions) ratio = videoHeight / videoWidth;
19380
- return ratio;
19381
- }
19382
- const dimensions_getRatio = getRatio;
19383
- function calculateHeight(responsive, videoWidth, options, ratio, element) {
19384
- const dimension = {
19385
- unit: "px"
19386
- };
19387
- let width = videoWidth;
19388
- if (responsive && element) {
19389
- const limitedDimension = dimensions_limitWidth(element, options, width);
19390
- width = limitedDimension.value;
19391
- }
19392
- const chosenRatio = null != ratio ? ratio : dimensions_getRatio(options, void 0, videoWidth);
19393
- const height = width ? Math.round(width * chosenRatio) : void 0;
19394
- if (height && Number.isInteger(height) && height < 1) throw error_createError({
19395
- message: "Just calculated a height less than 1 which is wrong.",
19396
- options
19397
- });
19398
- const minHeight = dimensions_figureMinHeight(height, options);
19399
- dimension.value = minHeight;
19400
- return dimension;
19401
- }
19402
- const dimensions_calculateHeight = calculateHeight;
19403
- function calculateWidth(responsive, options, videoHeight, ratio) {
19404
- const dimension = {
19405
- unit: "px"
19406
- };
19407
- let height = dimensions_figureMinHeight(videoHeight, options);
19408
- if (responsive) {
19409
- const limitedDimension = dimensions_limitHeight(height, options, "calculateWidth");
19410
- height = limitedDimension.value;
19411
- }
19412
- if (!height || height < 1) throw error_createError({
19413
- message: `Height ${height} cannot be smaller than 1 when calculating width.`,
19414
- options
19415
- });
19416
- const chosenRatio = null != ratio ? ratio : dimensions_getRatio(options, videoHeight);
19417
- const calculatedWidth = Math.round(height / chosenRatio);
19418
- if (calculatedWidth < 1) throw error_createError({
19419
- message: "Calculated width cannot be smaller than 1!",
19420
- options
19421
- });
19422
- dimension.value = calculatedWidth;
19423
- return dimension;
19424
- }
19425
- const dimensions_calculateWidth = calculateWidth;
19426
19357
  function isAutomatedUserAgent(userAgent = navigator.userAgent) {
19427
19358
  return /bot|crawler|spider|headlesschrome|phantomjs|puppeteer|playwright/iu.test(userAgent);
19428
19359
  }
19429
19360
  const util_isAutomatedUserAgent = isAutomatedUserAgent;
19430
- function summarize(title, contents) {
19431
- const lines = Object.entries(contents).sort(([keyA], [keyB])=>keyA.localeCompare(keyB)).map(([key, value])=>{
19432
- const formattedValue = void 0 === value ? "undefined" : "object" == typeof value ? string_pretty(value) : value;
19433
- return ` • ${key}: ${formattedValue}`;
19434
- });
19435
- const line = [
19436
- `🔎 ${title}`,
19437
- ...lines
19438
- ].join("\n");
19439
- return line;
19440
- }
19441
- const string_summarize = summarize;
19442
- function getFirstVideoTrack(localMediaStream) {
19443
- const videoTracks = localMediaStream.getVideoTracks();
19444
- let videoTrack;
19445
- if (videoTracks[0]) videoTrack = videoTracks[0];
19446
- return videoTrack;
19447
- }
19448
- const media_getFirstVideoTrack = getFirstVideoTrack;
19449
- const MEDIA_EVENTS = [
19450
- "loadstart",
19451
- "suspend",
19452
- "progress",
19453
- "abort",
19454
- "emptied",
19455
- "stalled",
19456
- "pause",
19457
- "loadeddata",
19458
- "waiting",
19459
- "playing",
19460
- "canplay",
19461
- "canplaythrough",
19462
- "seeking",
19463
- "seeked",
19464
- "ended",
19465
- "ratechange",
19466
- "durationchange",
19467
- "volumechange"
19468
- ];
19469
- const mediaEvents = MEDIA_EVENTS;
19470
- function esm_define_property(obj, key, value) {
19361
+ function boundedWriteQueue_define_property(obj, key, value) {
19471
19362
  if (key in obj) Object.defineProperty(obj, key, {
19472
19363
  value: value,
19473
19364
  enumerable: true,
@@ -19477,52 +19368,37 @@ var __webpack_exports__ = {};
19477
19368
  else obj[key] = value;
19478
19369
  return obj;
19479
19370
  }
19480
- class AudioSample {
19481
- toBuffer() {
19482
- const l = this.float32Array.length;
19483
- const arr = new Int16Array(l);
19484
- this.float32Array.forEach((sample, i)=>{
19485
- const clampedSample = Number.isFinite(sample) ? Math.max(-1, sample) : sample;
19486
- arr[i] = 0x7fff * Math.min(1, clampedSample);
19371
+ const DEFAULT_MAX_QUEUED_BYTES = 1048576;
19372
+ class BoundedWriteQueue {
19373
+ enqueue(buffer, params) {
19374
+ if (this.queuedBytes + buffer.length > this.maxBytes) return false;
19375
+ this.writes.push({
19376
+ buffer,
19377
+ params
19487
19378
  });
19488
- return typedarray_to_buffer(arr);
19379
+ this.queuedBytes += buffer.length;
19380
+ return true;
19489
19381
  }
19490
- constructor(float32Array){
19491
- esm_define_property(this, "float32Array", void 0);
19492
- this.float32Array = float32Array;
19382
+ dequeue() {
19383
+ const write = this.writes.shift();
19384
+ if (write) this.queuedBytes -= write.buffer.length;
19385
+ return write;
19493
19386
  }
19494
- }
19495
- const esm_src = AudioSample;
19496
- const esm = esm_src;
19497
- var is_power_of_two = __webpack_require__("dZ");
19498
- var is_power_of_two_default = /*#__PURE__*/ __webpack_require__.n(is_power_of_two);
19499
- function AudioRecorder_asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
19500
- try {
19501
- var info = gen[key](arg);
19502
- var value = info.value;
19503
- } catch (error) {
19504
- reject(error);
19505
- return;
19387
+ clear() {
19388
+ this.writes.length = 0;
19389
+ this.queuedBytes = 0;
19390
+ }
19391
+ constructor(maxBytes = DEFAULT_MAX_QUEUED_BYTES){
19392
+ boundedWriteQueue_define_property(this, "maxBytes", void 0);
19393
+ boundedWriteQueue_define_property(this, "writes", void 0);
19394
+ boundedWriteQueue_define_property(this, "queuedBytes", void 0);
19395
+ this.maxBytes = maxBytes;
19396
+ this.writes = [];
19397
+ this.queuedBytes = 0;
19506
19398
  }
19507
- if (info.done) resolve(value);
19508
- else Promise.resolve(value).then(_next, _throw);
19509
- }
19510
- function AudioRecorder_async_to_generator(fn) {
19511
- return function() {
19512
- var self1 = this, args = arguments;
19513
- return new Promise(function(resolve, reject) {
19514
- var gen = fn.apply(self1, args);
19515
- function _next(value) {
19516
- AudioRecorder_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
19517
- }
19518
- function _throw(err) {
19519
- AudioRecorder_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
19520
- }
19521
- _next(void 0);
19522
- });
19523
- };
19524
19399
  }
19525
- function AudioRecorder_define_property(obj, key, value) {
19400
+ const boundedWriteQueue = BoundedWriteQueue;
19401
+ function streamWriter_define_property(obj, key, value) {
19526
19402
  if (key in obj) Object.defineProperty(obj, key, {
19527
19403
  value: value,
19528
19404
  enumerable: true,
@@ -19532,149 +19408,183 @@ var __webpack_exports__ = {};
19532
19408
  else obj[key] = value;
19533
19409
  return obj;
19534
19410
  }
19535
- const CHANNELS = 1;
19536
- function getAudioContextClass() {
19537
- return window.AudioContext;
19538
- }
19539
- class AudioRecorder {
19540
- hasAudioContext() {
19541
- return Boolean(getAudioContextClass()) && Boolean(this.getAudioContext());
19411
+ const PIPE_SYMBOL = "°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸ ";
19412
+ class RecorderStreamWriter extends util_Despot {
19413
+ hasExceededWebSocketSendBuffer(buffer, opts) {
19414
+ return buffer.length > 0 && !this.pendingWrites.enqueue(buffer, opts);
19542
19415
  }
19543
- getAudioContext() {
19544
- if (!this.vcAudioContext) {
19545
- const AudioContext = getAudioContextClass();
19546
- this.vcAudioContext = new AudioContext();
19547
- }
19548
- return this.vcAudioContext;
19549
- }
19550
- onAudioProcess(e, cb) {
19551
- if (!this.userMedia.isRecording() || this.userMedia.isPaused()) return;
19552
- const float32Array = e.inputBuffer.getChannelData(0);
19553
- cb(new esm(float32Array));
19554
- }
19555
- disconnect() {
19556
- var _this_audioInput, _this_volume;
19557
- if (this.scriptProcessor) {
19558
- this.scriptProcessor.onaudioprocess = null;
19559
- this.scriptProcessor.disconnect();
19560
- }
19561
- null == (_this_audioInput = this.audioInput) || _this_audioInput.disconnect();
19562
- null == (_this_volume = this.volume) || _this_volume.disconnect();
19416
+ writeStream(buffer, opts) {
19417
+ if (this.stream) if (this.stream.destroyed) {
19418
+ this.stopPings();
19419
+ const err = error_createError({
19420
+ message: "Already disconnected",
19421
+ explanation: "Sorry, connection to the server has been destroyed. Please reload.",
19422
+ options: this.options
19423
+ });
19424
+ this.handleWriteFailure(err);
19425
+ } else if (this.streamBackpressured) {
19426
+ if (this.hasExceededWebSocketSendBuffer(buffer, opts)) this.failMediaTransmission(error_createError({
19427
+ message: "WebSocket send buffer limit exceeded",
19428
+ 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.",
19429
+ options: this.options
19430
+ }));
19431
+ } else this.writeToStream(this.stream, buffer, opts);
19563
19432
  }
19564
- init(localMediaStream) {
19565
- this.options.logger.debug("AudioRecorder: init()");
19566
- this.disconnect();
19567
- const volume = this.getAudioContext().createGain();
19568
- this.volume = volume;
19433
+ writeToStream(stream, buffer, opts) {
19434
+ const onFlushedCallback = null == opts ? void 0 : opts.onFlushedCallback;
19569
19435
  try {
19570
- this.audioInput = this.getAudioContext().createMediaStreamSource(localMediaStream);
19436
+ const canContinue = stream.write(buffer, ()=>{
19437
+ if (!onFlushedCallback) return;
19438
+ try {
19439
+ onFlushedCallback(opts);
19440
+ } catch (exc) {
19441
+ const err = error_createError({
19442
+ message: "Failed to write stream buffer",
19443
+ explanation: `stream.write() failed because of ${string_pretty(exc)}`,
19444
+ options: this.options,
19445
+ exc
19446
+ });
19447
+ this.emit("ERROR", {
19448
+ err
19449
+ });
19450
+ }
19451
+ });
19452
+ if (!canContinue) {
19453
+ this.streamBackpressured = true;
19454
+ this.startBackpressureTimeout();
19455
+ this.pauseForBackpressure();
19456
+ return false;
19457
+ }
19458
+ return true;
19571
19459
  } catch (exc) {
19572
- throw error_createError({
19573
- message: "Webcam has no audio",
19574
- exc,
19575
- options: this.options
19460
+ const err = error_createError({
19461
+ message: "Failed writing to server",
19462
+ explanation: `stream.write() failed because of ${string_pretty(exc)}`,
19463
+ options: this.options,
19464
+ exc
19576
19465
  });
19466
+ this.handleWriteFailure(err);
19467
+ return false;
19577
19468
  }
19578
- let { bufferSize } = this.options.audio;
19579
- if ("auto" === bufferSize) bufferSize = util_getBrowser(this.options).isFirefox() ? 512 : 2048;
19580
- if (void 0 === bufferSize || !is_power_of_two_default()(bufferSize)) throw error_createError({
19581
- message: `Audio buffer size must be a power of two. The current buffer size is ${bufferSize}.`,
19582
- options: this.options
19583
- });
19584
- if (!Number.isFinite(this.options.audio.volume) || this.options.audio.volume < 0 || this.options.audio.volume > 1) throw error_createError({
19585
- message: `Audio volume must be between zero and one. The current volume is ${this.options.audio.volume}.`,
19586
- options: this.options
19587
- });
19588
- volume.gain.value = this.options.audio.volume;
19589
- this.scriptProcessor = this.getAudioContext().createScriptProcessor(bufferSize, CHANNELS, CHANNELS);
19590
- this.audioInput.connect(volume);
19591
- volume.connect(this.scriptProcessor);
19592
- this.scriptProcessor.connect(this.getAudioContext().destination);
19593
19469
  }
19594
- record(cb) {
19595
- this.options.logger.debug("AudioRecorder: record()");
19596
- if (this.scriptProcessor) this.scriptProcessor.onaudioprocess = (e)=>{
19597
- this.onAudioProcess(e, cb);
19598
- };
19470
+ flushPendingWrites(stream) {
19471
+ if (this.isStaleStream(stream)) return;
19472
+ this.streamBackpressured = false;
19473
+ this.clearBackpressureTimeout();
19474
+ let pendingWrite = this.pendingWrites.dequeue();
19475
+ while(pendingWrite){
19476
+ if (!this.writeToStream(stream, pendingWrite.buffer, pendingWrite.params)) return;
19477
+ pendingWrite = this.pendingWrites.dequeue();
19478
+ }
19479
+ this.resumeRecordingAfterDrain();
19599
19480
  }
19600
- resume() {
19601
- return AudioRecorder_async_to_generator(function*() {
19602
- const context = this.getAudioContext();
19603
- if ("running" !== context.state) yield context.resume();
19604
- }).call(this);
19481
+ isStaleStream(stream) {
19482
+ return this.stream !== stream || stream.destroyed;
19605
19483
  }
19606
- stop() {
19607
- this.options.logger.debug("AudioRecorder: stop()");
19608
- this.disconnect();
19609
- if (this.hasAudioContext()) this.getAudioContext().close().then(()=>{
19610
- this.options.logger.debug("AudioRecorder: audio context is closed");
19611
- this.vcAudioContext = void 0;
19612
- }).catch((err)=>{
19613
- if (err instanceof Error) return void this.options.logger.error(error_createError({
19614
- err,
19615
- options: this.options
19616
- }));
19617
- this.options.logger.error(err);
19484
+ canPauseForBackpressure() {
19485
+ return this.recordingActive && !this.isUserMediaPaused() && this.isCaptureLoopRunning();
19486
+ }
19487
+ pauseForBackpressure() {
19488
+ if (!this.canPauseForBackpressure()) return;
19489
+ this.resumeAfterDrain = true;
19490
+ this.stopCaptureLoop();
19491
+ }
19492
+ resumeRecordingAfterDrain() {
19493
+ if (!this.resumeAfterDrain) return;
19494
+ this.resumeAfterDrain = false;
19495
+ if (this.canResumeAfterDrain()) this.startCaptureLoop();
19496
+ }
19497
+ canResumeAfterDrain() {
19498
+ return this.recordingActive && !this.isUserMediaPaused();
19499
+ }
19500
+ handleWriteFailure(err) {
19501
+ if (this.mediaTransmissionStarted) return void this.failMediaTransmission(err);
19502
+ this.emit("ERROR", {
19503
+ err
19618
19504
  });
19619
19505
  }
19620
- getSampleRate() {
19621
- if (this.hasAudioContext()) return this.getAudioContext().sampleRate;
19622
- return -1;
19506
+ failMediaTransmission(err) {
19507
+ if (this.recordingTransportFailed) return;
19508
+ this.recordingTransportFailed = true;
19509
+ this.recordingActive = false;
19510
+ this.mediaTransmissionStarted = false;
19511
+ this.streamBackpressured = false;
19512
+ this.resumeAfterDrain = false;
19513
+ this.pendingWrites.clear();
19514
+ this.connected = false;
19515
+ this.connecting = false;
19516
+ this.reconnecting = false;
19517
+ this.clearReconnectTimeout();
19518
+ this.clearBackpressureTimeout();
19519
+ this.discardSocket();
19520
+ this.reset();
19521
+ this.framesCount = this.samplesCount = 0;
19522
+ this.confirmedFrameNumber = this.confirmedSampleNumber = 0;
19523
+ delete this.recordingStats;
19524
+ this.recordingDimensions = void 0;
19525
+ this.emit("ERROR", {
19526
+ err
19527
+ });
19623
19528
  }
19624
- constructor(userMedia, options){
19625
- AudioRecorder_define_property(this, "scriptProcessor", void 0);
19626
- AudioRecorder_define_property(this, "audioInput", void 0);
19627
- AudioRecorder_define_property(this, "volume", void 0);
19628
- AudioRecorder_define_property(this, "vcAudioContext", void 0);
19629
- AudioRecorder_define_property(this, "userMedia", void 0);
19630
- AudioRecorder_define_property(this, "options", void 0);
19631
- this.options = options;
19632
- this.userMedia = userMedia;
19529
+ startBackpressureTimeout() {
19530
+ if (!this.shouldStartBackpressureTimeout()) return;
19531
+ this.backpressureTimeout = window.setTimeout(()=>{
19532
+ this.backpressureTimeout = void 0;
19533
+ if (this.shouldIgnoreBackpressureTimeout()) return;
19534
+ const err = error_createError({
19535
+ message: "WebSocket write timed out",
19536
+ explanation: "The server stopped accepting recording data. The recording has been stopped; please check your connection and start a new one.",
19537
+ options: this.options
19538
+ });
19539
+ this.failMediaTransmission(err);
19540
+ }, this.options.timeouts.connection);
19633
19541
  }
19634
- }
19635
- const media_AudioRecorder = AudioRecorder;
19636
- const VIRTUAL_KEYWORDS = [
19637
- "obs",
19638
- "virtual",
19639
- "manycam",
19640
- "vcam",
19641
- "splitcam",
19642
- "droidcam"
19643
- ];
19644
- function isVirtualCamera(videoTrack) {
19645
- const capabilities = videoTrack.getCapabilities();
19646
- if (!capabilities.frameRate) return true;
19647
- const isVirtual = VIRTUAL_KEYWORDS.some((keyword)=>videoTrack.label.toLowerCase().includes(keyword));
19648
- return isVirtual;
19649
- }
19650
- const util_isVirtualCamera = isVirtualCamera;
19651
- function userMedia_asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
19652
- try {
19653
- var info = gen[key](arg);
19654
- var value = info.value;
19655
- } catch (error) {
19656
- reject(error);
19657
- return;
19542
+ shouldStartBackpressureTimeout() {
19543
+ return void 0 === this.backpressureTimeout && this.mediaTransmissionStarted;
19658
19544
  }
19659
- if (info.done) resolve(value);
19660
- else Promise.resolve(value).then(_next, _throw);
19661
- }
19662
- function userMedia_async_to_generator(fn) {
19663
- return function() {
19664
- var self1 = this, args = arguments;
19665
- return new Promise(function(resolve, reject) {
19666
- var gen = fn.apply(self1, args);
19667
- function _next(value) {
19668
- userMedia_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
19669
- }
19670
- function _throw(err) {
19671
- userMedia_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
19545
+ shouldIgnoreBackpressureTimeout() {
19546
+ return !this.streamBackpressured || this.recordingTransportFailed;
19547
+ }
19548
+ failInterruptedRecording() {
19549
+ const unconfirmedFrames = Math.max(0, this.framesCount - this.confirmedFrameNumber);
19550
+ const unconfirmedSamples = Math.max(0, this.samplesCount - this.confirmedSampleNumber);
19551
+ const err = error_createError({
19552
+ message: "Recording interrupted",
19553
+ explanation: "The server connection closed before this recording completed. It cannot be safely resumed, so it has been stopped. Please start a new recording.",
19554
+ options: this.options,
19555
+ cause: {
19556
+ unconfirmedFrames,
19557
+ unconfirmedSamples
19672
19558
  }
19673
- _next(void 0);
19674
19559
  });
19675
- };
19560
+ this.failMediaTransmission(err);
19561
+ }
19562
+ handleStreamDrain(stream) {
19563
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *drain* event emitted`);
19564
+ this.flushPendingWrites(stream);
19565
+ }
19566
+ clearBackpressureTimeout() {
19567
+ if (void 0 === this.backpressureTimeout) return;
19568
+ window.clearTimeout(this.backpressureTimeout);
19569
+ this.backpressureTimeout = void 0;
19570
+ }
19571
+ discardSocket() {
19572
+ this.options.logger.debug("Recorder: discarding socket stream ...");
19573
+ this.clearBackpressureTimeout();
19574
+ this.pendingWrites.clear();
19575
+ this.streamBackpressured = false;
19576
+ this.resumeAfterDrain = false;
19577
+ const stream = this.stream;
19578
+ this.stream = void 0;
19579
+ if (stream && !stream.destroyed) stream.destroy();
19580
+ }
19581
+ constructor(options){
19582
+ super("Recorder", options), streamWriter_define_property(this, "pendingWrites", new boundedWriteQueue()), streamWriter_define_property(this, "samplesCount", 0), streamWriter_define_property(this, "framesCount", 0), streamWriter_define_property(this, "recordingStats", void 0), streamWriter_define_property(this, "confirmedFrameNumber", 0), streamWriter_define_property(this, "confirmedSampleNumber", 0), streamWriter_define_property(this, "backpressureTimeout", void 0), streamWriter_define_property(this, "recordingDimensions", void 0), streamWriter_define_property(this, "stream", void 0), streamWriter_define_property(this, "streamBackpressured", false), streamWriter_define_property(this, "resumeAfterDrain", false), streamWriter_define_property(this, "mediaTransmissionStarted", false), streamWriter_define_property(this, "recordingActive", false), streamWriter_define_property(this, "recordingTransportFailed", false), streamWriter_define_property(this, "connecting", false), streamWriter_define_property(this, "connected", false), streamWriter_define_property(this, "reconnecting", false);
19583
+ }
19676
19584
  }
19677
- function userMedia_define_property(obj, key, value) {
19585
+ const streamWriter = RecorderStreamWriter;
19586
+ var Buffer = __webpack_require__("dY").Buffer;
19587
+ function connection_define_property(obj, key, value) {
19678
19588
  if (key in obj) Object.defineProperty(obj, key, {
19679
19589
  value: value,
19680
19590
  enumerable: true,
@@ -19684,368 +19594,403 @@ var __webpack_exports__ = {};
19684
19594
  else obj[key] = value;
19685
19595
  return obj;
19686
19596
  }
19687
- const EVENT_ASCII = "|—O—|";
19688
- class UserMedia extends util_Despot {
19689
- attachMediaStream(stream) {
19690
- if (this.rawVisualUserMedia) {
19691
- this.rawVisualUserMedia.srcObject = stream;
19692
- const previousStream = this.currentVisualStream;
19693
- this.currentVisualStream = stream;
19694
- if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
19695
- track.stop();
19696
- });
19697
- } else throw error_createError({
19698
- message: "Error attaching stream to element.",
19699
- explanation: "Contact the developer about this",
19700
- options: this.options
19701
- });
19702
- }
19703
- setVisualStream(localMediaStream) {
19704
- if (localMediaStream) this.attachMediaStream(localMediaStream);
19705
- else {
19706
- if (this.rawVisualUserMedia) {
19707
- this.rawVisualUserMedia.srcObject = null;
19708
- this.rawVisualUserMedia.removeAttribute("src");
19709
- }
19710
- this.currentVisualStream = void 0;
19711
- }
19597
+ class RecorderConnection extends streamWriter {
19598
+ canReconnectAfterStreamClose() {
19599
+ return Boolean(this.connected && this.userMediaLoaded);
19712
19600
  }
19713
- restorePlayback() {
19714
- return userMedia_async_to_generator(function*() {
19715
- var _this_audioRecorder;
19716
- if (!this.rawVisualUserMedia) throw new Error("No camera preview is available.");
19717
- yield this.rawVisualUserMedia.play();
19718
- yield null == (_this_audioRecorder = this.audioRecorder) ? void 0 : _this_audioRecorder.resume();
19719
- }).call(this);
19601
+ shouldReportStreamClose() {
19602
+ return !this.connecting && !this.blocking;
19720
19603
  }
19721
- hasLiveStream() {
19722
- var _this_currentVisualStream, _this_currentVisualStream1, _this_currentVisualStream2;
19723
- const videoReady = null == (_this_currentVisualStream = this.currentVisualStream) ? void 0 : _this_currentVisualStream.getVideoTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
19724
- const audioReady = !isAudioEnabled(this.options) || (null == (_this_currentVisualStream1 = this.currentVisualStream) ? void 0 : _this_currentVisualStream1.getAudioTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted));
19725
- return Boolean((null == (_this_currentVisualStream2 = this.currentVisualStream) ? void 0 : _this_currentVisualStream2.active) && videoReady && audioReady);
19604
+ handleStreamClose(url2Connect) {
19605
+ const debugLine = string_summarize(`${PIPE_SYMBOL}Stream has closed:`, {
19606
+ connecting: this.connecting,
19607
+ connected: this.connected,
19608
+ userMediaLoaded: this.userMediaLoaded,
19609
+ blocking: this.blocking
19610
+ });
19611
+ this.options.logger.debug(debugLine);
19612
+ const canReconnect = this.canReconnectAfterStreamClose();
19613
+ this.connected = false;
19614
+ this.stopPings();
19615
+ if (this.shouldIgnoreStreamClose()) return;
19616
+ if (this.mediaTransmissionStarted) this.failInterruptedRecording();
19617
+ else if (canReconnect) {
19618
+ this.pendingWrites.clear();
19619
+ this.streamBackpressured = false;
19620
+ this.resumeAfterDrain = false;
19621
+ this.reconnecting = true;
19622
+ this.initSocket();
19623
+ } else if (this.shouldReportStreamClose()) window.setTimeout(()=>{
19624
+ this.handleConnectionFailure({
19625
+ url2Connect,
19626
+ cause: "closed"
19627
+ });
19628
+ }, 0);
19726
19629
  }
19727
- hasEnded() {
19728
- var _this_rawVisualUserMedia, _this_currentVisualStream;
19729
- if (null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.ended) return this.rawVisualUserMedia.ended;
19730
- return !(null == (_this_currentVisualStream = this.currentVisualStream) ? void 0 : _this_currentVisualStream.active);
19630
+ shouldIgnoreStreamClose() {
19631
+ return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
19731
19632
  }
19732
- hasInvalidDimensions() {
19733
- if (!this.rawVisualUserMedia) return false;
19734
- const { videoWidth, videoHeight } = this.rawVisualUserMedia;
19735
- return videoWidth > 0 && videoWidth < 3 || videoHeight > 0 && videoHeight < 3;
19633
+ handleStreamError(url2Connect, err) {
19634
+ var _this_lastSocketError;
19635
+ if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
19636
+ else this.lastSocketError = serializeError(err);
19637
+ const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
19638
+ error: serializeError(err)
19639
+ });
19640
+ this.options.logger.debug(debugLine);
19641
+ if (this.validate()) {
19642
+ this.connected = false;
19643
+ this.stopPings();
19644
+ this.discardSocket();
19645
+ return;
19646
+ }
19647
+ if (!this.connected) return void this.handleConnectionFailure({
19648
+ url2Connect,
19649
+ cause: "error"
19650
+ });
19651
+ const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
19652
+ cause: null != (_this_lastSocketError = this.lastSocketError) ? _this_lastSocketError : err
19653
+ });
19654
+ if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
19655
+ this.emit("ERROR", {
19656
+ err: error_createError({
19657
+ message: "WebSocket stream error",
19658
+ explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
19659
+ options: this.options,
19660
+ exc: streamError
19661
+ })
19662
+ });
19736
19663
  }
19737
- logEvent(eventType, params) {
19738
- this.options.logger.debug(`UserMedia: ... ${EVENT_ASCII} event ${eventType}: ${string_pretty(params)}`);
19664
+ sendPings() {
19665
+ this.stopPings();
19666
+ this.pingInterval = window.setInterval(()=>{
19667
+ this.options.logger.debug("Recorder: pinging...");
19668
+ this.writeStream(Buffer.from(""));
19669
+ }, this.options.timeouts.pingInterval);
19739
19670
  }
19740
- unloadRemainingEventListeners() {
19741
- var _this_clearInitializationListeners, _this, _this_rawVisualUserMedia;
19742
- this.options.logger.debug("UserMedia: unloadRemainingEventListeners()");
19743
- null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
19744
- mediaEvents.forEach((eventName)=>{
19745
- var _this_rawVisualUserMedia;
19746
- null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener(eventName, this.outputEvent);
19747
- });
19748
- null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener("error", this.handleMediaError);
19671
+ stopPings() {
19672
+ if (void 0 === this.pingInterval) return;
19673
+ this.options.logger.debug("Recorder: stopping pings...");
19674
+ window.clearInterval(this.pingInterval);
19675
+ this.pingInterval = void 0;
19749
19676
  }
19750
- audioRecord(audioCallback) {
19751
- var _this_audioRecorder;
19752
- this.removeListener("SENDING_FIRST_FRAME");
19753
- null == (_this_audioRecorder = this.audioRecorder) || _this_audioRecorder.record(audioCallback);
19677
+ clearConnectionTimeout() {
19678
+ if (void 0 === this.connectionTimeout) return;
19679
+ this.options.logger.debug("Recorder: clearConnectionTimeout()");
19680
+ window.clearTimeout(this.connectionTimeout);
19681
+ this.connectionTimeout = void 0;
19754
19682
  }
19755
- init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, params) {
19756
- var _this_clearInitializationListeners, _this;
19757
- null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
19758
- this.stop(localMediaStream, {
19759
- aboutToInitialize: true,
19760
- preserveRecordingState: Boolean((null == params ? void 0 : params.switchingFacingMode) || (null == params ? void 0 : params.recovering))
19761
- });
19762
- const initializationId = ++this.initializationId;
19763
- this.onPlayReached = false;
19764
- this.onLoadedMetaDataReached = false;
19765
- this.playingPromiseReached = false;
19766
- this.videoTrackLabel = void 0;
19767
- if (isAudioEnabled(this.options)) null != this.audioRecorder || (this.audioRecorder = new media_AudioRecorder(this, this.options));
19768
- const unloadAllEventListeners = ()=>{
19769
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
19770
- this.options.logger.debug("UserMedia: unloadAllEventListeners()");
19771
- this.unloadRemainingEventListeners();
19772
- this.removeListener("SENDING_FIRST_FRAME");
19773
- null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener("play", onPlay);
19774
- null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.removeEventListener("loadedmetadata", onLoadedMetaData);
19775
- };
19776
- this.clearInitializationListeners = ()=>{
19777
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
19778
- null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener("play", onPlay);
19779
- null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.removeEventListener("loadedmetadata", onLoadedMetaData);
19780
- this.clearInitializationListeners = void 0;
19781
- };
19782
- const play = ()=>{
19783
- try {
19784
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
19785
- null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.load();
19786
- if (null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.paused) {
19787
- 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)}`);
19788
- this.rawVisualUserMedia.play().then(()=>{
19789
- if (initializationId !== this.initializationId) return;
19790
- if (!this.playingPromiseReached) {
19791
- this.options.logger.debug("UserMedia: play promise successful. Playing now.");
19792
- this.playingPromiseReached = true;
19793
- }
19794
- }).catch((exc)=>{
19795
- if (initializationId !== this.initializationId) return;
19796
- if (exc instanceof Error && !(null == params ? void 0 : params.recovering)) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
19797
- else {
19798
- unloadAllEventListeners();
19799
- endedEarlyCallback(error_createError({
19800
- message: "Failed to play user media upon play event.",
19801
- exc,
19802
- options: this.options
19803
- }));
19804
- }
19805
- });
19806
- }
19807
- } catch (exc) {
19808
- unloadAllEventListeners();
19809
- endedEarlyCallback(exc);
19810
- }
19811
- };
19812
- const fireCallbacks = ()=>{
19813
- var _this_rawVisualUserMedia;
19814
- const readyState = null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState;
19815
- this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
19816
- if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) try {
19817
- this.audioRecorder.init(localMediaStream);
19818
- const onAudioReady = ()=>{
19819
- var _this_clearInitializationListeners, _this;
19820
- if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
19821
- if (this.recording) this.audioRecord(audioCallback);
19822
- else this.on("SENDING_FIRST_FRAME", ()=>{
19823
- this.audioRecord(audioCallback);
19824
- });
19825
- null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
19826
- videoCallback();
19827
- };
19828
- if (null == params ? void 0 : params.recovering) this.audioRecorder.resume().then(onAudioReady).catch(endedEarlyCallback);
19829
- else onAudioReady();
19830
- } catch (exc) {
19831
- unloadAllEventListeners();
19832
- this.stop(void 0, {
19833
- preserveRecordingState: Boolean(null == params ? void 0 : params.recovering)
19834
- });
19835
- endedEarlyCallback(exc);
19836
- }
19837
- else {
19838
- var _this_clearInitializationListeners, _this;
19839
- null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
19840
- videoCallback();
19841
- }
19842
- };
19843
- const onPlay = ()=>{
19844
- try {
19845
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3, _this_rawVisualUserMedia4, _this_rawVisualUserMedia5;
19846
- this.logEvent("play", {
19847
- readyState: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState,
19848
- audio: isAudioEnabled(this.options),
19849
- width: null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.width,
19850
- height: null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia2.height,
19851
- videoWidth: null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia3.videoWidth,
19852
- videoHeight: null == (_this_rawVisualUserMedia4 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia4.videoHeight
19853
- });
19854
- null == (_this_rawVisualUserMedia5 = this.rawVisualUserMedia) || _this_rawVisualUserMedia5.removeEventListener("play", onPlay);
19855
- if (this.hasEnded() || this.hasInvalidDimensions()) endedEarlyCallback(error_createError({
19856
- message: "Already busy",
19857
- explanation: "Probably another browser window is using your webcam?",
19858
- options: this.options
19859
- }));
19860
- else {
19861
- this.onPlayReached = true;
19862
- fireCallbacks();
19863
- }
19864
- } catch (exc) {
19865
- unloadAllEventListeners();
19866
- endedEarlyCallback(exc);
19867
- }
19683
+ clearReconnectTimeout() {
19684
+ if (void 0 === this.reconnectTimeout) return;
19685
+ this.options.logger.debug("Recorder: clearReconnectTimeout()");
19686
+ window.clearTimeout(this.reconnectTimeout);
19687
+ this.reconnectTimeout = void 0;
19688
+ }
19689
+ handleConnectionFailure(params) {
19690
+ if (this.retryConnection(params)) return;
19691
+ this.failConnection(params);
19692
+ }
19693
+ retryConnection(params) {
19694
+ if (!this.canRetryConnection()) return false;
19695
+ if (void 0 !== this.reconnectTimeout) return true;
19696
+ const delayMs = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000);
19697
+ this.reconnectAttempts++;
19698
+ this.connecting = false;
19699
+ this.clearConnectionTimeout();
19700
+ this.discardSocket();
19701
+ this.options.logger.debug(`Recorder: retrying WebSocket connection in ${delayMs}ms (cause ${params.cause}).`);
19702
+ this.reconnectTimeout = window.setTimeout(()=>{
19703
+ this.reconnectTimeout = void 0;
19704
+ if (this.shouldContinueReconnect()) this.initSocket();
19705
+ }, delayMs);
19706
+ return true;
19707
+ }
19708
+ canRetryConnection() {
19709
+ return Boolean(this.reconnecting && this.userMediaLoaded && this.isOnline() && !this.unloaded && !this.blocking);
19710
+ }
19711
+ shouldContinueReconnect() {
19712
+ return this.reconnecting && !this.unloaded && !this.blocking;
19713
+ }
19714
+ isOnline() {
19715
+ return navigator.onLine;
19716
+ }
19717
+ failConnection(params) {
19718
+ var _this_stream;
19719
+ if (this.shouldIgnoreConnectionFailure()) return;
19720
+ this.connectionFailed = true;
19721
+ this.connecting = false;
19722
+ this.reconnecting = false;
19723
+ this.reconnectAttempts = 0;
19724
+ this.clearReconnectTimeout();
19725
+ this.clearConnectionTimeout();
19726
+ const { url2Connect, cause } = params;
19727
+ const online = this.isOnline();
19728
+ const elapsedMs = this.connectingStartedAt ? Date.now() - this.connectingStartedAt : void 0;
19729
+ const closeEvent = this.lastCloseEvent;
19730
+ const socketError = this.lastSocketError;
19731
+ const contents = {
19732
+ cause,
19733
+ closeCode: null == closeEvent ? void 0 : closeEvent.code,
19734
+ closeReason: null == closeEvent ? void 0 : closeEvent.reason,
19735
+ elapsedMs,
19736
+ online,
19737
+ secureContext: globalThis.isSecureContext,
19738
+ socketReadyState: null == (_this_stream = this.stream) ? void 0 : _this_stream.socket.readyState,
19739
+ timeoutMs: this.options.timeouts.connection,
19740
+ url: url2Connect,
19741
+ unloaded: this.unloaded,
19742
+ userMediaLoaded: this.userMediaLoaded,
19743
+ userMediaLoading: this.userMediaLoading,
19744
+ wasClean: null == closeEvent ? void 0 : closeEvent.wasClean,
19745
+ socketError,
19746
+ blocking: this.blocking,
19747
+ visibilityState: document.visibilityState
19868
19748
  };
19869
- const onLoadedMetaData = ()=>{
19870
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3, _this_rawVisualUserMedia4, _this_rawVisualUserMedia5, _this_rawVisualUserMedia6;
19871
- this.logEvent("loadedmetadata", {
19872
- readyState: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState,
19873
- paused: null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.paused,
19874
- width: null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia2.width,
19875
- height: null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia3.height,
19876
- videoWidth: null == (_this_rawVisualUserMedia4 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia4.videoWidth,
19877
- videoHeight: null == (_this_rawVisualUserMedia5 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia5.videoHeight
19749
+ const debugLine = string_summarize("Recorder: failConnection() diagnostic", contents);
19750
+ this.options.logger.debug(debugLine);
19751
+ let explanation;
19752
+ 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.`;
19753
+ 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.`;
19754
+ else {
19755
+ const closeSuffix = closeEvent ? ` (code ${closeEvent.code})` : "";
19756
+ explanation = `Connection to ${url2Connect} is closed${closeSuffix}. Please check your internet connection and try again. If the problem persists, contact us.`;
19757
+ }
19758
+ else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
19759
+ this.discardSocket();
19760
+ const err = error_createError({
19761
+ message: "Unable to connect to the server",
19762
+ explanation,
19763
+ options: this.options,
19764
+ cause: contents,
19765
+ exc: socketError ? new Error("WebSocket connection error", {
19766
+ cause: socketError
19767
+ }) : void 0
19768
+ });
19769
+ this.emit("ERROR", {
19770
+ err
19771
+ });
19772
+ }
19773
+ shouldIgnoreConnectionFailure() {
19774
+ return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
19775
+ }
19776
+ initSocket(cb) {
19777
+ if (this.connected) {
19778
+ this.options.logger.debug("Recorder: already **connected**. Not going to initialize a new WebSocket connection.");
19779
+ null == cb || cb();
19780
+ return;
19781
+ }
19782
+ if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
19783
+ this.clearReconnectTimeout();
19784
+ if (util_isAutomatedUserAgent()) {
19785
+ this.connecting = false;
19786
+ this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
19787
+ return;
19788
+ }
19789
+ this.connecting = true;
19790
+ this.connectionFailed = false;
19791
+ this.connectingStartedAt = Date.now();
19792
+ this.lastCloseEvent = void 0;
19793
+ this.lastSocketError = void 0;
19794
+ this.emit("CONNECTING");
19795
+ let url2Connect;
19796
+ try {
19797
+ const socketUrlObj = new URL(this.options.socketUrl);
19798
+ socketUrlObj.searchParams.set(constants.WHITELIST_KEY_LABEL, this.options.whitelistKey);
19799
+ url2Connect = socketUrlObj.toString();
19800
+ } catch (exc) {
19801
+ this.connecting = this.connected = false;
19802
+ const err = error_createError({
19803
+ message: "Invalid WebSocket URL",
19804
+ explanation: `The configured socketUrl "${this.options.socketUrl}" is not a valid URL. Please check your videomail-client configuration.`,
19805
+ options: this.options,
19806
+ exc
19878
19807
  });
19879
- null == (_this_rawVisualUserMedia6 = this.rawVisualUserMedia) || _this_rawVisualUserMedia6.removeEventListener("loadedmetadata", onLoadedMetaData);
19880
- if (!this.hasEnded() && !this.hasInvalidDimensions()) {
19881
- this.emit("LOADED_META_DATA");
19882
- this.onLoadedMetaDataReached = true;
19883
- fireCallbacks();
19884
- }
19885
- };
19808
+ this.emit("ERROR", {
19809
+ err
19810
+ });
19811
+ return;
19812
+ }
19813
+ this.options.logger.debug(`Recorder: initializing web socket stream to ${url2Connect}`);
19814
+ let nativeSocket;
19886
19815
  try {
19887
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3;
19888
- const videoTrack = media_getFirstVideoTrack(localMediaStream);
19889
- this.videoTrackLabel = null == videoTrack ? void 0 : videoTrack.label;
19890
- if (videoTrack) if (videoTrack.enabled) if (util_isVirtualCamera(videoTrack)) throw error_createError({
19891
- message: "Virtual cameras are not allowed.",
19892
- explanation: `Please use a real camera, not "${videoTrack.label}".`,
19893
- options: this.options
19816
+ nativeSocket = new WebSocket(url2Connect);
19817
+ nativeSocket.addEventListener("close", (event)=>{
19818
+ this.lastCloseEvent = {
19819
+ code: event.code,
19820
+ reason: event.reason,
19821
+ wasClean: event.wasClean
19822
+ };
19894
19823
  });
19895
- else {
19896
- let description = "";
19897
- if (videoTrack.label.length > 0) description = description.concat(videoTrack.label);
19898
- description = description.concat(` with enabled=${videoTrack.enabled}, muted=${videoTrack.muted}, readyState=${videoTrack.readyState}`);
19899
- this.options.logger.debug(`UserMedia: ${videoTrack.kind} detected. ${description}`);
19900
- }
19901
- else throw error_createError({
19902
- message: "Webcam is disabled",
19903
- explanation: "The video track seems to be disabled. Enable it in your system.",
19904
- options: this.options
19824
+ nativeSocket.addEventListener("error", (event)=>{
19825
+ this.lastSocketError = error_getEventDetails(event);
19905
19826
  });
19906
- else this.options.logger.debug("UserMedia: detected (but no video tracks exist");
19907
- null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.addEventListener("loadedmetadata", onLoadedMetaData);
19908
- null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.addEventListener("play", onPlay);
19909
- null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) || _this_rawVisualUserMedia2.removeEventListener("error", this.handleMediaError);
19910
- null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) || _this_rawVisualUserMedia3.addEventListener("error", this.handleMediaError);
19911
- this.setVisualStream(localMediaStream);
19912
- play();
19913
19827
  } catch (exc) {
19914
- unloadAllEventListeners();
19915
- endedEarlyCallback(exc);
19828
+ this.connecting = this.connected = false;
19829
+ const diagnostic = error_getWebSocketDiagnostic(url2Connect);
19830
+ const message = diagnostic.looksAutomated ? "Automated crawler: WebSocket not supported in this environment" : `Failed to construct WebSocket to ${url2Connect}`;
19831
+ 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}`;
19832
+ const err = error_createError({
19833
+ message,
19834
+ explanation,
19835
+ options: this.options,
19836
+ exc
19837
+ });
19838
+ this.emit("ERROR", {
19839
+ err
19840
+ });
19841
+ return;
19916
19842
  }
19917
- }
19918
- getVideoTrackLabel() {
19919
- return this.videoTrackLabel;
19920
- }
19921
- isReady() {
19922
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
19923
- return Boolean((null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.srcObject) || (null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.currentSrc));
19924
- }
19925
- stop(visualStream, params) {
19926
19843
  try {
19927
- let chosenStream = visualStream;
19928
- if (!(null == params ? void 0 : params.aboutToInitialize)) {
19929
- var _this_clearInitializationListeners, _this, _this_audioRecorder;
19930
- this.initializationId++;
19931
- null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
19932
- null != chosenStream || (chosenStream = this.currentVisualStream);
19933
- const tracks = null == chosenStream ? void 0 : chosenStream.getTracks();
19934
- if (tracks) tracks.forEach((track)=>{
19935
- track.stop();
19936
- });
19937
- this.setVisualStream();
19938
- null == (_this_audioRecorder = this.audioRecorder) || _this_audioRecorder.stop();
19939
- this.audioRecorder = void 0;
19940
- }
19941
- if (!(null == params ? void 0 : params.preserveRecordingState)) this.paused = this.recording = false;
19844
+ this.stream = stream_default()(nativeSocket);
19942
19845
  } catch (exc) {
19943
- this.emit("ERROR", {
19846
+ this.connecting = this.connected = false;
19847
+ const err = error_createError({
19848
+ message: `Failed to create a stream to ${url2Connect}`,
19849
+ explanation: "Please check your connection and try again. If the problem persists, contact us.",
19850
+ options: this.options,
19944
19851
  exc
19945
19852
  });
19853
+ this.emit("ERROR", {
19854
+ err
19855
+ });
19856
+ }
19857
+ const stream = this.stream;
19858
+ if (stream) {
19859
+ const connectionTimeoutMs = this.options.timeouts.connection;
19860
+ this.connectionTimeout = window.setTimeout(()=>{
19861
+ if (this.isOnline() && this.isUserMediaLoaded()) {
19862
+ this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
19863
+ this.reconnecting = true;
19864
+ this.handleConnectionFailure({
19865
+ url2Connect,
19866
+ cause: "timeout"
19867
+ });
19868
+ } else this.handleConnectionFailure({
19869
+ url2Connect,
19870
+ cause: "timeout"
19871
+ });
19872
+ }, connectionTimeoutMs);
19873
+ stream.on("close", ()=>{
19874
+ this.handleStreamClose(url2Connect);
19875
+ });
19876
+ stream.on("connect", (args)=>{
19877
+ var _this_stream;
19878
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *connect* event emitted with args: ${string_pretty(args)}`);
19879
+ this.clearConnectionTimeout();
19880
+ const isClosing = (null == (_this_stream = this.stream) ? void 0 : _this_stream.socket.readyState) === WebSocket.CLOSING;
19881
+ if (!this.connected && !isClosing && !this.unloaded) {
19882
+ this.connected = true;
19883
+ this.connecting = this.unloaded = false;
19884
+ this.reconnecting = false;
19885
+ this.reconnectAttempts = 0;
19886
+ this.clearReconnectTimeout();
19887
+ this.emit("CONNECTED");
19888
+ null == cb || cb();
19889
+ }
19890
+ });
19891
+ stream.on("data", (data)=>{
19892
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *data* event emitted`);
19893
+ try {
19894
+ const command = JSON.parse(data.toString());
19895
+ this.executeCommand(command);
19896
+ } catch (exc) {
19897
+ this.options.logger.error(`Failed to parse command: ${exc}`);
19898
+ const err = error_createError({
19899
+ message: "Invalid server command",
19900
+ explanation: `Contact us. The invalid command was: ${data.toString()}.`,
19901
+ options: this.options,
19902
+ exc
19903
+ });
19904
+ this.emit("ERROR", {
19905
+ err
19906
+ });
19907
+ }
19908
+ });
19909
+ stream.on("error", (err)=>{
19910
+ this.handleStreamError(url2Connect, err);
19911
+ });
19912
+ stream.on("drain", ()=>{
19913
+ this.handleStreamDrain(stream);
19914
+ });
19915
+ stream.on("preend", (args)=>{
19916
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *preend* event emitted with args: ${string_pretty(args)}`);
19917
+ });
19918
+ stream.on("end", ()=>{
19919
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *end* event emitted`);
19920
+ });
19921
+ stream.on("pipe", (src)=>{
19922
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *pipe* event emitted with src: ${string_pretty(src)}`);
19923
+ });
19924
+ stream.on("unpipe", (src)=>{
19925
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *unpipe* event emitted with src: ${string_pretty(src)}`);
19926
+ });
19927
+ stream.on("resume", (args)=>{
19928
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *resume* event emitted with args: ${string_pretty(args)}`);
19929
+ });
19930
+ stream.on("uncork", (args)=>{
19931
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *uncork* event emitted with args: ${string_pretty(args)}`);
19932
+ });
19933
+ stream.on("readable", (args)=>{
19934
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *readable* event emitted with args: ${string_pretty(args)}`);
19935
+ });
19936
+ stream.on("prefinish", (args)=>{
19937
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *prefinish* event emitted with args: ${string_pretty(args)}`);
19938
+ });
19939
+ stream.on("finish", (args)=>{
19940
+ this.options.logger.debug(`${PIPE_SYMBOL}Stream *finish* event emitted with args: ${string_pretty(args)}`);
19941
+ });
19946
19942
  }
19947
19943
  }
19948
- createCanvas() {
19949
- const canvas = document.createElement("canvas");
19950
- const rawWidthDimension = this.getRawWidth(true);
19951
- if (rawWidthDimension.value) canvas.width = rawWidthDimension.value;
19952
- const rawHeight = this.getRawHeight(true);
19953
- if (rawHeight) canvas.height = rawHeight;
19954
- return canvas;
19955
- }
19956
- getVideoHeight() {
19957
- if (!this.rawVisualUserMedia) return;
19958
- return this.rawVisualUserMedia.videoHeight || this.rawVisualUserMedia.height;
19959
- }
19960
- getVideoWidth() {
19961
- if (!this.rawVisualUserMedia) return;
19962
- return this.rawVisualUserMedia.videoWidth || this.rawVisualUserMedia.width;
19963
- }
19964
- hasVideoWidth() {
19965
- const videoWidth = this.getVideoWidth();
19966
- return videoWidth && videoWidth > 0;
19967
- }
19968
- getRawWidth(responsive) {
19969
- let rawWidth;
19970
- const widthDimension = {
19971
- unit: "px"
19972
- };
19973
- rawWidth = responsive ? this.getVideoWidth() : this.options.video.width ? this.options.video.width : this.getVideoWidth();
19974
- if (responsive) {
19975
- const widthDimension = this.recorder.limitWidth(rawWidth);
19976
- rawWidth = null == widthDimension ? void 0 : widthDimension.value;
19977
- }
19978
- widthDimension.value = rawWidth;
19979
- return widthDimension;
19980
- }
19981
- getRawHeight(responsive) {
19982
- let rawHeight;
19983
- if (responsive) {
19984
- rawHeight = this.options.video.height || this.getVideoHeight();
19985
- const ratio = this.recorder.getRatio();
19986
- const rawWidth = this.getRawWidth(responsive);
19987
- if (void 0 !== ratio && rawWidth.value) rawHeight = rawWidth.value * ratio;
19988
- } else rawHeight = this.options.video.height ? this.options.video.height : this.getVideoHeight();
19989
- if (responsive) {
19990
- const heightDimension = this.recorder.limitHeight(rawHeight);
19991
- rawHeight = heightDimension.value;
19992
- }
19993
- return rawHeight;
19994
- }
19995
- getRawVisuals() {
19996
- return this.rawVisualUserMedia;
19997
- }
19998
- pause() {
19999
- this.paused = true;
20000
- }
20001
- isPaused() {
20002
- return this.paused;
20003
- }
20004
- resume() {
20005
- this.paused = false;
20006
- }
20007
- record() {
20008
- this.recording = true;
20009
- }
20010
- isRecording() {
20011
- return this.recording;
20012
- }
20013
- getAudioSampleRate() {
20014
- if (this.audioRecorder) return this.audioRecorder.getSampleRate();
20015
- return -1;
20016
- }
20017
- getCharacteristics() {
20018
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3, _this_rawVisualUserMedia4;
20019
- return {
20020
- audioSampleRate: this.getAudioSampleRate(),
20021
- muted: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.muted,
20022
- width: null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.width,
20023
- height: null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia2.height,
20024
- videoWidth: null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia3.videoWidth,
20025
- videoHeight: null == (_this_rawVisualUserMedia4 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia4.videoHeight
20026
- };
20027
- }
20028
- constructor(recorder, options){
20029
- super("UserMedia", options), userMedia_define_property(this, "recorder", void 0), userMedia_define_property(this, "rawVisualUserMedia", void 0), userMedia_define_property(this, "paused", false), userMedia_define_property(this, "recording", false), userMedia_define_property(this, "audioRecorder", void 0), userMedia_define_property(this, "currentVisualStream", void 0), userMedia_define_property(this, "onPlayReached", false), userMedia_define_property(this, "onLoadedMetaDataReached", false), userMedia_define_property(this, "playingPromiseReached", false), userMedia_define_property(this, "videoTrackLabel", void 0), userMedia_define_property(this, "clearInitializationListeners", void 0), userMedia_define_property(this, "initializationId", 0), userMedia_define_property(this, "outputEvent", (e)=>{
20030
- var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
20031
- this.logEvent(e.type, {
20032
- readyState: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState
19944
+ writeCommand(command, args, cb) {
19945
+ if (this.connected) {
19946
+ if (this.stream) {
19947
+ if (args) this.options.logger.debug(`$ ${command} with ${string_pretty(args)}`);
19948
+ else this.options.logger.debug(`$ ${command}`);
19949
+ const commandObj = {
19950
+ command,
19951
+ args
19952
+ };
19953
+ this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
19954
+ onFlushedCallback: ()=>{
19955
+ queueMicrotask(cb);
19956
+ }
19957
+ } : void 0);
19958
+ }
19959
+ } else {
19960
+ this.options.logger.debug(`Reconnecting for the command ${command} …`);
19961
+ this.initSocket(()=>{
19962
+ this.writeCommand(command, args, cb);
20033
19963
  });
20034
- null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.removeEventListener(e.type, this.outputEvent);
20035
- }), userMedia_define_property(this, "handleMediaError", (event)=>{
20036
- this.options.logger.warn(`Caught video element error event: ${string_pretty(event)}`);
20037
- });
20038
- this.recorder = recorder;
20039
- this.rawVisualUserMedia = recorder.getRawVisualUserMedia();
20040
- mediaEvents.forEach((eventName)=>{
20041
- var _this_rawVisualUserMedia;
20042
- null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.addEventListener(eventName, this.outputEvent, false);
20043
- });
19964
+ }
19965
+ }
19966
+ constructor(options){
19967
+ super(options), connection_define_property(this, "reconnectTimeout", void 0), connection_define_property(this, "connectionTimeout", void 0), connection_define_property(this, "userMediaLoaded", void 0), connection_define_property(this, "userMediaLoading", false), connection_define_property(this, "unloaded", void 0), connection_define_property(this, "connectionFailed", false), connection_define_property(this, "reconnectAttempts", 0), connection_define_property(this, "blocking", false), connection_define_property(this, "connectingStartedAt", void 0), connection_define_property(this, "lastCloseEvent", void 0), connection_define_property(this, "lastSocketError", void 0), connection_define_property(this, "pingInterval", void 0);
20044
19968
  }
20045
19969
  }
20046
- const visuals_userMedia = UserMedia;
20047
- var Buffer = __webpack_require__("dY").Buffer;
20048
- function recorder_define_property(obj, key, value) {
19970
+ const connection = RecorderConnection;
19971
+ function createUserMediaConstraints({ options, facingMode, limitedWidth, userMediaParams }) {
19972
+ const video = {
19973
+ frameRate: {
19974
+ ideal: options.video.fps
19975
+ }
19976
+ };
19977
+ if ((null == userMediaParams ? void 0 : userMediaParams.switchingFacingMode) || (null == userMediaParams ? void 0 : userMediaParams.recovering)) video.facingMode = userMediaParams.switchingFacingMode || facingMode;
19978
+ if (options.video.width) video.width = {
19979
+ ideal: options.video.width
19980
+ };
19981
+ else if (null == limitedWidth ? void 0 : limitedWidth.value) video.width = {
19982
+ ideal: limitedWidth.value
19983
+ };
19984
+ if (options.video.height) video.height = {
19985
+ ideal: options.video.height
19986
+ };
19987
+ return {
19988
+ audio: isAudioEnabled(options),
19989
+ video
19990
+ };
19991
+ }
19992
+ const mediaConstraints = createUserMediaConstraints;
19993
+ function media_define_property(obj, key, value) {
20049
19994
  if (key in obj) Object.defineProperty(obj, key, {
20050
19995
  value: value,
20051
19996
  enumerable: true,
@@ -20055,258 +20000,310 @@ var __webpack_exports__ = {};
20055
20000
  else obj[key] = value;
20056
20001
  return obj;
20057
20002
  }
20058
- function recorder_object_spread(target) {
20059
- for(var i = 1; i < arguments.length; i++){
20060
- var source = null != arguments[i] ? arguments[i] : {};
20061
- var ownKeys = Object.keys(source);
20062
- if ("function" == typeof Object.getOwnPropertySymbols) ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
20063
- return Object.getOwnPropertyDescriptor(source, sym).enumerable;
20064
- }));
20065
- ownKeys.forEach(function(key) {
20066
- recorder_define_property(target, key, source[key]);
20067
- });
20003
+ class RecorderMedia extends connection {
20004
+ show() {
20005
+ html_showElement(this.recorderElement);
20068
20006
  }
20069
- return target;
20070
- }
20071
- const PIPE_SYMBOL = "°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸ ";
20072
- class Recorder extends util_Despot {
20073
- hasExceededWebSocketSendBuffer(buffer, opts) {
20074
- return buffer.length > 0 && !this.pendingWrites.enqueue(buffer, opts);
20007
+ clearRetryTimeout() {
20008
+ if (void 0 === this.retryTimeout) return;
20009
+ this.options.logger.debug("Recorder: clearRetryTimeout()");
20010
+ window.clearTimeout(this.retryTimeout);
20011
+ this.retryTimeout = void 0;
20075
20012
  }
20076
- writeStream(buffer, opts) {
20077
- if (this.stream) if (this.stream.destroyed) {
20078
- this.stopPings();
20079
- const err = error_createError({
20080
- message: "Already disconnected",
20081
- explanation: "Sorry, connection to the server has been destroyed. Please reload.",
20082
- options: this.options
20013
+ showUserMedia() {
20014
+ if (!this.connected) return false;
20015
+ const hidden = this.isHidden();
20016
+ if (!hidden) return true;
20017
+ const notifying = this.isNotifying();
20018
+ if (notifying) return true;
20019
+ return this.blocking;
20020
+ }
20021
+ userMediaErrorCallback(err, usedConstraints) {
20022
+ var _this_userMedia;
20023
+ if (this.cameraRecovery) return void this.failCameraRecovery(err);
20024
+ if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${string_pretty(err)}`);
20025
+ this.userMediaLoading = false;
20026
+ this.clearUserMediaTimeout();
20027
+ const characteristics = null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.getCharacteristics();
20028
+ this.options.logger.debug(`Recorder: userMediaErrorCallback(), name: ${err.name}, message: ${err.message} and Webcam characteristics: ${characteristics ? string_pretty(characteristics) : "none"}`);
20029
+ const errorListeners = this.getListeners("ERROR");
20030
+ if (null == errorListeners ? void 0 : errorListeners.length) if (err.name !== error_VideomailError.MEDIA_DEVICE_NOT_SUPPORTED) {
20031
+ const videomailError = error_createError({
20032
+ err,
20033
+ options: this.options,
20034
+ usedConstraints
20083
20035
  });
20084
- this.handleWriteFailure(err);
20085
- } else if (this.streamBackpressured) {
20086
- if (this.hasExceededWebSocketSendBuffer(buffer, opts)) this.failMediaTransmission(error_createError({
20087
- message: "WebSocket send buffer limit exceeded",
20088
- 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.",
20036
+ this.emit("ERROR", {
20037
+ err: videomailError
20038
+ });
20039
+ } else {
20040
+ this.options.logger.debug(`Recorder: ignore user media error ${string_pretty(err)}`);
20041
+ this.retryTimeout = window.setTimeout(()=>{
20042
+ this.retryTimeout = void 0;
20043
+ if (!this.unloaded) this.loadUserMedia();
20044
+ }, this.options.timeouts.userMedia);
20045
+ }
20046
+ else {
20047
+ this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${string_pretty(err)}`);
20048
+ throw error_createError({
20049
+ err,
20050
+ message: "Unable to process this error since there are no error listeners anymore.",
20089
20051
  options: this.options
20090
- }));
20091
- } else this.writeToStream(this.stream, buffer, opts);
20052
+ });
20053
+ }
20092
20054
  }
20093
- writeToStream(stream, buffer, opts) {
20094
- const onFlushedCallback = null == opts ? void 0 : opts.onFlushedCallback;
20095
- try {
20096
- const canContinue = stream.write(buffer, ()=>{
20097
- if (!onFlushedCallback) return;
20098
- try {
20099
- onFlushedCallback(opts);
20100
- } catch (exc) {
20101
- const err = error_createError({
20102
- message: "Failed to write stream buffer",
20103
- explanation: `stream.write() failed because of ${string_pretty(exc)}`,
20104
- options: this.options,
20105
- exc
20106
- });
20107
- this.emit("ERROR", {
20108
- err
20109
- });
20110
- }
20055
+ getUserMediaCallback(localStream, params, requestId = this.mediaRequestId) {
20056
+ if (!this.userMedia) {
20057
+ localStream.getTracks().forEach((track)=>{
20058
+ track.stop();
20111
20059
  });
20112
- if (!canContinue) {
20113
- this.streamBackpressured = true;
20114
- this.startBackpressureTimeout();
20115
- this.pauseForBackpressure();
20116
- return false;
20117
- }
20118
- return true;
20060
+ throw error_createError({
20061
+ message: "No user media is defined",
20062
+ options: this.options
20063
+ });
20064
+ }
20065
+ this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? string_pretty(params) : ""})`);
20066
+ if (this.showUserMedia()) try {
20067
+ this.userMedia.init(localStream, ()=>{
20068
+ if (!this.unloaded && requestId === this.mediaRequestId) this.onUserMediaReady(params);
20069
+ }, this.onAudioSample.bind(this), (err)=>{
20070
+ if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(err, {
20071
+ audio: isAudioEnabled(this.options)
20072
+ });
20073
+ }, params);
20119
20074
  } catch (exc) {
20120
- const err = error_createError({
20121
- message: "Failed writing to server",
20122
- explanation: `stream.write() failed because of ${string_pretty(exc)}`,
20123
- options: this.options,
20124
- exc
20075
+ localStream.getTracks().forEach((track)=>{
20076
+ track.stop();
20125
20077
  });
20126
- this.handleWriteFailure(err);
20127
- return false;
20078
+ this.userMediaLoading = false;
20079
+ this.clearUserMediaTimeout();
20080
+ this.userMediaErrorCallback(exc, {
20081
+ audio: isAudioEnabled(this.options)
20082
+ });
20083
+ }
20084
+ else {
20085
+ localStream.getTracks().forEach((track)=>{
20086
+ track.stop();
20087
+ });
20088
+ this.userMediaLoading = false;
20089
+ this.clearUserMediaTimeout();
20090
+ this.cancelCameraRecovery();
20128
20091
  }
20129
20092
  }
20130
- flushPendingWrites(stream) {
20131
- if (this.isStaleStream(stream)) return;
20132
- this.streamBackpressured = false;
20133
- this.clearBackpressureTimeout();
20134
- let pendingWrite = this.pendingWrites.dequeue();
20135
- while(pendingWrite){
20136
- if (!this.writeToStream(stream, pendingWrite.buffer, pendingWrite.params)) return;
20137
- pendingWrite = this.pendingWrites.dequeue();
20093
+ loadGenuineUserMedia(params) {
20094
+ this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? string_pretty(params) : ""})`);
20095
+ this.emit("ASKING_WEBCAM_PERMISSION");
20096
+ this.userMediaLoading = true;
20097
+ const requestId = ++this.mediaRequestId;
20098
+ const limitedWidth = this.options.video.width ? void 0 : this.visuals.limitWidth();
20099
+ const constraints = mediaConstraints({
20100
+ facingMode: this.facingMode,
20101
+ limitedWidth,
20102
+ options: this.options,
20103
+ userMediaParams: params
20104
+ });
20105
+ this.options.logger.debug(string_summarize("Recorder: our webcam constraints are:", constraints));
20106
+ this.options.logger.debug(string_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
20107
+ const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
20108
+ streamPromise.then((localStream)=>{
20109
+ if (this.unloaded || requestId !== this.mediaRequestId) return void localStream.getTracks().forEach((track)=>{
20110
+ track.stop();
20111
+ });
20112
+ this.getUserMediaCallback(localStream, params, requestId);
20113
+ }).catch((reason)=>{
20114
+ if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(reason, constraints);
20115
+ });
20116
+ }
20117
+ loadUserMedia(params) {
20118
+ if (this.userMediaLoaded) {
20119
+ this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already loaded");
20120
+ this.onUserMediaReady(params);
20121
+ return;
20122
+ } else if (this.userMediaLoading) return void this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already asking for permission");
20123
+ this.options.logger.debug(`Recorder: loadUserMedia(${params ? string_pretty(params) : ""})`);
20124
+ this.emit("LOADING_USER_MEDIA");
20125
+ try {
20126
+ if (!navigator.mediaDevices) throw new Error("No media devices are available.");
20127
+ this.userMediaTimeout = window.setTimeout(()=>{
20128
+ if (!this.userMediaLoaded) {
20129
+ this.userMediaLoading = false;
20130
+ const err = util_getBrowser(this.options).getNoAccessIssue();
20131
+ this.userMediaErrorCallback(err, {
20132
+ audio: isAudioEnabled(this.options)
20133
+ });
20134
+ }
20135
+ }, this.options.timeouts.userMedia);
20136
+ this.userMediaLoading = true;
20137
+ this.loadGenuineUserMedia(params);
20138
+ } catch (exc) {
20139
+ this.clearUserMediaTimeout();
20140
+ this.options.logger.debug("Recorder: failed to load genuine user media");
20141
+ this.userMediaLoading = false;
20142
+ if (this.cameraRecovery) return void this.failCameraRecovery(exc);
20143
+ const errorListeners = this.getListeners("ERROR");
20144
+ if (null == errorListeners ? void 0 : errorListeners.length) this.emit("ERROR", {
20145
+ exc
20146
+ });
20147
+ else {
20148
+ this.options.logger.debug("Recorder: no error listeners attached but throwing exception further");
20149
+ throw exc;
20150
+ }
20138
20151
  }
20139
- this.resumeRecordingAfterDrain();
20140
- }
20141
- isStaleStream(stream) {
20142
- return this.stream !== stream || stream.destroyed;
20143
20152
  }
20144
- canPauseForBackpressure() {
20145
- var _this_userMedia, _this_loop;
20146
- return Boolean(this.recordingActive && !(null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.isPaused()) && (null == (_this_loop = this.loop) ? void 0 : _this_loop.isRunning()));
20153
+ isNotifying() {
20154
+ return this.visuals.isNotifying();
20147
20155
  }
20148
- pauseForBackpressure() {
20149
- if (!this.canPauseForBackpressure()) return;
20150
- this.resumeAfterDrain = true;
20151
- this.loop.stop();
20156
+ isHidden() {
20157
+ return !this.recorderElement || html_isHidden(this.recorderElement);
20152
20158
  }
20153
- resumeRecordingAfterDrain() {
20154
- if (!this.resumeAfterDrain) return;
20155
- this.resumeAfterDrain = false;
20156
- if (this.canResumeAfterDrain()) {
20157
- var _this_loop;
20158
- null == (_this_loop = this.loop) || _this_loop.start();
20159
+ clearUserMediaTimeout() {
20160
+ if (void 0 !== this.userMediaTimeout) {
20161
+ this.options.logger.debug("Recorder: clearUserMediaTimeout()");
20162
+ window.clearTimeout(this.userMediaTimeout);
20163
+ this.userMediaTimeout = void 0;
20159
20164
  }
20160
20165
  }
20161
- canResumeAfterDrain() {
20166
+ clearStopTimeout() {
20167
+ if (void 0 === this.stopTimeout) return;
20168
+ window.clearTimeout(this.stopTimeout);
20169
+ this.stopTimeout = void 0;
20170
+ }
20171
+ isReady() {
20162
20172
  var _this_userMedia;
20163
- return this.recordingActive && !(null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.isPaused());
20173
+ return null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.isReady();
20164
20174
  }
20165
- handleWriteFailure(err) {
20166
- if (this.mediaTransmissionStarted) return void this.failMediaTransmission(err);
20167
- this.emit("ERROR", {
20168
- err
20175
+ finishCameraRecovery(restored) {
20176
+ this.clearUserMediaTimeout();
20177
+ const resolve = this.resolveCameraRecovery;
20178
+ const resumeRequested = restored && this.resumeAfterCameraRecovery;
20179
+ this.cameraRecovery = void 0;
20180
+ this.resolveCameraRecovery = void 0;
20181
+ this.resumeAfterCameraRecovery = false;
20182
+ null == resolve || resolve(restored);
20183
+ if (resumeRequested && this.isPaused()) this.resume();
20184
+ }
20185
+ cancelCameraRecovery() {
20186
+ var _this_userMedia;
20187
+ if (!this.cameraRecovery) return;
20188
+ this.mediaRequestId++;
20189
+ this.userMediaLoading = false;
20190
+ this.userMediaLoaded = false;
20191
+ this.clearUserMediaTimeout();
20192
+ null == (_this_userMedia = this.userMedia) || _this_userMedia.stop(void 0, {
20193
+ preserveRecordingState: true
20169
20194
  });
20195
+ this.finishCameraRecovery(false);
20170
20196
  }
20171
- failMediaTransmission(err) {
20172
- if (this.recordingTransportFailed) return;
20173
- this.recordingTransportFailed = true;
20174
- this.recordingActive = false;
20175
- this.mediaTransmissionStarted = false;
20176
- this.streamBackpressured = false;
20177
- this.resumeAfterDrain = false;
20178
- this.pendingWrites.clear();
20179
- this.connected = false;
20180
- this.connecting = false;
20181
- this.reconnecting = false;
20182
- this.clearReconnectTimeout();
20183
- this.clearBackpressureTimeout();
20184
- this.discardSocket();
20185
- this.reset();
20186
- this.framesCount = this.samplesCount = 0;
20187
- this.confirmedFrameNumber = this.confirmedSampleNumber = 0;
20188
- delete this.recordingStats;
20189
- this.recordingDimensions = void 0;
20190
- this.emit("ERROR", {
20197
+ failCameraRecovery(exc) {
20198
+ this.cancelCameraRecovery();
20199
+ const err = error_createError({
20200
+ message: "Camera recovery failed",
20201
+ explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
20202
+ exc,
20203
+ options: this.options
20204
+ });
20205
+ this.emit("CAMERA_RECOVERY_FAILED", {
20191
20206
  err
20192
20207
  });
20193
20208
  }
20194
- startBackpressureTimeout() {
20195
- if (!this.shouldStartBackpressureTimeout()) return;
20196
- this.backpressureTimeout = window.setTimeout(()=>{
20197
- this.backpressureTimeout = void 0;
20198
- if (this.shouldIgnoreBackpressureTimeout()) return;
20199
- const err = error_createError({
20200
- message: "WebSocket write timed out",
20201
- explanation: "The server stopped accepting recording data. The recording has been stopped; please check your connection and start a new one.",
20202
- options: this.options
20209
+ recoverCamera() {
20210
+ if (this.cameraRecovery) return this.cameraRecovery;
20211
+ if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
20212
+ const liveStream = this.userMedia.hasLiveStream();
20213
+ if (liveStream && this.isRecording()) return Promise.resolve(true);
20214
+ if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
20215
+ const recovery = new Promise((resolve)=>{
20216
+ this.resolveCameraRecovery = resolve;
20217
+ });
20218
+ this.cameraRecovery = recovery;
20219
+ this.emit("CAMERA_RECOVERING");
20220
+ if (liveStream) {
20221
+ this.userMediaTimeout = window.setTimeout(()=>{
20222
+ this.failCameraRecovery(new Error("Camera playback recovery timed out."));
20223
+ }, this.options.timeouts.userMedia);
20224
+ this.userMedia.restorePlayback().then(()=>{
20225
+ if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
20226
+ }).catch((exc)=>{
20227
+ if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
20203
20228
  });
20204
- this.failMediaTransmission(err);
20205
- }, this.options.timeouts.connection);
20206
- }
20207
- shouldStartBackpressureTimeout() {
20208
- return void 0 === this.backpressureTimeout && this.mediaTransmissionStarted;
20209
- }
20210
- shouldIgnoreBackpressureTimeout() {
20211
- return !this.streamBackpressured || this.recordingTransportFailed;
20229
+ } else {
20230
+ this.userMediaLoaded = false;
20231
+ this.loadUserMedia({
20232
+ recovering: true
20233
+ });
20234
+ }
20235
+ return recovery;
20212
20236
  }
20213
- failInterruptedRecording() {
20214
- const unconfirmedFrames = Math.max(0, this.framesCount - this.confirmedFrameNumber);
20215
- const unconfirmedSamples = Math.max(0, this.samplesCount - this.confirmedSampleNumber);
20216
- const err = error_createError({
20217
- message: "Recording interrupted",
20218
- explanation: "The server connection closed before this recording completed. It cannot be safely resumed, so it has been stopped. Please start a new recording.",
20219
- options: this.options,
20220
- cause: {
20221
- unconfirmedFrames,
20222
- unconfirmedSamples
20223
- }
20237
+ switchFacingMode() {
20238
+ if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
20239
+ if ("user" === this.facingMode) this.facingMode = "environment";
20240
+ else if ("environment" === this.facingMode) this.facingMode = "user";
20241
+ else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
20242
+ this.loadGenuineUserMedia({
20243
+ switchingFacingMode: this.facingMode
20224
20244
  });
20225
- this.failMediaTransmission(err);
20226
- }
20227
- handleStreamDrain(stream) {
20228
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *drain* event emitted`);
20229
- this.flushPendingWrites(stream);
20230
20245
  }
20231
- canReconnectAfterStreamClose() {
20232
- return Boolean(this.connected && this.userMediaLoaded);
20246
+ hide() {
20247
+ if (!this.isHidden()) {
20248
+ if (this.recorderElement) html_hideElement(this.recorderElement);
20249
+ this.clearUserMediaTimeout();
20250
+ this.clearRetryTimeout();
20251
+ }
20233
20252
  }
20234
- shouldReportStreamClose() {
20235
- return !this.connecting && !this.blocking;
20253
+ isUnloaded() {
20254
+ return this.unloaded;
20236
20255
  }
20237
- handleStreamClose(url2Connect) {
20238
- const debugLine = string_summarize(`${PIPE_SYMBOL}Stream has closed:`, {
20239
- connecting: this.connecting,
20240
- connected: this.connected,
20241
- userMediaLoaded: this.userMediaLoaded,
20242
- blocking: this.blocking
20243
- });
20244
- this.options.logger.debug(debugLine);
20245
- const canReconnect = this.canReconnectAfterStreamClose();
20246
- this.connected = false;
20247
- this.stopPings();
20248
- if (this.shouldIgnoreStreamClose()) return;
20249
- if (this.mediaTransmissionStarted) this.failInterruptedRecording();
20250
- else if (canReconnect) {
20251
- this.pendingWrites.clear();
20252
- this.streamBackpressured = false;
20253
- this.resumeAfterDrain = false;
20254
- this.reconnecting = true;
20255
- this.initSocket();
20256
- } else if (this.shouldReportStreamClose()) window.setTimeout(()=>{
20257
- this.handleConnectionFailure({
20258
- url2Connect,
20259
- cause: "closed"
20260
- });
20261
- }, 0);
20256
+ isUserMediaLoaded() {
20257
+ return this.userMediaLoaded;
20262
20258
  }
20263
- shouldIgnoreStreamClose() {
20264
- return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
20259
+ constructor(visuals, options){
20260
+ super(options), media_define_property(this, "visuals", void 0), media_define_property(this, "recorderElement", void 0), media_define_property(this, "userMedia", void 0), media_define_property(this, "userMediaTimeout", void 0), media_define_property(this, "retryTimeout", void 0), media_define_property(this, "stopTimeout", void 0), media_define_property(this, "mediaRequestId", 0), media_define_property(this, "cameraRecovery", void 0), media_define_property(this, "resolveCameraRecovery", void 0), media_define_property(this, "resumeAfterCameraRecovery", false), media_define_property(this, "submitting", false), media_define_property(this, "built", false), media_define_property(this, "key", void 0), media_define_property(this, "facingMode", void 0);
20261
+ this.visuals = visuals;
20262
+ this.facingMode = options.video.facingMode;
20265
20263
  }
20266
- handleStreamError(url2Connect, err) {
20267
- var _this_lastSocketError;
20268
- if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
20269
- else this.lastSocketError = serializeError(err);
20270
- const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
20271
- error: serializeError(err)
20272
- });
20273
- this.options.logger.debug(debugLine);
20274
- if (this.validate()) {
20275
- this.connected = false;
20276
- this.stopPings();
20277
- this.discardSocket();
20278
- return;
20279
- }
20280
- if (!this.connected) return void this.handleConnectionFailure({
20281
- url2Connect,
20282
- cause: "error"
20283
- });
20284
- const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
20285
- cause: null != (_this_lastSocketError = this.lastSocketError) ? _this_lastSocketError : err
20286
- });
20287
- if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
20288
- this.emit("ERROR", {
20289
- err: error_createError({
20290
- message: "WebSocket stream error",
20291
- explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
20292
- options: this.options,
20293
- exc: streamError
20294
- })
20264
+ }
20265
+ const media = RecorderMedia;
20266
+ function calculateProgress(confirmedCount, totalCount) {
20267
+ return `${(confirmedCount / (totalCount || 1) * 100).toFixed(2)}%`;
20268
+ }
20269
+ function getConfirmedProgressNumber({ args, kind, options }) {
20270
+ const label = "frame" === kind ? "frame" : "audio sample";
20271
+ if (!args) throw error_createError({
20272
+ message: `Arguments are missing for updating the ${label} progress`,
20273
+ options
20274
+ });
20275
+ const confirmedNumber = args[kind];
20276
+ if (!confirmedNumber) throw error_createError({
20277
+ message: `The ${label} number is missing`,
20278
+ options
20279
+ });
20280
+ return confirmedNumber;
20281
+ }
20282
+ var capture_Buffer = __webpack_require__("dY").Buffer;
20283
+ function capture_define_property(obj, key, value) {
20284
+ if (key in obj) Object.defineProperty(obj, key, {
20285
+ value: value,
20286
+ enumerable: true,
20287
+ configurable: true,
20288
+ writable: true
20289
+ });
20290
+ else obj[key] = value;
20291
+ return obj;
20292
+ }
20293
+ function capture_object_spread(target) {
20294
+ for(var i = 1; i < arguments.length; i++){
20295
+ var source = null != arguments[i] ? arguments[i] : {};
20296
+ var ownKeys = Object.keys(source);
20297
+ if ("function" == typeof Object.getOwnPropertySymbols) ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
20298
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
20299
+ }));
20300
+ ownKeys.forEach(function(key) {
20301
+ capture_define_property(target, key, source[key]);
20295
20302
  });
20296
20303
  }
20297
- sendPings() {
20298
- this.stopPings();
20299
- this.pingInterval = window.setInterval(()=>{
20300
- this.options.logger.debug("Recorder: pinging...");
20301
- this.writeStream(Buffer.from(""));
20302
- }, this.options.timeouts.pingInterval);
20303
- }
20304
- stopPings() {
20305
- if (void 0 === this.pingInterval) return;
20306
- this.options.logger.debug("Recorder: stopping pings...");
20307
- window.clearInterval(this.pingInterval);
20308
- this.pingInterval = void 0;
20309
- }
20304
+ return target;
20305
+ }
20306
+ class RecorderCapture extends media {
20310
20307
  onAudioSample(audioSample) {
20311
20308
  var _this_userMedia;
20312
20309
  if (this.recordingTransportFailed || this.cameraRecovery) return;
@@ -20317,9 +20314,6 @@ var __webpack_exports__ = {};
20317
20314
  const audioBuffer = audioSample.toBuffer();
20318
20315
  this.writeStream(audioBuffer);
20319
20316
  }
20320
- show() {
20321
- html_showElement(this.recorderElement);
20322
- }
20323
20317
  onUserMediaReady(params) {
20324
20318
  try {
20325
20319
  this.options.logger.debug(`Recorder: onUserMediaReady(${params ? string_pretty(params) : ""})`);
@@ -20349,885 +20343,1417 @@ var __webpack_exports__ = {};
20349
20343
  });
20350
20344
  }
20351
20345
  }
20352
- clearRetryTimeout() {
20353
- if (void 0 === this.retryTimeout) return;
20354
- this.options.logger.debug("Recorder: clearRetryTimeout()");
20355
- window.clearTimeout(this.retryTimeout);
20356
- this.retryTimeout = void 0;
20357
- }
20358
- clearConnectionTimeout() {
20359
- if (void 0 === this.connectionTimeout) return;
20360
- this.options.logger.debug("Recorder: clearConnectionTimeout()");
20361
- window.clearTimeout(this.connectionTimeout);
20362
- this.connectionTimeout = void 0;
20346
+ calculateFrameProgress() {
20347
+ return calculateProgress(this.confirmedFrameNumber, this.framesCount);
20363
20348
  }
20364
- clearBackpressureTimeout() {
20365
- if (void 0 === this.backpressureTimeout) return;
20366
- window.clearTimeout(this.backpressureTimeout);
20367
- this.backpressureTimeout = void 0;
20349
+ calculateSampleProgress() {
20350
+ return calculateProgress(this.confirmedSampleNumber, this.samplesCount);
20368
20351
  }
20369
- clearReconnectTimeout() {
20370
- if (void 0 === this.reconnectTimeout) return;
20371
- this.options.logger.debug("Recorder: clearReconnectTimeout()");
20372
- window.clearTimeout(this.reconnectTimeout);
20373
- this.reconnectTimeout = void 0;
20352
+ updateOverallProgress() {
20353
+ this.frameProgress = this.calculateFrameProgress();
20354
+ if (isAudioEnabled(this.options)) this.sampleProgress = this.calculateSampleProgress();
20355
+ this.emit("PROGRESS", {
20356
+ frameProgress: this.frameProgress,
20357
+ sampleProgress: this.sampleProgress
20358
+ });
20374
20359
  }
20375
- handleConnectionFailure(params) {
20376
- if (this.retryConnection(params)) return;
20377
- this.failConnection(params);
20360
+ updateFrameProgress(args) {
20361
+ this.confirmedFrameNumber = getConfirmedProgressNumber({
20362
+ args,
20363
+ kind: "frame",
20364
+ options: this.options
20365
+ });
20366
+ this.frameProgress = this.calculateFrameProgress();
20367
+ this.updateOverallProgress();
20378
20368
  }
20379
- retryConnection(params) {
20380
- if (!this.canRetryConnection()) return false;
20381
- if (void 0 !== this.reconnectTimeout) return true;
20382
- const delayMs = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000);
20383
- this.reconnectAttempts++;
20384
- this.connecting = false;
20385
- this.clearConnectionTimeout();
20386
- this.discardSocket();
20387
- this.options.logger.debug(`Recorder: retrying WebSocket connection in ${delayMs}ms (cause ${params.cause}).`);
20388
- this.reconnectTimeout = window.setTimeout(()=>{
20389
- this.reconnectTimeout = void 0;
20390
- if (this.shouldContinueReconnect()) this.initSocket();
20391
- }, delayMs);
20392
- return true;
20369
+ updateSampleProgress(args) {
20370
+ this.confirmedSampleNumber = getConfirmedProgressNumber({
20371
+ args,
20372
+ kind: "sample",
20373
+ options: this.options
20374
+ });
20375
+ this.sampleProgress = this.calculateSampleProgress();
20376
+ this.updateOverallProgress();
20393
20377
  }
20394
- canRetryConnection() {
20395
- return Boolean(this.reconnecting && this.userMediaLoaded && this.isOnline() && !this.unloaded && !this.blocking);
20378
+ preview(args) {
20379
+ var _args_duration;
20380
+ if (!args) throw error_createError({
20381
+ message: "Preview arguments are missing.",
20382
+ options: this.options
20383
+ });
20384
+ const hasAudio = this.samplesCount > 0;
20385
+ this.mediaTransmissionStarted = false;
20386
+ this.recordingActive = false;
20387
+ this.recordingTransportFailed = false;
20388
+ this.confirmedFrameNumber = this.confirmedSampleNumber = this.samplesCount = this.framesCount = 0;
20389
+ this.sampleProgress = this.frameProgress = void 0;
20390
+ this.key = args.key;
20391
+ if (args.mp4) this.replay.setMp4Source(`${args.mp4 + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.mp4`, true);
20392
+ if (args.webm) this.replay.setWebMSource(`${args.webm + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.webm`, true);
20393
+ this.hide();
20394
+ const widthDimension = this.getRecorderWidth(true);
20395
+ const heightDimension = this.getRecorderHeight(true);
20396
+ const duration = null != (_args_duration = args.duration) ? _args_duration : -1;
20397
+ this.emit("PREVIEW", {
20398
+ key: this.key,
20399
+ width: null == widthDimension ? void 0 : widthDimension.value,
20400
+ height: null == heightDimension ? void 0 : heightDimension.value,
20401
+ hasAudio,
20402
+ duration
20403
+ });
20404
+ if (this.stopTime) this.waitingTime = Date.now() - this.stopTime;
20405
+ null != this.recordingStats || (this.recordingStats = {});
20406
+ this.recordingStats.waitingTime = this.waitingTime;
20396
20407
  }
20397
- shouldContinueReconnect() {
20398
- return this.reconnecting && !this.unloaded && !this.blocking;
20408
+ cancelAnimationFrame() {
20409
+ var _this_loop;
20410
+ null == (_this_loop = this.loop) || _this_loop.dispose();
20399
20411
  }
20400
- discardSocket() {
20401
- this.options.logger.debug("Recorder: discarding socket stream ...");
20402
- this.clearBackpressureTimeout();
20403
- this.pendingWrites.clear();
20404
- this.streamBackpressured = false;
20405
- this.resumeAfterDrain = false;
20406
- const stream = this.stream;
20407
- this.stream = void 0;
20408
- if (stream && !stream.destroyed) stream.destroy();
20412
+ getIntervalSum() {
20413
+ if (!this.loop) return;
20414
+ return this.loop.getElapsedTime();
20409
20415
  }
20410
- isOnline() {
20411
- return navigator.onLine;
20416
+ getAvgInterval() {
20417
+ var _this_getIntervalSum;
20418
+ const intervalSum = null != (_this_getIntervalSum = this.getIntervalSum()) ? _this_getIntervalSum : 0;
20419
+ if (!this.hasValidInterval(intervalSum)) return;
20420
+ return intervalSum / this.framesCount;
20412
20421
  }
20413
- failConnection(params) {
20414
- var _this_stream;
20415
- if (this.shouldIgnoreConnectionFailure()) return;
20416
- this.connectionFailed = true;
20417
- this.connecting = false;
20418
- this.reconnecting = false;
20419
- this.reconnectAttempts = 0;
20420
- this.clearReconnectTimeout();
20421
- this.clearConnectionTimeout();
20422
- const { url2Connect, cause } = params;
20423
- const online = this.isOnline();
20424
- const elapsedMs = this.connectingStartedAt ? Date.now() - this.connectingStartedAt : void 0;
20425
- const closeEvent = this.lastCloseEvent;
20426
- const socketError = this.lastSocketError;
20427
- const contents = {
20428
- cause,
20429
- closeCode: null == closeEvent ? void 0 : closeEvent.code,
20430
- closeReason: null == closeEvent ? void 0 : closeEvent.reason,
20431
- elapsedMs,
20432
- online,
20433
- secureContext: globalThis.isSecureContext,
20434
- socketReadyState: null == (_this_stream = this.stream) ? void 0 : _this_stream.socket.readyState,
20435
- timeoutMs: this.options.timeouts.connection,
20436
- url: url2Connect,
20437
- unloaded: this.unloaded,
20438
- userMediaLoaded: this.userMediaLoaded,
20439
- userMediaLoading: this.userMediaLoading,
20440
- wasClean: null == closeEvent ? void 0 : closeEvent.wasClean,
20441
- socketError,
20442
- blocking: this.blocking,
20443
- visibilityState: document.visibilityState
20444
- };
20445
- const debugLine = string_summarize("Recorder: failConnection() diagnostic", contents);
20446
- this.options.logger.debug(debugLine);
20447
- let explanation;
20448
- 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.`;
20449
- 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.`;
20450
- else {
20451
- const closeSuffix = closeEvent ? ` (code ${closeEvent.code})` : "";
20452
- explanation = `Connection to ${url2Connect} is closed${closeSuffix}. Please check your internet connection and try again. If the problem persists, contact us.`;
20453
- }
20454
- else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
20455
- this.discardSocket();
20456
- const err = error_createError({
20457
- message: "Unable to connect to the server",
20458
- explanation,
20459
- options: this.options,
20460
- cause: contents,
20461
- exc: socketError ? new Error("WebSocket connection error", {
20462
- cause: socketError
20463
- }) : void 0
20464
- });
20465
- this.emit("ERROR", {
20466
- err
20467
- });
20422
+ getAvgFps() {
20423
+ var _this_getIntervalSum;
20424
+ const intervalSum = null != (_this_getIntervalSum = this.getIntervalSum()) ? _this_getIntervalSum : 0;
20425
+ if (!this.hasValidInterval(intervalSum)) return;
20426
+ return this.framesCount / intervalSum * 1000;
20468
20427
  }
20469
- shouldIgnoreConnectionFailure() {
20470
- return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
20428
+ hasValidInterval(intervalSum) {
20429
+ return intervalSum > 0;
20471
20430
  }
20472
- calculateFrameProgress() {
20473
- return `${(this.confirmedFrameNumber / (this.framesCount || 1) * 100).toFixed(2)}%`;
20431
+ getRecordingStats() {
20432
+ return this.recordingStats;
20474
20433
  }
20475
- calculateSampleProgress() {
20476
- return `${(this.confirmedSampleNumber / (this.samplesCount || 1) * 100).toFixed(2)}%`;
20434
+ getAudioSampleRate() {
20435
+ var _this_recordingAudioSampleRate;
20436
+ var _this_userMedia;
20437
+ return null != (_this_recordingAudioSampleRate = this.recordingAudioSampleRate) ? _this_recordingAudioSampleRate : null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.getAudioSampleRate();
20477
20438
  }
20478
- updateOverallProgress() {
20479
- this.frameProgress = this.calculateFrameProgress();
20480
- if (isAudioEnabled(this.options)) this.sampleProgress = this.calculateSampleProgress();
20481
- this.emit("PROGRESS", {
20482
- frameProgress: this.frameProgress,
20483
- sampleProgress: this.sampleProgress
20439
+ stop(params) {
20440
+ var _this_loop_complete, _this_loop;
20441
+ this.cancelCameraRecovery();
20442
+ if (!this.userMedia) throw new Error("No user media defined, unable to stop");
20443
+ this.options.logger.debug(`Recorder: stop(${params ? string_pretty(params) : ""})`);
20444
+ this.recordingActive = false;
20445
+ const limitReached = null == params ? void 0 : params.limitReached;
20446
+ this.emit("STOPPING", {
20447
+ limitReached
20484
20448
  });
20449
+ null == (_this_loop = this.loop) || null == (_this_loop_complete = _this_loop.complete) || _this_loop_complete.call(_this_loop);
20450
+ this.stopTimeout = window.setTimeout(()=>{
20451
+ this.stopTimeout = void 0;
20452
+ this.stopTime = Date.now();
20453
+ const videoType = this.replay.getVideoType();
20454
+ if (!videoType) throw new Error("Unable to video record when no video type is defined.");
20455
+ this.recordingStats = {
20456
+ avgFps: this.getAvgFps(),
20457
+ wantedFps: this.options.video.fps,
20458
+ avgInterval: this.getAvgInterval(),
20459
+ wantedInterval: 1e3 / this.options.video.fps,
20460
+ intervalSum: this.getIntervalSum(),
20461
+ framesCount: this.framesCount,
20462
+ videoType
20463
+ };
20464
+ if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
20465
+ if (isAudioEnabled(this.options) && this.userMedia) {
20466
+ var _this_recordingAudioSampleRate;
20467
+ this.recordingStats.samplesCount = this.samplesCount;
20468
+ this.recordingStats.sampleRate = null != (_this_recordingAudioSampleRate = this.recordingAudioSampleRate) ? _this_recordingAudioSampleRate : this.userMedia.getAudioSampleRate();
20469
+ }
20470
+ this.writeCommand("stop", this.recordingStats, ()=>{
20471
+ this.emit("STOPPED", {
20472
+ recordingStats: this.recordingStats
20473
+ });
20474
+ });
20475
+ this.reset();
20476
+ }, 60);
20477
+ }
20478
+ back(cb) {
20479
+ this.emit("GOING_BACK");
20480
+ this.key = void 0;
20481
+ this.recordingDimensions = void 0;
20482
+ this.unloaded = false;
20483
+ this.show();
20484
+ this.writeCommand("back", void 0, cb);
20485
+ }
20486
+ reInitializeAudio() {
20487
+ var _this_userMedia;
20488
+ this.options.logger.debug("Recorder: reInitializeAudio()");
20489
+ this.clearUserMediaTimeout();
20490
+ this.userMediaLoading = false;
20491
+ null == (_this_userMedia = this.userMedia) || _this_userMedia.stop();
20492
+ this.recordingDimensions = void 0;
20493
+ this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
20494
+ this.loadUserMedia();
20485
20495
  }
20486
- updateFrameProgress(args) {
20487
- if (args) {
20488
- if (!args.frame) throw error_createError({
20489
- message: "The frame number is missing",
20490
- options: this.options
20491
- });
20492
- } else throw error_createError({
20493
- message: "Arguments are missing for updating the frame progress",
20494
- options: this.options
20495
- });
20496
- this.confirmedFrameNumber = args.frame;
20497
- this.frameProgress = this.calculateFrameProgress();
20498
- this.updateOverallProgress();
20496
+ reset() {
20497
+ this.cancelCameraRecovery();
20498
+ if (!this.unloaded) {
20499
+ var _this_userMedia;
20500
+ this.options.logger.debug("Recorder: reset()");
20501
+ this.emit("RESETTING");
20502
+ this.cancelAnimationFrame();
20503
+ null == (_this_userMedia = this.userMedia) || _this_userMedia.stop();
20504
+ this.replay.reset();
20505
+ this.userMediaLoaded = this.key = this.canvas = this.ctx = this.recordingBuffer = void 0;
20506
+ }
20499
20507
  }
20500
- updateSampleProgress(args) {
20501
- if (args) {
20502
- if (!args.sample) throw error_createError({
20503
- message: "The audio sample number is missing",
20504
- options: this.options
20505
- });
20506
- } else throw error_createError({
20507
- message: "Arguments are missing for updating the audio sample progress",
20508
- options: this.options
20509
- });
20510
- this.confirmedSampleNumber = args.sample;
20511
- this.sampleProgress = this.calculateSampleProgress();
20512
- this.updateOverallProgress();
20508
+ validate() {
20509
+ return Boolean(this.key) && void 0 === this.canvas;
20513
20510
  }
20514
- preview(args) {
20515
- var _args_duration;
20516
- if (!args) throw error_createError({
20517
- message: "Preview arguments are missing.",
20518
- options: this.options
20519
- });
20520
- const hasAudio = this.samplesCount > 0;
20521
- this.mediaTransmissionStarted = false;
20522
- this.recordingActive = false;
20523
- this.recordingTransportFailed = false;
20524
- this.confirmedFrameNumber = this.confirmedSampleNumber = this.samplesCount = this.framesCount = 0;
20525
- this.sampleProgress = this.frameProgress = void 0;
20526
- this.key = args.key;
20527
- if (args.mp4) this.replay.setMp4Source(`${args.mp4 + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.mp4`, true);
20528
- if (args.webm) this.replay.setWebMSource(`${args.webm + constants.WHITELIST_KEY_LABEL}/${this.options.whitelistKey}/videomail.webm`, true);
20529
- this.hide();
20530
- const widthDimension = this.getRecorderWidth(true);
20531
- const heightDimension = this.getRecorderHeight(true);
20532
- const duration = null != (_args_duration = args.duration) ? _args_duration : -1;
20533
- this.emit("PREVIEW", {
20534
- key: this.key,
20535
- width: null == widthDimension ? void 0 : widthDimension.value,
20536
- height: null == heightDimension ? void 0 : heightDimension.value,
20537
- hasAudio,
20538
- duration
20539
- });
20540
- if (this.stopTime) this.waitingTime = Date.now() - this.stopTime;
20541
- null != this.recordingStats || (this.recordingStats = {});
20542
- this.recordingStats.waitingTime = this.waitingTime;
20511
+ pause(params) {
20512
+ var _this_userMedia;
20513
+ this.cancelCameraRecovery();
20514
+ this.resumeAfterDrain = false;
20515
+ const loop = this.loop;
20516
+ if (!loop) throw new Error("No recording loop defined");
20517
+ if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
20518
+ else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
20519
+ null == (_this_userMedia = this.userMedia) || _this_userMedia.pause();
20520
+ loop.stop();
20521
+ this.emit("PAUSED");
20522
+ this.sendPings();
20543
20523
  }
20544
- initSocket(cb) {
20545
- if (this.connected) {
20546
- this.options.logger.debug("Recorder: already **connected**. Not going to initialize a new WebSocket connection.");
20547
- null == cb || cb();
20548
- return;
20549
- }
20550
- if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
20551
- this.clearReconnectTimeout();
20552
- if (util_isAutomatedUserAgent()) {
20553
- this.connecting = false;
20554
- this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
20555
- return;
20556
- }
20557
- this.connecting = true;
20558
- this.connectionFailed = false;
20559
- this.connectingStartedAt = Date.now();
20560
- this.lastCloseEvent = void 0;
20561
- this.lastSocketError = void 0;
20562
- this.emit("CONNECTING");
20563
- let url2Connect;
20564
- try {
20565
- const socketUrlObj = new URL(this.options.socketUrl);
20566
- socketUrlObj.searchParams.set(constants.WHITELIST_KEY_LABEL, this.options.whitelistKey);
20567
- url2Connect = socketUrlObj.toString();
20568
- } catch (exc) {
20569
- this.connecting = this.connected = false;
20570
- const err = error_createError({
20571
- message: "Invalid WebSocket URL",
20572
- explanation: `The configured socketUrl "${this.options.socketUrl}" is not a valid URL. Please check your videomail-client configuration.`,
20573
- options: this.options,
20574
- exc
20575
- });
20576
- this.emit("ERROR", {
20577
- err
20578
- });
20524
+ resume() {
20525
+ var _this_userMedia;
20526
+ if (this.cameraRecovery || !(null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.hasLiveStream())) {
20527
+ this.recoverCamera();
20528
+ this.resumeAfterCameraRecovery = Boolean(this.cameraRecovery);
20579
20529
  return;
20580
20530
  }
20581
- this.options.logger.debug(`Recorder: initializing web socket stream to ${url2Connect}`);
20582
- let nativeSocket;
20531
+ const loop = this.loop;
20532
+ if (!loop) throw new Error("No recording loop defined");
20533
+ this.options.logger.debug(`Recorder: resume() with frame ${this.framesCount}`);
20534
+ this.stopPings();
20535
+ this.emit("RESUMING");
20536
+ this.userMedia.resume();
20537
+ if (this.streamBackpressured) this.resumeAfterDrain = true;
20538
+ else loop.start();
20539
+ }
20540
+ onFlushed(opts) {
20541
+ const frameNumber = opts.frameNumber;
20542
+ if (1 === frameNumber) this.emit("FIRST_FRAME_SENT");
20543
+ }
20544
+ draw(_deltaTime, elapsedTime) {
20545
+ if (this.recordingTransportFailed) return;
20546
+ if (!this.userMedia) throw new Error("No user media defined, unable to draw on canvas");
20583
20547
  try {
20584
- nativeSocket = new WebSocket(url2Connect);
20585
- nativeSocket.addEventListener("close", (event)=>{
20586
- this.lastCloseEvent = {
20587
- code: event.code,
20588
- reason: event.reason,
20589
- wasClean: event.wasClean
20590
- };
20591
- });
20592
- nativeSocket.addEventListener("error", (event)=>{
20593
- this.lastSocketError = error_getEventDetails(event);
20594
- });
20548
+ if (!this.isPaused() && this.stream && this.ctx) {
20549
+ var _this_frame, _this_recordingBuffer;
20550
+ if (0 === this.framesCount) this.emit("SENDING_FIRST_FRAME");
20551
+ this.framesCount++;
20552
+ const imageSource = this.userMedia.getRawVisuals();
20553
+ if (this.canvas && imageSource) this.ctx.drawImage(imageSource, 0, 0, this.canvas.width, this.canvas.height);
20554
+ else throw new Error("Unable to draw an image without a defined canvas");
20555
+ this.recordingBuffer = null == (_this_frame = this.frame) ? void 0 : _this_frame.toBuffer();
20556
+ const recordingBufferLength = null == (_this_recordingBuffer = this.recordingBuffer) ? void 0 : _this_recordingBuffer.length;
20557
+ if (recordingBufferLength) {
20558
+ if (this.recordingBuffer) {
20559
+ const frameControlBuffer = capture_Buffer.from(JSON.stringify({
20560
+ frameNumber: this.framesCount
20561
+ }));
20562
+ const frameBuffer = capture_Buffer.concat([
20563
+ this.recordingBuffer,
20564
+ frameControlBuffer
20565
+ ]);
20566
+ this.mediaTransmissionStarted = true;
20567
+ this.writeStream(frameBuffer, {
20568
+ frameNumber: this.framesCount,
20569
+ onFlushedCallback: this.onFlushed.bind(this)
20570
+ });
20571
+ this.visuals.checkTimer(elapsedTime);
20572
+ }
20573
+ } else throw error_createError({
20574
+ message: "Failed to extract webcam data.",
20575
+ options: this.options
20576
+ });
20577
+ }
20595
20578
  } catch (exc) {
20596
- this.connecting = this.connected = false;
20597
- const diagnostic = error_getWebSocketDiagnostic(url2Connect);
20598
- const message = diagnostic.looksAutomated ? "Automated crawler: WebSocket not supported in this environment" : `Failed to construct WebSocket to ${url2Connect}`;
20599
- 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}`;
20600
- const err = error_createError({
20601
- message,
20602
- explanation,
20603
- options: this.options,
20604
- exc
20605
- });
20606
20579
  this.emit("ERROR", {
20607
- err
20608
- });
20609
- return;
20610
- }
20611
- try {
20612
- this.stream = stream_default()(nativeSocket);
20613
- } catch (exc) {
20614
- this.connecting = this.connected = false;
20615
- const err = error_createError({
20616
- message: `Failed to create a stream to ${url2Connect}`,
20617
- explanation: "Please check your connection and try again. If the problem persists, contact us.",
20618
- options: this.options,
20619
20580
  exc
20620
20581
  });
20621
- this.emit("ERROR", {
20622
- err
20623
- });
20624
20582
  }
20625
- const stream = this.stream;
20626
- if (stream) {
20627
- const connectionTimeoutMs = this.options.timeouts.connection;
20628
- this.connectionTimeout = window.setTimeout(()=>{
20629
- if (this.isOnline() && this.isUserMediaLoaded()) {
20630
- this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
20631
- this.reconnecting = true;
20632
- this.handleConnectionFailure({
20633
- url2Connect,
20634
- cause: "timeout"
20635
- });
20636
- } else this.handleConnectionFailure({
20637
- url2Connect,
20638
- cause: "timeout"
20639
- });
20640
- }, connectionTimeoutMs);
20641
- stream.on("close", ()=>{
20642
- this.handleStreamClose(url2Connect);
20643
- });
20644
- stream.on("connect", (args)=>{
20645
- var _this_stream;
20646
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *connect* event emitted with args: ${string_pretty(args)}`);
20647
- this.clearConnectionTimeout();
20648
- const isClosing = (null == (_this_stream = this.stream) ? void 0 : _this_stream.socket.readyState) === WebSocket.CLOSING;
20649
- if (!this.connected && !isClosing && !this.unloaded) {
20650
- this.connected = true;
20651
- this.connecting = this.unloaded = false;
20652
- this.reconnecting = false;
20653
- this.reconnectAttempts = 0;
20654
- this.clearReconnectTimeout();
20655
- this.emit("CONNECTED");
20656
- null == cb || cb();
20657
- }
20658
- });
20659
- stream.on("data", (data)=>{
20660
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *data* event emitted`);
20661
- try {
20662
- const command = JSON.parse(data.toString());
20663
- this.executeCommand(command);
20664
- } catch (exc) {
20665
- this.options.logger.error(`Failed to parse command: ${exc}`);
20666
- const err = error_createError({
20667
- message: "Invalid server command",
20668
- explanation: `Contact us. The invalid command was: ${data.toString()}.`,
20669
- options: this.options,
20670
- exc
20671
- });
20672
- this.emit("ERROR", {
20673
- err
20583
+ }
20584
+ createLoop() {
20585
+ const newLoop = animitter_default()({
20586
+ fps: this.options.video.fps
20587
+ }, this.draw.bind(this));
20588
+ this.originalAnimationFrameObject = newLoop.getRequestAnimationFrameObject();
20589
+ return newLoop;
20590
+ }
20591
+ record() {
20592
+ if (this.unloaded || this.cameraRecovery) return;
20593
+ if (!this.connected) {
20594
+ this.options.logger.debug("Recorder: reconnecting before recording …");
20595
+ this.initSocket(()=>{
20596
+ this.once("SERVER_READY", ()=>{
20597
+ if (this.unloaded) return;
20598
+ if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
20599
+ else this.once("USER_MEDIA_READY", ()=>{
20600
+ if (!this.unloaded) this.record();
20674
20601
  });
20675
- }
20676
- });
20677
- stream.on("error", (err)=>{
20678
- this.handleStreamError(url2Connect, err);
20679
- });
20680
- stream.on("drain", ()=>{
20681
- this.handleStreamDrain(stream);
20682
- });
20683
- stream.on("preend", (args)=>{
20684
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *preend* event emitted with args: ${string_pretty(args)}`);
20685
- });
20686
- stream.on("end", ()=>{
20687
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *end* event emitted`);
20602
+ });
20688
20603
  });
20689
- stream.on("pipe", (src)=>{
20690
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *pipe* event emitted with src: ${string_pretty(src)}`);
20604
+ return;
20605
+ }
20606
+ if (!this.userMediaLoaded) {
20607
+ if (this.options.loadUserMediaOnRecord) this.loadUserMedia({
20608
+ recordWhenReady: true
20691
20609
  });
20692
- stream.on("unpipe", (src)=>{
20693
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *unpipe* event emitted with src: ${string_pretty(src)}`);
20610
+ else {
20611
+ const err = error_createError({
20612
+ message: "Load and enable your camera first",
20613
+ options: this.options
20614
+ });
20615
+ this.emit("ERROR", {
20616
+ err
20617
+ });
20618
+ }
20619
+ return;
20620
+ }
20621
+ const loop = this.loop;
20622
+ if (!loop) throw new Error("No recording loop defined");
20623
+ this.recordingDimensions = void 0;
20624
+ try {
20625
+ if (!this.userMedia) throw new Error("No user media defined, unable to create canvas");
20626
+ this.canvas = this.userMedia.createCanvas();
20627
+ } catch (exc) {
20628
+ const err = error_createError({
20629
+ exc,
20630
+ options: this.options
20694
20631
  });
20695
- stream.on("resume", (args)=>{
20696
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *resume* event emitted with args: ${string_pretty(args)}`);
20632
+ this.emit("ERROR", {
20633
+ err
20697
20634
  });
20698
- stream.on("uncork", (args)=>{
20699
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *uncork* event emitted with args: ${string_pretty(args)}`);
20635
+ return;
20636
+ }
20637
+ this.ctx = this.canvas.getContext("2d");
20638
+ if (!this.canvas.width) {
20639
+ const err = error_createError({
20640
+ message: "Canvas has an invalid width.",
20641
+ options: this.options
20700
20642
  });
20701
- stream.on("readable", (args)=>{
20702
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *readable* event emitted with args: ${string_pretty(args)}`);
20643
+ this.emit("ERROR", {
20644
+ err
20703
20645
  });
20704
- stream.on("prefinish", (args)=>{
20705
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *prefinish* event emitted with args: ${string_pretty(args)}`);
20646
+ return;
20647
+ }
20648
+ if (!this.canvas.height) {
20649
+ const err = error_createError({
20650
+ message: "Canvas has an invalid height.",
20651
+ options: this.options
20706
20652
  });
20707
- stream.on("finish", (args)=>{
20708
- this.options.logger.debug(`${PIPE_SYMBOL}Stream *finish* event emitted with args: ${string_pretty(args)}`);
20653
+ this.emit("ERROR", {
20654
+ err
20709
20655
  });
20656
+ return;
20710
20657
  }
20658
+ this.recordingDimensions = {
20659
+ width: this.canvas.width,
20660
+ height: this.canvas.height
20661
+ };
20662
+ this.frame = new canvas_to_buffer_modern_r(this.canvas, this.options.image.types, this.options.image.quality);
20663
+ this.options.logger.debug("Recorder: record()");
20664
+ this.recordingActive = true;
20665
+ this.recordingTransportFailed = false;
20666
+ this.mediaTransmissionStarted = false;
20667
+ this.recordingAudioSampleRate = void 0;
20668
+ this.userMedia.record();
20669
+ this.emit("RECORDING", {
20670
+ framesCount: this.framesCount
20671
+ });
20672
+ loop.on("update", (_deltaTime, elapsedTime)=>{
20673
+ let avgFPS;
20674
+ avgFPS = 0 !== elapsedTime ? Math.round(this.framesCount / elapsedTime * 1000) : void 0;
20675
+ this.options.logger.debug(`Recorder updates avgFps = ${avgFPS} at frame ${this.framesCount}`);
20676
+ });
20677
+ loop.start();
20711
20678
  }
20712
- showUserMedia() {
20713
- if (!this.connected) return false;
20714
- const hidden = this.isHidden();
20715
- if (!hidden) return true;
20716
- const notifying = this.isNotifying();
20717
- if (notifying) return true;
20718
- return this.blocking;
20679
+ setAnimationFrameObject(newObj) {
20680
+ if (this.loop) {
20681
+ const isRecording = this.isRecording();
20682
+ this.loop.stop();
20683
+ this.loop.setRequestAnimationFrameObject(newObj);
20684
+ if (isRecording) this.loop.start();
20685
+ }
20719
20686
  }
20720
- userMediaErrorCallback(err, usedConstraints) {
20687
+ restoreAnimationFrameObject() {
20688
+ this.options.logger.debug("Recorder: restoreAnimationFrameObject()");
20689
+ if (this.originalAnimationFrameObject) this.setAnimationFrameObject(this.originalAnimationFrameObject);
20690
+ }
20691
+ loopWithTimeouts() {
20692
+ this.options.logger.debug("Recorder: loopWithTimeouts()");
20693
+ const wantedInterval = 1e3 / this.options.video.fps;
20694
+ let processingTime = 0;
20695
+ const raf = (fn)=>window.setTimeout(()=>{
20696
+ const start = Date.now();
20697
+ fn();
20698
+ processingTime = Date.now() - start;
20699
+ }, wantedInterval - processingTime);
20700
+ const cancel = (id)=>{
20701
+ window.clearTimeout(id);
20702
+ };
20703
+ this.setAnimationFrameObject({
20704
+ requestAnimationFrame: raf,
20705
+ cancelAnimationFrame: cancel
20706
+ });
20707
+ }
20708
+ isPaused() {
20709
+ var _this_userMedia, _this_loop;
20710
+ return (null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.isPaused()) && !(null == (_this_loop = this.loop) ? void 0 : _this_loop.isRunning());
20711
+ }
20712
+ isUserMediaPaused() {
20713
+ var _ref;
20721
20714
  var _this_userMedia;
20722
- if (this.cameraRecovery) return void this.failCameraRecovery(err);
20723
- if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${string_pretty(err)}`);
20724
- this.userMediaLoading = false;
20725
- this.clearUserMediaTimeout();
20726
- const characteristics = null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.getCharacteristics();
20727
- this.options.logger.debug(`Recorder: userMediaErrorCallback(), name: ${err.name}, message: ${err.message} and Webcam characteristics: ${characteristics ? string_pretty(characteristics) : "none"}`);
20728
- const errorListeners = this.getListeners("ERROR");
20729
- if (null == errorListeners ? void 0 : errorListeners.length) if (err.name !== error_VideomailError.MEDIA_DEVICE_NOT_SUPPORTED) {
20730
- const videomailError = error_createError({
20731
- err,
20732
- options: this.options,
20733
- usedConstraints
20734
- });
20735
- this.emit("ERROR", {
20736
- err: videomailError
20737
- });
20715
+ return null != (_ref = null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.isPaused()) ? _ref : false;
20716
+ }
20717
+ isCaptureLoopRunning() {
20718
+ var _ref;
20719
+ var _this_loop;
20720
+ return null != (_ref = null == (_this_loop = this.loop) ? void 0 : _this_loop.isRunning()) ? _ref : false;
20721
+ }
20722
+ stopCaptureLoop() {
20723
+ var _this_loop;
20724
+ null == (_this_loop = this.loop) || _this_loop.stop();
20725
+ }
20726
+ startCaptureLoop() {
20727
+ var _this_loop;
20728
+ null == (_this_loop = this.loop) || _this_loop.start();
20729
+ }
20730
+ isRecording() {
20731
+ var _this_loop;
20732
+ return Boolean((null == (_this_loop = this.loop) ? void 0 : _this_loop.isRunning()) && !this.isPaused() && !this.isNotifying() && this.stream && !this.stream.destroyed);
20733
+ }
20734
+ getRecordingDimensions() {
20735
+ return this.recordingDimensions && capture_object_spread({}, this.recordingDimensions);
20736
+ }
20737
+ constructor(visuals, replay, options){
20738
+ super(visuals, options), capture_define_property(this, "replay", void 0), capture_define_property(this, "loop", void 0), capture_define_property(this, "originalAnimationFrameObject", void 0), capture_define_property(this, "recordingAudioSampleRate", void 0), capture_define_property(this, "frameProgress", void 0), capture_define_property(this, "sampleProgress", void 0), capture_define_property(this, "canvas", void 0), capture_define_property(this, "ctx", void 0), capture_define_property(this, "stopTime", void 0), capture_define_property(this, "waitingTime", void 0), capture_define_property(this, "frame", void 0), capture_define_property(this, "recordingBuffer", void 0);
20739
+ this.replay = replay;
20740
+ }
20741
+ }
20742
+ const capture = RecorderCapture;
20743
+ function figureMinHeight(height, options) {
20744
+ let minHeight;
20745
+ if (options.video.height) {
20746
+ minHeight = height ? Math.min(options.video.height, height) : options.video.height;
20747
+ if (minHeight < 1) throw error_createError({
20748
+ message: `Got a min height less than 1 (${minHeight})!`,
20749
+ options
20750
+ });
20751
+ } else minHeight = height;
20752
+ return minHeight;
20753
+ }
20754
+ const dimensions_figureMinHeight = figureMinHeight;
20755
+ function getRatio(options, videoHeight, videoWidth) {
20756
+ let ratio = 1;
20757
+ const hasVideoDimensions = videoHeight && videoWidth;
20758
+ const desiredHeight = options.video.height;
20759
+ const desiredWidth = options.video.width;
20760
+ const hasDesiredDimensions = desiredHeight && desiredWidth;
20761
+ if (hasDesiredDimensions) ratio = hasVideoDimensions ? videoHeight < desiredHeight || videoWidth < desiredWidth ? videoHeight / videoWidth : desiredHeight / desiredWidth : desiredHeight / desiredWidth;
20762
+ else if (hasVideoDimensions) ratio = videoHeight / videoWidth;
20763
+ return ratio;
20764
+ }
20765
+ const dimensions_getRatio = getRatio;
20766
+ function calculateHeight(responsive, videoWidth, options, ratio, element) {
20767
+ const dimension = {
20768
+ unit: "px"
20769
+ };
20770
+ let width = videoWidth;
20771
+ if (responsive && element) {
20772
+ const limitedDimension = dimensions_limitWidth(element, options, width);
20773
+ width = limitedDimension.value;
20774
+ }
20775
+ const chosenRatio = null != ratio ? ratio : dimensions_getRatio(options, void 0, videoWidth);
20776
+ const height = width ? Math.round(width * chosenRatio) : void 0;
20777
+ if (height && Number.isInteger(height) && height < 1) throw error_createError({
20778
+ message: "Just calculated a height less than 1 which is wrong.",
20779
+ options
20780
+ });
20781
+ const minHeight = dimensions_figureMinHeight(height, options);
20782
+ dimension.value = minHeight;
20783
+ return dimension;
20784
+ }
20785
+ const dimensions_calculateHeight = calculateHeight;
20786
+ function calculateWidth(responsive, options, videoHeight, ratio) {
20787
+ const dimension = {
20788
+ unit: "px"
20789
+ };
20790
+ let height = dimensions_figureMinHeight(videoHeight, options);
20791
+ if (responsive) {
20792
+ const limitedDimension = dimensions_limitHeight(height, options, "calculateWidth");
20793
+ height = limitedDimension.value;
20794
+ }
20795
+ if (!height || height < 1) throw error_createError({
20796
+ message: `Height ${height} cannot be smaller than 1 when calculating width.`,
20797
+ options
20798
+ });
20799
+ const chosenRatio = null != ratio ? ratio : dimensions_getRatio(options, videoHeight);
20800
+ const calculatedWidth = Math.round(height / chosenRatio);
20801
+ if (calculatedWidth < 1) throw error_createError({
20802
+ message: "Calculated width cannot be smaller than 1!",
20803
+ options
20804
+ });
20805
+ dimension.value = calculatedWidth;
20806
+ return dimension;
20807
+ }
20808
+ const dimensions_calculateWidth = calculateWidth;
20809
+ function dimensions_define_property(obj, key, value) {
20810
+ if (key in obj) Object.defineProperty(obj, key, {
20811
+ value: value,
20812
+ enumerable: true,
20813
+ configurable: true,
20814
+ writable: true
20815
+ });
20816
+ else obj[key] = value;
20817
+ return obj;
20818
+ }
20819
+ class RecorderDimensions {
20820
+ correctDimensions() {
20821
+ const recorderElement = this.context.getRecorderElement();
20822
+ if (!recorderElement) return;
20823
+ const widthDimension = dimensions_useFullWidth(this.context.options.video.mobileBreakPoint);
20824
+ if (this.context.options.video.stretch || widthDimension) {
20825
+ recorderElement.style.width = "100%";
20826
+ recorderElement.style.removeProperty("height");
20738
20827
  } else {
20739
- this.options.logger.debug(`Recorder: ignore user media error ${string_pretty(err)}`);
20740
- this.retryTimeout = window.setTimeout(()=>{
20741
- this.retryTimeout = void 0;
20742
- if (!this.unloaded) this.loadUserMedia();
20743
- }, this.options.timeouts.userMedia);
20744
- }
20745
- else {
20746
- this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${string_pretty(err)}`);
20747
- throw error_createError({
20748
- err,
20749
- message: "Unable to process this error since there are no error listeners anymore.",
20750
- options: this.options
20751
- });
20828
+ if (this.context.options.video.width) {
20829
+ const recorderWidth = this.getRecorderWidth(true);
20830
+ if (null == recorderWidth ? void 0 : recorderWidth.value) recorderElement.width = recorderWidth.value;
20831
+ else recorderElement.style.removeProperty("width");
20832
+ }
20833
+ if (this.context.options.video.height) {
20834
+ const recorderHeight = this.getRecorderHeight(true);
20835
+ if (null == recorderHeight ? void 0 : recorderHeight.value) recorderElement.height = recorderHeight.value;
20836
+ else recorderElement.style.removeProperty("height");
20837
+ }
20752
20838
  }
20753
20839
  }
20754
- getUserMediaCallback(localStream, params, requestId = this.mediaRequestId) {
20755
- if (!this.userMedia) {
20756
- localStream.getTracks().forEach((track)=>{
20757
- track.stop();
20758
- });
20759
- throw error_createError({
20760
- message: "No user media is defined",
20761
- options: this.options
20762
- });
20840
+ getRecorderWidth(responsive) {
20841
+ const userMedia = this.context.getUserMedia();
20842
+ if (null == userMedia ? void 0 : userMedia.hasVideoWidth()) return userMedia.getRawWidth(responsive);
20843
+ else if (responsive && this.context.options.video.width) return this.context.visuals.limitWidth(this.context.options.video.width);
20844
+ const dimension = {
20845
+ unit: "px",
20846
+ value: this.context.options.video.width
20847
+ };
20848
+ return dimension;
20849
+ }
20850
+ getRecorderHeight(responsive, useBoundingClientRect) {
20851
+ const recorderElement = this.context.getRecorderElement();
20852
+ const userMedia = this.context.getUserMedia();
20853
+ let recorderHeight;
20854
+ if (recorderElement && useBoundingClientRect) {
20855
+ const height = recorderElement.getBoundingClientRect().height;
20856
+ recorderHeight = {
20857
+ unit: "px",
20858
+ value: height
20859
+ };
20860
+ } else if (userMedia) {
20861
+ let height = userMedia.getRawHeight(responsive);
20862
+ if (void 0 !== height && this.context.options.video.width) {
20863
+ const ratio = this.getRatio();
20864
+ if (void 0 !== ratio) {
20865
+ const idealHeight = this.context.options.video.width * ratio;
20866
+ height = Math.min(idealHeight, height);
20867
+ }
20868
+ }
20869
+ recorderHeight = {
20870
+ unit: "px",
20871
+ value: height
20872
+ };
20873
+ } else if (responsive && this.context.options.video.height) recorderHeight = this.calculateHeight(responsive);
20874
+ else if (this.context.options.video.height) {
20875
+ const height = this.context.options.video.height;
20876
+ recorderHeight = {
20877
+ unit: "px",
20878
+ value: height
20879
+ };
20763
20880
  }
20764
- this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? string_pretty(params) : ""})`);
20765
- if (this.showUserMedia()) try {
20766
- this.userMedia.init(localStream, ()=>{
20767
- if (!this.unloaded && requestId === this.mediaRequestId) this.onUserMediaReady(params);
20768
- }, this.onAudioSample.bind(this), (err)=>{
20769
- if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(err, {
20770
- audio: isAudioEnabled(this.options)
20771
- });
20772
- }, params);
20773
- } catch (exc) {
20774
- localStream.getTracks().forEach((track)=>{
20775
- track.stop();
20776
- });
20777
- this.userMediaLoading = false;
20778
- this.clearUserMediaTimeout();
20779
- this.userMediaErrorCallback(exc, {
20780
- audio: isAudioEnabled(this.options)
20781
- });
20881
+ return recorderHeight;
20882
+ }
20883
+ getRatio() {
20884
+ const userMedia = this.context.getUserMedia();
20885
+ let ratio;
20886
+ if (userMedia) {
20887
+ const userMediaVideoWidth = userMedia.getVideoWidth();
20888
+ const userMediaVideoHeight = userMedia.getVideoHeight();
20889
+ if (!userMediaVideoWidth || userMediaVideoWidth < 1) ratio = this.context.visuals.getRatio();
20890
+ else if (userMediaVideoHeight) ratio = userMediaVideoHeight / userMediaVideoWidth;
20891
+ } else ratio = dimensions_getRatio(this.context.options);
20892
+ return ratio;
20893
+ }
20894
+ calculateWidth(responsive) {
20895
+ const userMedia = this.context.getUserMedia();
20896
+ const recorderElement = this.context.getRecorderElement();
20897
+ let videoHeight;
20898
+ if (userMedia) videoHeight = userMedia.getVideoHeight();
20899
+ else if (recorderElement) videoHeight = recorderElement.videoHeight || recorderElement.height;
20900
+ return dimensions_calculateWidth(responsive, this.context.options, videoHeight, this.getRatio());
20901
+ }
20902
+ calculateHeight(responsive) {
20903
+ const userMedia = this.context.getUserMedia();
20904
+ const recorderElement = this.context.getRecorderElement();
20905
+ let videoDimension;
20906
+ if (userMedia) {
20907
+ const videoHeight = userMedia.getVideoHeight();
20908
+ videoDimension = {
20909
+ value: videoHeight,
20910
+ unit: "px"
20911
+ };
20912
+ } else if (recorderElement) {
20913
+ const videoHeight = recorderElement.videoHeight || recorderElement.height;
20914
+ videoDimension = {
20915
+ value: videoHeight,
20916
+ unit: "px"
20917
+ };
20918
+ } else videoDimension = dimensions_calculateHeight(responsive, void 0, this.context.options, this.getRatio(), recorderElement);
20919
+ return videoDimension;
20920
+ }
20921
+ constructor(context){
20922
+ dimensions_define_property(this, "context", void 0);
20923
+ this.context = context;
20924
+ }
20925
+ }
20926
+ const recorder_dimensions = RecorderDimensions;
20927
+ function getFirstVideoTrack(localMediaStream) {
20928
+ const videoTracks = localMediaStream.getVideoTracks();
20929
+ let videoTrack;
20930
+ if (videoTracks[0]) videoTrack = videoTracks[0];
20931
+ return videoTrack;
20932
+ }
20933
+ const media_getFirstVideoTrack = getFirstVideoTrack;
20934
+ const MEDIA_EVENTS = [
20935
+ "loadstart",
20936
+ "suspend",
20937
+ "progress",
20938
+ "abort",
20939
+ "emptied",
20940
+ "stalled",
20941
+ "pause",
20942
+ "loadeddata",
20943
+ "waiting",
20944
+ "playing",
20945
+ "canplay",
20946
+ "canplaythrough",
20947
+ "seeking",
20948
+ "seeked",
20949
+ "ended",
20950
+ "ratechange",
20951
+ "durationchange",
20952
+ "volumechange"
20953
+ ];
20954
+ const mediaEvents = MEDIA_EVENTS;
20955
+ function esm_define_property(obj, key, value) {
20956
+ if (key in obj) Object.defineProperty(obj, key, {
20957
+ value: value,
20958
+ enumerable: true,
20959
+ configurable: true,
20960
+ writable: true
20961
+ });
20962
+ else obj[key] = value;
20963
+ return obj;
20964
+ }
20965
+ class AudioSample {
20966
+ toBuffer() {
20967
+ const l = this.float32Array.length;
20968
+ const arr = new Int16Array(l);
20969
+ this.float32Array.forEach((sample, i)=>{
20970
+ const clampedSample = Number.isFinite(sample) ? Math.max(-1, sample) : sample;
20971
+ arr[i] = 0x7fff * Math.min(1, clampedSample);
20972
+ });
20973
+ return typedarray_to_buffer(arr);
20974
+ }
20975
+ constructor(float32Array){
20976
+ esm_define_property(this, "float32Array", void 0);
20977
+ this.float32Array = float32Array;
20978
+ }
20979
+ }
20980
+ const esm_src = AudioSample;
20981
+ const esm = esm_src;
20982
+ var is_power_of_two = __webpack_require__("dZ");
20983
+ var is_power_of_two_default = /*#__PURE__*/ __webpack_require__.n(is_power_of_two);
20984
+ const AUDIO_RECORDER_PROCESSOR_NAME = "videomail-pcm-recorder";
20985
+ function registerAudioRecorderProcessor(name, Processor, register) {
20986
+ class PcmRecorderProcessor extends Processor {
20987
+ process(inputs) {
20988
+ var _inputs_;
20989
+ if (this.recordingId < 0) return false;
20990
+ const channel = null == (_inputs_ = inputs[0]) ? void 0 : _inputs_[0];
20991
+ if (!channel || 0 === this.recordingId) return true;
20992
+ let inputOffset = 0;
20993
+ while(inputOffset < channel.length){
20994
+ const count = Math.min(this.samples.length - this.offset, channel.length - inputOffset);
20995
+ this.samples.set(channel.subarray(inputOffset, inputOffset + count), this.offset);
20996
+ this.offset += count;
20997
+ inputOffset += count;
20998
+ if (this.offset === this.samples.length) {
20999
+ const samples = this.samples;
21000
+ this.port.postMessage({
21001
+ recordingId: this.recordingId,
21002
+ samples
21003
+ }, [
21004
+ samples.buffer
21005
+ ]);
21006
+ this.samples = new Float32Array(this.offset);
21007
+ this.offset = 0;
21008
+ }
21009
+ }
21010
+ return true;
20782
21011
  }
20783
- else {
20784
- localStream.getTracks().forEach((track)=>{
20785
- track.stop();
20786
- });
20787
- this.userMediaLoading = false;
20788
- this.clearUserMediaTimeout();
20789
- this.cancelCameraRecovery();
21012
+ constructor(options){
21013
+ super();
21014
+ this.samples = new Float32Array(options.processorOptions.bufferSize);
21015
+ this.offset = 0;
21016
+ this.recordingId = 0;
21017
+ this.port.onmessage = (event)=>{
21018
+ const recordingId = event.data;
21019
+ if ("number" != typeof recordingId) return;
21020
+ if (!Number.isSafeInteger(recordingId) || recordingId < -1) return;
21021
+ this.recordingId = recordingId;
21022
+ this.offset = 0;
21023
+ };
20790
21024
  }
20791
21025
  }
20792
- loadGenuineUserMedia(params) {
20793
- this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? string_pretty(params) : ""})`);
20794
- this.emit("ASKING_WEBCAM_PERMISSION");
20795
- this.userMediaLoading = true;
20796
- const requestId = ++this.mediaRequestId;
20797
- const constraints = {
20798
- video: {
20799
- frameRate: {
20800
- ideal: this.options.video.fps
20801
- }
20802
- },
20803
- audio: isAudioEnabled(this.options)
20804
- };
20805
- this.configureVideoConstraints(constraints, params);
20806
- this.options.logger.debug(string_summarize("Recorder: our webcam constraints are:", constraints));
20807
- this.options.logger.debug(string_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
20808
- const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
20809
- streamPromise.then((localStream)=>{
20810
- if (this.unloaded || requestId !== this.mediaRequestId) return void localStream.getTracks().forEach((track)=>{
20811
- track.stop();
20812
- });
20813
- this.getUserMediaCallback(localStream, params, requestId);
20814
- }).catch((reason)=>{
20815
- if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(reason, constraints);
21026
+ register(name, PcmRecorderProcessor);
21027
+ }
21028
+ function AudioRecorder_asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
21029
+ try {
21030
+ var info = gen[key](arg);
21031
+ var value = info.value;
21032
+ } catch (error) {
21033
+ reject(error);
21034
+ return;
21035
+ }
21036
+ if (info.done) resolve(value);
21037
+ else Promise.resolve(value).then(_next, _throw);
21038
+ }
21039
+ function AudioRecorder_async_to_generator(fn) {
21040
+ return function() {
21041
+ var self1 = this, args = arguments;
21042
+ return new Promise(function(resolve, reject) {
21043
+ var gen = fn.apply(self1, args);
21044
+ function _next(value) {
21045
+ AudioRecorder_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
21046
+ }
21047
+ function _throw(err) {
21048
+ AudioRecorder_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
21049
+ }
21050
+ _next(void 0);
21051
+ });
21052
+ };
21053
+ }
21054
+ function AudioRecorder_define_property(obj, key, value) {
21055
+ if (key in obj) Object.defineProperty(obj, key, {
21056
+ value: value,
21057
+ enumerable: true,
21058
+ configurable: true,
21059
+ writable: true
21060
+ });
21061
+ else obj[key] = value;
21062
+ return obj;
21063
+ }
21064
+ const CHANNELS = 1;
21065
+ const DEFAULT_BUFFER_SIZE = 2048;
21066
+ const MIN_BUFFER_SIZE = 256;
21067
+ const MAX_BUFFER_SIZE = 16384;
21068
+ class AudioRecorder extends util_Despot {
21069
+ getAudioContext() {
21070
+ if (!this.vcAudioContext) this.vcAudioContext = new window.AudioContext();
21071
+ return this.vcAudioContext;
21072
+ }
21073
+ getBufferSize() {
21074
+ const configuredSize = this.options.audio.bufferSize;
21075
+ const bufferSize = "auto" === configuredSize ? DEFAULT_BUFFER_SIZE : configuredSize;
21076
+ if (void 0 === bufferSize) throw error_createError({
21077
+ message: "Audio buffer size is required.",
21078
+ options: this.options
21079
+ });
21080
+ const validBufferSize = Number.isInteger(bufferSize) && bufferSize >= MIN_BUFFER_SIZE && bufferSize <= MAX_BUFFER_SIZE && is_power_of_two_default()(bufferSize);
21081
+ if (!validBufferSize) throw error_createError({
21082
+ message: `Audio buffer size must be a power of two between ${MIN_BUFFER_SIZE} and ${MAX_BUFFER_SIZE}. The current buffer size is ${bufferSize}.`,
21083
+ options: this.options
20816
21084
  });
21085
+ return bufferSize;
20817
21086
  }
20818
- configureVideoConstraints(constraints, params) {
20819
- const videoConstraints = "object" == typeof constraints.video ? constraints.video : void 0;
20820
- if (!videoConstraints) return;
20821
- if ((null == params ? void 0 : params.switchingFacingMode) || (null == params ? void 0 : params.recovering)) videoConstraints.facingMode = params.switchingFacingMode || this.facingMode;
20822
- if (this.options.video.width) videoConstraints.width = {
20823
- ideal: this.options.video.width
20824
- };
20825
- else {
20826
- const limitedWidth = this.limitWidth();
20827
- if (null == limitedWidth ? void 0 : limitedWidth.value) videoConstraints.width = {
20828
- ideal: limitedWidth.value
20829
- };
21087
+ validateVolume() {
21088
+ const { volume } = this.options.audio;
21089
+ const validVolume = Number.isFinite(volume) && volume >= 0 && volume <= 1;
21090
+ if (!validVolume) throw error_createError({
21091
+ message: `Audio volume must be between zero and one. The current volume is ${volume}.`,
21092
+ options: this.options
21093
+ });
21094
+ }
21095
+ revokeModuleUrl() {
21096
+ if (this.moduleUrl) {
21097
+ URL.revokeObjectURL(this.moduleUrl);
21098
+ this.moduleUrl = void 0;
20830
21099
  }
20831
- if (this.options.video.height) videoConstraints.height = {
20832
- ideal: this.options.video.height
20833
- };
20834
21100
  }
20835
- loadUserMedia(params) {
20836
- if (this.userMediaLoaded) {
20837
- this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already loaded");
20838
- this.onUserMediaReady(params);
20839
- return;
20840
- } else if (this.userMediaLoading) return void this.options.logger.debug("Recorder: skipping loadUserMedia() because it is already asking for permission");
20841
- this.options.logger.debug(`Recorder: loadUserMedia(${params ? string_pretty(params) : ""})`);
20842
- this.emit("LOADING_USER_MEDIA");
20843
- try {
20844
- if (!navigator.mediaDevices) throw new Error("No media devices are available.");
20845
- this.userMediaTimeout = window.setTimeout(()=>{
20846
- if (!this.userMediaLoaded) {
20847
- this.userMediaLoading = false;
20848
- const err = util_getBrowser(this.options).getNoAccessIssue();
20849
- this.userMediaErrorCallback(err, {
20850
- audio: isAudioEnabled(this.options)
20851
- });
20852
- }
20853
- }, this.options.timeouts.userMedia);
20854
- this.userMediaLoading = true;
20855
- this.loadGenuineUserMedia(params);
20856
- } catch (exc) {
20857
- this.clearUserMediaTimeout();
20858
- this.options.logger.debug("Recorder: failed to load genuine user media");
20859
- this.userMediaLoading = false;
20860
- if (this.cameraRecovery) return void this.failCameraRecovery(exc);
20861
- const errorListeners = this.getListeners("ERROR");
20862
- if (null == errorListeners ? void 0 : errorListeners.length) this.emit("ERROR", {
20863
- exc
20864
- });
20865
- else {
20866
- this.options.logger.debug("Recorder: no error listeners attached but throwing exception further");
20867
- throw exc;
21101
+ loadWorklet(context) {
21102
+ return AudioRecorder_async_to_generator(function*() {
21103
+ const source = `(${registerAudioRecorderProcessor.toString()})(${JSON.stringify(AUDIO_RECORDER_PROCESSOR_NAME)}, AudioWorkletProcessor, registerProcessor);`;
21104
+ const moduleUrl = URL.createObjectURL(new Blob([
21105
+ source
21106
+ ], {
21107
+ type: "text/javascript"
21108
+ }));
21109
+ this.moduleUrl = moduleUrl;
21110
+ try {
21111
+ yield context.audioWorklet.addModule(moduleUrl);
21112
+ } finally{
21113
+ if (this.moduleUrl === moduleUrl) this.revokeModuleUrl();
20868
21114
  }
21115
+ }).call(this);
21116
+ }
21117
+ onAudioProcess(event) {
21118
+ const callback = this.audioCallback;
21119
+ if (!callback) return;
21120
+ if (!this.userMedia.isRecording() || this.userMedia.isPaused()) return;
21121
+ const message = event.data;
21122
+ if ("object" != typeof message || null === message) return;
21123
+ if (!("recordingId" in message) || message.recordingId !== this.recordingId) return;
21124
+ if (!("samples" in message) || !(message.samples instanceof Float32Array)) return;
21125
+ if (0 === message.samples.length) return;
21126
+ callback(new esm(message.samples));
21127
+ }
21128
+ disconnect() {
21129
+ var _this_audioInput, _this_volume;
21130
+ if (this.worklet) {
21131
+ this.worklet.port.onmessage = null;
21132
+ this.worklet.onprocessorerror = null;
21133
+ this.worklet.port.postMessage(-1);
21134
+ this.worklet.port.close();
21135
+ this.worklet.disconnect();
21136
+ this.worklet = void 0;
20869
21137
  }
21138
+ null == (_this_audioInput = this.audioInput) || _this_audioInput.disconnect();
21139
+ null == (_this_volume = this.volume) || _this_volume.disconnect();
21140
+ this.audioInput = void 0;
21141
+ this.volume = void 0;
21142
+ this.audioCallback = void 0;
21143
+ this.recordingId = 0;
20870
21144
  }
20871
- executeCommand(command) {
20872
- if (this.unloaded) return;
20873
- try {
20874
- if (command.args) {
20875
- const debugLine = string_summarize(`Server commanded: ${command.command} with:`, command.args);
20876
- this.options.logger.debug(debugLine);
20877
- } else this.options.logger.debug(`Server commanded: ${command.command}`);
20878
- switch(command.command){
20879
- case "ready":
20880
- this.emit("SERVER_READY");
20881
- if (!this.userMediaTimeout) if (this.options.loadUserMediaOnRecord) this.show();
20882
- else this.loadUserMedia();
20883
- break;
20884
- case "preview":
20885
- this.preview(command.args);
20886
- break;
20887
- case "error":
20888
- {
20889
- var _command_args_err, _command_args, _command_args1;
20890
- let explanation = "(No explanation given)";
20891
- if (null == (_command_args = command.args) ? void 0 : null == (_command_args_err = _command_args.err) ? void 0 : _command_args_err.message) explanation = command.args.err.message;
20892
- const err = error_createError({
20893
- message: "Websocket error from Videomail server",
20894
- explanation,
20895
- err: deserializeError(null == (_command_args1 = command.args) ? void 0 : _command_args1.err),
20896
- options: this.options
20897
- });
20898
- this.emit("ERROR", {
20899
- err
20900
- });
20901
- break;
21145
+ init(localMediaStream) {
21146
+ return AudioRecorder_async_to_generator(function*() {
21147
+ var _this_workletReady;
21148
+ this.options.logger.debug("AudioRecorder: init()");
21149
+ const initializationId = ++this.initializationId;
21150
+ this.disconnect();
21151
+ const bufferSize = this.getBufferSize();
21152
+ this.validateVolume();
21153
+ if ("function" != typeof window.AudioContext) throw error_createError({
21154
+ message: "Audio recording requires Web Audio support.",
21155
+ options: this.options
21156
+ });
21157
+ const context = this.getAudioContext();
21158
+ if (void 0 === context.audioWorklet || "function" != typeof window.AudioWorkletNode) throw error_createError({
21159
+ message: "Audio recording requires AudioWorklet support in a secure context.",
21160
+ options: this.options
21161
+ });
21162
+ const workletReady = null != (_this_workletReady = this.workletReady) ? _this_workletReady : this.workletReady = this.loadWorklet(context);
21163
+ try {
21164
+ yield workletReady;
21165
+ } catch (exc) {
21166
+ if (this.workletReady === workletReady) this.workletReady = void 0;
21167
+ if (initializationId !== this.initializationId) return;
21168
+ throw error_createError({
21169
+ message: "Unable to load the audio recorder worklet. Check AudioWorklet and Content Security Policy support for blob: scripts.",
21170
+ cause: exc,
21171
+ options: this.options
21172
+ });
21173
+ }
21174
+ if (initializationId !== this.initializationId) return;
21175
+ try {
21176
+ this.audioInput = context.createMediaStreamSource(localMediaStream);
21177
+ } catch (exc) {
21178
+ throw error_createError({
21179
+ message: "Webcam has no audio",
21180
+ exc,
21181
+ options: this.options
21182
+ });
21183
+ }
21184
+ try {
21185
+ this.volume = context.createGain();
21186
+ this.volume.gain.value = this.options.audio.volume;
21187
+ this.worklet = new window.AudioWorkletNode(context, AUDIO_RECORDER_PROCESSOR_NAME, {
21188
+ channelCount: CHANNELS,
21189
+ channelCountMode: "explicit",
21190
+ outputChannelCount: [
21191
+ CHANNELS
21192
+ ],
21193
+ processorOptions: {
21194
+ bufferSize
20902
21195
  }
20903
- case "confirmFrame":
20904
- this.updateFrameProgress(command.args);
20905
- break;
20906
- case "confirmSample":
20907
- this.updateSampleProgress(command.args);
20908
- break;
20909
- case "beginAudioEncoding":
20910
- this.emit("BEGIN_AUDIO_ENCODING");
20911
- break;
20912
- case "beginVideoEncoding":
20913
- this.emit("BEGIN_VIDEO_ENCODING");
20914
- break;
20915
- default:
20916
- {
20917
- const err = error_createError({
20918
- message: `Unknown server command: ${command.command}`,
21196
+ });
21197
+ this.worklet.port.onmessage = (event)=>{
21198
+ this.onAudioProcess(event);
21199
+ };
21200
+ this.worklet.onprocessorerror = ()=>{
21201
+ this.stop();
21202
+ this.emit("ERROR", {
21203
+ err: error_createError({
21204
+ message: "Audio recording processor failed.",
20919
21205
  options: this.options
20920
- });
20921
- this.emit("ERROR", {
20922
- err
20923
- });
20924
- break;
20925
- }
21206
+ })
21207
+ });
21208
+ };
21209
+ this.audioInput.connect(this.volume);
21210
+ this.volume.connect(this.worklet);
21211
+ this.worklet.connect(context.destination);
21212
+ } catch (exc) {
21213
+ this.disconnect();
21214
+ throw error_createError({
21215
+ message: "Failed to connect the audio recorder.",
21216
+ exc,
21217
+ options: this.options
21218
+ });
20926
21219
  }
20927
- } catch (exc) {
20928
- this.emit("ERROR", {
20929
- exc
20930
- });
20931
- }
21220
+ }).call(this);
20932
21221
  }
20933
- isNotifying() {
20934
- return this.visuals.isNotifying();
21222
+ record(cb) {
21223
+ this.options.logger.debug("AudioRecorder: record()");
21224
+ this.audioCallback = cb;
21225
+ this.updateRecordingState();
20935
21226
  }
20936
- isHidden() {
20937
- return !this.recorderElement || html_isHidden(this.recorderElement);
21227
+ updateRecordingState() {
21228
+ if (!this.worklet) return;
21229
+ const shouldRecord = Boolean(this.audioCallback) && this.userMedia.isRecording() && !this.userMedia.isPaused();
21230
+ this.recordingId = shouldRecord ? ++this.recordingSequence : 0;
21231
+ this.worklet.port.postMessage(this.recordingId);
20938
21232
  }
20939
- writeCommand(command, args, cb) {
20940
- if (this.connected) {
20941
- if (this.stream) {
20942
- if (args) this.options.logger.debug(`$ ${command} with ${string_pretty(args)}`);
20943
- else this.options.logger.debug(`$ ${command}`);
20944
- const commandObj = {
20945
- command,
20946
- args
20947
- };
20948
- this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
20949
- onFlushedCallback: ()=>{
20950
- queueMicrotask(cb);
20951
- }
20952
- } : void 0);
21233
+ resume() {
21234
+ return AudioRecorder_async_to_generator(function*() {
21235
+ const context = this.vcAudioContext;
21236
+ if (!context) return;
21237
+ if ("running" !== context.state) yield context.resume();
21238
+ }).call(this);
21239
+ }
21240
+ stop() {
21241
+ this.options.logger.debug("AudioRecorder: stop()");
21242
+ this.initializationId++;
21243
+ this.disconnect();
21244
+ this.revokeModuleUrl();
21245
+ const context = this.vcAudioContext;
21246
+ this.vcAudioContext = void 0;
21247
+ this.workletReady = void 0;
21248
+ if (context && "closed" !== context.state) context.close().then(()=>{
21249
+ this.options.logger.debug("AudioRecorder: audio context is closed");
21250
+ }).catch((err)=>{
21251
+ if (err instanceof Error) return void this.options.logger.error(error_createError({
21252
+ err,
21253
+ options: this.options
21254
+ }));
21255
+ this.options.logger.error(err);
21256
+ });
21257
+ }
21258
+ getSampleRate() {
21259
+ if ("function" == typeof window.AudioContext) return this.getAudioContext().sampleRate;
21260
+ return -1;
21261
+ }
21262
+ constructor(userMedia, options){
21263
+ super("AudioRecorder", options), AudioRecorder_define_property(this, "userMedia", void 0), AudioRecorder_define_property(this, "worklet", void 0), AudioRecorder_define_property(this, "audioInput", void 0), AudioRecorder_define_property(this, "volume", void 0), AudioRecorder_define_property(this, "vcAudioContext", void 0), AudioRecorder_define_property(this, "workletReady", void 0), AudioRecorder_define_property(this, "moduleUrl", void 0), AudioRecorder_define_property(this, "audioCallback", void 0), AudioRecorder_define_property(this, "initializationId", 0), AudioRecorder_define_property(this, "recordingSequence", 0), AudioRecorder_define_property(this, "recordingId", 0);
21264
+ this.userMedia = userMedia;
21265
+ }
21266
+ }
21267
+ const media_AudioRecorder = AudioRecorder;
21268
+ const VIRTUAL_KEYWORDS = [
21269
+ "obs",
21270
+ "virtual",
21271
+ "manycam",
21272
+ "vcam",
21273
+ "splitcam",
21274
+ "droidcam"
21275
+ ];
21276
+ function isVirtualCamera(videoTrack) {
21277
+ const capabilities = videoTrack.getCapabilities();
21278
+ if (!capabilities.frameRate) return true;
21279
+ const isVirtual = VIRTUAL_KEYWORDS.some((keyword)=>videoTrack.label.toLowerCase().includes(keyword));
21280
+ return isVirtual;
21281
+ }
21282
+ const util_isVirtualCamera = isVirtualCamera;
21283
+ function userMedia_asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
21284
+ try {
21285
+ var info = gen[key](arg);
21286
+ var value = info.value;
21287
+ } catch (error) {
21288
+ reject(error);
21289
+ return;
21290
+ }
21291
+ if (info.done) resolve(value);
21292
+ else Promise.resolve(value).then(_next, _throw);
21293
+ }
21294
+ function userMedia_async_to_generator(fn) {
21295
+ return function() {
21296
+ var self1 = this, args = arguments;
21297
+ return new Promise(function(resolve, reject) {
21298
+ var gen = fn.apply(self1, args);
21299
+ function _next(value) {
21300
+ userMedia_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
20953
21301
  }
20954
- } else {
20955
- this.options.logger.debug(`Reconnecting for the command ${command} …`);
20956
- this.initSocket(()=>{
20957
- this.writeCommand(command, args, cb);
21302
+ function _throw(err) {
21303
+ userMedia_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
21304
+ }
21305
+ _next(void 0);
21306
+ });
21307
+ };
21308
+ }
21309
+ function userMedia_define_property(obj, key, value) {
21310
+ if (key in obj) Object.defineProperty(obj, key, {
21311
+ value: value,
21312
+ enumerable: true,
21313
+ configurable: true,
21314
+ writable: true
21315
+ });
21316
+ else obj[key] = value;
21317
+ return obj;
21318
+ }
21319
+ const EVENT_ASCII = "|—O—|";
21320
+ class UserMedia extends util_Despot {
21321
+ attachMediaStream(stream) {
21322
+ if (this.rawVisualUserMedia) {
21323
+ this.rawVisualUserMedia.srcObject = stream;
21324
+ const previousStream = this.currentVisualStream;
21325
+ this.currentVisualStream = stream;
21326
+ if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
21327
+ track.stop();
20958
21328
  });
20959
- }
21329
+ } else throw error_createError({
21330
+ message: "Error attaching stream to element.",
21331
+ explanation: "Contact the developer about this",
21332
+ options: this.options
21333
+ });
20960
21334
  }
20961
- cancelAnimationFrame() {
20962
- var _this_loop;
20963
- null == (_this_loop = this.loop) || _this_loop.dispose();
21335
+ setVisualStream(localMediaStream) {
21336
+ if (localMediaStream) this.attachMediaStream(localMediaStream);
21337
+ else {
21338
+ if (this.rawVisualUserMedia) {
21339
+ this.rawVisualUserMedia.srcObject = null;
21340
+ this.rawVisualUserMedia.removeAttribute("src");
21341
+ }
21342
+ this.currentVisualStream = void 0;
21343
+ }
20964
21344
  }
20965
- getIntervalSum() {
20966
- if (!this.loop) return;
20967
- return this.loop.getElapsedTime();
21345
+ restorePlayback() {
21346
+ return userMedia_async_to_generator(function*() {
21347
+ var _this_audioRecorder;
21348
+ if (!this.rawVisualUserMedia) throw new Error("No camera preview is available.");
21349
+ yield this.rawVisualUserMedia.play();
21350
+ yield null == (_this_audioRecorder = this.audioRecorder) ? void 0 : _this_audioRecorder.resume();
21351
+ }).call(this);
20968
21352
  }
20969
- getAvgInterval() {
20970
- var _this_getIntervalSum;
20971
- const intervalSum = null != (_this_getIntervalSum = this.getIntervalSum()) ? _this_getIntervalSum : 0;
20972
- if (!this.hasValidInterval(intervalSum)) return;
20973
- return intervalSum / this.framesCount;
21353
+ hasLiveStream() {
21354
+ var _this_currentVisualStream, _this_currentVisualStream1, _this_currentVisualStream2;
21355
+ const videoReady = null == (_this_currentVisualStream = this.currentVisualStream) ? void 0 : _this_currentVisualStream.getVideoTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
21356
+ const audioReady = !isAudioEnabled(this.options) || (null == (_this_currentVisualStream1 = this.currentVisualStream) ? void 0 : _this_currentVisualStream1.getAudioTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted));
21357
+ return Boolean((null == (_this_currentVisualStream2 = this.currentVisualStream) ? void 0 : _this_currentVisualStream2.active) && videoReady && audioReady);
20974
21358
  }
20975
- getAvgFps() {
20976
- var _this_getIntervalSum;
20977
- const intervalSum = null != (_this_getIntervalSum = this.getIntervalSum()) ? _this_getIntervalSum : 0;
20978
- if (!this.hasValidInterval(intervalSum)) return;
20979
- return this.framesCount / intervalSum * 1000;
21359
+ hasEnded() {
21360
+ var _this_rawVisualUserMedia, _this_currentVisualStream;
21361
+ if (null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.ended) return this.rawVisualUserMedia.ended;
21362
+ return !(null == (_this_currentVisualStream = this.currentVisualStream) ? void 0 : _this_currentVisualStream.active);
20980
21363
  }
20981
- hasValidInterval(intervalSum) {
20982
- return intervalSum > 0;
21364
+ hasInvalidDimensions() {
21365
+ if (!this.rawVisualUserMedia) return false;
21366
+ const { videoWidth, videoHeight } = this.rawVisualUserMedia;
21367
+ return videoWidth > 0 && videoWidth < 3 || videoHeight > 0 && videoHeight < 3;
20983
21368
  }
20984
- getRecordingStats() {
20985
- return this.recordingStats;
21369
+ logEvent(eventType, params) {
21370
+ this.options.logger.debug(`UserMedia: ... ${EVENT_ASCII} event ${eventType}: ${string_pretty(params)}`);
20986
21371
  }
20987
- getAudioSampleRate() {
20988
- var _this_recordingAudioSampleRate;
20989
- var _this_userMedia;
20990
- return null != (_this_recordingAudioSampleRate = this.recordingAudioSampleRate) ? _this_recordingAudioSampleRate : null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.getAudioSampleRate();
21372
+ unloadRemainingEventListeners() {
21373
+ var _this_clearInitializationListeners, _this, _this_rawVisualUserMedia;
21374
+ this.options.logger.debug("UserMedia: unloadRemainingEventListeners()");
21375
+ null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
21376
+ mediaEvents.forEach((eventName)=>{
21377
+ var _this_rawVisualUserMedia;
21378
+ null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener(eventName, this.outputEvent);
21379
+ });
21380
+ null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener("error", this.handleMediaError);
20991
21381
  }
20992
- stop(params) {
20993
- var _this_loop;
20994
- this.cancelCameraRecovery();
20995
- if (!this.userMedia) throw new Error("No user media defined, unable to stop");
20996
- this.options.logger.debug(`Recorder: stop(${params ? string_pretty(params) : ""})`);
20997
- this.recordingActive = false;
20998
- const limitReached = null == params ? void 0 : params.limitReached;
20999
- this.emit("STOPPING", {
21000
- limitReached
21382
+ audioRecord(audioCallback) {
21383
+ var _this_audioRecorder;
21384
+ this.removeListener("SENDING_FIRST_FRAME");
21385
+ null == (_this_audioRecorder = this.audioRecorder) || _this_audioRecorder.record(audioCallback);
21386
+ }
21387
+ init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, params) {
21388
+ var _this_clearInitializationListeners, _this;
21389
+ null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
21390
+ this.stop(localMediaStream, {
21391
+ aboutToInitialize: true,
21392
+ preserveRecordingState: Boolean((null == params ? void 0 : params.switchingFacingMode) || (null == params ? void 0 : params.recovering))
21001
21393
  });
21002
- null == (_this_loop = this.loop) || _this_loop.complete();
21003
- this.stopTimeout = window.setTimeout(()=>{
21004
- this.stopTimeout = void 0;
21005
- this.stopTime = Date.now();
21006
- const videoType = this.replay.getVideoType();
21007
- if (!videoType) throw new Error("Unable to video record when no video type is defined.");
21008
- this.recordingStats = {
21009
- avgFps: this.getAvgFps(),
21010
- wantedFps: this.options.video.fps,
21011
- avgInterval: this.getAvgInterval(),
21012
- wantedInterval: 1e3 / this.options.video.fps,
21013
- intervalSum: this.getIntervalSum(),
21014
- framesCount: this.framesCount,
21015
- videoType
21016
- };
21017
- if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
21018
- if (isAudioEnabled(this.options) && this.userMedia) {
21019
- var _this_recordingAudioSampleRate;
21020
- this.recordingStats.samplesCount = this.samplesCount;
21021
- this.recordingStats.sampleRate = null != (_this_recordingAudioSampleRate = this.recordingAudioSampleRate) ? _this_recordingAudioSampleRate : this.userMedia.getAudioSampleRate();
21394
+ const initializationId = ++this.initializationId;
21395
+ this.onPlayReached = false;
21396
+ this.onLoadedMetaDataReached = false;
21397
+ this.playingPromiseReached = false;
21398
+ this.videoTrackLabel = void 0;
21399
+ if (isAudioEnabled(this.options)) null != this.audioRecorder || (this.audioRecorder = new media_AudioRecorder(this, this.options));
21400
+ const unloadAllEventListeners = ()=>{
21401
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
21402
+ this.options.logger.debug("UserMedia: unloadAllEventListeners()");
21403
+ this.unloadRemainingEventListeners();
21404
+ this.removeListener("SENDING_FIRST_FRAME");
21405
+ null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener("play", onPlay);
21406
+ null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.removeEventListener("loadedmetadata", onLoadedMetaData);
21407
+ };
21408
+ this.clearInitializationListeners = ()=>{
21409
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
21410
+ null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.removeEventListener("play", onPlay);
21411
+ null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.removeEventListener("loadedmetadata", onLoadedMetaData);
21412
+ this.clearInitializationListeners = void 0;
21413
+ };
21414
+ const play = ()=>{
21415
+ try {
21416
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
21417
+ null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.load();
21418
+ if (null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.paused) {
21419
+ 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)}`);
21420
+ this.rawVisualUserMedia.play().then(()=>{
21421
+ if (initializationId !== this.initializationId) return;
21422
+ if (!this.playingPromiseReached) {
21423
+ this.options.logger.debug("UserMedia: play promise successful. Playing now.");
21424
+ this.playingPromiseReached = true;
21425
+ }
21426
+ }).catch((exc)=>{
21427
+ if (initializationId !== this.initializationId) return;
21428
+ if (exc instanceof Error && !(null == params ? void 0 : params.recovering)) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
21429
+ else {
21430
+ unloadAllEventListeners();
21431
+ endedEarlyCallback(error_createError({
21432
+ message: "Failed to play user media upon play event.",
21433
+ exc,
21434
+ options: this.options
21435
+ }));
21436
+ }
21437
+ });
21438
+ }
21439
+ } catch (exc) {
21440
+ unloadAllEventListeners();
21441
+ endedEarlyCallback(exc);
21022
21442
  }
21023
- this.writeCommand("stop", this.recordingStats, ()=>{
21024
- this.emit("STOPPED", {
21025
- recordingStats: this.recordingStats
21443
+ };
21444
+ const fireCallbacks = ()=>{
21445
+ var _this_rawVisualUserMedia;
21446
+ const readyState = null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState;
21447
+ this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
21448
+ if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) {
21449
+ const initializeAudio = (audioRecorder)=>userMedia_async_to_generator(function*() {
21450
+ try {
21451
+ yield audioRecorder.init(localMediaStream);
21452
+ if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
21453
+ const onAudioReady = ()=>{
21454
+ var _this_clearInitializationListeners, _this;
21455
+ if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
21456
+ if (this.recording) this.audioRecord(audioCallback);
21457
+ else this.on("SENDING_FIRST_FRAME", ()=>{
21458
+ this.audioRecord(audioCallback);
21459
+ });
21460
+ null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
21461
+ videoCallback();
21462
+ };
21463
+ if (null == params ? void 0 : params.recovering) yield audioRecorder.resume();
21464
+ onAudioReady();
21465
+ } catch (exc) {
21466
+ if (initializationId !== this.initializationId) return;
21467
+ unloadAllEventListeners();
21468
+ this.stop(void 0, {
21469
+ preserveRecordingState: Boolean(null == params ? void 0 : params.recovering)
21470
+ });
21471
+ endedEarlyCallback(exc);
21472
+ }
21473
+ }).call(this);
21474
+ initializeAudio(this.audioRecorder);
21475
+ } else {
21476
+ var _this_clearInitializationListeners, _this;
21477
+ null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
21478
+ videoCallback();
21479
+ }
21480
+ };
21481
+ const onPlay = ()=>{
21482
+ try {
21483
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3, _this_rawVisualUserMedia4, _this_rawVisualUserMedia5;
21484
+ this.logEvent("play", {
21485
+ readyState: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState,
21486
+ audio: isAudioEnabled(this.options),
21487
+ width: null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.width,
21488
+ height: null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia2.height,
21489
+ videoWidth: null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia3.videoWidth,
21490
+ videoHeight: null == (_this_rawVisualUserMedia4 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia4.videoHeight
21026
21491
  });
21492
+ null == (_this_rawVisualUserMedia5 = this.rawVisualUserMedia) || _this_rawVisualUserMedia5.removeEventListener("play", onPlay);
21493
+ if (this.hasEnded() || this.hasInvalidDimensions()) endedEarlyCallback(error_createError({
21494
+ message: "Already busy",
21495
+ explanation: "Probably another browser window is using your webcam?",
21496
+ options: this.options
21497
+ }));
21498
+ else {
21499
+ this.onPlayReached = true;
21500
+ fireCallbacks();
21501
+ }
21502
+ } catch (exc) {
21503
+ unloadAllEventListeners();
21504
+ endedEarlyCallback(exc);
21505
+ }
21506
+ };
21507
+ const onLoadedMetaData = ()=>{
21508
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3, _this_rawVisualUserMedia4, _this_rawVisualUserMedia5, _this_rawVisualUserMedia6;
21509
+ this.logEvent("loadedmetadata", {
21510
+ readyState: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState,
21511
+ paused: null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.paused,
21512
+ width: null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia2.width,
21513
+ height: null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia3.height,
21514
+ videoWidth: null == (_this_rawVisualUserMedia4 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia4.videoWidth,
21515
+ videoHeight: null == (_this_rawVisualUserMedia5 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia5.videoHeight
21027
21516
  });
21028
- this.reset();
21029
- }, 60);
21030
- }
21031
- back(cb) {
21032
- this.emit("GOING_BACK");
21033
- this.key = void 0;
21034
- this.recordingDimensions = void 0;
21035
- this.unloaded = false;
21036
- this.show();
21037
- this.writeCommand("back", void 0, cb);
21517
+ null == (_this_rawVisualUserMedia6 = this.rawVisualUserMedia) || _this_rawVisualUserMedia6.removeEventListener("loadedmetadata", onLoadedMetaData);
21518
+ if (!this.hasEnded() && !this.hasInvalidDimensions()) {
21519
+ this.emit("LOADED_META_DATA");
21520
+ this.onLoadedMetaDataReached = true;
21521
+ fireCallbacks();
21522
+ }
21523
+ };
21524
+ try {
21525
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3;
21526
+ const videoTrack = media_getFirstVideoTrack(localMediaStream);
21527
+ this.videoTrackLabel = null == videoTrack ? void 0 : videoTrack.label;
21528
+ if (videoTrack) if (videoTrack.enabled) if (util_isVirtualCamera(videoTrack)) throw error_createError({
21529
+ message: "Virtual cameras are not allowed.",
21530
+ explanation: `Please use a real camera, not "${videoTrack.label}".`,
21531
+ options: this.options
21532
+ });
21533
+ else {
21534
+ let description = "";
21535
+ if (videoTrack.label.length > 0) description = description.concat(videoTrack.label);
21536
+ description = description.concat(` with enabled=${videoTrack.enabled}, muted=${videoTrack.muted}, readyState=${videoTrack.readyState}`);
21537
+ this.options.logger.debug(`UserMedia: ${videoTrack.kind} detected. ${description}`);
21538
+ }
21539
+ else throw error_createError({
21540
+ message: "Webcam is disabled",
21541
+ explanation: "The video track seems to be disabled. Enable it in your system.",
21542
+ options: this.options
21543
+ });
21544
+ else this.options.logger.debug("UserMedia: detected (but no video tracks exist");
21545
+ null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.addEventListener("loadedmetadata", onLoadedMetaData);
21546
+ null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.addEventListener("play", onPlay);
21547
+ null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) || _this_rawVisualUserMedia2.removeEventListener("error", this.handleMediaError);
21548
+ null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) || _this_rawVisualUserMedia3.addEventListener("error", this.handleMediaError);
21549
+ this.setVisualStream(localMediaStream);
21550
+ play();
21551
+ } catch (exc) {
21552
+ unloadAllEventListeners();
21553
+ endedEarlyCallback(exc);
21554
+ }
21038
21555
  }
21039
- reInitializeAudio() {
21040
- var _this_userMedia;
21041
- this.options.logger.debug("Recorder: reInitializeAudio()");
21042
- this.clearUserMediaTimeout();
21043
- this.userMediaLoading = false;
21044
- null == (_this_userMedia = this.userMedia) || _this_userMedia.stop();
21045
- this.recordingDimensions = void 0;
21046
- this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
21047
- this.loadUserMedia();
21556
+ getVideoTrackLabel() {
21557
+ return this.videoTrackLabel;
21048
21558
  }
21049
- unload(params) {
21050
- if (this.unloaded || !this.built) return;
21051
- const e = null == params ? void 0 : params.e;
21052
- let cause;
21053
- if (e) cause = e.type;
21054
- const prettyCause = cause ? `, cause: ${cause}` : "";
21055
- this.options.logger.debug(`Recorder: unload()${prettyCause}`);
21056
- this.recordingActive = false;
21057
- this.mediaTransmissionStarted = false;
21058
- this.reset();
21059
- this.recordingDimensions = void 0;
21060
- this.clearUserMediaTimeout();
21061
- this.clearConnectionTimeout();
21062
- this.clearReconnectTimeout();
21063
- this.clearRetryTimeout();
21064
- this.clearStopTimeout();
21065
- this.stopPings();
21066
- this.userMediaLoading = false;
21067
- this.connecting = false;
21068
- this.reconnecting = false;
21069
- this.reconnectAttempts = 0;
21070
- if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
21071
- if (this.submitting) ;
21072
- else if (this.stream) this.discardSocket();
21073
- this.unloaded = true;
21074
- this.built = this.connecting = this.connected = false;
21559
+ isReady() {
21560
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
21561
+ return Boolean((null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.srcObject) || (null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.currentSrc));
21075
21562
  }
21076
- reset() {
21077
- this.cancelCameraRecovery();
21078
- if (!this.unloaded) {
21079
- var _this_userMedia;
21080
- this.options.logger.debug("Recorder: reset()");
21081
- this.emit("RESETTING");
21082
- this.cancelAnimationFrame();
21083
- null == (_this_userMedia = this.userMedia) || _this_userMedia.stop();
21084
- this.replay.reset();
21085
- this.userMediaLoaded = this.key = this.canvas = this.ctx = this.recordingBuffer = void 0;
21563
+ stop(visualStream, params) {
21564
+ try {
21565
+ let chosenStream = visualStream;
21566
+ if (!(null == params ? void 0 : params.aboutToInitialize)) {
21567
+ var _this_clearInitializationListeners, _this, _this_audioRecorder;
21568
+ this.initializationId++;
21569
+ null == (_this_clearInitializationListeners = (_this = this).clearInitializationListeners) || _this_clearInitializationListeners.call(_this);
21570
+ null != chosenStream || (chosenStream = this.currentVisualStream);
21571
+ const tracks = null == chosenStream ? void 0 : chosenStream.getTracks();
21572
+ if (tracks) tracks.forEach((track)=>{
21573
+ track.stop();
21574
+ });
21575
+ this.setVisualStream();
21576
+ null == (_this_audioRecorder = this.audioRecorder) || _this_audioRecorder.stop();
21577
+ this.audioRecorder = void 0;
21578
+ }
21579
+ if (!(null == params ? void 0 : params.preserveRecordingState)) this.paused = this.recording = false;
21580
+ } catch (exc) {
21581
+ this.emit("ERROR", {
21582
+ exc
21583
+ });
21086
21584
  }
21087
21585
  }
21088
- clearUserMediaTimeout() {
21089
- if (void 0 !== this.userMediaTimeout) {
21090
- this.options.logger.debug("Recorder: clearUserMediaTimeout()");
21091
- window.clearTimeout(this.userMediaTimeout);
21092
- this.userMediaTimeout = void 0;
21093
- }
21586
+ createCanvas() {
21587
+ const canvas = document.createElement("canvas");
21588
+ const rawWidthDimension = this.getRawWidth(true);
21589
+ if (rawWidthDimension.value) canvas.width = rawWidthDimension.value;
21590
+ const rawHeight = this.getRawHeight(true);
21591
+ if (rawHeight) canvas.height = rawHeight;
21592
+ return canvas;
21094
21593
  }
21095
- clearStopTimeout() {
21096
- if (void 0 === this.stopTimeout) return;
21097
- window.clearTimeout(this.stopTimeout);
21098
- this.stopTimeout = void 0;
21594
+ getVideoHeight() {
21595
+ if (!this.rawVisualUserMedia) return;
21596
+ return this.rawVisualUserMedia.videoHeight || this.rawVisualUserMedia.height;
21099
21597
  }
21100
- validate() {
21101
- return Boolean(this.key) && void 0 === this.canvas;
21598
+ getVideoWidth() {
21599
+ if (!this.rawVisualUserMedia) return;
21600
+ return this.rawVisualUserMedia.videoWidth || this.rawVisualUserMedia.width;
21102
21601
  }
21103
- isReady() {
21104
- var _this_userMedia;
21105
- return null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.isReady();
21602
+ hasVideoWidth() {
21603
+ const videoWidth = this.getVideoWidth();
21604
+ return videoWidth && videoWidth > 0;
21106
21605
  }
21107
- finishCameraRecovery(restored) {
21108
- this.clearUserMediaTimeout();
21109
- const resolve = this.resolveCameraRecovery;
21110
- const resumeRequested = restored && this.resumeAfterCameraRecovery;
21111
- this.cameraRecovery = void 0;
21112
- this.resolveCameraRecovery = void 0;
21113
- this.resumeAfterCameraRecovery = false;
21114
- null == resolve || resolve(restored);
21115
- if (resumeRequested && this.isPaused()) this.resume();
21606
+ getRawWidth(responsive) {
21607
+ let rawWidth;
21608
+ const widthDimension = {
21609
+ unit: "px"
21610
+ };
21611
+ rawWidth = responsive ? this.getVideoWidth() : this.options.video.width ? this.options.video.width : this.getVideoWidth();
21612
+ if (responsive) {
21613
+ const widthDimension = this.recorder.limitWidth(rawWidth);
21614
+ rawWidth = null == widthDimension ? void 0 : widthDimension.value;
21615
+ }
21616
+ widthDimension.value = rawWidth;
21617
+ return widthDimension;
21116
21618
  }
21117
- cancelCameraRecovery() {
21118
- var _this_userMedia;
21119
- if (!this.cameraRecovery) return;
21120
- this.mediaRequestId++;
21121
- this.userMediaLoading = false;
21122
- this.userMediaLoaded = false;
21123
- this.clearUserMediaTimeout();
21124
- null == (_this_userMedia = this.userMedia) || _this_userMedia.stop(void 0, {
21125
- preserveRecordingState: true
21126
- });
21127
- this.finishCameraRecovery(false);
21619
+ getRawHeight(responsive) {
21620
+ let rawHeight;
21621
+ if (responsive) {
21622
+ rawHeight = this.options.video.height || this.getVideoHeight();
21623
+ const ratio = this.recorder.getRatio();
21624
+ const rawWidth = this.getRawWidth(responsive);
21625
+ if (void 0 !== ratio && rawWidth.value) rawHeight = rawWidth.value * ratio;
21626
+ } else rawHeight = this.options.video.height ? this.options.video.height : this.getVideoHeight();
21627
+ if (responsive) {
21628
+ const heightDimension = this.recorder.limitHeight(rawHeight);
21629
+ rawHeight = heightDimension.value;
21630
+ }
21631
+ return rawHeight;
21128
21632
  }
21129
- failCameraRecovery(exc) {
21130
- this.cancelCameraRecovery();
21131
- const err = error_createError({
21132
- message: "Camera recovery failed",
21133
- explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
21134
- exc,
21135
- options: this.options
21136
- });
21137
- this.emit("CAMERA_RECOVERY_FAILED", {
21138
- err
21139
- });
21633
+ getRawVisuals() {
21634
+ return this.rawVisualUserMedia;
21140
21635
  }
21141
- recoverCamera() {
21142
- if (this.cameraRecovery) return this.cameraRecovery;
21143
- if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
21144
- const liveStream = this.userMedia.hasLiveStream();
21145
- if (liveStream && this.isRecording()) return Promise.resolve(true);
21146
- if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
21147
- const recovery = new Promise((resolve)=>{
21148
- this.resolveCameraRecovery = resolve;
21149
- });
21150
- this.cameraRecovery = recovery;
21151
- this.emit("CAMERA_RECOVERING");
21152
- if (liveStream) {
21153
- this.userMediaTimeout = window.setTimeout(()=>{
21154
- this.failCameraRecovery(new Error("Camera playback recovery timed out."));
21155
- }, this.options.timeouts.userMedia);
21156
- this.userMedia.restorePlayback().then(()=>{
21157
- if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
21158
- }).catch((exc)=>{
21159
- if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
21160
- });
21161
- } else {
21162
- this.userMediaLoaded = false;
21163
- this.loadUserMedia({
21164
- recovering: true
21165
- });
21166
- }
21167
- return recovery;
21636
+ pause() {
21637
+ var _this_audioRecorder;
21638
+ this.paused = true;
21639
+ null == (_this_audioRecorder = this.audioRecorder) || _this_audioRecorder.updateRecordingState();
21168
21640
  }
21169
- pause(params) {
21170
- var _this_userMedia;
21171
- this.cancelCameraRecovery();
21172
- this.resumeAfterDrain = false;
21173
- if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
21174
- else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
21175
- null == (_this_userMedia = this.userMedia) || _this_userMedia.pause();
21176
- this.loop.stop();
21177
- this.emit("PAUSED");
21178
- this.sendPings();
21641
+ isPaused() {
21642
+ return this.paused;
21179
21643
  }
21180
21644
  resume() {
21181
- var _this_userMedia;
21182
- if (this.cameraRecovery || !(null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.hasLiveStream())) {
21183
- this.recoverCamera();
21184
- this.resumeAfterCameraRecovery = Boolean(this.cameraRecovery);
21185
- return;
21186
- }
21187
- this.options.logger.debug(`Recorder: resume() with frame ${this.framesCount}`);
21188
- this.stopPings();
21189
- this.emit("RESUMING");
21190
- this.userMedia.resume();
21191
- if (this.streamBackpressured) this.resumeAfterDrain = true;
21192
- else this.loop.start();
21645
+ var _this_audioRecorder;
21646
+ this.paused = false;
21647
+ null == (_this_audioRecorder = this.audioRecorder) || _this_audioRecorder.updateRecordingState();
21193
21648
  }
21194
- onFlushed(opts) {
21195
- const frameNumber = opts.frameNumber;
21196
- if (1 === frameNumber) this.emit("FIRST_FRAME_SENT");
21649
+ record() {
21650
+ var _this_audioRecorder;
21651
+ this.recording = true;
21652
+ null == (_this_audioRecorder = this.audioRecorder) || _this_audioRecorder.updateRecordingState();
21197
21653
  }
21198
- draw(_deltaTime, elapsedTime) {
21199
- if (this.recordingTransportFailed) return;
21200
- if (!this.userMedia) throw new Error("No user media defined, unable to draw on canvas");
21654
+ isRecording() {
21655
+ return this.recording;
21656
+ }
21657
+ getAudioSampleRate() {
21658
+ if (this.audioRecorder) return this.audioRecorder.getSampleRate();
21659
+ return -1;
21660
+ }
21661
+ getCharacteristics() {
21662
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1, _this_rawVisualUserMedia2, _this_rawVisualUserMedia3, _this_rawVisualUserMedia4;
21663
+ return {
21664
+ audioSampleRate: this.getAudioSampleRate(),
21665
+ muted: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.muted,
21666
+ width: null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia1.width,
21667
+ height: null == (_this_rawVisualUserMedia2 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia2.height,
21668
+ videoWidth: null == (_this_rawVisualUserMedia3 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia3.videoWidth,
21669
+ videoHeight: null == (_this_rawVisualUserMedia4 = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia4.videoHeight
21670
+ };
21671
+ }
21672
+ constructor(recorder, options){
21673
+ super("UserMedia", options), userMedia_define_property(this, "recorder", void 0), userMedia_define_property(this, "rawVisualUserMedia", void 0), userMedia_define_property(this, "paused", false), userMedia_define_property(this, "recording", false), userMedia_define_property(this, "audioRecorder", void 0), userMedia_define_property(this, "currentVisualStream", void 0), userMedia_define_property(this, "onPlayReached", false), userMedia_define_property(this, "onLoadedMetaDataReached", false), userMedia_define_property(this, "playingPromiseReached", false), userMedia_define_property(this, "videoTrackLabel", void 0), userMedia_define_property(this, "clearInitializationListeners", void 0), userMedia_define_property(this, "initializationId", 0), userMedia_define_property(this, "outputEvent", (e)=>{
21674
+ var _this_rawVisualUserMedia, _this_rawVisualUserMedia1;
21675
+ this.logEvent(e.type, {
21676
+ readyState: null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState
21677
+ });
21678
+ null == (_this_rawVisualUserMedia1 = this.rawVisualUserMedia) || _this_rawVisualUserMedia1.removeEventListener(e.type, this.outputEvent);
21679
+ }), userMedia_define_property(this, "handleMediaError", (event)=>{
21680
+ this.options.logger.warn(`Caught video element error event: ${string_pretty(event)}`);
21681
+ });
21682
+ this.recorder = recorder;
21683
+ this.rawVisualUserMedia = recorder.getRawVisualUserMedia();
21684
+ mediaEvents.forEach((eventName)=>{
21685
+ var _this_rawVisualUserMedia;
21686
+ null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) || _this_rawVisualUserMedia.addEventListener(eventName, this.outputEvent, false);
21687
+ });
21688
+ }
21689
+ }
21690
+ const visuals_userMedia = UserMedia;
21691
+ function recorder_define_property(obj, key, value) {
21692
+ if (key in obj) Object.defineProperty(obj, key, {
21693
+ value: value,
21694
+ enumerable: true,
21695
+ configurable: true,
21696
+ writable: true
21697
+ });
21698
+ else obj[key] = value;
21699
+ return obj;
21700
+ }
21701
+ class Recorder extends capture {
21702
+ executeCommand(command) {
21703
+ if (this.unloaded) return;
21201
21704
  try {
21202
- if (!this.isPaused() && this.stream && this.ctx) {
21203
- var _this_frame, _this_recordingBuffer;
21204
- if (0 === this.framesCount) this.emit("SENDING_FIRST_FRAME");
21205
- this.framesCount++;
21206
- const imageSource = this.userMedia.getRawVisuals();
21207
- if (this.canvas && imageSource) this.ctx.drawImage(imageSource, 0, 0, this.canvas.width, this.canvas.height);
21208
- else throw new Error("Unable to draw an image without a defined canvas");
21209
- this.recordingBuffer = null == (_this_frame = this.frame) ? void 0 : _this_frame.toBuffer();
21210
- const recordingBufferLength = null == (_this_recordingBuffer = this.recordingBuffer) ? void 0 : _this_recordingBuffer.length;
21211
- if (recordingBufferLength) {
21212
- if (this.recordingBuffer) {
21213
- const frameControlBuffer = Buffer.from(JSON.stringify({
21214
- frameNumber: this.framesCount
21215
- }));
21216
- const frameBuffer = Buffer.concat([
21217
- this.recordingBuffer,
21218
- frameControlBuffer
21219
- ]);
21220
- this.mediaTransmissionStarted = true;
21221
- this.writeStream(frameBuffer, {
21222
- frameNumber: this.framesCount,
21223
- onFlushedCallback: this.onFlushed.bind(this)
21705
+ if (command.args) {
21706
+ const debugLine = string_summarize(`Server commanded: ${command.command} with:`, command.args);
21707
+ this.options.logger.debug(debugLine);
21708
+ } else this.options.logger.debug(`Server commanded: ${command.command}`);
21709
+ switch(command.command){
21710
+ case "ready":
21711
+ this.emit("SERVER_READY");
21712
+ if (!this.userMediaTimeout) if (this.options.loadUserMediaOnRecord) this.show();
21713
+ else this.loadUserMedia();
21714
+ break;
21715
+ case "preview":
21716
+ this.preview(command.args);
21717
+ break;
21718
+ case "error":
21719
+ {
21720
+ var _command_args_err, _command_args, _command_args1;
21721
+ let explanation = "(No explanation given)";
21722
+ if (null == (_command_args = command.args) ? void 0 : null == (_command_args_err = _command_args.err) ? void 0 : _command_args_err.message) explanation = command.args.err.message;
21723
+ const err = error_createError({
21724
+ message: "Websocket error from Videomail server",
21725
+ explanation,
21726
+ err: deserializeError(null == (_command_args1 = command.args) ? void 0 : _command_args1.err),
21727
+ options: this.options
21224
21728
  });
21225
- this.visuals.checkTimer(elapsedTime);
21729
+ this.emit("ERROR", {
21730
+ err
21731
+ });
21732
+ break;
21733
+ }
21734
+ case "confirmFrame":
21735
+ this.updateFrameProgress(command.args);
21736
+ break;
21737
+ case "confirmSample":
21738
+ this.updateSampleProgress(command.args);
21739
+ break;
21740
+ case "beginAudioEncoding":
21741
+ this.emit("BEGIN_AUDIO_ENCODING");
21742
+ break;
21743
+ case "beginVideoEncoding":
21744
+ this.emit("BEGIN_VIDEO_ENCODING");
21745
+ break;
21746
+ default:
21747
+ {
21748
+ const err = error_createError({
21749
+ message: `Unknown server command: ${command.command}`,
21750
+ options: this.options
21751
+ });
21752
+ this.emit("ERROR", {
21753
+ err
21754
+ });
21755
+ break;
21226
21756
  }
21227
- } else throw error_createError({
21228
- message: "Failed to extract webcam data.",
21229
- options: this.options
21230
- });
21231
21757
  }
21232
21758
  } catch (exc) {
21233
21759
  this.emit("ERROR", {
@@ -21235,156 +21761,34 @@ var __webpack_exports__ = {};
21235
21761
  });
21236
21762
  }
21237
21763
  }
21238
- createLoop() {
21239
- const newLoop = animitter_default()({
21240
- fps: this.options.video.fps
21241
- }, this.draw.bind(this));
21242
- this.originalAnimationFrameObject = newLoop.getRequestAnimationFrameObject();
21243
- return newLoop;
21244
- }
21245
- record() {
21246
- if (this.unloaded || this.cameraRecovery) return;
21247
- if (!this.connected) {
21248
- this.options.logger.debug("Recorder: reconnecting before recording …");
21249
- this.initSocket(()=>{
21250
- this.once("SERVER_READY", ()=>{
21251
- if (this.unloaded) return;
21252
- if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
21253
- else this.once("USER_MEDIA_READY", ()=>{
21254
- if (!this.unloaded) this.record();
21255
- });
21256
- });
21257
- });
21258
- return;
21259
- }
21260
- if (!this.userMediaLoaded) {
21261
- if (this.options.loadUserMediaOnRecord) this.loadUserMedia({
21262
- recordWhenReady: true
21263
- });
21264
- else {
21265
- const err = error_createError({
21266
- message: "Load and enable your camera first",
21267
- options: this.options
21268
- });
21269
- this.emit("ERROR", {
21270
- err
21271
- });
21272
- }
21273
- return;
21274
- }
21275
- this.recordingDimensions = void 0;
21276
- try {
21277
- if (!this.userMedia) throw new Error("No user media defined, unable to create canvas");
21278
- this.canvas = this.userMedia.createCanvas();
21279
- } catch (exc) {
21280
- const err = error_createError({
21281
- exc,
21282
- options: this.options
21283
- });
21284
- this.emit("ERROR", {
21285
- err
21286
- });
21287
- return;
21288
- }
21289
- this.ctx = this.canvas.getContext("2d");
21290
- if (!this.canvas.width) {
21291
- const err = error_createError({
21292
- message: "Canvas has an invalid width.",
21293
- options: this.options
21294
- });
21295
- this.emit("ERROR", {
21296
- err
21297
- });
21298
- return;
21299
- }
21300
- if (!this.canvas.height) {
21301
- const err = error_createError({
21302
- message: "Canvas has an invalid height.",
21303
- options: this.options
21304
- });
21305
- this.emit("ERROR", {
21306
- err
21307
- });
21308
- return;
21309
- }
21310
- this.recordingDimensions = {
21311
- width: this.canvas.width,
21312
- height: this.canvas.height
21313
- };
21314
- this.frame = new canvas_to_buffer_modern_r(this.canvas, this.options.image.types, this.options.image.quality);
21315
- this.options.logger.debug("Recorder: record()");
21316
- this.recordingActive = true;
21317
- this.recordingTransportFailed = false;
21764
+ unload(params) {
21765
+ if (this.unloaded || !this.built) return;
21766
+ const e = null == params ? void 0 : params.e;
21767
+ let cause;
21768
+ if (e) cause = e.type;
21769
+ const prettyCause = cause ? `, cause: ${cause}` : "";
21770
+ this.options.logger.debug(`Recorder: unload()${prettyCause}`);
21771
+ this.recordingActive = false;
21318
21772
  this.mediaTransmissionStarted = false;
21319
- this.recordingAudioSampleRate = void 0;
21320
- this.userMedia.record();
21321
- this.emit("RECORDING", {
21322
- framesCount: this.framesCount
21323
- });
21324
- this.loop.on("update", (_deltaTime, elapsedTime)=>{
21325
- let avgFPS;
21326
- avgFPS = 0 !== elapsedTime ? Math.round(this.framesCount / elapsedTime * 1000) : void 0;
21327
- this.options.logger.debug(`Recorder updates avgFps = ${avgFPS} at frame ${this.framesCount}`);
21328
- });
21329
- this.loop.start();
21330
- }
21331
- setAnimationFrameObject(newObj) {
21332
- if (this.loop) {
21333
- const isRecording = this.isRecording();
21334
- this.loop.stop();
21335
- this.loop.setRequestAnimationFrameObject(newObj);
21336
- if (isRecording) this.loop.start();
21337
- }
21338
- }
21339
- restoreAnimationFrameObject() {
21340
- this.options.logger.debug("Recorder: restoreAnimationFrameObject()");
21341
- this.setAnimationFrameObject(this.originalAnimationFrameObject);
21342
- }
21343
- loopWithTimeouts() {
21344
- this.options.logger.debug("Recorder: loopWithTimeouts()");
21345
- const wantedInterval = 1e3 / this.options.video.fps;
21346
- let processingTime = 0;
21347
- let start;
21348
- const raf = (fn)=>setTimeout(()=>{
21349
- start = Date.now();
21350
- fn();
21351
- processingTime = Date.now() - start;
21352
- }, wantedInterval - processingTime);
21353
- const cancel = (id)=>{
21354
- window.clearTimeout(id);
21355
- };
21356
- this.setAnimationFrameObject({
21357
- requestAnimationFrame: raf,
21358
- cancelAnimationFrame: cancel
21359
- });
21773
+ this.reset();
21774
+ this.recordingDimensions = void 0;
21775
+ this.clearUserMediaTimeout();
21776
+ this.clearConnectionTimeout();
21777
+ this.clearReconnectTimeout();
21778
+ this.clearRetryTimeout();
21779
+ this.clearStopTimeout();
21780
+ this.stopPings();
21781
+ this.userMediaLoading = false;
21782
+ this.connecting = false;
21783
+ this.reconnecting = false;
21784
+ this.reconnectAttempts = 0;
21785
+ if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
21786
+ this.unloaded = true;
21787
+ this.discardSocket();
21788
+ this.built = this.connecting = this.connected = false;
21360
21789
  }
21361
21790
  correctDimensions() {
21362
- if (!this.recorderElement) return;
21363
- const widthDimension = dimensions_useFullWidth(this.options.video.mobileBreakPoint);
21364
- if (this.options.video.stretch || widthDimension) {
21365
- this.recorderElement.style.width = "100%";
21366
- this.recorderElement.style.removeProperty("height");
21367
- } else {
21368
- if (this.options.video.width) {
21369
- const recorderWidth = this.getRecorderWidth(true);
21370
- if (null == recorderWidth ? void 0 : recorderWidth.value) this.recorderElement.width = recorderWidth.value;
21371
- else this.recorderElement.style.removeProperty("width");
21372
- }
21373
- if (this.options.video.height) {
21374
- const recorderHeight = this.getRecorderHeight(true);
21375
- if (null == recorderHeight ? void 0 : recorderHeight.value) this.recorderElement.height = recorderHeight.value;
21376
- else this.recorderElement.style.removeProperty("height");
21377
- }
21378
- }
21379
- }
21380
- switchFacingMode() {
21381
- if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
21382
- if ("user" === this.facingMode) this.facingMode = "environment";
21383
- else if ("environment" === this.facingMode) this.facingMode = "user";
21384
- else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
21385
- this.loadGenuineUserMedia({
21386
- switchingFacingMode: this.facingMode
21387
- });
21791
+ this.dimensions.correctDimensions();
21388
21792
  }
21389
21793
  initEvents() {
21390
21794
  this.options.logger.debug("Recorder: initEvents()");
@@ -21457,101 +21861,20 @@ var __webpack_exports__ = {};
21457
21861
  }
21458
21862
  this.built = true;
21459
21863
  }
21460
- isPaused() {
21461
- var _this_userMedia;
21462
- return (null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.isPaused()) && !this.loop.isRunning();
21463
- }
21464
- isRecording() {
21465
- var _this_loop;
21466
- return Boolean((null == (_this_loop = this.loop) ? void 0 : _this_loop.isRunning()) && !this.isPaused() && !this.isNotifying() && this.stream && !this.stream.destroyed);
21467
- }
21468
- hide() {
21469
- if (!this.isHidden()) {
21470
- if (this.recorderElement) html_hideElement(this.recorderElement);
21471
- this.clearUserMediaTimeout();
21472
- this.clearRetryTimeout();
21473
- }
21474
- }
21475
- isUnloaded() {
21476
- return this.unloaded;
21477
- }
21478
21864
  getRecorderWidth(responsive) {
21479
- var _this_userMedia;
21480
- if (null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.hasVideoWidth()) return this.userMedia.getRawWidth(responsive);
21481
- else if (responsive && this.options.video.width) return this.limitWidth(this.options.video.width);
21482
- const dimension = {
21483
- unit: "px",
21484
- value: this.options.video.width
21485
- };
21486
- return dimension;
21487
- }
21488
- getRecordingDimensions() {
21489
- return this.recordingDimensions && recorder_object_spread({}, this.recordingDimensions);
21865
+ return this.dimensions.getRecorderWidth(responsive);
21490
21866
  }
21491
21867
  getRecorderHeight(responsive, useBoundingClientRect) {
21492
- let recorderHeight;
21493
- if (this.recorderElement && useBoundingClientRect) {
21494
- const height = this.recorderElement.getBoundingClientRect().height;
21495
- recorderHeight = {
21496
- unit: "px",
21497
- value: height
21498
- };
21499
- } else if (this.userMedia) {
21500
- let height = this.userMedia.getRawHeight(responsive);
21501
- if (void 0 !== height && this.options.video.width) {
21502
- const ratio = this.getRatio();
21503
- if (void 0 !== ratio) {
21504
- const idealHeight = this.options.video.width * ratio;
21505
- height = Math.min(idealHeight, height);
21506
- }
21507
- }
21508
- recorderHeight = {
21509
- unit: "px",
21510
- value: height
21511
- };
21512
- } else if (responsive && this.options.video.height) recorderHeight = this.calculateHeight(responsive);
21513
- else if (this.options.video.height) {
21514
- const height = this.options.video.height;
21515
- recorderHeight = {
21516
- unit: "px",
21517
- value: height
21518
- };
21519
- }
21520
- return recorderHeight;
21868
+ return this.dimensions.getRecorderHeight(responsive, useBoundingClientRect);
21521
21869
  }
21522
21870
  getRatio() {
21523
- let ratio;
21524
- if (this.userMedia) {
21525
- const userMediaVideoWidth = this.userMedia.getVideoWidth();
21526
- const userMediaVideoHeight = this.userMedia.getVideoHeight();
21527
- if (!userMediaVideoWidth || userMediaVideoWidth < 1) ratio = this.visuals.getRatio();
21528
- else if (userMediaVideoHeight) ratio = userMediaVideoHeight / userMediaVideoWidth;
21529
- } else ratio = dimensions_getRatio(this.options);
21530
- return ratio;
21871
+ return this.dimensions.getRatio();
21531
21872
  }
21532
21873
  calculateWidth(responsive) {
21533
- let videoHeight;
21534
- if (this.userMedia) videoHeight = this.userMedia.getVideoHeight();
21535
- else if (this.recorderElement) videoHeight = this.recorderElement.videoHeight || this.recorderElement.height;
21536
- const ratio = this.getRatio();
21537
- return dimensions_calculateWidth(responsive, this.options, videoHeight, ratio);
21874
+ return this.dimensions.calculateWidth(responsive);
21538
21875
  }
21539
21876
  calculateHeight(responsive) {
21540
- let videoDimension;
21541
- if (this.userMedia) {
21542
- const videoHeight = this.userMedia.getVideoHeight();
21543
- videoDimension = {
21544
- value: videoHeight,
21545
- unit: "px"
21546
- };
21547
- } else if (this.recorderElement) {
21548
- const videoHeight = this.recorderElement.videoHeight || this.recorderElement.height;
21549
- videoDimension = {
21550
- value: videoHeight,
21551
- unit: "px"
21552
- };
21553
- } else videoDimension = dimensions_calculateHeight(responsive, void 0, this.options, this.getRatio(), this.recorderElement);
21554
- return videoDimension;
21877
+ return this.dimensions.calculateHeight(responsive);
21555
21878
  }
21556
21879
  getRawVisualUserMedia() {
21557
21880
  return this.recorderElement;
@@ -21568,14 +21891,14 @@ var __webpack_exports__ = {};
21568
21891
  limitHeight(height) {
21569
21892
  return this.visuals.limitHeight(height);
21570
21893
  }
21571
- isUserMediaLoaded() {
21572
- return this.userMediaLoaded;
21573
- }
21574
21894
  constructor(visuals, replay, options){
21575
- super("Recorder", options), recorder_define_property(this, "visuals", void 0), recorder_define_property(this, "replay", void 0), recorder_define_property(this, "pendingWrites", new boundedWriteQueue()), recorder_define_property(this, "loop", void 0), recorder_define_property(this, "originalAnimationFrameObject", void 0), recorder_define_property(this, "samplesCount", 0), recorder_define_property(this, "framesCount", 0), recorder_define_property(this, "recordingAudioSampleRate", void 0), recorder_define_property(this, "recordingStats", void 0), recorder_define_property(this, "confirmedFrameNumber", 0), recorder_define_property(this, "confirmedSampleNumber", 0), recorder_define_property(this, "recorderElement", void 0), recorder_define_property(this, "userMedia", void 0), recorder_define_property(this, "userMediaTimeout", void 0), recorder_define_property(this, "retryTimeout", void 0), recorder_define_property(this, "reconnectTimeout", void 0), recorder_define_property(this, "connectionTimeout", void 0), recorder_define_property(this, "backpressureTimeout", void 0), recorder_define_property(this, "stopTimeout", void 0), recorder_define_property(this, "frameProgress", void 0), recorder_define_property(this, "sampleProgress", void 0), recorder_define_property(this, "canvas", void 0), recorder_define_property(this, "recordingDimensions", void 0), recorder_define_property(this, "ctx", void 0), recorder_define_property(this, "userMediaLoaded", void 0), recorder_define_property(this, "userMediaLoading", false), recorder_define_property(this, "mediaRequestId", 0), recorder_define_property(this, "cameraRecovery", void 0), recorder_define_property(this, "resolveCameraRecovery", void 0), recorder_define_property(this, "resumeAfterCameraRecovery", false), recorder_define_property(this, "submitting", false), recorder_define_property(this, "unloaded", void 0), recorder_define_property(this, "stopTime", void 0), recorder_define_property(this, "stream", void 0), recorder_define_property(this, "streamBackpressured", false), recorder_define_property(this, "resumeAfterDrain", false), recorder_define_property(this, "mediaTransmissionStarted", false), recorder_define_property(this, "recordingActive", false), recorder_define_property(this, "recordingTransportFailed", false), recorder_define_property(this, "connecting", false), recorder_define_property(this, "connected", false), recorder_define_property(this, "connectionFailed", false), recorder_define_property(this, "reconnecting", false), recorder_define_property(this, "reconnectAttempts", 0), recorder_define_property(this, "blocking", false), recorder_define_property(this, "built", false), recorder_define_property(this, "key", void 0), recorder_define_property(this, "waitingTime", void 0), recorder_define_property(this, "connectingStartedAt", void 0), recorder_define_property(this, "lastCloseEvent", void 0), recorder_define_property(this, "lastSocketError", void 0), recorder_define_property(this, "pingInterval", void 0), recorder_define_property(this, "frame", void 0), recorder_define_property(this, "recordingBuffer", void 0), recorder_define_property(this, "facingMode", void 0);
21576
- this.visuals = visuals;
21577
- this.replay = replay;
21578
- this.facingMode = options.video.facingMode;
21895
+ super(visuals, replay, options), recorder_define_property(this, "dimensions", void 0);
21896
+ this.dimensions = new recorder_dimensions({
21897
+ options,
21898
+ visuals,
21899
+ getRecorderElement: ()=>this.recorderElement,
21900
+ getUserMedia: ()=>this.userMedia
21901
+ });
21579
21902
  }
21580
21903
  }
21581
21904
  const visuals_recorder = Recorder;
@@ -22539,8 +22862,8 @@ var __webpack_exports__ = {};
22539
22862
  }
22540
22863
  else runValidation = false;
22541
22864
  if (runValidation) {
22542
- var _event_target;
22543
- const targetName = null == event ? void 0 : null == (_event_target = event.target) ? void 0 : _event_target.name;
22865
+ const target = null == event ? void 0 : event.target;
22866
+ const targetName = target instanceof Element ? target.getAttribute("name") : void 0;
22544
22867
  if (targetName) this.emit("VALIDATING", {
22545
22868
  targetName
22546
22869
  });
@@ -22575,27 +22898,10 @@ var __webpack_exports__ = {};
22575
22898
  }
22576
22899
  }
22577
22900
  if (valid) {
22578
- const recipients = this.form.getRecipients();
22579
- const toIsConfigured = "to" in recipients;
22580
- const ccIsConfigured = "cc" in recipients;
22581
- const bccIsConfigured = "bcc" in recipients;
22582
- const hasTo = recipients.to && recipients.to.length > 0;
22583
- const hasCc = recipients.cc && recipients.cc.length > 0;
22584
- const hasBcc = recipients.bcc && recipients.bcc.length > 0;
22585
- if (toIsConfigured) {
22586
- if (!hasTo) if (ccIsConfigured && bccIsConfigured) {
22587
- if (!hasCc && !hasBcc) valid = false;
22588
- } else if (ccIsConfigured) {
22589
- if (!hasCc) valid = false;
22590
- } else if (bccIsConfigured) {
22591
- if (!hasBcc) valid = false;
22592
- } else valid = false;
22593
- } else if (ccIsConfigured) {
22594
- if (!hasCc) {
22595
- if (bccIsConfigured && !hasBcc) valid = false;
22596
- }
22901
+ if (!email_isRecipientSelectionValid(this.form.getRecipients())) {
22902
+ valid = false;
22903
+ whyInvalid = "At least one recipient is required";
22597
22904
  }
22598
- if (!valid) whyInvalid = "At least one recipient is required";
22599
22905
  }
22600
22906
  } else {
22601
22907
  valid = visualsValid;