videomail-client 15.8.4 → 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.4"
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,31 @@ 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
+ }
19687
19720
  discardSocket() {
19688
19721
  this.options.logger.debug("Recorder: discarding socket stream ...");
19689
19722
  const stream = this.stream;
@@ -19698,6 +19731,9 @@ var __webpack_exports__ = {};
19698
19731
  if (this.connectionFailed || this.connected || this.unloaded) return;
19699
19732
  this.connectionFailed = true;
19700
19733
  this.connecting = false;
19734
+ this.reconnecting = false;
19735
+ this.reconnectAttempts = 0;
19736
+ this.clearReconnectTimeout();
19701
19737
  this.clearConnectionTimeout();
19702
19738
  const { url2Connect, cause } = params;
19703
19739
  const online = this.isOnline();
@@ -19822,6 +19858,7 @@ var __webpack_exports__ = {};
19822
19858
  return;
19823
19859
  }
19824
19860
  if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
19861
+ this.clearReconnectTimeout();
19825
19862
  if (util_isAutomatedUserAgent()) {
19826
19863
  this.connecting = false;
19827
19864
  this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
@@ -19900,8 +19937,12 @@ var __webpack_exports__ = {};
19900
19937
  this.connectionTimeout = window.setTimeout(()=>{
19901
19938
  if (this.isOnline() && this.isUserMediaLoaded()) {
19902
19939
  this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
19903
- this.initSocket();
19904
- } else this.failConnection({
19940
+ this.reconnecting = true;
19941
+ this.handleConnectionFailure({
19942
+ url2Connect,
19943
+ cause: "timeout"
19944
+ });
19945
+ } else this.handleConnectionFailure({
19905
19946
  url2Connect,
19906
19947
  cause: "timeout"
19907
19948
  });
@@ -19916,9 +19957,11 @@ var __webpack_exports__ = {};
19916
19957
  this.options.logger.debug(debugLine);
19917
19958
  const tryReconnect = this.connected && this.userMediaLoaded;
19918
19959
  this.connected = false;
19919
- if (tryReconnect) this.initSocket();
19920
- else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19921
- this.failConnection({
19960
+ if (tryReconnect) {
19961
+ this.reconnecting = true;
19962
+ this.initSocket();
19963
+ } else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19964
+ this.handleConnectionFailure({
19922
19965
  url2Connect,
19923
19966
  cause: "closed"
19924
19967
  });
@@ -19932,6 +19975,9 @@ var __webpack_exports__ = {};
19932
19975
  if (!this.connected && !isClosing && !this.unloaded) {
19933
19976
  this.connected = true;
19934
19977
  this.connecting = this.unloaded = false;
19978
+ this.reconnecting = false;
19979
+ this.reconnectAttempts = 0;
19980
+ this.clearReconnectTimeout();
19935
19981
  this.emit("CONNECTED");
19936
19982
  null == cb || cb();
19937
19983
  }
@@ -19962,7 +20008,7 @@ var __webpack_exports__ = {};
19962
20008
  error: serializeError(err)
19963
20009
  });
19964
20010
  this.options.logger.debug(debugLine);
19965
- if (!this.connected) return void this.failConnection({
20011
+ if (!this.connected) return void this.handleConnectionFailure({
19966
20012
  url2Connect,
19967
20013
  cause: "error"
19968
20014
  });
@@ -20023,6 +20069,7 @@ var __webpack_exports__ = {};
20023
20069
  }
20024
20070
  userMediaErrorCallback(err, usedConstraints) {
20025
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)}`);
20026
20073
  this.userMediaLoading = false;
20027
20074
  this.clearUserMediaTimeout();
20028
20075
  const characteristics = null == (_this_userMedia = this.userMedia) ? void 0 : _this_userMedia.getCharacteristics();
@@ -20041,10 +20088,9 @@ var __webpack_exports__ = {};
20041
20088
  this.options.logger.debug(`Recorder: ignore user media error ${util_pretty(err)}`);
20042
20089
  this.retryTimeout = window.setTimeout(()=>{
20043
20090
  this.retryTimeout = void 0;
20044
- this.loadUserMedia();
20091
+ if (!this.unloaded) this.loadUserMedia();
20045
20092
  }, this.options.timeouts.userMedia);
20046
20093
  }
20047
- else if (this.unloaded) this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
20048
20094
  else {
20049
20095
  this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${util_pretty(err)}`);
20050
20096
  throw error_createError({
@@ -20055,26 +20101,46 @@ var __webpack_exports__ = {};
20055
20101
  }
20056
20102
  }
20057
20103
  getUserMediaCallback(localStream, params) {
20058
- 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
+ }
20059
20113
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? util_pretty(params) : ""})`);
20060
20114
  if (this.showUserMedia()) try {
20061
- this.clearUserMediaTimeout();
20062
20115
  this.userMedia.init(localStream, ()=>{
20063
- this.onUserMediaReady(params);
20116
+ if (!this.unloaded) this.onUserMediaReady(params);
20064
20117
  }, this.onAudioSample.bind(this), (err)=>{
20065
- this.emit("ERROR", {
20066
- err
20118
+ if (!this.unloaded) this.userMediaErrorCallback(err, {
20119
+ audio: isAudioEnabled(this.options)
20067
20120
  });
20068
20121
  }, null == params ? void 0 : params.switchingFacingMode);
20069
20122
  } catch (exc) {
20123
+ localStream.getTracks().forEach((track)=>{
20124
+ track.stop();
20125
+ });
20126
+ this.userMediaLoading = false;
20127
+ this.clearUserMediaTimeout();
20070
20128
  this.emit("ERROR", {
20071
20129
  exc
20072
20130
  });
20073
20131
  }
20132
+ else {
20133
+ localStream.getTracks().forEach((track)=>{
20134
+ track.stop();
20135
+ });
20136
+ this.userMediaLoading = false;
20137
+ this.clearUserMediaTimeout();
20138
+ }
20074
20139
  }
20075
20140
  loadGenuineUserMedia(params) {
20076
20141
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? util_pretty(params) : ""})`);
20077
20142
  this.emit("ASKING_WEBCAM_PERMISSION");
