videomail-client 16.0.2 → 16.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -3620,7 +3620,7 @@ const constants = {
3620
3620
  }
3621
3621
  };
3622
3622
  var package_namespaceObject = {
3623
- rE: "16.0.2"
3623
+ rE: "16.1.0"
3624
3624
  };
3625
3625
  function isAudioEnabled(options) {
3626
3626
  return Boolean(options.audio.enabled);
@@ -4368,19 +4368,42 @@ class Form extends util_Despot {
4368
4368
  if (type?.toLowerCase() === "hidden") inputElement.value = "";
4369
4369
  }
4370
4370
  }
4371
- startListeningToSubmitEvents() {
4371
+ connectSubmitButton() {
4372
4372
  const submitButton = this.container.getSubmitButton();
4373
- if (submitButton) submitButton.onclick = this.doTheSubmit.bind(this);
4373
+ if (!submitButton) return;
4374
+ if (submitButton.form === this.formElement) return;
4375
+ if (!this.formElement.id) {
4376
+ let formId;
4377
+ do formId = `videomail-form-${Form.nextFormId++}`;
4378
+ while (document.getElementById(formId))
4379
+ this.formElement.id = formId;
4380
+ this.generatedFormId = formId;
4381
+ }
4382
+ this.submitButton = submitButton;
4383
+ this.submitButtonFormAttribute = submitButton.getAttribute("form");
4384
+ submitButton.setAttribute("form", this.formElement.id);
4385
+ }
4386
+ startListeningToSubmitEvents() {
4387
+ this.connectSubmitButton();
4388
+ this.formElement.addEventListener("submit", this.handleSubmit);
4374
4389
  }
4375
4390
  stopListeningToSubmitEvents() {
4376
- const submitButton = this.container.getSubmitButton();
4377
- if (submitButton) submitButton.onclick = null;
4391
+ this.formElement.removeEventListener("submit", this.handleSubmit);
4392
+ if (this.submitButton) {
4393
+ if (null === this.submitButtonFormAttribute) this.submitButton.removeAttribute("form");
4394
+ else this.submitButton.setAttribute("form", this.submitButtonFormAttribute);
4395
+ this.submitButton = void 0;
4396
+ this.submitButtonFormAttribute = null;
4397
+ }
4398
+ if (this.generatedFormId === this.formElement.id) this.formElement.removeAttribute("id");
4399
+ this.generatedFormId = void 0;
4378
4400
  }
4379
4401
  async doTheSubmit(e) {
4380
4402
  if (e) {
4381
4403
  this.options.logger.debug(`Form: doTheSubmit(${string_pretty(e)})`);
4382
4404
  e.preventDefault();
4383
4405
  } else this.options.logger.debug("Form: doTheSubmit()");
4406
+ if (this.isSubmitting) return false;
4384
4407
  const hasHostAction = this.formElement.hasAttribute("action");
4385
4408
  const url = this.formElement.getAttribute("action") ?? this.options.apiUrl;
4386
4409
  const method = this.formElement.getAttribute("method");
@@ -4395,7 +4418,14 @@ class Form extends util_Despot {
4395
4418
  default:
4396
4419
  chosenMethod = FormMethod.POST;
4397
4420
  }
4398
- if (this.container.hasElement()) await this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
4421
+ if (this.container.hasElement()) {
4422
+ this.isSubmitting = true;
4423
+ try {
4424
+ await this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
4425
+ } finally{
4426
+ this.isSubmitting = false;
4427
+ }
4428
+ }
4399
4429
  return false;
4400
4430
  }
4401
4431
  getInvalidElement() {
@@ -4416,9 +4446,11 @@ class Form extends util_Despot {
4416
4446
  html_showElement(this.formElement);
4417
4447
  }
4418
4448
  constructor(container, formElement, options){
4419
- super("Form", options), form_define_property(this, "container", void 0), form_define_property(this, "formElement", void 0), form_define_property(this, "validate", (event)=>{
4449
+ super("Form", options), form_define_property(this, "container", void 0), form_define_property(this, "formElement", void 0), form_define_property(this, "keyInput", void 0), form_define_property(this, "submitButton", void 0), form_define_property(this, "submitButtonFormAttribute", null), form_define_property(this, "generatedFormId", void 0), form_define_property(this, "isSubmitting", false), form_define_property(this, "FORM_FIELDS", {}), form_define_property(this, "validate", (event)=>{
4420
4450
  this.container.validate(event);
4421
- }), form_define_property(this, "keyInput", void 0), form_define_property(this, "FORM_FIELDS", {});
4451
+ }), form_define_property(this, "handleSubmit", (event)=>{
4452
+ if (!event.defaultPrevented) this.doTheSubmit(event);
4453
+ });
4422
4454
  this.container = container;
4423
4455
  this.formElement = formElement;
4424
4456
  this.FORM_FIELDS = {
@@ -4434,6 +4466,7 @@ class Form extends util_Despot {
4434
4466
  };
4435
4467
  }
4436
4468
  }
4469
+ form_define_property(Form, "nextFormId", 0);
4437
4470
  const wrappers_form = Form;
4438
4471
  function resource_define_property(obj, key, value) {
4439
4472
  if (key in obj) Object.defineProperty(obj, key, {
@@ -4970,7 +5003,7 @@ class Buttons extends util_Despot {
4970
5003
  html_disableElement(radioButtonElement);
4971
5004
  return radioButtonElement;
4972
5005
  }
4973
- makeButton(buttonClass, text, clickHandler, show, id, type, selector, disabled = true) {
5006
+ makeButton(buttonClass, text, clickHandler, show, id, type = "button", selector, disabled = true) {
4974
5007
  let buttonElement;
4975
5008
  buttonElement = id ? document.querySelector(`#${id}`) : selector ? document.querySelector(selector) : this.buttonsElement?.querySelector(`.${buttonClass}`);
4976
5009
  if (buttonElement) buttonElement = html_adjustButton(buttonElement, show, type, disabled);
@@ -5727,10 +5760,14 @@ class RecorderInsides extends util_Despot {
5727
5760
  this.on("USER_MEDIA_READY", ()=>{
5728
5761
  this.facingMode?.show();
5729
5762
  });
5763
+ this.on("CAMERA_RECOVERING", ()=>{
5764
+ this.facingMode?.hide();
5765
+ });
5730
5766
  this.on("RECORDING", ()=>{
5731
5767
  this.startRecording();
5732
5768
  });
5733
5769
  this.on("RESUMING", ()=>{
5770
+ this.hidePause();
5734
5771
  this.resumeRecording();
5735
5772
  });
5736
5773
  this.on("STOPPING", ()=>{
@@ -6300,9 +6337,19 @@ class AudioRecorder {
6300
6337
  const float32Array = e.inputBuffer.getChannelData(0);
6301
6338
  cb(new audio_sample(float32Array));
6302
6339
  }
6340
+ disconnect() {
6341
+ if (this.scriptProcessor) {
6342
+ this.scriptProcessor.onaudioprocess = null;
6343
+ this.scriptProcessor.disconnect();
6344
+ }
6345
+ this.audioInput?.disconnect();
6346
+ this.volume?.disconnect();
6347
+ }
6303
6348
  init(localMediaStream) {
6304
6349
  this.options.logger.debug("AudioRecorder: init()");
6350
+ this.disconnect();
6305
6351
  const volume = this.getAudioContext().createGain();
6352
+ this.volume = volume;
6306
6353
  try {
6307
6354
  this.audioInput = this.getAudioContext().createMediaStreamSource(localMediaStream);
6308
6355
  } catch (exc) {
@@ -6334,10 +6381,13 @@ class AudioRecorder {
6334
6381
  this.onAudioProcess(e, cb);
6335
6382
  };
6336
6383
  }
6384
+ async resume() {
6385
+ const context = this.getAudioContext();
6386
+ if ("running" !== context.state) await context.resume();
6387
+ }
6337
6388
  stop() {
6338
6389
  this.options.logger.debug("AudioRecorder: stop()");
6339
- if (this.scriptProcessor) this.scriptProcessor.onaudioprocess = null;
6340
- if (this.audioInput) this.audioInput.disconnect();
6390
+ this.disconnect();
6341
6391
  if (this.hasAudioContext()) this.getAudioContext().close().then(()=>{
6342
6392
  this.options.logger.debug("AudioRecorder: audio context is closed");
6343
6393
  this.vcAudioContext = void 0;
@@ -6356,6 +6406,7 @@ class AudioRecorder {
6356
6406
  constructor(userMedia, options){
6357
6407
  AudioRecorder_define_property(this, "scriptProcessor", void 0);
6358
6408
  AudioRecorder_define_property(this, "audioInput", void 0);
6409
+ AudioRecorder_define_property(this, "volume", void 0);
6359
6410
  AudioRecorder_define_property(this, "vcAudioContext", void 0);
6360
6411
  AudioRecorder_define_property(this, "userMedia", void 0);
6361
6412
  AudioRecorder_define_property(this, "options", void 0);
@@ -6415,6 +6466,16 @@ class UserMedia extends util_Despot {
6415
6466
  this.currentVisualStream = void 0;
6416
6467
  }
6417
6468
  }
6469
+ async restorePlayback() {
6470
+ if (!this.rawVisualUserMedia) throw new Error("No camera preview is available.");
6471
+ await this.rawVisualUserMedia.play();
6472
+ await this.audioRecorder?.resume();
6473
+ }
6474
+ hasLiveStream() {
6475
+ const videoReady = this.currentVisualStream?.getVideoTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
6476
+ const audioReady = !isAudioEnabled(this.options) || this.currentVisualStream?.getAudioTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
6477
+ return Boolean(this.currentVisualStream?.active && videoReady && audioReady);
6478
+ }
6418
6479
  hasEnded() {
6419
6480
  if (this.rawVisualUserMedia?.ended) return this.rawVisualUserMedia.ended;
6420
6481
  return !this.currentVisualStream?.active;
@@ -6429,6 +6490,7 @@ class UserMedia extends util_Despot {
6429
6490
  }
6430
6491
  unloadRemainingEventListeners() {
6431
6492
  this.options.logger.debug("UserMedia: unloadRemainingEventListeners()");
6493
+ this.clearInitializationListeners?.();
6432
6494
  mediaEvents.forEach((eventName)=>{
6433
6495
  this.rawVisualUserMedia?.removeEventListener(eventName, this.outputEvent);
6434
6496
  });
@@ -6438,11 +6500,13 @@ class UserMedia extends util_Despot {
6438
6500
  this.removeListener("SENDING_FIRST_FRAME");
6439
6501
  this.audioRecorder?.record(audioCallback);
6440
6502
  }
6441
- init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, switchingFacingMode) {
6503
+ init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, params) {
6504
+ this.clearInitializationListeners?.();
6442
6505
  this.stop(localMediaStream, {
6443
6506
  aboutToInitialize: true,
6444
- switchingFacingMode
6507
+ preserveRecordingState: Boolean(params?.switchingFacingMode || params?.recovering)
6445
6508
  });
6509
+ const initializationId = ++this.initializationId;
6446
6510
  this.onPlayReached = false;
6447
6511
  this.onLoadedMetaDataReached = false;
6448
6512
  this.playingPromiseReached = false;
@@ -6455,18 +6519,25 @@ class UserMedia extends util_Despot {
6455
6519
  this.rawVisualUserMedia?.removeEventListener("play", onPlay);
6456
6520
  this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
6457
6521
  };
6522
+ this.clearInitializationListeners = ()=>{
6523
+ this.rawVisualUserMedia?.removeEventListener("play", onPlay);
6524
+ this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
6525
+ this.clearInitializationListeners = void 0;
6526
+ };
6458
6527
  const play = ()=>{
6459
6528
  try {
6460
6529
  this.rawVisualUserMedia?.load();
6461
6530
  if (this.rawVisualUserMedia?.paused) {
6462
6531
  this.options.logger.debug(`UserMedia: play(): media.readyState=${this.rawVisualUserMedia.readyState}, media.paused=${this.rawVisualUserMedia.paused}, media.ended=${this.rawVisualUserMedia.ended}, media.played=${string_pretty(this.rawVisualUserMedia.played)}`);
6463
6532
  this.rawVisualUserMedia.play().then(()=>{
6533
+ if (initializationId !== this.initializationId) return;
6464
6534
  if (!this.playingPromiseReached) {
6465
6535
  this.options.logger.debug("UserMedia: play promise successful. Playing now.");
6466
6536
  this.playingPromiseReached = true;
6467
6537
  }
6468
6538
  }).catch((exc)=>{
6469
- if (exc instanceof Error) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
6539
+ if (initializationId !== this.initializationId) return;
6540
+ if (exc instanceof Error && !params?.recovering) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
6470
6541
  else {
6471
6542
  unloadAllEventListeners();
6472
6543
  endedEarlyCallback(error_createError({
@@ -6487,16 +6558,28 @@ class UserMedia extends util_Despot {
6487
6558
  this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
6488
6559
  if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) try {
6489
6560
  this.audioRecorder.init(localMediaStream);
6490
- this.on("SENDING_FIRST_FRAME", ()=>{
6491
- this.audioRecord(audioCallback);
6492
- });
6493
- videoCallback();
6561
+ const onAudioReady = ()=>{
6562
+ if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
6563
+ if (this.recording) this.audioRecord(audioCallback);
6564
+ else this.on("SENDING_FIRST_FRAME", ()=>{
6565
+ this.audioRecord(audioCallback);
6566
+ });
6567
+ this.clearInitializationListeners?.();
6568
+ videoCallback();
6569
+ };
6570
+ if (params?.recovering) this.audioRecorder.resume().then(onAudioReady).catch(endedEarlyCallback);
6571
+ else onAudioReady();
6494
6572
  } catch (exc) {
6495
6573
  unloadAllEventListeners();
6496
- this.stop();
6574
+ this.stop(void 0, {
6575
+ preserveRecordingState: Boolean(params?.recovering)
6576
+ });
6497
6577
  endedEarlyCallback(exc);
6498
6578
  }
6499
- else videoCallback();
6579
+ else {
6580
+ this.clearInitializationListeners?.();
6581
+ videoCallback();
6582
+ }
6500
6583
  };
6501
6584
  const onPlay = ()=>{
6502
6585
  try {
@@ -6566,9 +6649,8 @@ class UserMedia extends util_Despot {
6566
6649
  this.setVisualStream(localMediaStream);
6567
6650
  play();
6568
6651
  } catch (exc) {
6569
- this.emit("ERROR", {
6570
- exc
6571
- });
6652
+ unloadAllEventListeners();
6653
+ endedEarlyCallback(exc);
6572
6654
  }
6573
6655
  }
6574
6656
  getVideoTrackLabel() {
@@ -6581,6 +6663,8 @@ class UserMedia extends util_Despot {
6581
6663
  try {
6582
6664
  let chosenStream = visualStream;
6583
6665
  if (!params?.aboutToInitialize) {
6666
+ this.initializationId++;
6667
+ this.clearInitializationListeners?.();
6584
6668
  chosenStream ??= this.currentVisualStream;
6585
6669
  const tracks = chosenStream?.getTracks();
6586
6670
  if (tracks) tracks.forEach((track)=>{
@@ -6590,7 +6674,7 @@ class UserMedia extends util_Despot {
6590
6674
  this.audioRecorder?.stop();
6591
6675
  this.audioRecorder = void 0;
6592
6676
  }
6593
- if (!params?.switchingFacingMode) this.paused = this.recording = false;
6677
+ if (!params?.preserveRecordingState) this.paused = this.recording = false;
6594
6678
  } catch (exc) {
6595
6679
  this.emit("ERROR", {
6596
6680
  exc
@@ -6677,7 +6761,7 @@ class UserMedia extends util_Despot {
6677
6761
  };
6678
6762
  }
6679
6763
  constructor(recorder, options){
6680
- super("UserMedia", options), userMedia_define_property(this, "recorder", void 0), userMedia_define_property(this, "rawVisualUserMedia", void 0), userMedia_define_property(this, "paused", false), userMedia_define_property(this, "recording", false), userMedia_define_property(this, "audioRecorder", void 0), userMedia_define_property(this, "currentVisualStream", void 0), userMedia_define_property(this, "onPlayReached", false), userMedia_define_property(this, "onLoadedMetaDataReached", false), userMedia_define_property(this, "playingPromiseReached", false), userMedia_define_property(this, "videoTrackLabel", void 0), userMedia_define_property(this, "outputEvent", (e)=>{
6764
+ super("UserMedia", options), userMedia_define_property(this, "recorder", void 0), userMedia_define_property(this, "rawVisualUserMedia", void 0), userMedia_define_property(this, "paused", false), userMedia_define_property(this, "recording", false), userMedia_define_property(this, "audioRecorder", void 0), userMedia_define_property(this, "currentVisualStream", void 0), userMedia_define_property(this, "onPlayReached", false), userMedia_define_property(this, "onLoadedMetaDataReached", false), userMedia_define_property(this, "playingPromiseReached", false), userMedia_define_property(this, "videoTrackLabel", void 0), userMedia_define_property(this, "clearInitializationListeners", void 0), userMedia_define_property(this, "initializationId", 0), userMedia_define_property(this, "outputEvent", (e)=>{
6681
6765
  this.logEvent(e.type, {
6682
6766
  readyState: this.rawVisualUserMedia?.readyState
6683
6767
  });
@@ -6938,7 +7022,9 @@ class Recorder extends util_Despot {
6938
7022
  this.pingInterval = void 0;
6939
7023
  }
6940
7024
  onAudioSample(audioSample) {
6941
- if (this.recordingTransportFailed) return;
7025
+ if (this.recordingTransportFailed || this.cameraRecovery) return;
7026
+ const sampleRate = this.userMedia?.getAudioSampleRate();
7027
+ if (sampleRate && sampleRate > 0) this.recordingAudioSampleRate ??= sampleRate;
6942
7028
  this.samplesCount++;
6943
7029
  this.mediaTransmissionStarted = true;
6944
7030
  const audioBuffer = audioSample.toBuffer();
@@ -6951,19 +7037,26 @@ class Recorder extends util_Despot {
6951
7037
  try {
6952
7038
  this.options.logger.debug(`Recorder: onUserMediaReady(${params ? string_pretty(params) : ""})`);
6953
7039
  const switchingFacingMode = params?.switchingFacingMode;
7040
+ if (params?.recovering) {
7041
+ if (!this.userMedia?.hasLiveStream()) return void this.failCameraRecovery(new Error("The restored camera has no live tracks."));
7042
+ if (void 0 !== this.recordingAudioSampleRate && this.userMedia.getAudioSampleRate() !== this.recordingAudioSampleRate) return void this.failCameraRecovery(new Error("The audio sample rate changed during recovery."));
7043
+ }
6954
7044
  this.userMediaLoading = this.blocking = this.unloaded = this.submitting = false;
6955
7045
  this.userMediaLoaded = true;
6956
7046
  this.clearUserMediaTimeout();
6957
- if (!switchingFacingMode) this.loop = this.createLoop();
7047
+ if (!switchingFacingMode && !params?.recovering) this.loop = this.createLoop();
6958
7048
  this.show();
6959
7049
  if (params?.recordWhenReady) this.record();
6960
7050
  this.emit("USER_MEDIA_READY", {
6961
7051
  switchingFacingMode: params?.switchingFacingMode,
7052
+ recovering: params?.recovering,
6962
7053
  paused: this.isPaused(),
6963
7054
  recordWhenReady: params?.recordWhenReady
6964
7055
  });
7056
+ if (params?.recovering) this.finishCameraRecovery(true);
6965
7057
  } catch (exc) {
6966
- this.emit("ERROR", {
7058
+ if (params?.recovering) this.failCameraRecovery(exc);
7059
+ else this.emit("ERROR", {
6967
7060
  exc
6968
7061
  });
6969
7062
  }
@@ -7334,6 +7427,7 @@ class Recorder extends util_Despot {
7334
7427
  return this.blocking;
7335
7428
  }
7336
7429
  userMediaErrorCallback(err, usedConstraints) {
7430
+ if (this.cameraRecovery) return void this.failCameraRecovery(err);
7337
7431
  if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${string_pretty(err)}`);
7338
7432
  this.userMediaLoading = false;
7339
7433
  this.clearUserMediaTimeout();
@@ -7365,7 +7459,7 @@ class Recorder extends util_Despot {
7365
7459
  });
7366
7460
  }
7367
7461
  }
7368
- getUserMediaCallback(localStream, params) {
7462
+ getUserMediaCallback(localStream, params, requestId = this.mediaRequestId) {
7369
7463
  if (!this.userMedia) {
7370
7464
  localStream.getTracks().forEach((track)=>{
7371
7465
  track.stop();
@@ -7378,20 +7472,20 @@ class Recorder extends util_Despot {
7378
7472
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? string_pretty(params) : ""})`);
7379
7473
  if (this.showUserMedia()) try {
7380
7474
  this.userMedia.init(localStream, ()=>{
7381
- if (!this.unloaded) this.onUserMediaReady(params);
7475
+ if (!this.unloaded && requestId === this.mediaRequestId) this.onUserMediaReady(params);
7382
7476
  }, this.onAudioSample.bind(this), (err)=>{
7383
- if (!this.unloaded) this.userMediaErrorCallback(err, {
7477
+ if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(err, {
7384
7478
  audio: isAudioEnabled(this.options)
7385
7479
  });
7386
- }, params?.switchingFacingMode);
7480
+ }, params);
7387
7481
  } catch (exc) {
7388
7482
  localStream.getTracks().forEach((track)=>{
7389
7483
  track.stop();
7390
7484
  });
7391
7485
  this.userMediaLoading = false;
7392
7486
  this.clearUserMediaTimeout();
7393
- this.emit("ERROR", {
7394
- exc
7487
+ this.userMediaErrorCallback(exc, {
7488
+ audio: isAudioEnabled(this.options)
7395
7489
  });
7396
7490
  }
7397
7491
  else {
@@ -7400,12 +7494,14 @@ class Recorder extends util_Despot {
7400
7494
  });
7401
7495
  this.userMediaLoading = false;
7402
7496
  this.clearUserMediaTimeout();
7497
+ this.cancelCameraRecovery();
7403
7498
  }
7404
7499
  }
7405
7500
  loadGenuineUserMedia(params) {
7406
7501
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? string_pretty(params) : ""})`);
7407
7502
  this.emit("ASKING_WEBCAM_PERMISSION");
7408
7503
  this.userMediaLoading = true;
7504
+ const requestId = ++this.mediaRequestId;
7409
7505
  const constraints = {
7410
7506
  video: {
7411
7507
  frameRate: {
@@ -7419,18 +7515,18 @@ class Recorder extends util_Despot {
7419
7515
  this.options.logger.debug(string_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
7420
7516
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
7421
7517
  streamPromise.then((localStream)=>{
7422
- if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
7518
+ if (this.unloaded || requestId !== this.mediaRequestId) return void localStream.getTracks().forEach((track)=>{
7423
7519
  track.stop();
7424
7520
  });
7425
- this.getUserMediaCallback(localStream, params);
7521
+ this.getUserMediaCallback(localStream, params, requestId);
7426
7522
  }).catch((reason)=>{
7427
- if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
7523
+ if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(reason, constraints);
7428
7524
  });
7429
7525
  }
7430
7526
  configureVideoConstraints(constraints, params) {
7431
7527
  const videoConstraints = "object" == typeof constraints.video ? constraints.video : void 0;
7432
7528
  if (!videoConstraints) return;
7433
- if (params?.switchingFacingMode) videoConstraints.facingMode = params.switchingFacingMode;
7529
+ if (params?.switchingFacingMode || params?.recovering) videoConstraints.facingMode = params.switchingFacingMode || this.facingMode;
7434
7530
  if (this.options.video.width) videoConstraints.width = {
7435
7531
  ideal: this.options.video.width
7436
7532
  };
@@ -7458,8 +7554,8 @@ class Recorder extends util_Despot {
7458
7554
  if (!this.userMediaLoaded) {
7459
7555
  this.userMediaLoading = false;
7460
7556
  const err = util_getBrowser(this.options).getNoAccessIssue();
7461
- this.emit("ERROR", {
7462
- err
7557
+ this.userMediaErrorCallback(err, {
7558
+ audio: isAudioEnabled(this.options)
7463
7559
  });
7464
7560
  }
7465
7561
  }, this.options.timeouts.userMedia);
@@ -7469,6 +7565,7 @@ class Recorder extends util_Despot {
7469
7565
  this.clearUserMediaTimeout();
7470
7566
  this.options.logger.debug("Recorder: failed to load genuine user media");
7471
7567
  this.userMediaLoading = false;
7568
+ if (this.cameraRecovery) return void this.failCameraRecovery(exc);
7472
7569
  const errorListeners = this.getListeners("ERROR");
7473
7570
  if (errorListeners?.length) this.emit("ERROR", {
7474
7571
  exc
@@ -7592,9 +7689,10 @@ class Recorder extends util_Despot {
7592
7689
  return this.recordingStats;
7593
7690
  }
7594
7691
  getAudioSampleRate() {
7595
- return this.userMedia?.getAudioSampleRate();
7692
+ return this.recordingAudioSampleRate ?? this.userMedia?.getAudioSampleRate();
7596
7693
  }
7597
7694
  stop(params) {
7695
+ this.cancelCameraRecovery();
7598
7696
  if (!this.userMedia) throw new Error("No user media defined, unable to stop");
7599
7697
  this.options.logger.debug(`Recorder: stop(${params ? string_pretty(params) : ""})`);
7600
7698
  this.recordingActive = false;
@@ -7620,7 +7718,7 @@ class Recorder extends util_Despot {
7620
7718
  if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
7621
7719
  if (isAudioEnabled(this.options) && this.userMedia) {
7622
7720
  this.recordingStats.samplesCount = this.samplesCount;
7623
- this.recordingStats.sampleRate = this.userMedia.getAudioSampleRate();
7721
+ this.recordingStats.sampleRate = this.recordingAudioSampleRate ?? this.userMedia.getAudioSampleRate();
7624
7722
  }
7625
7723
  this.writeCommand("stop", this.recordingStats, ()=>{
7626
7724
  this.emit("STOPPED", {
@@ -7675,6 +7773,7 @@ class Recorder extends util_Despot {
7675
7773
  this.built = this.connecting = this.connected = false;
7676
7774
  }
7677
7775
  reset() {
7776
+ this.cancelCameraRecovery();
7678
7777
  if (!this.unloaded) {
7679
7778
  this.options.logger.debug("Recorder: reset()");
7680
7779
  this.emit("RESETTING");
@@ -7702,7 +7801,70 @@ class Recorder extends util_Despot {
7702
7801
  isReady() {
7703
7802
  return this.userMedia?.isReady();
7704
7803
  }
7804
+ finishCameraRecovery(restored) {
7805
+ this.clearUserMediaTimeout();
7806
+ const resolve = this.resolveCameraRecovery;
7807
+ const resumeRequested = restored && this.resumeAfterCameraRecovery;
7808
+ this.cameraRecovery = void 0;
7809
+ this.resolveCameraRecovery = void 0;
7810
+ this.resumeAfterCameraRecovery = false;
7811
+ resolve?.(restored);
7812
+ if (resumeRequested && this.isPaused()) this.resume();
7813
+ }
7814
+ cancelCameraRecovery() {
7815
+ if (!this.cameraRecovery) return;
7816
+ this.mediaRequestId++;
7817
+ this.userMediaLoading = false;
7818
+ this.userMediaLoaded = false;
7819
+ this.clearUserMediaTimeout();
7820
+ this.userMedia?.stop(void 0, {
7821
+ preserveRecordingState: true
7822
+ });
7823
+ this.finishCameraRecovery(false);
7824
+ }
7825
+ failCameraRecovery(exc) {
7826
+ this.cancelCameraRecovery();
7827
+ const err = error_createError({
7828
+ message: "Camera recovery failed",
7829
+ explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
7830
+ exc,
7831
+ options: this.options
7832
+ });
7833
+ this.emit("CAMERA_RECOVERY_FAILED", {
7834
+ err
7835
+ });
7836
+ }
7837
+ recoverCamera() {
7838
+ if (this.cameraRecovery) return this.cameraRecovery;
7839
+ if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
7840
+ const liveStream = this.userMedia.hasLiveStream();
7841
+ if (liveStream && this.isRecording()) return Promise.resolve(true);
7842
+ if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
7843
+ const recovery = new Promise((resolve)=>{
7844
+ this.resolveCameraRecovery = resolve;
7845
+ });
7846
+ this.cameraRecovery = recovery;
7847
+ this.emit("CAMERA_RECOVERING");
7848
+ if (liveStream) {
7849
+ this.userMediaTimeout = window.setTimeout(()=>{
7850
+ this.failCameraRecovery(new Error("Camera playback recovery timed out."));
7851
+ }, this.options.timeouts.userMedia);
7852
+ this.userMedia.restorePlayback().then(()=>{
7853
+ if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
7854
+ }).catch((exc)=>{
7855
+ if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
7856
+ });
7857
+ } else {
7858
+ this.userMediaLoaded = false;
7859
+ this.loadUserMedia({
7860
+ recovering: true
7861
+ });
7862
+ }
7863
+ return recovery;
7864
+ }
7705
7865
  pause(params) {
7866
+ this.cancelCameraRecovery();
7867
+ this.resumeAfterDrain = false;
7706
7868
  if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
7707
7869
  else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
7708
7870
  this.userMedia?.pause();
@@ -7711,10 +7873,15 @@ class Recorder extends util_Despot {
7711
7873
  this.sendPings();
7712
7874
  }
7713
7875
  resume() {
7876
+ if (this.cameraRecovery || !this.userMedia?.hasLiveStream()) {
7877
+ this.recoverCamera();
7878
+ this.resumeAfterCameraRecovery = Boolean(this.cameraRecovery);
7879
+ return;
7880
+ }
7714
7881
  this.options.logger.debug(`Recorder: resume() with frame ${this.framesCount}`);
7715
7882
  this.stopPings();
7716
7883
  this.emit("RESUMING");
7717
- this.userMedia?.resume();
7884
+ this.userMedia.resume();
7718
7885
  if (this.streamBackpressured) this.resumeAfterDrain = true;
7719
7886
  else this.loop.start();
7720
7887
  }
@@ -7769,7 +7936,7 @@ class Recorder extends util_Despot {
7769
7936
  return newLoop;
7770
7937
  }
7771
7938
  record() {
7772
- if (this.unloaded) return;
7939
+ if (this.unloaded || this.cameraRecovery) return;
7773
7940
  if (!this.connected) {
7774
7941
  this.options.logger.debug("Recorder: reconnecting before recording …");
7775
7942
  this.initSocket(()=>{
@@ -7842,6 +8009,7 @@ class Recorder extends util_Despot {
7842
8009
  this.recordingActive = true;
7843
8010
  this.recordingTransportFailed = false;
7844
8011
  this.mediaTransmissionStarted = false;
8012
+ this.recordingAudioSampleRate = void 0;
7845
8013
  this.userMedia.record();
7846
8014
  this.emit("RECORDING", {
7847
8015
  framesCount: this.framesCount
@@ -7903,7 +8071,7 @@ class Recorder extends util_Despot {
7903
8071
  }
7904
8072
  }
7905
8073
  switchFacingMode() {
7906
- if (!util_getBrowser(this.options).isMobile()) return;
8074
+ if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
7907
8075
  if ("user" === this.facingMode) this.facingMode = "environment";
7908
8076
  else if ("environment" === this.facingMode) this.facingMode = "user";
7909
8077
  else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
@@ -7914,12 +8082,14 @@ class Recorder extends util_Despot {
7914
8082
  initEvents() {
7915
8083
  this.options.logger.debug("Recorder: initEvents()");
7916
8084
  this.on("SUBMITTING", ()=>{
8085
+ this.cancelCameraRecovery();
7917
8086
  this.submitting = true;
7918
8087
  });
7919
8088
  this.on("SUBMITTED", ()=>{
7920
8089
  this.submitting = false;
7921
8090
  });
7922
8091
  this.on("BLOCKING", ()=>{
8092
+ this.cancelCameraRecovery();
7923
8093
  this.blocking = true;
7924
8094
  this.clearUserMediaTimeout();
7925
8095
  });
@@ -8093,7 +8263,7 @@ class Recorder extends util_Despot {
8093
8263
  return this.userMediaLoaded;
8094
8264
  }
8095
8265
  constructor(visuals, replay, options){
8096
- super("Recorder", options), recorder_define_property(this, "visuals", void 0), recorder_define_property(this, "replay", void 0), recorder_define_property(this, "pendingWrites", new boundedWriteQueue()), recorder_define_property(this, "loop", void 0), recorder_define_property(this, "originalAnimationFrameObject", void 0), recorder_define_property(this, "samplesCount", 0), recorder_define_property(this, "framesCount", 0), recorder_define_property(this, "recordingStats", void 0), recorder_define_property(this, "confirmedFrameNumber", 0), recorder_define_property(this, "confirmedSampleNumber", 0), recorder_define_property(this, "recorderElement", void 0), recorder_define_property(this, "userMedia", void 0), recorder_define_property(this, "userMediaTimeout", void 0), recorder_define_property(this, "retryTimeout", void 0), recorder_define_property(this, "reconnectTimeout", void 0), recorder_define_property(this, "connectionTimeout", void 0), recorder_define_property(this, "backpressureTimeout", void 0), recorder_define_property(this, "stopTimeout", void 0), recorder_define_property(this, "frameProgress", void 0), recorder_define_property(this, "sampleProgress", void 0), recorder_define_property(this, "canvas", void 0), recorder_define_property(this, "recordingDimensions", void 0), recorder_define_property(this, "ctx", void 0), recorder_define_property(this, "userMediaLoaded", void 0), recorder_define_property(this, "userMediaLoading", false), recorder_define_property(this, "submitting", false), recorder_define_property(this, "unloaded", void 0), recorder_define_property(this, "stopTime", void 0), recorder_define_property(this, "stream", void 0), recorder_define_property(this, "streamBackpressured", false), recorder_define_property(this, "resumeAfterDrain", false), recorder_define_property(this, "mediaTransmissionStarted", false), recorder_define_property(this, "recordingActive", false), recorder_define_property(this, "recordingTransportFailed", false), recorder_define_property(this, "connecting", false), recorder_define_property(this, "connected", false), recorder_define_property(this, "connectionFailed", false), recorder_define_property(this, "reconnecting", false), recorder_define_property(this, "reconnectAttempts", 0), recorder_define_property(this, "blocking", false), recorder_define_property(this, "built", false), recorder_define_property(this, "key", void 0), recorder_define_property(this, "waitingTime", void 0), recorder_define_property(this, "connectingStartedAt", void 0), recorder_define_property(this, "lastCloseEvent", void 0), recorder_define_property(this, "lastSocketError", void 0), recorder_define_property(this, "pingInterval", void 0), recorder_define_property(this, "frame", void 0), recorder_define_property(this, "recordingBuffer", void 0), recorder_define_property(this, "facingMode", void 0);
8266
+ super("Recorder", options), recorder_define_property(this, "visuals", void 0), recorder_define_property(this, "replay", void 0), recorder_define_property(this, "pendingWrites", new boundedWriteQueue()), recorder_define_property(this, "loop", void 0), recorder_define_property(this, "originalAnimationFrameObject", void 0), recorder_define_property(this, "samplesCount", 0), recorder_define_property(this, "framesCount", 0), recorder_define_property(this, "recordingAudioSampleRate", void 0), recorder_define_property(this, "recordingStats", void 0), recorder_define_property(this, "confirmedFrameNumber", 0), recorder_define_property(this, "confirmedSampleNumber", 0), recorder_define_property(this, "recorderElement", void 0), recorder_define_property(this, "userMedia", void 0), recorder_define_property(this, "userMediaTimeout", void 0), recorder_define_property(this, "retryTimeout", void 0), recorder_define_property(this, "reconnectTimeout", void 0), recorder_define_property(this, "connectionTimeout", void 0), recorder_define_property(this, "backpressureTimeout", void 0), recorder_define_property(this, "stopTimeout", void 0), recorder_define_property(this, "frameProgress", void 0), recorder_define_property(this, "sampleProgress", void 0), recorder_define_property(this, "canvas", void 0), recorder_define_property(this, "recordingDimensions", void 0), recorder_define_property(this, "ctx", void 0), recorder_define_property(this, "userMediaLoaded", void 0), recorder_define_property(this, "userMediaLoading", false), recorder_define_property(this, "mediaRequestId", 0), recorder_define_property(this, "cameraRecovery", void 0), recorder_define_property(this, "resolveCameraRecovery", void 0), recorder_define_property(this, "resumeAfterCameraRecovery", false), recorder_define_property(this, "submitting", false), recorder_define_property(this, "unloaded", void 0), recorder_define_property(this, "stopTime", void 0), recorder_define_property(this, "stream", void 0), recorder_define_property(this, "streamBackpressured", false), recorder_define_property(this, "resumeAfterDrain", false), recorder_define_property(this, "mediaTransmissionStarted", false), recorder_define_property(this, "recordingActive", false), recorder_define_property(this, "recordingTransportFailed", false), recorder_define_property(this, "connecting", false), recorder_define_property(this, "connected", false), recorder_define_property(this, "connectionFailed", false), recorder_define_property(this, "reconnecting", false), recorder_define_property(this, "reconnectAttempts", 0), recorder_define_property(this, "blocking", false), recorder_define_property(this, "built", false), recorder_define_property(this, "key", void 0), recorder_define_property(this, "waitingTime", void 0), recorder_define_property(this, "connectingStartedAt", void 0), recorder_define_property(this, "lastCloseEvent", void 0), recorder_define_property(this, "lastSocketError", void 0), recorder_define_property(this, "pingInterval", void 0), recorder_define_property(this, "frame", void 0), recorder_define_property(this, "recordingBuffer", void 0), recorder_define_property(this, "facingMode", void 0);
8097
8267
  this.visuals = visuals;
8098
8268
  this.replay = replay;
8099
8269
  this.facingMode = options.video.facingMode;
@@ -8386,6 +8556,17 @@ class Visuals extends util_Despot {
8386
8556
  this.endWaiting();
8387
8557
  this.container.enableForm(false);
8388
8558
  });
8559
+ this.on("CAMERA_RECOVERING", ()=>{
8560
+ this.clearCameraRecoveryNotice();
8561
+ });
8562
+ this.on("CAMERA_RECOVERY_FAILED", ({ err })=>{
8563
+ this.cameraRecoveryFailed = true;
8564
+ this.options.logger.warn(err);
8565
+ if (this.options.displayErrors) this.notifier.notify(err.message, err.explanation, {
8566
+ problem: true
8567
+ });
8568
+ this.endWaiting();
8569
+ });
8389
8570
  this.on("PREVIEW", ()=>{
8390
8571
  this.endWaiting();
8391
8572
  });
@@ -8464,6 +8645,7 @@ class Visuals extends util_Despot {
8464
8645
  child.remove();
8465
8646
  }
8466
8647
  reset() {
8648
+ this.clearCameraRecoveryNotice();
8467
8649
  this.endWaiting();
8468
8650
  this.recorder.reset();
8469
8651
  }
@@ -8474,11 +8656,13 @@ class Visuals extends util_Despot {
8474
8656
  this.container.endWaiting();
8475
8657
  }
8476
8658
  stop(params) {
8659
+ this.clearCameraRecoveryNotice();
8477
8660
  this.recorder.stop(params);
8478
8661
  this.recorderInsides.hidePause();
8479
8662
  }
8480
8663
  back(keepHidden = false, cb) {
8481
8664
  this.options.logger.debug(`Visuals: back(keepHidden = ${keepHidden})`);
8665
+ this.cameraRecoveryFailed = false;
8482
8666
  this.replay.hide();
8483
8667
  this.notifier.hide();
8484
8668
  if (keepHidden) {
@@ -8500,6 +8684,7 @@ class Visuals extends util_Despot {
8500
8684
  if (!this.built) return;
8501
8685
  const e = params?.e;
8502
8686
  this.options.logger.debug(`Visuals: unload(${e ? string_pretty(e) : ""})`);
8687
+ this.cameraRecoveryFailed = false;
8503
8688
  this.recorder.unload(params);
8504
8689
  this.recorderInsides.unload();
8505
8690
  this.replay.unload(params);
@@ -8513,13 +8698,31 @@ class Visuals extends util_Despot {
8513
8698
  this.recorder.pause(params);
8514
8699
  this.recorderInsides.showPause();
8515
8700
  }
8701
+ clearCameraRecoveryNotice() {
8702
+ if (this.cameraRecoveryFailed) {
8703
+ this.notifier.hide();
8704
+ this.recorder.show();
8705
+ this.cameraRecoveryFailed = false;
8706
+ }
8707
+ }
8708
+ recoverCamera() {
8709
+ if (this.replay.isShown() || this.isNotifying() && !this.cameraRecoveryFailed) return Promise.resolve(false);
8710
+ this.clearCameraRecoveryNotice();
8711
+ return this.recorder.recoverCamera();
8712
+ }
8516
8713
  resume() {
8517
- if (this.recorderInsides.isCountingDown()) this.recorderInsides.resumeCountdown();
8518
- else this.recorder.resume();
8519
- this.recorderInsides.hidePause();
8714
+ this.clearCameraRecoveryNotice();
8715
+ if (this.recorderInsides.isCountingDown()) {
8716
+ this.recorderInsides.resumeCountdown();
8717
+ this.recorderInsides.hidePause();
8718
+ } else {
8719
+ this.recorder.resume();
8720
+ if (!this.recorder.isPaused()) this.recorderInsides.hidePause();
8721
+ }
8520
8722
  }
8521
8723
  pauseOrResume() {
8522
- if (this.isRecordable()) {
8724
+ if (this.cameraRecoveryFailed && this.recorder.isPaused()) this.resume();
8725
+ else if (this.isRecordable()) {
8523
8726
  if (this.isRecording()) this.pause();
8524
8727
  else if (this.recorder.isPaused()) this.resume();
8525
8728
  else if (this.recorder.isReady()) this.recorder.record();
@@ -8641,7 +8844,7 @@ class Visuals extends util_Despot {
8641
8844
  return this.visualsElement;
8642
8845
  }
8643
8846
  constructor(container, options){
8644
- super("Visuals", options), visuals_define_property(this, "container", void 0), visuals_define_property(this, "replay", void 0), visuals_define_property(this, "recorder", void 0), visuals_define_property(this, "recorderInsides", void 0), visuals_define_property(this, "notifier", void 0), visuals_define_property(this, "visualsElement", void 0), visuals_define_property(this, "built", false);
8847
+ super("Visuals", options), visuals_define_property(this, "container", void 0), visuals_define_property(this, "replay", void 0), visuals_define_property(this, "recorder", void 0), visuals_define_property(this, "recorderInsides", void 0), visuals_define_property(this, "notifier", void 0), visuals_define_property(this, "visualsElement", void 0), visuals_define_property(this, "built", false), visuals_define_property(this, "cameraRecoveryFailed", false);
8645
8848
  this.container = container;
8646
8849
  this.replay = new visuals_replay(this, options);
8647
8850
  this.recorder = new visuals_recorder(this, this.replay, options);
@@ -8743,10 +8946,10 @@ class Container extends util_Despot {
8743
8946
  });
8744
8947
  if (!playerOnly) this.stopListeningToVisibility = this.visibility.onChange((visible)=>{
8745
8948
  if (this.built) if (visible) {
8746
- if (isAutoPauseEnabled(this.options) && this.isCountingDown()) this.resume();
8949
+ if (isAutoPauseEnabled(this.options)) this.recoverCamera();
8747
8950
  this.emit("VISIBLE");
8748
8951
  } else {
8749
- if (isAutoPauseEnabled(this.options) && (this.isCountingDown() || this.isRecording())) this.pause();
8952
+ if (isAutoPauseEnabled(this.options)) this.suspend();
8750
8953
  this.emit("INVISIBLE");
8751
8954
  }
8752
8955
  });
@@ -8934,6 +9137,13 @@ class Container extends util_Despot {
8934
9137
  pause(params) {
8935
9138
  this.visuals.pause(params);
8936
9139
  }
9140
+ suspend() {
9141
+ if (this.built && (this.isCountingDown() || this.isRecording() || this.isPaused())) this.pause();
9142
+ }
9143
+ recoverCamera() {
9144
+ if (!this.built || this.hasError || this.submitted) return Promise.resolve(false);
9145
+ return this.visuals.recoverCamera();
9146
+ }
8937
9147
  validate(event, force = false) {
8938
9148
  let runValidation = true;
8939
9149
  let valid = true;
@@ -9275,6 +9485,12 @@ class VideomailClient extends util_Despot {
9275
9485
  record() {
9276
9486
  this.container.record();
9277
9487
  }
9488
+ pause() {
9489
+ this.container.suspend();
9490
+ }
9491
+ recoverCamera() {
9492
+ return this.container.recoverCamera();
9493
+ }
9278
9494
  getLogLines() {
9279
9495
  if (this.options.logger.getLines) return this.options.logger.getLines();
9280
9496
  }