videomail-client 15.8.3 → 15.9.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.
@@ -10972,7 +10972,7 @@ var __webpack_exports__ = {};
10972
10972
  var client = __webpack_require__("Q");
10973
10973
  var client_default = /*#__PURE__*/ __webpack_require__.n(client);
10974
10974
  var package_namespaceObject = {
10975
- rE: "15.8.3"
10975
+ rE: "15.9.0"
10976
10976
  };
10977
10977
  function isAudioEnabled(options) {
10978
10978
  return Boolean(options.audio.enabled);
@@ -19188,7 +19188,7 @@ var __webpack_exports__ = {};
19188
19188
  }
19189
19189
  let { bufferSize } = this.options.audio;
19190
19190
  if ("auto" === bufferSize) bufferSize = util_getBrowser(this.options).isFirefox() ? 512 : 2048;
19191
- if (!is_power_of_two_default()(bufferSize)) throw error_createError({
19191
+ if (void 0 === bufferSize || !is_power_of_two_default()(bufferSize)) throw error_createError({
19192
19192
  message: `Audio buffer size must be a power of two. The current buffer size is ${bufferSize}.`,
19193
19193
  options: this.options
19194
19194
  });
@@ -19267,9 +19267,14 @@ var __webpack_exports__ = {};
19267
19267
  const EVENT_ASCII = "|—O—|";
19268
19268
  class UserMedia extends util_Despot {
19269
19269
  attachMediaStream(stream) {
19270
- this.currentVisualStream = stream;
19271
- if (this.rawVisualUserMedia) this.rawVisualUserMedia.srcObject = stream;
19272
- else throw error_createError({
19270
+ if (this.rawVisualUserMedia) {
19271
+ this.rawVisualUserMedia.srcObject = stream;
19272
+ const previousStream = this.currentVisualStream;
19273
+ this.currentVisualStream = stream;
19274
+ if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
19275
+ track.stop();
19276
+ });
19277
+ } else throw error_createError({
19273
19278
  message: "Error attaching stream to element.",
19274
19279
  explanation: "Contact the developer about this",
19275
19280
  options: this.options
@@ -19343,11 +19348,14 @@ var __webpack_exports__ = {};
19343
19348
  }
19344
19349
  }).catch((exc)=>{
19345
19350
  if (exc instanceof Error) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
19346
- else throw error_createError({
19347
- message: "Failed to play user media upon play event.",
19348
- exc,
19349
- options: this.options
19350
- });
19351
+ else {
19352
+ unloadAllEventListeners();
19353
+ endedEarlyCallback(error_createError({
19354
+ message: "Failed to play user media upon play event.",
19355
+ exc,
19356
+ options: this.options
19357
+ }));
19358
+ }
19351
19359
  });
19352
19360
  }
19353
19361
  } catch (exc) {
@@ -19359,18 +19367,18 @@ var __webpack_exports__ = {};
19359
19367
  var _this_rawVisualUserMedia;
19360
19368
  const readyState = null == (_this_rawVisualUserMedia = this.rawVisualUserMedia) ? void 0 : _this_rawVisualUserMedia.readyState;
19361
19369
  this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
19362
- if (this.onPlayReached && this.onLoadedMetaDataReached) {
19370
+ if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) try {
19371
+ this.audioRecorder.init(localMediaStream);
19372
+ this.on("SENDING_FIRST_FRAME", ()=>{
19373
+ this.audioRecord(audioCallback);
19374
+ });
19363
19375
  videoCallback();
19364
- if (this.audioRecorder) try {
19365
- this.audioRecorder.init(localMediaStream);
19366
- this.on("SENDING_FIRST_FRAME", ()=>{
19367
- this.audioRecord(audioCallback);
19368
- });
19369
- } catch (exc) {
19370
- unloadAllEventListeners();
19371
- endedEarlyCallback(exc);
19372
- }
19376
+ } catch (exc) {
19377
+ unloadAllEventListeners();
19378
+ this.stop();
19379
+ endedEarlyCallback(exc);
19373
19380
  }
19381
+ else videoCallback();
19374
19382
  };
19375
19383
  const onPlay = ()=>{
19376
19384
  try {
@@ -19684,6 +19692,37 @@ var __webpack_exports__ = {};
19684
19692
  window.clearTimeout(this.connectionTimeout);
19685
19693
  this.connectionTimeout = void 0;
19686
19694
  }
19695
+ clearReconnectTimeout() {
19696
+ if (void 0 === this.reconnectTimeout) return;
19697
+ this.options.logger.debug("Recorder: clearReconnectTimeout()");
19698
+ window.clearTimeout(this.reconnectTimeout);
19699
+ this.reconnectTimeout = void 0;
19700
+ }
19701
+ handleConnectionFailure(params) {
19702
+ if (this.retryConnection(params)) return;
19703
+ this.failConnection(params);
19704
+ }
19705
+ retryConnection(params) {
19706
+ if (!this.reconnecting || !this.userMediaLoaded || !this.isOnline() || this.unloaded || this.blocking) return false;
19707
+ if (void 0 !== this.reconnectTimeout) return true;
19708
+ const delayMs = Math.min(500 * Math.pow(2, this.reconnectAttempts), 10000);
19709
+ this.reconnectAttempts++;
19710
+ this.connecting = false;
19711
+ this.clearConnectionTimeout();
19712
+ this.discardSocket();
19713
+ this.options.logger.debug(`Recorder: WebSocket reconnect failed (${params.cause}); retrying in ${delayMs}ms.`);
19714
+ this.reconnectTimeout = window.setTimeout(()=>{
19715
+ this.reconnectTimeout = void 0;
19716
+ if (this.reconnecting && !this.unloaded && !this.blocking) this.initSocket();
19717
+ }, delayMs);
19718
+ return true;
19719
+ }
19720
+ discardSocket() {
19721
+ this.options.logger.debug("Recorder: discarding socket stream ...");
19722
+ const stream = this.stream;
19723
+ this.stream = void 0;
19724
+ if (stream && !stream.destroyed) stream.destroy();
19725
+ }
19687
19726
  isOnline() {
19688
19727
  return navigator.onLine;
19689
19728
  }
@@ -19692,6 +19731,9 @@ var __webpack_exports__ = {};
19692
19731
  if (this.connectionFailed || this.connected || this.unloaded) return;
19693
19732
  this.connectionFailed = true;
19694
19733
  this.connecting = false;
19734
+ this.reconnecting = false;
19735
+ this.reconnectAttempts = 0;
19736
+ this.clearReconnectTimeout();
19695
19737
  this.clearConnectionTimeout();
19696
19738
  const { url2Connect, cause } = params;
19697
19739
  const online = this.isOnline();
@@ -19726,10 +19768,7 @@ var __webpack_exports__ = {};
19726
19768
  explanation = `Connection to ${url2Connect} is closed${closeSuffix}. Please check your internet connection and try again. If the problem persists, contact us.`;
19727
19769
  }
19728
19770
  else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
19729
- if (this.stream) {
19730
- this.stream.destroy();
19731
- this.stream = void 0;
19732
- }
19771
+ this.discardSocket();
19733
19772
  const err = error_createError({
19734
19773
  message: "Unable to connect to the server",
19735
19774
  explanation,
@@ -19813,8 +19852,13 @@ var __webpack_exports__ = {};
19813
19852
  this.recordingStats.waitingTime = this.waitingTime;
19814
19853
  }
19815
19854
  initSocket(cb) {
19816
- if (this.connected) return void (null == cb || cb());
19817
- if (this.connecting) return;
19855
+ if (this.connected) {
19856
+ this.options.logger.debug("Recorder: already **connected**. Not going to initialize a new WebSocket connection.");
19857
+ null == cb || cb();
19858
+ return;
19859
+ }
19860
+ if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
19861
+ this.clearReconnectTimeout();
19818
19862
  if (util_isAutomatedUserAgent()) {
19819
19863
  this.connecting = false;
19820
19864
  this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
@@ -19893,8 +19937,12 @@ var __webpack_exports__ = {};
19893
19937
  this.connectionTimeout = window.setTimeout(()=>{
19894
19938
  if (this.isOnline() && this.isUserMediaLoaded()) {
19895
19939
  this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
19896
- this.initSocket();
19897
- } else this.failConnection({
19940
+ this.reconnecting = true;
19941
+ this.handleConnectionFailure({
19942
+ url2Connect,
19943
+ cause: "timeout"
19944
+ });
19945
+ } else this.handleConnectionFailure({
19898
19946
  url2Connect,
19899
19947
  cause: "timeout"
19900
19948
  });
@@ -19909,9 +19957,11 @@ var __webpack_exports__ = {};
19909
19957
  this.options.logger.debug(debugLine);
19910
19958
  const tryReconnect = this.connected && this.userMediaLoaded;
19911
19959
  this.connected = false;
19912
- if (tryReconnect) this.initSocket();
19913
- else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19914
- this.failConnection({
19960
+ if (tryReconnect) {
19961
+ this.reconnecting = true;
19962
+ this.initSocket();
19963
+ } else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19964
+ this.handleConnectionFailure({
19915
19965
  url2Connect,
19916
19966
  cause: "closed"
19917
19967
  });
@@ -19925,6 +19975,9 @@ var __webpack_exports__ = {};
19925
19975
  if (!this.connected && !isClosing && !this.unloaded) {
19926
19976
  this.connected = true;
19927
19977
  this.connecting = this.unloaded = false;
19978
+ this.reconnecting = false;
19979
+ this.reconnectAttempts = 0;
19980
+ this.clearReconnectTimeout();
19928
19981
  this.emit("CONNECTED");
19929
19982
  null == cb || cb();
19930
19983
  }
@@ -19955,7 +20008,7 @@ var __webpack_exports__ = {};
19955
20008
  error: serializeError(err)
19956
20009
  });
19957
20010
  this.options.logger.debug(debugLine);
19958
- if (!this.connected) return void this.failConnection({
20011
+ if (!this.connected) return void this.handleConnectionFailure({
19959
20012
  url2Connect,
19960
20013
  cause: "error"
19961
20014
  });
@@ -20016,6 +20069,7 @@ var __webpack_exports__ = {};
20016
20069
  }
20017
20070
  userMediaErrorCallback(err, usedConstraints) {
20018
20071
  var _this_userMedia;
20072
+ if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
20019
20073
  this.userMediaLoading = false;
20020
20074
  this.clearUserMediaTimeout();
20021
20075
  const characteristics = null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.getCharacteristics();
@@ -20034,10 +20088,9 @@ var __webpack_exports__ = {};
20034
20088
  this.options.logger.debug(`Recorder: ignore user media error ${util_pretty(err)}`);
20035
20089
  this.retryTimeout = window.setTimeout(()=>{
20036
20090
  this.retryTimeout = void 0;
20037
- this.loadUserMedia();
20091
+ if (!this.unloaded) this.loadUserMedia();
20038
20092
  }, this.options.timeouts.userMedia);
20039
20093
  }
20040
- else if (this.unloaded) this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
20041
20094
  else {
20042
20095
  this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${util_pretty(err)}`);
20043
20096
  throw error_createError({
@@ -20048,26 +20101,46 @@ var __webpack_exports__ = {};
20048
20101
  }
20049
20102
  }
20050
20103
  getUserMediaCallback(localStream, params) {
20051
- if (!this.userMedia) throw new Error("No user media is defined");
20104
+ if (!this.userMedia) {
20105
+ localStream.getTracks().forEach((track)=>{
20106
+ track.stop();
20107
+ });
20108
+ throw error_createError({
20109
+ message: "No user media is defined",
20110
+ options: this.options
20111
+ });
20112
+ }
20052
20113
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? util_pretty(params) : ""})`);
20053
20114
  if (this.showUserMedia()) try {
20054
- this.clearUserMediaTimeout();
20055
20115
  this.userMedia.init(localStream, ()=>{
20056
- this.onUserMediaReady(params);
20116
+ if (!this.unloaded) this.onUserMediaReady(params);
20057
20117
  }, this.onAudioSample.bind(this), (err)=>{
20058
- this.emit("ERROR", {
20059
- err
20118
+ if (!this.unloaded) this.userMediaErrorCallback(err, {
20119
+ audio: isAudioEnabled(this.options)
20060
20120
  });
20061
20121
  }, null == params ? void 0 : params.switchingFacingMode);
20062
20122
  } catch (exc) {
20123
+ localStream.getTracks().forEach((track)=>{
20124
+ track.stop();
20125
+ });
20126
+ this.userMediaLoading = false;
20127
+ this.clearUserMediaTimeout();
20063
20128
  this.emit("ERROR", {
20064
20129
  exc
20065
20130
  });
20066
20131
  }
20132
+ else {
20133
+ localStream.getTracks().forEach((track)=>{
20134
+ track.stop();
20135
+ });
20136
+ this.userMediaLoading = false;
20137
+ this.clearUserMediaTimeout();
20138
+ }
20067
20139
  }
20068
20140
  loadGenuineUserMedia(params) {
20069
20141
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? util_pretty(params) : ""})`);
20070
20142
  this.emit("ASKING_WEBCAM_PERMISSION");
20143
+ this.userMediaLoading = true;
20071
20144
  const constraints = {
20072
20145
  video: {
20073
20146
  frameRate: {
@@ -20098,9 +20171,12 @@ var __webpack_exports__ = {};
20098
20171
  this.options.logger.debug(util_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
20099
20172
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
20100
20173
  streamPromise.then((localStream)=>{
20174
+ if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
20175
+ track.stop();
20176
+ });
20101
20177
  this.getUserMediaCallback(localStream, params);
20102
20178
  }).catch((reason)=>{
20103
- this.userMediaErrorCallback(reason, constraints);
20179
+ if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
20104
20180
  });
20105
20181
  }
20106
20182
  loadUserMedia(params) {
@@ -20114,7 +20190,8 @@ var __webpack_exports__ = {};
20114
20190
  try {
20115
20191
  if (!navigator.mediaDevices) throw new Error("No media devices are available.");
20116
20192
  this.userMediaTimeout = window.setTimeout(()=>{
20117
- if (!this.isReady()) {
20193
+ if (!this.userMediaLoaded) {
20194
+ this.userMediaLoading = false;
20118
20195
  const err = util_getBrowser(this.options).getNoAccessIssue();
20119
20196
  this.emit("ERROR", {
20120
20197
  err
@@ -20214,16 +20291,16 @@ var __webpack_exports__ = {};
20214
20291
  command,
20215
20292
  args
20216
20293
  };
20217
- this.writeStream(Buffer.from(JSON.stringify(commandObj)));
20218
- if (cb) setTimeout(function() {
20219
- cb();
20220
- }, 0);
20294
+ this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
20295
+ onFlushedCallback: ()=>{
20296
+ queueMicrotask(cb);
20297
+ }
20298
+ } : void 0);
20221
20299
  }
20222
20300
  } else {
20223
20301
  this.options.logger.debug(`Reconnecting for the command ${command} …`);
20224
20302
  this.initSocket(()=>{
20225
- this.writeCommand(command, args);
20226
- null == cb || cb();
20303
+ this.writeCommand(command, args, cb);
20227
20304
  });
20228
20305
  }
20229
20306
  }
@@ -20298,6 +20375,7 @@ var __webpack_exports__ = {};
20298
20375
  var _this_userMedia;
20299
20376
  this.options.logger.debug("Recorder: reInitializeAudio()");
20300
20377
  this.clearUserMediaTimeout();
20378
+ this.userMediaLoading = false;
20301
20379
  null == (_this_userMedia = this.userMedia) || _this_userMedia.stop();
20302
20380
  this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
20303
20381
  this.loadUserMedia();
@@ -20312,17 +20390,17 @@ var __webpack_exports__ = {};
20312
20390
  this.reset();
20313
20391
  this.clearUserMediaTimeout();
20314
20392
  this.clearConnectionTimeout();
20393
+ this.clearReconnectTimeout();
20315
20394
  this.clearRetryTimeout();
20316
20395
  this.clearStopTimeout();
20317
20396
  this.stopPings();
20397
+ this.userMediaLoading = false;
20318
20398
  this.connecting = false;
20399
+ this.reconnecting = false;
20400
+ this.reconnectAttempts = 0;
20319
20401
  if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
20320
20402
  if (this.submitting) ;
20321
- else if (this.stream) {
20322
- this.options.logger.debug("Recorder: destroying stream ...");
20323
- this.stream.destroy();
20324
- this.stream = void 0;
20325
- }
20403
+ else if (this.stream) this.discardSocket();
20326
20404
  this.unloaded = true;
20327
20405
  this.built = this.connecting = this.connected = false;
20328
20406
  }
@@ -20427,7 +20505,13 @@ var __webpack_exports__ = {};
20427
20505
  if (!this.connected) {
20428
20506
  this.options.logger.debug("Recorder: reconnecting before recording …");
20429
20507
  this.initSocket(()=>{
20430
- this.once("USER_MEDIA_READY", this.record.bind(this));
20508
+ this.once("SERVER_READY", ()=>{
20509
+ if (this.unloaded) return;
20510
+ if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
20511
+ else this.once("USER_MEDIA_READY", ()=>{
20512
+ if (!this.unloaded) this.record();
20513
+ });
20514
+ });
20431
20515
  });
20432
20516
  return;
20433
20517
  }
@@ -20732,7 +20816,7 @@ var __webpack_exports__ = {};
20732
20816
  return this.userMediaLoaded;
20733
20817
  }
20734
20818
  constructor(visuals, replay, options){
20735
- super("Recorder", options), recorder_define_property(this, "visuals", void 0), recorder_define_property(this, "replay", void 0), 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, "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, "connectionTimeout", 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, "ctx", void 0), recorder_define_property(this, "userMediaLoaded", void 0), recorder_define_property(this, "userMediaLoading", 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, "connecting", false), recorder_define_property(this, "connected", false), recorder_define_property(this, "connectionFailed", false), 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);
20819
+ super("Recorder", options), recorder_define_property(this, "visuals", void 0), recorder_define_property(this, "replay", void 0), 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, "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, "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, "ctx", void 0), recorder_define_property(this, "userMediaLoaded", void 0), recorder_define_property(this, "userMediaLoading", 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, "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);
20736
20820
  this.visuals = visuals;
20737
20821
  this.replay = replay;
20738
20822
  this.facingMode = options.video.facingMode;
package/dist/esm/index.js CHANGED
@@ -3620,7 +3620,7 @@ const constants = {
3620
3620
  }
3621
3621
  };
3622
3622
  var package_namespaceObject = {
3623
- rE: "15.8.3"
3623
+ rE: "15.9.0"
3624
3624
  };
3625
3625
  function isAudioEnabled(options) {
3626
3626
  return Boolean(options.audio.enabled);
@@ -6232,7 +6232,7 @@ class AudioRecorder {
6232
6232
  }
6233
6233
  let { bufferSize } = this.options.audio;
6234
6234
  if ("auto" === bufferSize) bufferSize = util_getBrowser(this.options).isFirefox() ? 512 : 2048;
6235
- if (!is_power_of_two(bufferSize)) throw error_createError({
6235
+ if (void 0 === bufferSize || !is_power_of_two(bufferSize)) throw error_createError({
6236
6236
  message: `Audio buffer size must be a power of two. The current buffer size is ${bufferSize}.`,
6237
6237
  options: this.options
6238
6238
  });
@@ -6311,9 +6311,14 @@ function userMedia_define_property(obj, key, value) {
6311
6311
  const EVENT_ASCII = "|—O—|";
6312
6312
  class UserMedia extends util_Despot {
6313
6313
  attachMediaStream(stream) {
6314
- this.currentVisualStream = stream;
6315
- if (this.rawVisualUserMedia) this.rawVisualUserMedia.srcObject = stream;
6316
- else throw error_createError({
6314
+ if (this.rawVisualUserMedia) {
6315
+ this.rawVisualUserMedia.srcObject = stream;
6316
+ const previousStream = this.currentVisualStream;
6317
+ this.currentVisualStream = stream;
6318
+ if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
6319
+ track.stop();
6320
+ });
6321
+ } else throw error_createError({
6317
6322
  message: "Error attaching stream to element.",
6318
6323
  explanation: "Contact the developer about this",
6319
6324
  options: this.options
@@ -6381,11 +6386,14 @@ class UserMedia extends util_Despot {
6381
6386
  }
6382
6387
  }).catch((exc)=>{
6383
6388
  if (exc instanceof Error) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
6384
- else throw error_createError({
6385
- message: "Failed to play user media upon play event.",
6386
- exc,
6387
- options: this.options
6388
- });
6389
+ else {
6390
+ unloadAllEventListeners();
6391
+ endedEarlyCallback(error_createError({
6392
+ message: "Failed to play user media upon play event.",
6393
+ exc,
6394
+ options: this.options
6395
+ }));
6396
+ }
6389
6397
  });
6390
6398
  }
6391
6399
  } catch (exc) {
@@ -6396,18 +6404,18 @@ class UserMedia extends util_Despot {
6396
6404
  const fireCallbacks = ()=>{
6397
6405
  const readyState = this.rawVisualUserMedia?.readyState;
6398
6406
  this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
6399
- if (this.onPlayReached && this.onLoadedMetaDataReached) {
6407
+ if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) try {
6408
+ this.audioRecorder.init(localMediaStream);
6409
+ this.on("SENDING_FIRST_FRAME", ()=>{
6410
+ this.audioRecord(audioCallback);
6411
+ });
6400
6412
  videoCallback();
6401
- if (this.audioRecorder) try {
6402
- this.audioRecorder.init(localMediaStream);
6403
- this.on("SENDING_FIRST_FRAME", ()=>{
6404
- this.audioRecord(audioCallback);
6405
- });
6406
- } catch (exc) {
6407
- unloadAllEventListeners();
6408
- endedEarlyCallback(exc);
6409
- }
6413
+ } catch (exc) {
6414
+ unloadAllEventListeners();
6415
+ this.stop();
6416
+ endedEarlyCallback(exc);
6410
6417
  }
6418
+ else videoCallback();
6411
6419
  };
6412
6420
  const onPlay = ()=>{
6413
6421
  try {
@@ -6713,6 +6721,37 @@ class Recorder extends util_Despot {
6713
6721
  window.clearTimeout(this.connectionTimeout);
6714
6722
  this.connectionTimeout = void 0;
6715
6723
  }
6724
+ clearReconnectTimeout() {
6725
+ if (void 0 === this.reconnectTimeout) return;
6726
+ this.options.logger.debug("Recorder: clearReconnectTimeout()");
6727
+ window.clearTimeout(this.reconnectTimeout);
6728
+ this.reconnectTimeout = void 0;
6729
+ }
6730
+ handleConnectionFailure(params) {
6731
+ if (this.retryConnection(params)) return;
6732
+ this.failConnection(params);
6733
+ }
6734
+ retryConnection(params) {
6735
+ if (!this.reconnecting || !this.userMediaLoaded || !this.isOnline() || this.unloaded || this.blocking) return false;
6736
+ if (void 0 !== this.reconnectTimeout) return true;
6737
+ const delayMs = Math.min(500 * 2 ** this.reconnectAttempts, 10000);
6738
+ this.reconnectAttempts++;
6739
+ this.connecting = false;
6740
+ this.clearConnectionTimeout();
6741
+ this.discardSocket();
6742
+ this.options.logger.debug(`Recorder: WebSocket reconnect failed (${params.cause}); retrying in ${delayMs}ms.`);
6743
+ this.reconnectTimeout = window.setTimeout(()=>{
6744
+ this.reconnectTimeout = void 0;
6745
+ if (this.reconnecting && !this.unloaded && !this.blocking) this.initSocket();
6746
+ }, delayMs);
6747
+ return true;
6748
+ }
6749
+ discardSocket() {
6750
+ this.options.logger.debug("Recorder: discarding socket stream ...");
6751
+ const stream = this.stream;
6752
+ this.stream = void 0;
6753
+ if (stream && !stream.destroyed) stream.destroy();
6754
+ }
6716
6755
  isOnline() {
6717
6756
  return navigator.onLine;
6718
6757
  }
@@ -6720,6 +6759,9 @@ class Recorder extends util_Despot {
6720
6759
  if (this.connectionFailed || this.connected || this.unloaded) return;
6721
6760
  this.connectionFailed = true;
6722
6761
  this.connecting = false;
6762
+ this.reconnecting = false;
6763
+ this.reconnectAttempts = 0;
6764
+ this.clearReconnectTimeout();
6723
6765
  this.clearConnectionTimeout();
6724
6766
  const { url2Connect, cause } = params;
6725
6767
  const online = this.isOnline();
@@ -6754,10 +6796,7 @@ class Recorder extends util_Despot {
6754
6796
  explanation = `Connection to ${url2Connect} is closed${closeSuffix}. Please check your internet connection and try again. If the problem persists, contact us.`;
6755
6797
  }
6756
6798
  else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
6757
- if (this.stream) {
6758
- this.stream.destroy();
6759
- this.stream = void 0;
6760
- }
6799
+ this.discardSocket();
6761
6800
  const err = error_createError({
6762
6801
  message: "Unable to connect to the server",
6763
6802
  explanation,
@@ -6840,8 +6879,13 @@ class Recorder extends util_Despot {
6840
6879
  this.recordingStats.waitingTime = this.waitingTime;
6841
6880
  }
6842
6881
  initSocket(cb) {
6843
- if (this.connected) return void cb?.();
6844
- if (this.connecting) return;
6882
+ if (this.connected) {
6883
+ this.options.logger.debug("Recorder: already **connected**. Not going to initialize a new WebSocket connection.");
6884
+ cb?.();
6885
+ return;
6886
+ }
6887
+ if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
6888
+ this.clearReconnectTimeout();
6845
6889
  if (util_isAutomatedUserAgent()) {
6846
6890
  this.connecting = false;
6847
6891
  this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
@@ -6920,8 +6964,12 @@ class Recorder extends util_Despot {
6920
6964
  this.connectionTimeout = window.setTimeout(()=>{
6921
6965
  if (this.isOnline() && this.isUserMediaLoaded()) {
6922
6966
  this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
6923
- this.initSocket();
6924
- } else this.failConnection({
6967
+ this.reconnecting = true;
6968
+ this.handleConnectionFailure({
6969
+ url2Connect,
6970
+ cause: "timeout"
6971
+ });
6972
+ } else this.handleConnectionFailure({
6925
6973
  url2Connect,
6926
6974
  cause: "timeout"
6927
6975
  });
@@ -6936,9 +6984,11 @@ class Recorder extends util_Despot {
6936
6984
  this.options.logger.debug(debugLine);
6937
6985
  const tryReconnect = this.connected && this.userMediaLoaded;
6938
6986
  this.connected = false;
6939
- if (tryReconnect) this.initSocket();
6940
- else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
6941
- this.failConnection({
6987
+ if (tryReconnect) {
6988
+ this.reconnecting = true;
6989
+ this.initSocket();
6990
+ } else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
6991
+ this.handleConnectionFailure({
6942
6992
  url2Connect,
6943
6993
  cause: "closed"
6944
6994
  });
@@ -6951,6 +7001,9 @@ class Recorder extends util_Despot {
6951
7001
  if (!this.connected && !isClosing && !this.unloaded) {
6952
7002
  this.connected = true;
6953
7003
  this.connecting = this.unloaded = false;
7004
+ this.reconnecting = false;
7005
+ this.reconnectAttempts = 0;
7006
+ this.clearReconnectTimeout();
6954
7007
  this.emit("CONNECTED");
6955
7008
  cb?.();
6956
7009
  }
@@ -6980,7 +7033,7 @@ class Recorder extends util_Despot {
6980
7033
  error: serializeError(err)
6981
7034
  });
6982
7035
  this.options.logger.debug(debugLine);
6983
- if (!this.connected) return void this.failConnection({
7036
+ if (!this.connected) return void this.handleConnectionFailure({
6984
7037
  url2Connect,
6985
7038
  cause: "error"
6986
7039
  });
@@ -7040,6 +7093,7 @@ class Recorder extends util_Despot {
7040
7093
  return this.blocking;
7041
7094
  }
7042
7095
  userMediaErrorCallback(err, usedConstraints) {
7096
+ if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
7043
7097
  this.userMediaLoading = false;
7044
7098
  this.clearUserMediaTimeout();
7045
7099
  const characteristics = this.userMedia?.getCharacteristics();
@@ -7058,10 +7112,9 @@ class Recorder extends util_Despot {
7058
7112
  this.options.logger.debug(`Recorder: ignore user media error ${util_pretty(err)}`);
7059
7113
  this.retryTimeout = window.setTimeout(()=>{
7060
7114
  this.retryTimeout = void 0;
7061
- this.loadUserMedia();
7115
+ if (!this.unloaded) this.loadUserMedia();
7062
7116
  }, this.options.timeouts.userMedia);
7063
7117
  }
7064
- else if (this.unloaded) this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
7065
7118
  else {
7066
7119
  this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${util_pretty(err)}`);
7067
7120
  throw error_createError({
@@ -7072,26 +7125,46 @@ class Recorder extends util_Despot {
7072
7125
  }
7073
7126
  }
7074
7127
  getUserMediaCallback(localStream, params) {
7075
- if (!this.userMedia) throw new Error("No user media is defined");
7128
+ if (!this.userMedia) {
7129
+ localStream.getTracks().forEach((track)=>{
7130
+ track.stop();
7131
+ });
7132
+ throw error_createError({
7133
+ message: "No user media is defined",
7134
+ options: this.options
7135
+ });
7136
+ }
7076
7137
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? util_pretty(params) : ""})`);
7077
7138
  if (this.showUserMedia()) try {
7078
- this.clearUserMediaTimeout();
7079
7139
  this.userMedia.init(localStream, ()=>{
7080
- this.onUserMediaReady(params);
7140
+ if (!this.unloaded) this.onUserMediaReady(params);
7081
7141
  }, this.onAudioSample.bind(this), (err)=>{
7082
- this.emit("ERROR", {
7083
- err
7142
+ if (!this.unloaded) this.userMediaErrorCallback(err, {
7143
+ audio: isAudioEnabled(this.options)
7084
7144
  });
7085
7145
  }, params?.switchingFacingMode);
7086
7146
  } catch (exc) {
7147
+ localStream.getTracks().forEach((track)=>{
7148
+ track.stop();
7149
+ });
7150
+ this.userMediaLoading = false;
7151
+ this.clearUserMediaTimeout();
7087
7152
  this.emit("ERROR", {
7088
7153
  exc
7089
7154
  });
7090
7155
  }
7156
+ else {
7157
+ localStream.getTracks().forEach((track)=>{
7158
+ track.stop();
7159
+ });
7160
+ this.userMediaLoading = false;
7161
+ this.clearUserMediaTimeout();
7162
+ }
7091
7163
  }
7092
7164
  loadGenuineUserMedia(params) {
7093
7165
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? util_pretty(params) : ""})`);
7094
7166
  this.emit("ASKING_WEBCAM_PERMISSION");
7167
+ this.userMediaLoading = true;
7095
7168
  const constraints = {
7096
7169
  video: {
7097
7170
  frameRate: {
@@ -7122,9 +7195,12 @@ class Recorder extends util_Despot {
7122
7195
  this.options.logger.debug(util_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
7123
7196
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
7124
7197
  streamPromise.then((localStream)=>{
7198
+ if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
7199
+ track.stop();
7200
+ });
7125
7201
  this.getUserMediaCallback(localStream, params);
7126
7202
  }).catch((reason)=>{
7127
- this.userMediaErrorCallback(reason, constraints);
7203
+ if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
7128
7204
  });
7129
7205
  }
7130
7206
  loadUserMedia(params) {
@@ -7138,7 +7214,8 @@ class Recorder extends util_Despot {
7138
7214
  try {
7139
7215
  if (!navigator.mediaDevices) throw new Error("No media devices are available.");
7140
7216
  this.userMediaTimeout = window.setTimeout(()=>{
7141
- if (!this.isReady()) {
7217
+ if (!this.userMediaLoaded) {
7218
+ this.userMediaLoading = false;
7142
7219
  const err = util_getBrowser(this.options).getNoAccessIssue();
7143
7220
  this.emit("ERROR", {
7144
7221
  err
@@ -7237,16 +7314,16 @@ class Recorder extends util_Despot {
7237
7314
  command,
7238
7315
  args
7239
7316
  };
7240
- this.writeStream(Buffer.from(JSON.stringify(commandObj)));
7241
- if (cb) setTimeout(function() {
7242
- cb();
7243
- }, 0);
7317
+ this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
7318
+ onFlushedCallback: ()=>{
7319
+ queueMicrotask(cb);
7320
+ }
7321
+ } : void 0);
7244
7322
  }
7245
7323
  } else {
7246
7324
  this.options.logger.debug(`Reconnecting for the command ${command} …`);
7247
7325
  this.initSocket(()=>{
7248
- this.writeCommand(command, args);
7249
- cb?.();
7326
+ this.writeCommand(command, args, cb);
7250
7327
  });
7251
7328
  }
7252
7329
  }
@@ -7317,6 +7394,7 @@ class Recorder extends util_Despot {
7317
7394
  reInitializeAudio() {
7318
7395
  this.options.logger.debug("Recorder: reInitializeAudio()");
7319
7396
  this.clearUserMediaTimeout();
7397
+ this.userMediaLoading = false;
7320
7398
  this.userMedia?.stop();
7321
7399
  this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
7322
7400
  this.loadUserMedia();
@@ -7331,17 +7409,17 @@ class Recorder extends util_Despot {
7331
7409
  this.reset();
7332
7410
  this.clearUserMediaTimeout();
7333
7411
  this.clearConnectionTimeout();
7412
+ this.clearReconnectTimeout();
7334
7413
  this.clearRetryTimeout();
7335
7414
  this.clearStopTimeout();
7336
7415
  this.stopPings();
7416
+ this.userMediaLoading = false;
7337
7417
  this.connecting = false;
7418
+ this.reconnecting = false;
7419
+ this.reconnectAttempts = 0;
7338
7420
  if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
7339
7421
  if (this.submitting) ;
7340
- else if (this.stream) {
7341
- this.options.logger.debug("Recorder: destroying stream ...");
7342
- this.stream.destroy();
7343
- this.stream = void 0;
7344
- }
7422
+ else if (this.stream) this.discardSocket();
7345
7423
  this.unloaded = true;
7346
7424
  this.built = this.connecting = this.connected = false;
7347
7425
  }
@@ -7441,7 +7519,13 @@ class Recorder extends util_Despot {
7441
7519
  if (!this.connected) {
7442
7520
  this.options.logger.debug("Recorder: reconnecting before recording …");
7443
7521
  this.initSocket(()=>{
7444
- this.once("USER_MEDIA_READY", this.record.bind(this));
7522
+ this.once("SERVER_READY", ()=>{
7523
+ if (this.unloaded) return;
7524
+ if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
7525
+ else this.once("USER_MEDIA_READY", ()=>{
7526
+ if (!this.unloaded) this.record();
7527
+ });
7528
+ });
7445
7529
  });
7446
7530
  return;
7447
7531
  }
@@ -7742,7 +7826,7 @@ class Recorder extends util_Despot {
7742
7826
  return this.userMediaLoaded;
7743
7827
  }
7744
7828
  constructor(visuals, replay, options){
7745
- super("Recorder", options), recorder_define_property(this, "visuals", void 0), recorder_define_property(this, "replay", void 0), 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, "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, "connectionTimeout", 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, "ctx", void 0), recorder_define_property(this, "userMediaLoaded", void 0), recorder_define_property(this, "userMediaLoading", 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, "connecting", false), recorder_define_property(this, "connected", false), recorder_define_property(this, "connectionFailed", false), 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);
7829
+ super("Recorder", options), recorder_define_property(this, "visuals", void 0), recorder_define_property(this, "replay", void 0), 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, "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, "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, "ctx", void 0), recorder_define_property(this, "userMediaLoaded", void 0), recorder_define_property(this, "userMediaLoading", 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, "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);
7746
7830
  this.visuals = visuals;
7747
7831
  this.replay = replay;
7748
7832
  this.facingMode = options.video.facingMode;
@@ -25,6 +25,7 @@ declare class Recorder extends Despot {
25
25
  private userMedia?;
26
26
  private userMediaTimeout?;
27
27
  private retryTimeout?;
28
+ private reconnectTimeout?;
28
29
  private connectionTimeout?;
29
30
  private stopTimeout?;
30
31
  private frameProgress?;
@@ -40,6 +41,8 @@ declare class Recorder extends Despot {
40
41
  private connecting;
41
42
  private connected;
42
43
  private connectionFailed;
44
+ private reconnecting;
45
+ private reconnectAttempts;
43
46
  private blocking;
44
47
  private built;
45
48
  private key?;
@@ -60,6 +63,10 @@ declare class Recorder extends Despot {
60
63
  private onUserMediaReady;
61
64
  private clearRetryTimeout;
62
65
  private clearConnectionTimeout;
66
+ private clearReconnectTimeout;
67
+ private handleConnectionFailure;
68
+ private retryConnection;
69
+ private discardSocket;
63
70
  private isOnline;
64
71
  private failConnection;
65
72
  private calculateFrameProgress;
package/dist/umd/index.js CHANGED
@@ -10978,7 +10978,7 @@
10978
10978
  var client = __webpack_require__("Q");
10979
10979
  var client_default = /*#__PURE__*/ __webpack_require__.n(client);
10980
10980
  var package_namespaceObject = {
10981
- rE: "15.8.3"
10981
+ rE: "15.9.0"
10982
10982
  };
10983
10983
  function isAudioEnabled(options) {
10984
10984
  return Boolean(options.audio.enabled);
@@ -18953,7 +18953,7 @@
18953
18953
  }
18954
18954
  let { bufferSize } = this.options.audio;
18955
18955
  if ("auto" === bufferSize) bufferSize = util_getBrowser(this.options).isFirefox() ? 512 : 2048;
18956
- if (!is_power_of_two_default()(bufferSize)) throw error_createError({
18956
+ if (void 0 === bufferSize || !is_power_of_two_default()(bufferSize)) throw error_createError({
18957
18957
  message: `Audio buffer size must be a power of two. The current buffer size is ${bufferSize}.`,
18958
18958
  options: this.options
18959
18959
  });
@@ -19040,9 +19040,14 @@
19040
19040
  });
19041
19041
  }
19042
19042
  attachMediaStream(stream) {
19043
- this.currentVisualStream = stream;
19044
- if (this.rawVisualUserMedia) this.rawVisualUserMedia.srcObject = stream;
19045
- else throw error_createError({
19043
+ if (this.rawVisualUserMedia) {
19044
+ this.rawVisualUserMedia.srcObject = stream;
19045
+ const previousStream = this.currentVisualStream;
19046
+ this.currentVisualStream = stream;
19047
+ if (previousStream && previousStream !== stream) previousStream.getTracks().forEach((track)=>{
19048
+ track.stop();
19049
+ });
19050
+ } else throw error_createError({
19046
19051
  message: "Error attaching stream to element.",
19047
19052
  explanation: "Contact the developer about this",
19048
19053
  options: this.options
@@ -19110,11 +19115,14 @@
19110
19115
  }
19111
19116
  }).catch((exc)=>{
19112
19117
  if (exc instanceof Error) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
19113
- else throw error_createError({
19114
- message: "Failed to play user media upon play event.",
19115
- exc,
19116
- options: this.options
19117
- });
19118
+ else {
19119
+ unloadAllEventListeners();
19120
+ endedEarlyCallback(error_createError({
19121
+ message: "Failed to play user media upon play event.",
19122
+ exc,
19123
+ options: this.options
19124
+ }));
19125
+ }
19118
19126
  });
19119
19127
  }
19120
19128
  } catch (exc) {
@@ -19125,18 +19133,18 @@
19125
19133
  const fireCallbacks = ()=>{
19126
19134
  const readyState = this.rawVisualUserMedia?.readyState;
19127
19135
  this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
19128
- if (this.onPlayReached && this.onLoadedMetaDataReached) {
19136
+ if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) try {
19137
+ this.audioRecorder.init(localMediaStream);
19138
+ this.on("SENDING_FIRST_FRAME", ()=>{
19139
+ this.audioRecord(audioCallback);
19140
+ });
19129
19141
  videoCallback();
19130
- if (this.audioRecorder) try {
19131
- this.audioRecorder.init(localMediaStream);
19132
- this.on("SENDING_FIRST_FRAME", ()=>{
19133
- this.audioRecord(audioCallback);
19134
- });
19135
- } catch (exc) {
19136
- unloadAllEventListeners();
19137
- endedEarlyCallback(exc);
19138
- }
19142
+ } catch (exc) {
19143
+ unloadAllEventListeners();
19144
+ this.stop();
19145
+ endedEarlyCallback(exc);
19139
19146
  }
19147
+ else videoCallback();
19140
19148
  };
19141
19149
  const onPlay = ()=>{
19142
19150
  try {
@@ -19334,6 +19342,7 @@
19334
19342
  userMedia;
19335
19343
  userMediaTimeout;
19336
19344
  retryTimeout;
19345
+ reconnectTimeout;
19337
19346
  connectionTimeout;
19338
19347
  stopTimeout;
19339
19348
  frameProgress;
@@ -19349,6 +19358,8 @@
19349
19358
  connecting = false;
19350
19359
  connected = false;
19351
19360
  connectionFailed = false;
19361
+ reconnecting = false;
19362
+ reconnectAttempts = 0;
19352
19363
  blocking = false;
19353
19364
  built = false;
19354
19365
  key;
@@ -19462,6 +19473,37 @@
19462
19473
  window.clearTimeout(this.connectionTimeout);
19463
19474
  this.connectionTimeout = void 0;
19464
19475
  }
19476
+ clearReconnectTimeout() {
19477
+ if (void 0 === this.reconnectTimeout) return;
19478
+ this.options.logger.debug("Recorder: clearReconnectTimeout()");
19479
+ window.clearTimeout(this.reconnectTimeout);
19480
+ this.reconnectTimeout = void 0;
19481
+ }
19482
+ handleConnectionFailure(params) {
19483
+ if (this.retryConnection(params)) return;
19484
+ this.failConnection(params);
19485
+ }
19486
+ retryConnection(params) {
19487
+ if (!this.reconnecting || !this.userMediaLoaded || !this.isOnline() || this.unloaded || this.blocking) return false;
19488
+ if (void 0 !== this.reconnectTimeout) return true;
19489
+ const delayMs = Math.min(500 * 2 ** this.reconnectAttempts, 10000);
19490
+ this.reconnectAttempts++;
19491
+ this.connecting = false;
19492
+ this.clearConnectionTimeout();
19493
+ this.discardSocket();
19494
+ this.options.logger.debug(`Recorder: WebSocket reconnect failed (${params.cause}); retrying in ${delayMs}ms.`);
19495
+ this.reconnectTimeout = window.setTimeout(()=>{
19496
+ this.reconnectTimeout = void 0;
19497
+ if (this.reconnecting && !this.unloaded && !this.blocking) this.initSocket();
19498
+ }, delayMs);
19499
+ return true;
19500
+ }
19501
+ discardSocket() {
19502
+ this.options.logger.debug("Recorder: discarding socket stream ...");
19503
+ const stream = this.stream;
19504
+ this.stream = void 0;
19505
+ if (stream && !stream.destroyed) stream.destroy();
19506
+ }
19465
19507
  isOnline() {
19466
19508
  return navigator.onLine;
19467
19509
  }
@@ -19469,6 +19511,9 @@
19469
19511
  if (this.connectionFailed || this.connected || this.unloaded) return;
19470
19512
  this.connectionFailed = true;
19471
19513
  this.connecting = false;
19514
+ this.reconnecting = false;
19515
+ this.reconnectAttempts = 0;
19516
+ this.clearReconnectTimeout();
19472
19517
  this.clearConnectionTimeout();
19473
19518
  const { url2Connect, cause } = params;
19474
19519
  const online = this.isOnline();
@@ -19503,10 +19548,7 @@
19503
19548
  explanation = `Connection to ${url2Connect} is closed${closeSuffix}. Please check your internet connection and try again. If the problem persists, contact us.`;
19504
19549
  }
19505
19550
  else explanation = "Your device appears to be offline. Please check your internet connection and try again.";
19506
- if (this.stream) {
19507
- this.stream.destroy();
19508
- this.stream = void 0;
19509
- }
19551
+ this.discardSocket();
19510
19552
  const err = error_createError({
19511
19553
  message: "Unable to connect to the server",
19512
19554
  explanation,
@@ -19589,8 +19631,13 @@
19589
19631
  this.recordingStats.waitingTime = this.waitingTime;
19590
19632
  }
19591
19633
  initSocket(cb) {
19592
- if (this.connected) return void cb?.();
19593
- if (this.connecting) return;
19634
+ if (this.connected) {
19635
+ this.options.logger.debug("Recorder: already **connected**. Not going to initialize a new WebSocket connection.");
19636
+ cb?.();
19637
+ return;
19638
+ }
19639
+ if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
19640
+ this.clearReconnectTimeout();
19594
19641
  if (util_isAutomatedUserAgent()) {
19595
19642
  this.connecting = false;
19596
19643
  this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
@@ -19669,8 +19716,12 @@
19669
19716
  this.connectionTimeout = window.setTimeout(()=>{
19670
19717
  if (this.isOnline() && this.isUserMediaLoaded()) {
19671
19718
  this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
19672
- this.initSocket();
19673
- } else this.failConnection({
19719
+ this.reconnecting = true;
19720
+ this.handleConnectionFailure({
19721
+ url2Connect,
19722
+ cause: "timeout"
19723
+ });
19724
+ } else this.handleConnectionFailure({
19674
19725
  url2Connect,
19675
19726
  cause: "timeout"
19676
19727
  });
@@ -19685,9 +19736,11 @@
19685
19736
  this.options.logger.debug(debugLine);
19686
19737
  const tryReconnect = this.connected && this.userMediaLoaded;
19687
19738
  this.connected = false;
19688
- if (tryReconnect) this.initSocket();
19689
- else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19690
- this.failConnection({
19739
+ if (tryReconnect) {
19740
+ this.reconnecting = true;
19741
+ this.initSocket();
19742
+ } else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19743
+ this.handleConnectionFailure({
19691
19744
  url2Connect,
19692
19745
  cause: "closed"
19693
19746
  });
@@ -19700,6 +19753,9 @@
19700
19753
  if (!this.connected && !isClosing && !this.unloaded) {
19701
19754
  this.connected = true;
19702
19755
  this.connecting = this.unloaded = false;
19756
+ this.reconnecting = false;
19757
+ this.reconnectAttempts = 0;
19758
+ this.clearReconnectTimeout();
19703
19759
  this.emit("CONNECTED");
19704
19760
  cb?.();
19705
19761
  }
@@ -19729,7 +19785,7 @@
19729
19785
  error: serializeError(err)
19730
19786
  });
19731
19787
  this.options.logger.debug(debugLine);
19732
- if (!this.connected) return void this.failConnection({
19788
+ if (!this.connected) return void this.handleConnectionFailure({
19733
19789
  url2Connect,
19734
19790
  cause: "error"
19735
19791
  });
@@ -19789,6 +19845,7 @@
19789
19845
  return this.blocking;
19790
19846
  }
19791
19847
  userMediaErrorCallback(err, usedConstraints) {
19848
+ if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
19792
19849
  this.userMediaLoading = false;
19793
19850
  this.clearUserMediaTimeout();
19794
19851
  const characteristics = this.userMedia?.getCharacteristics();
@@ -19807,10 +19864,9 @@
19807
19864
  this.options.logger.debug(`Recorder: ignore user media error ${util_pretty(err)}`);
19808
19865
  this.retryTimeout = window.setTimeout(()=>{
19809
19866
  this.retryTimeout = void 0;
19810
- this.loadUserMedia();
19867
+ if (!this.unloaded) this.loadUserMedia();
19811
19868
  }, this.options.timeouts.userMedia);
19812
19869
  }
19813
- else if (this.unloaded) this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
19814
19870
  else {
19815
19871
  this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${util_pretty(err)}`);
19816
19872
  throw error_createError({
@@ -19821,26 +19877,46 @@
19821
19877
  }
19822
19878
  }
19823
19879
  getUserMediaCallback(localStream, params) {
19824
- if (!this.userMedia) throw new Error("No user media is defined");
19880
+ if (!this.userMedia) {
19881
+ localStream.getTracks().forEach((track)=>{
19882
+ track.stop();
19883
+ });
19884
+ throw error_createError({
19885
+ message: "No user media is defined",
19886
+ options: this.options
19887
+ });
19888
+ }
19825
19889
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? util_pretty(params) : ""})`);
19826
19890
  if (this.showUserMedia()) try {
19827
- this.clearUserMediaTimeout();
19828
19891
  this.userMedia.init(localStream, ()=>{
19829
- this.onUserMediaReady(params);
19892
+ if (!this.unloaded) this.onUserMediaReady(params);
19830
19893
  }, this.onAudioSample.bind(this), (err)=>{
19831
- this.emit("ERROR", {
19832
- err
19894
+ if (!this.unloaded) this.userMediaErrorCallback(err, {
19895
+ audio: isAudioEnabled(this.options)
19833
19896
  });
19834
19897
  }, params?.switchingFacingMode);
19835
19898
  } catch (exc) {
19899
+ localStream.getTracks().forEach((track)=>{
19900
+ track.stop();
19901
+ });
19902
+ this.userMediaLoading = false;
19903
+ this.clearUserMediaTimeout();
19836
19904
  this.emit("ERROR", {
19837
19905
  exc
19838
19906
  });
19839
19907
  }
19908
+ else {
19909
+ localStream.getTracks().forEach((track)=>{
19910
+ track.stop();
19911
+ });
19912
+ this.userMediaLoading = false;
19913
+ this.clearUserMediaTimeout();
19914
+ }
19840
19915
  }
19841
19916
  loadGenuineUserMedia(params) {
19842
19917
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? util_pretty(params) : ""})`);
19843
19918
  this.emit("ASKING_WEBCAM_PERMISSION");
19919
+ this.userMediaLoading = true;
19844
19920
  const constraints = {
19845
19921
  video: {
19846
19922
  frameRate: {
@@ -19871,9 +19947,12 @@
19871
19947
  this.options.logger.debug(util_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
19872
19948
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
19873
19949
  streamPromise.then((localStream)=>{
19950
+ if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
19951
+ track.stop();
19952
+ });
19874
19953
  this.getUserMediaCallback(localStream, params);
19875
19954
  }).catch((reason)=>{
19876
- this.userMediaErrorCallback(reason, constraints);
19955
+ if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
19877
19956
  });
19878
19957
  }
19879
19958
  loadUserMedia(params) {
@@ -19887,7 +19966,8 @@
19887
19966
  try {
19888
19967
  if (!navigator.mediaDevices) throw new Error("No media devices are available.");
19889
19968
  this.userMediaTimeout = window.setTimeout(()=>{
19890
- if (!this.isReady()) {
19969
+ if (!this.userMediaLoaded) {
19970
+ this.userMediaLoading = false;
19891
19971
  const err = util_getBrowser(this.options).getNoAccessIssue();
19892
19972
  this.emit("ERROR", {
19893
19973
  err
@@ -19986,16 +20066,16 @@
19986
20066
  command,
19987
20067
  args
19988
20068
  };
19989
- this.writeStream(Buffer.from(JSON.stringify(commandObj)));
19990
- if (cb) setTimeout(function() {
19991
- cb();
19992
- }, 0);
20069
+ this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
20070
+ onFlushedCallback: ()=>{
20071
+ queueMicrotask(cb);
20072
+ }
20073
+ } : void 0);
19993
20074
  }
19994
20075
  } else {
19995
20076
  this.options.logger.debug(`Reconnecting for the command ${command} …`);
19996
20077
  this.initSocket(()=>{
19997
- this.writeCommand(command, args);
19998
- cb?.();
20078
+ this.writeCommand(command, args, cb);
19999
20079
  });
20000
20080
  }
20001
20081
  }
@@ -20066,6 +20146,7 @@
20066
20146
  reInitializeAudio() {
20067
20147
  this.options.logger.debug("Recorder: reInitializeAudio()");
20068
20148
  this.clearUserMediaTimeout();
20149
+ this.userMediaLoading = false;
20069
20150
  this.userMedia?.stop();
20070
20151
  this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
20071
20152
  this.loadUserMedia();
@@ -20080,17 +20161,17 @@
20080
20161
  this.reset();
20081
20162
  this.clearUserMediaTimeout();
20082
20163
  this.clearConnectionTimeout();
20164
+ this.clearReconnectTimeout();
20083
20165
  this.clearRetryTimeout();
20084
20166
  this.clearStopTimeout();
20085
20167
  this.stopPings();
20168
+ this.userMediaLoading = false;
20086
20169
  this.connecting = false;
20170
+ this.reconnecting = false;
20171
+ this.reconnectAttempts = 0;
20087
20172
  if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
20088
20173
  if (this.submitting) ;
20089
- else if (this.stream) {
20090
- this.options.logger.debug("Recorder: destroying stream ...");
20091
- this.stream.destroy();
20092
- this.stream = void 0;
20093
- }
20174
+ else if (this.stream) this.discardSocket();
20094
20175
  this.unloaded = true;
20095
20176
  this.built = this.connecting = this.connected = false;
20096
20177
  }
@@ -20190,7 +20271,13 @@
20190
20271
  if (!this.connected) {
20191
20272
  this.options.logger.debug("Recorder: reconnecting before recording …");
20192
20273
  this.initSocket(()=>{
20193
- this.once("USER_MEDIA_READY", this.record.bind(this));
20274
+ this.once("SERVER_READY", ()=>{
20275
+ if (this.unloaded) return;
20276
+ if (this.userMediaLoaded || this.options.loadUserMediaOnRecord) this.record();
20277
+ else this.once("USER_MEDIA_READY", ()=>{
20278
+ if (!this.unloaded) this.record();
20279
+ });
20280
+ });
20194
20281
  });
20195
20282
  return;
20196
20283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "videomail-client",
3
- "version": "15.8.3",
3
+ "version": "15.9.0",
4
4
  "description": "A wicked npm package to record videos directly in the browser, for Deaf and Sign Language!",
5
5
  "keywords": [
6
6
  "webcam",
@@ -101,7 +101,7 @@
101
101
  "@types/ua-parser-js": "0.7.39",
102
102
  "@vitest/eslint-plugin": "1.6.27",
103
103
  "audit-ci": "7.1.0",
104
- "chromatic": "18.10.2",
104
+ "chromatic": "18.11.0",
105
105
  "cross-env": "10.1.0",
106
106
  "eslint": "10.12.0",
107
107
  "eslint-import-resolver-typescript": "4.4.5",
@@ -112,7 +112,7 @@
112
112
  "eslint-plugin-regexp": "3.3.1",
113
113
  "eslint-plugin-simple-import-sort": "14.0.0",
114
114
  "globals": "17.13.0",
115
- "jsdom": "30.1.1",
115
+ "jsdom": "30.1.2",
116
116
  "msw": "2.15.0",
117
117
  "msw-storybook-addon": "3.0.3",
118
118
  "prettier": "3.9.9",
@@ -125,7 +125,7 @@
125
125
  "storybook-html-rsbuild": "3.6.0",
126
126
  "type-fest": "5.10.0",
127
127
  "typescript": "6.0.3",
128
- "typescript-eslint": "8.71.0",
128
+ "typescript-eslint": "8.71.1",
129
129
  "vitest": "5.0.3"
130
130
  },
131
131
  "engines": {