20143
+ this.userMediaLoading = true;
20078
20144
  const constraints = {
20079
20145
  video: {
20080
20146
  frameRate: {
@@ -20105,9 +20171,12 @@ var __webpack_exports__ = {};
20105
20171
  this.options.logger.debug(util_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
20106
20172
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
20107
20173
  streamPromise.then((localStream)=>{
20174
+ if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
20175
+ track.stop();
20176
+ });
20108
20177
  this.getUserMediaCallback(localStream, params);
20109
20178
  }).catch((reason)=>{
20110
- this.userMediaErrorCallback(reason, constraints);
20179
+ if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
20111
20180
  });
20112
20181
  }
20113
20182
  loadUserMedia(params) {
@@ -20121,7 +20190,8 @@ var __webpack_exports__ = {};
20121
20190
  try {
20122
20191
  if (!navigator.mediaDevices) throw new Error("No media devices are available.");
20123
20192
  this.userMediaTimeout = window.setTimeout(()=>{
20124
- if (!this.isReady()) {
20193
+ if (!this.userMediaLoaded) {
20194
+ this.userMediaLoading = false;
20125
20195
  const err = util_getBrowser(this.options).getNoAccessIssue();
20126
20196
  this.emit("ERROR", {
20127
20197
  err
@@ -20221,16 +20291,16 @@ var __webpack_exports__ = {};
20221
20291
  command,
20222
20292
  args
20223
20293
  };
20224
- this.writeStream(Buffer.from(JSON.stringify(commandObj)));
20225
- if (cb) setTimeout(function() {
20226
- cb();
20227
- }, 0);
20294
+ this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
20295
+ onFlushedCallback: ()=>{
20296
+ queueMicrotask(cb);
20297
+ }
20298
+ } : void 0);
20228
20299
  }
20229
20300
  } else {
20230
20301
  this.options.logger.debug(`Reconnecting for the command ${command} …`);
20231
20302
  this.initSocket(()=>{
20232
- this.writeCommand(command, args);
20233
- null == cb || cb();
20303
+ this.writeCommand(command, args, cb);
20234
20304
  });
20235
20305
  }
20236
20306
  }
@@ -20305,6 +20375,7 @@ var __webpack_exports__ = {};
20305
20375
  var _this_userMedia;
20306
20376
  this.options.logger.debug("Recorder: reInitializeAudio()");
20307
20377
  this.clearUserMediaTimeout();
20378
+ this.userMediaLoading = false;
20308
20379
  null == (_this_userMedia = this.userMedia) || _this_userMedia.stop();
20309
20380
  this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
20310
20381
  this.loadUserMedia();
@@ -20319,10 +20390,14 @@ var __webpack_exports__ = {};
20319
20390
  this.reset();
20320
20391
  this.clearUserMediaTimeout();
20321
20392
  this.clearConnectionTimeout();
20393
+ this.clearReconnectTimeout();
20322
20394
  this.clearRetryTimeout();
20323
20395
  this.clearStopTimeout();
20324
20396
  this.stopPings();
20397
+ this.userMediaLoading = false;
20325
20398
  this.connecting = false;
20399
+ this.reconnecting = false;
20400
+ this.reconnectAttempts = 0;
20326
20401
  if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
20327
20402
  if (this.submitting) ;
20328
20403
  else if (this.stream) this.discardSocket();
@@ -20430,7 +20505,13 @@ var __webpack_exports__ = {};
20430
20505
  if (!this.connected) {
20431
20506
  this.options.logger.debug("Recorder: reconnecting before recording …");
20432
20507
  this.initSocket(()=>{
20433
- 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
+ });
20434
20515
  });
20435
20516
  return;
20436
20517
  }
@@ -20735,7 +20816,7 @@ var __webpack_exports__ = {};
20735
20816
  return this.userMediaLoaded;
20736
20817
  }
20737
20818
  constructor(visuals, replay, options){
20738
- 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);
20739
20820
  this.visuals = visuals;
20740
20821
  this.replay = replay;
20741
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.4"
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,31 @@ 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
+ }
6716
6749
  discardSocket() {
6717
6750
  this.options.logger.debug("Recorder: discarding socket stream ...");
6718
6751
  const stream = this.stream;
@@ -6726,6 +6759,9 @@ class Recorder extends util_Despot {
6726
6759
  if (this.connectionFailed || this.connected || this.unloaded) return;
6727
6760
  this.connectionFailed = true;
6728
6761
  this.connecting = false;
6762
+ this.reconnecting = false;
6763
+ this.reconnectAttempts = 0;
6764
+ this.clearReconnectTimeout();
6729
6765
  this.clearConnectionTimeout();
6730
6766
  const { url2Connect, cause } = params;
6731
6767
  const online = this.isOnline();
@@ -6849,6 +6885,7 @@ class Recorder extends util_Despot {
6849
6885
  return;
6850
6886
  }
6851
6887
  if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
6888
+ this.clearReconnectTimeout();
6852
6889
  if (util_isAutomatedUserAgent()) {
6853
6890
  this.connecting = false;
6854
6891
  this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
@@ -6927,8 +6964,12 @@ class Recorder extends util_Despot {
6927
6964
  this.connectionTimeout = window.setTimeout(()=>{
6928
6965
  if (this.isOnline() && this.isUserMediaLoaded()) {
6929
6966
  this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
6930
- this.initSocket();
6931
- } else this.failConnection({
6967
+ this.reconnecting = true;
6968
+ this.handleConnectionFailure({
6969
+ url2Connect,
6970
+ cause: "timeout"
6971
+ });
6972
+ } else this.handleConnectionFailure({
6932
6973
  url2Connect,
6933
6974
  cause: "timeout"
6934
6975
  });
@@ -6943,9 +6984,11 @@ class Recorder extends util_Despot {
6943
6984
  this.options.logger.debug(debugLine);
6944
6985
  const tryReconnect = this.connected && this.userMediaLoaded;
6945
6986
  this.connected = false;
6946
- if (tryReconnect) this.initSocket();
6947
- else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
6948
- this.failConnection({
6987
+ if (tryReconnect) {
6988
+ this.reconnecting = true;
6989
+ this.initSocket();
6990
+ } else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
6991
+ this.handleConnectionFailure({
6949
6992
  url2Connect,
6950
6993
  cause: "closed"
6951
6994
  });
@@ -6958,6 +7001,9 @@ class Recorder extends util_Despot {
6958
7001
  if (!this.connected && !isClosing && !this.unloaded) {
6959
7002
  this.connected = true;
6960
7003
  this.connecting = this.unloaded = false;
7004
+ this.reconnecting = false;
7005
+ this.reconnectAttempts = 0;
7006
+ this.clearReconnectTimeout();
6961
7007
  this.emit("CONNECTED");
6962
7008
  cb?.();
6963
7009
  }
@@ -6987,7 +7033,7 @@ class Recorder extends util_Despot {
6987
7033
  error: serializeError(err)
6988
7034
  });
6989
7035
  this.options.logger.debug(debugLine);
6990
- if (!this.connected) return void this.failConnection({
7036
+ if (!this.connected) return void this.handleConnectionFailure({
6991
7037
  url2Connect,
6992
7038
  cause: "error"
6993
7039
  });
@@ -7047,6 +7093,7 @@ class Recorder extends util_Despot {
7047
7093
  return this.blocking;
7048
7094
  }
7049
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)}`);
7050
7097
  this.userMediaLoading = false;
7051
7098
  this.clearUserMediaTimeout();
7052
7099
  const characteristics = this.userMedia?.getCharacteristics();
@@ -7065,10 +7112,9 @@ class Recorder extends util_Despot {
7065
7112
  this.options.logger.debug(`Recorder: ignore user media error ${util_pretty(err)}`);
7066
7113
  this.retryTimeout = window.setTimeout(()=>{
7067
7114
  this.retryTimeout = void 0;
7068
- this.loadUserMedia();
7115
+ if (!this.unloaded) this.loadUserMedia();
7069
7116
  }, this.options.timeouts.userMedia);
7070
7117
  }
7071
- else if (this.unloaded) this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
7072
7118
  else {
7073
7119
  this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${util_pretty(err)}`);
7074
7120
  throw error_createError({
@@ -7079,26 +7125,46 @@ class Recorder extends util_Despot {
7079
7125
  }
7080
7126
  }
7081
7127
  getUserMediaCallback(localStream, params) {
7082
- 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
+ }
7083
7137
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? util_pretty(params) : ""})`);
7084
7138
  if (this.showUserMedia()) try {
7085
- this.clearUserMediaTimeout();
7086
7139
  this.userMedia.init(localStream, ()=>{
7087
- this.onUserMediaReady(params);
7140
+ if (!this.unloaded) this.onUserMediaReady(params);
7088
7141
  }, this.onAudioSample.bind(this), (err)=>{
7089
- this.emit("ERROR", {
7090
- err
7142
+ if (!this.unloaded) this.userMediaErrorCallback(err, {
7143
+ audio: isAudioEnabled(this.options)
7091
7144
  });
7092
7145
  }, params?.switchingFacingMode);
7093
7146
  } catch (exc) {
7147
+ localStream.getTracks().forEach((track)=>{
7148
+ track.stop();
7149
+ });
7150
+ this.userMediaLoading = false;
7151
+ this.clearUserMediaTimeout();
7094
7152
  this.emit("ERROR", {
7095
7153
  exc
7096
7154
  });
7097
7155
  }
7156
+ else {
7157
+ localStream.getTracks().forEach((track)=>{
7158
+ track.stop();
7159
+ });
7160
+ this.userMediaLoading = false;
7161
+ this.clearUserMediaTimeout();
7162
+ }
7098
7163
  }
7099
7164
  loadGenuineUserMedia(params) {
7100
7165
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? util_pretty(params) : ""})`);
7101
7166
  this.emit("ASKING_WEBCAM_PERMISSION");
7167
+ this.userMediaLoading = true;
7102
7168
  const constraints = {
7103
7169
  video: {
7104
7170
  frameRate: {
@@ -7129,9 +7195,12 @@ class Recorder extends util_Despot {
7129
7195
  this.options.logger.debug(util_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
7130
7196
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
7131
7197
  streamPromise.then((localStream)=>{
7198
+ if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
7199
+ track.stop();
7200
+ });
7132
7201
  this.getUserMediaCallback(localStream, params);
7133
7202
  }).catch((reason)=>{
7134
- this.userMediaErrorCallback(reason, constraints);
7203
+ if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
7135
7204
  });
7136
7205
  }
7137
7206
  loadUserMedia(params) {
@@ -7145,7 +7214,8 @@ class Recorder extends util_Despot {
7145
7214
  try {
7146
7215
  if (!navigator.mediaDevices) throw new Error("No media devices are available.");
7147
7216
  this.userMediaTimeout = window.setTimeout(()=>{
7148
- if (!this.isReady()) {
7217
+ if (!this.userMediaLoaded) {
7218
+ this.userMediaLoading = false;
7149
7219
  const err = util_getBrowser(this.options).getNoAccessIssue();
7150
7220
  this.emit("ERROR", {
7151
7221
  err
@@ -7244,16 +7314,16 @@ class Recorder extends util_Despot {
7244
7314
  command,
7245
7315
  args
7246
7316
  };
7247
- this.writeStream(Buffer.from(JSON.stringify(commandObj)));
7248
- if (cb) setTimeout(function() {
7249
- cb();
7250
- }, 0);
7317
+ this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
7318
+ onFlushedCallback: ()=>{
7319
+ queueMicrotask(cb);
7320
+ }
7321
+ } : void 0);
7251
7322
  }
7252
7323
  } else {
7253
7324
  this.options.logger.debug(`Reconnecting for the command ${command} …`);
7254
7325
  this.initSocket(()=>{
7255
- this.writeCommand(command, args);
7256
- cb?.();
7326
+ this.writeCommand(command, args, cb);
7257
7327
  });
7258
7328
  }
7259
7329
  }
@@ -7324,6 +7394,7 @@ class Recorder extends util_Despot {
7324
7394
  reInitializeAudio() {
7325
7395
  this.options.logger.debug("Recorder: reInitializeAudio()");
7326
7396
  this.clearUserMediaTimeout();
7397
+ this.userMediaLoading = false;
7327
7398
  this.userMedia?.stop();
7328
7399
  this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
7329
7400
  this.loadUserMedia();
@@ -7338,10 +7409,14 @@ class Recorder extends util_Despot {
7338
7409
  this.reset();
7339
7410
  this.clearUserMediaTimeout();
7340
7411
  this.clearConnectionTimeout();
7412
+ this.clearReconnectTimeout();
7341
7413
  this.clearRetryTimeout();
7342
7414
  this.clearStopTimeout();
7343
7415
  this.stopPings();
7416
+ this.userMediaLoading = false;
7344
7417
  this.connecting = false;
7418
+ this.reconnecting = false;
7419
+ this.reconnectAttempts = 0;
7345
7420
  if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
7346
7421
  if (this.submitting) ;
7347
7422
  else if (this.stream) this.discardSocket();
@@ -7444,7 +7519,13 @@ class Recorder extends util_Despot {
7444
7519
  if (!this.connected) {
7445
7520
  this.options.logger.debug("Recorder: reconnecting before recording …");
7446
7521
  this.initSocket(()=>{
7447
- 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
+ });
7448
7529
  });
7449
7530
  return;
7450
7531
  }
@@ -7745,7 +7826,7 @@ class Recorder extends util_Despot {
7745
7826
  return this.userMediaLoaded;
7746
7827
  }
7747
7828
  constructor(visuals, replay, options){
7748
- 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);
7749
7830
  this.visuals = visuals;
7750
7831
  this.replay = replay;
7751
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,9 @@ 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;
63
69
  private discardSocket;
64
70
  private isOnline;
65
71
  private failConnection;
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.4"
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,31 @@
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
+ }
19465
19501
  discardSocket() {
19466
19502
  this.options.logger.debug("Recorder: discarding socket stream ...");
19467
19503
  const stream = this.stream;
@@ -19475,6 +19511,9 @@
19475
19511
  if (this.connectionFailed || this.connected || this.unloaded) return;
19476
19512
  this.connectionFailed = true;
19477
19513
  this.connecting = false;
19514
+ this.reconnecting = false;
19515
+ this.reconnectAttempts = 0;
19516
+ this.clearReconnectTimeout();
19478
19517
  this.clearConnectionTimeout();
19479
19518
  const { url2Connect, cause } = params;
19480
19519
  const online = this.isOnline();
@@ -19598,6 +19637,7 @@
19598
19637
  return;
19599
19638
  }
19600
19639
  if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
19640
+ this.clearReconnectTimeout();
19601
19641
  if (util_isAutomatedUserAgent()) {
19602
19642
  this.connecting = false;
19603
19643
  this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
@@ -19676,8 +19716,12 @@
19676
19716
  this.connectionTimeout = window.setTimeout(()=>{
19677
19717
  if (this.isOnline() && this.isUserMediaLoaded()) {
19678
19718
  this.options.logger.debug(`${PIPE_SYMBOL}Reconnecting due to connection timeout.`);
19679
- this.initSocket();
19680
- } else this.failConnection({
19719
+ this.reconnecting = true;
19720
+ this.handleConnectionFailure({
19721
+ url2Connect,
19722
+ cause: "timeout"
19723
+ });
19724
+ } else this.handleConnectionFailure({
19681
19725
  url2Connect,
19682
19726
  cause: "timeout"
19683
19727
  });
@@ -19692,9 +19736,11 @@
19692
19736
  this.options.logger.debug(debugLine);
19693
19737
  const tryReconnect = this.connected && this.userMediaLoaded;
19694
19738
  this.connected = false;
19695
- if (tryReconnect) this.initSocket();
19696
- else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19697
- this.failConnection({
19739
+ if (tryReconnect) {
19740
+ this.reconnecting = true;
19741
+ this.initSocket();
19742
+ } else if (!this.connecting && !this.blocking) window.setTimeout(()=>{
19743
+ this.handleConnectionFailure({
19698
19744
  url2Connect,
19699
19745
  cause: "closed"
19700
19746
  });
@@ -19707,6 +19753,9 @@
19707
19753
  if (!this.connected && !isClosing && !this.unloaded) {
19708
19754
  this.connected = true;
19709
19755
  this.connecting = this.unloaded = false;
19756
+ this.reconnecting = false;
19757
+ this.reconnectAttempts = 0;
19758
+ this.clearReconnectTimeout();
19710
19759
  this.emit("CONNECTED");
19711
19760
  cb?.();
19712
19761
  }
@@ -19736,7 +19785,7 @@
19736
19785
  error: serializeError(err)
19737
19786
  });
19738
19787
  this.options.logger.debug(debugLine);
19739
- if (!this.connected) return void this.failConnection({
19788
+ if (!this.connected) return void this.handleConnectionFailure({
19740
19789
  url2Connect,
19741
19790
  cause: "error"
19742
19791
  });
@@ -19796,6 +19845,7 @@
19796
19845
  return this.blocking;
19797
19846
  }
19798
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)}`);
19799
19849
  this.userMediaLoading = false;
19800
19850
  this.clearUserMediaTimeout();
19801
19851
  const characteristics = this.userMedia?.getCharacteristics();
@@ -19814,10 +19864,9 @@
19814
19864
  this.options.logger.debug(`Recorder: ignore user media error ${util_pretty(err)}`);
19815
19865
  this.retryTimeout = window.setTimeout(()=>{
19816
19866
  this.retryTimeout = void 0;
19817
- this.loadUserMedia();
19867
+ if (!this.unloaded) this.loadUserMedia();
19818
19868
  }, this.options.timeouts.userMedia);
19819
19869
  }
19820
- else if (this.unloaded) this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${util_pretty(err)}`);
19821
19870
  else {
19822
19871
  this.options.logger.debug(`Recorder: no error listeners attached but throwing error ${util_pretty(err)}`);
19823
19872
  throw error_createError({
@@ -19828,26 +19877,46 @@
19828
19877
  }
19829
19878
  }
19830
19879
  getUserMediaCallback(localStream, params) {
19831
- 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
+ }
19832
19889
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? util_pretty(params) : ""})`);
19833
19890
  if (this.showUserMedia()) try {
19834
- this.clearUserMediaTimeout();
19835
19891
  this.userMedia.init(localStream, ()=>{
19836
- this.onUserMediaReady(params);
19892
+ if (!this.unloaded) this.onUserMediaReady(params);
19837
19893
  }, this.onAudioSample.bind(this), (err)=>{
19838
- this.emit("ERROR", {
19839
- err
19894
+ if (!this.unloaded) this.userMediaErrorCallback(err, {
19895
+ audio: isAudioEnabled(this.options)
19840
19896
  });
19841
19897
  }, params?.switchingFacingMode);
19842
19898
  } catch (exc) {
19899
+ localStream.getTracks().forEach((track)=>{
19900
+ track.stop();
19901
+ });
19902
+ this.userMediaLoading = false;
19903
+ this.clearUserMediaTimeout();
19843
19904
  this.emit("ERROR", {
19844
19905
  exc
19845
19906
  });
19846
19907
  }
19908
+ else {
19909
+ localStream.getTracks().forEach((track)=>{
19910
+ track.stop();
19911
+ });
19912
+ this.userMediaLoading = false;
19913
+ this.clearUserMediaTimeout();
19914
+ }
19847
19915
  }
19848
19916
  loadGenuineUserMedia(params) {
19849
19917
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? util_pretty(params) : ""})`);
19850
19918
  this.emit("ASKING_WEBCAM_PERMISSION");
19919
+ this.userMediaLoading = true;
19851
19920
  const constraints = {
19852
19921
  video: {
19853
19922
  frameRate: {
@@ -19878,9 +19947,12 @@
19878
19947
  this.options.logger.debug(util_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
19879
19948
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
19880
19949
  streamPromise.then((localStream)=>{
19950
+ if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
19951
+ track.stop();
19952
+ });
19881
19953
  this.getUserMediaCallback(localStream, params);
19882
19954
  }).catch((reason)=>{
19883
- this.userMediaErrorCallback(reason, constraints);
19955
+ if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
19884
19956
  });
19885
19957
  }
19886
19958
  loadUserMedia(params) {
@@ -19894,7 +19966,8 @@
19894
19966
  try {
19895
19967
  if (!navigator.mediaDevices) throw new Error("No media devices are available.");
19896
19968
  this.userMediaTimeout = window.setTimeout(()=>{
19897
- if (!this.isReady()) {
19969
+ if (!this.userMediaLoaded) {
19970
+ this.userMediaLoading = false;
19898
19971
  const err = util_getBrowser(this.options).getNoAccessIssue();
19899
19972
  this.emit("ERROR", {
19900
19973
  err
@@ -19993,16 +20066,16 @@
19993
20066
  command,
19994
20067
  args
19995
20068
  };
19996
- this.writeStream(Buffer.from(JSON.stringify(commandObj)));
19997
- if (cb) setTimeout(function() {
19998
- cb();
19999
- }, 0);
20069
+ this.writeStream(Buffer.from(JSON.stringify(commandObj)), cb ? {
20070
+ onFlushedCallback: ()=>{
20071
+ queueMicrotask(cb);
20072
+ }
20073
+ } : void 0);
20000
20074
  }
20001
20075
  } else {
20002
20076
  this.options.logger.debug(`Reconnecting for the command ${command} …`);
20003
20077
  this.initSocket(()=>{
20004
- this.writeCommand(command, args);
20005
- cb?.();
20078
+ this.writeCommand(command, args, cb);
20006
20079
  });
20007
20080
  }
20008
20081
  }
@@ -20073,6 +20146,7 @@
20073
20146
  reInitializeAudio() {
20074
20147
  this.options.logger.debug("Recorder: reInitializeAudio()");
20075
20148
  this.clearUserMediaTimeout();
20149
+ this.userMediaLoading = false;
20076
20150
  this.userMedia?.stop();
20077
20151
  this.userMediaLoaded = this.key = this.canvas = this.ctx = void 0;
20078
20152
  this.loadUserMedia();
@@ -20087,10 +20161,14 @@
20087
20161
  this.reset();
20088
20162
  this.clearUserMediaTimeout();
20089
20163
  this.clearConnectionTimeout();
20164
+ this.clearReconnectTimeout();
20090
20165
  this.clearRetryTimeout();
20091
20166
  this.clearStopTimeout();
20092
20167
  this.stopPings();
20168
+ this.userMediaLoading = false;
20093
20169
  this.connecting = false;
20170
+ this.reconnecting = false;
20171
+ this.reconnectAttempts = 0;
20094
20172
  if (this.userMedia) this.userMedia.unloadRemainingEventListeners();
20095
20173
  if (this.submitting) ;
20096
20174
  else if (this.stream) this.discardSocket();
@@ -20193,7 +20271,13 @@
20193
20271
  if (!this.connected) {
20194
20272
  this.options.logger.debug("Recorder: reconnecting before recording …");
20195
20273
  this.initSocket(()=>{
20196
- 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
+ });
20197
20281
  });
20198
20282
  return;
20199
20283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "videomail-client",
3
- "version": "15.8.4",
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": {