videomail-client 16.0.3 → 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/umd/index.js CHANGED
@@ -11121,7 +11121,7 @@
11121
11121
  var client = __webpack_require__("Q");
11122
11122
  var client_default = /*#__PURE__*/ __webpack_require__.n(client);
11123
11123
  var package_namespaceObject = {
11124
- rE: "16.0.3"
11124
+ rE: "16.1.0"
11125
11125
  };
11126
11126
  function isAudioEnabled(options) {
11127
11127
  return Boolean(options.audio.enabled);
@@ -18636,10 +18636,14 @@
18636
18636
  this.on("USER_MEDIA_READY", ()=>{
18637
18637
  this.facingMode?.show();
18638
18638
  });
18639
+ this.on("CAMERA_RECOVERING", ()=>{
18640
+ this.facingMode?.hide();
18641
+ });
18639
18642
  this.on("RECORDING", ()=>{
18640
18643
  this.startRecording();
18641
18644
  });
18642
18645
  this.on("RESUMING", ()=>{
18646
+ this.hidePause();
18643
18647
  this.resumeRecording();
18644
18648
  });
18645
18649
  this.on("STOPPING", ()=>{
@@ -19254,6 +19258,7 @@
19254
19258
  class AudioRecorder {
19255
19259
  scriptProcessor;
19256
19260
  audioInput;
19261
+ volume;
19257
19262
  vcAudioContext;
19258
19263
  userMedia;
19259
19264
  options;
@@ -19276,9 +19281,19 @@
19276
19281
  const float32Array = e.inputBuffer.getChannelData(0);
19277
19282
  cb(new esm(float32Array));
19278
19283
  }
19284
+ disconnect() {
19285
+ if (this.scriptProcessor) {
19286
+ this.scriptProcessor.onaudioprocess = null;
19287
+ this.scriptProcessor.disconnect();
19288
+ }
19289
+ this.audioInput?.disconnect();
19290
+ this.volume?.disconnect();
19291
+ }
19279
19292
  init(localMediaStream) {
19280
19293
  this.options.logger.debug("AudioRecorder: init()");
19294
+ this.disconnect();
19281
19295
  const volume = this.getAudioContext().createGain();
19296
+ this.volume = volume;
19282
19297
  try {
19283
19298
  this.audioInput = this.getAudioContext().createMediaStreamSource(localMediaStream);
19284
19299
  } catch (exc) {
@@ -19310,10 +19325,13 @@
19310
19325
  this.onAudioProcess(e, cb);
19311
19326
  };
19312
19327
  }
19328
+ async resume() {
19329
+ const context = this.getAudioContext();
19330
+ if ("running" !== context.state) await context.resume();
19331
+ }
19313
19332
  stop() {
19314
19333
  this.options.logger.debug("AudioRecorder: stop()");
19315
- if (this.scriptProcessor) this.scriptProcessor.onaudioprocess = null;
19316
- if (this.audioInput) this.audioInput.disconnect();
19334
+ this.disconnect();
19317
19335
  if (this.hasAudioContext()) this.getAudioContext().close().then(()=>{
19318
19336
  this.options.logger.debug("AudioRecorder: audio context is closed");
19319
19337
  this.vcAudioContext = void 0;
@@ -19358,6 +19376,8 @@
19358
19376
  onLoadedMetaDataReached = false;
19359
19377
  playingPromiseReached = false;
19360
19378
  videoTrackLabel;
19379
+ clearInitializationListeners;
19380
+ initializationId = 0;
19361
19381
  outputEvent = (e)=>{
19362
19382
  this.logEvent(e.type, {
19363
19383
  readyState: this.rawVisualUserMedia?.readyState
@@ -19399,6 +19419,16 @@
19399
19419
  this.currentVisualStream = void 0;
19400
19420
  }
19401
19421
  }
19422
+ async restorePlayback() {
19423
+ if (!this.rawVisualUserMedia) throw new Error("No camera preview is available.");
19424
+ await this.rawVisualUserMedia.play();
19425
+ await this.audioRecorder?.resume();
19426
+ }
19427
+ hasLiveStream() {
19428
+ const videoReady = this.currentVisualStream?.getVideoTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
19429
+ const audioReady = !isAudioEnabled(this.options) || this.currentVisualStream?.getAudioTracks().some((track)=>"live" === track.readyState && track.enabled && !track.muted);
19430
+ return Boolean(this.currentVisualStream?.active && videoReady && audioReady);
19431
+ }
19402
19432
  hasEnded() {
19403
19433
  if (this.rawVisualUserMedia?.ended) return this.rawVisualUserMedia.ended;
19404
19434
  return !this.currentVisualStream?.active;
@@ -19413,6 +19443,7 @@
19413
19443
  }
19414
19444
  unloadRemainingEventListeners() {
19415
19445
  this.options.logger.debug("UserMedia: unloadRemainingEventListeners()");
19446
+ this.clearInitializationListeners?.();
19416
19447
  mediaEvents.forEach((eventName)=>{
19417
19448
  this.rawVisualUserMedia?.removeEventListener(eventName, this.outputEvent);
19418
19449
  });
@@ -19422,11 +19453,13 @@
19422
19453
  this.removeListener("SENDING_FIRST_FRAME");
19423
19454
  this.audioRecorder?.record(audioCallback);
19424
19455
  }
19425
- init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, switchingFacingMode) {
19456
+ init(localMediaStream, videoCallback, audioCallback, endedEarlyCallback, params) {
19457
+ this.clearInitializationListeners?.();
19426
19458
  this.stop(localMediaStream, {
19427
19459
  aboutToInitialize: true,
19428
- switchingFacingMode
19460
+ preserveRecordingState: Boolean(params?.switchingFacingMode || params?.recovering)
19429
19461
  });
19462
+ const initializationId = ++this.initializationId;
19430
19463
  this.onPlayReached = false;
19431
19464
  this.onLoadedMetaDataReached = false;
19432
19465
  this.playingPromiseReached = false;
@@ -19439,18 +19472,25 @@
19439
19472
  this.rawVisualUserMedia?.removeEventListener("play", onPlay);
19440
19473
  this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
19441
19474
  };
19475
+ this.clearInitializationListeners = ()=>{
19476
+ this.rawVisualUserMedia?.removeEventListener("play", onPlay);
19477
+ this.rawVisualUserMedia?.removeEventListener("loadedmetadata", onLoadedMetaData);
19478
+ this.clearInitializationListeners = void 0;
19479
+ };
19442
19480
  const play = ()=>{
19443
19481
  try {
19444
19482
  this.rawVisualUserMedia?.load();
19445
19483
  if (this.rawVisualUserMedia?.paused) {
19446
19484
  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)}`);
19447
19485
  this.rawVisualUserMedia.play().then(()=>{
19486
+ if (initializationId !== this.initializationId) return;
19448
19487
  if (!this.playingPromiseReached) {
19449
19488
  this.options.logger.debug("UserMedia: play promise successful. Playing now.");
19450
19489
  this.playingPromiseReached = true;
19451
19490
  }
19452
19491
  }).catch((exc)=>{
19453
- if (exc instanceof Error) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
19492
+ if (initializationId !== this.initializationId) return;
19493
+ if (exc instanceof Error && !params?.recovering) this.options.logger.warn(`Caught pending user media promise exception: ${exc.toString()}`);
19454
19494
  else {
19455
19495
  unloadAllEventListeners();
19456
19496
  endedEarlyCallback(error_createError({
@@ -19471,16 +19511,28 @@
19471
19511
  this.options.logger.debug(`UserMedia: fireCallbacks(readyState=${readyState}, onPlayReached=${this.onPlayReached}, onLoadedMetaDataReached=${this.onLoadedMetaDataReached})`);
19472
19512
  if (this.onPlayReached && this.onLoadedMetaDataReached) if (this.audioRecorder) try {
19473
19513
  this.audioRecorder.init(localMediaStream);
19474
- this.on("SENDING_FIRST_FRAME", ()=>{
19475
- this.audioRecord(audioCallback);
19476
- });
19477
- videoCallback();
19514
+ const onAudioReady = ()=>{
19515
+ if (initializationId !== this.initializationId || this.currentVisualStream !== localMediaStream) return;
19516
+ if (this.recording) this.audioRecord(audioCallback);
19517
+ else this.on("SENDING_FIRST_FRAME", ()=>{
19518
+ this.audioRecord(audioCallback);
19519
+ });
19520
+ this.clearInitializationListeners?.();
19521
+ videoCallback();
19522
+ };
19523
+ if (params?.recovering) this.audioRecorder.resume().then(onAudioReady).catch(endedEarlyCallback);
19524
+ else onAudioReady();
19478
19525
  } catch (exc) {
19479
19526
  unloadAllEventListeners();
19480
- this.stop();
19527
+ this.stop(void 0, {
19528
+ preserveRecordingState: Boolean(params?.recovering)
19529
+ });
19481
19530
  endedEarlyCallback(exc);
19482
19531
  }
19483
- else videoCallback();
19532
+ else {
19533
+ this.clearInitializationListeners?.();
19534
+ videoCallback();
19535
+ }
19484
19536
  };
19485
19537
  const onPlay = ()=>{
19486
19538
  try {
@@ -19550,9 +19602,8 @@
19550
19602
  this.setVisualStream(localMediaStream);
19551
19603
  play();
19552
19604
  } catch (exc) {
19553
- this.emit("ERROR", {
19554
- exc
19555
- });
19605
+ unloadAllEventListeners();
19606
+ endedEarlyCallback(exc);
19556
19607
  }
19557
19608
  }
19558
19609
  getVideoTrackLabel() {
@@ -19565,6 +19616,8 @@
19565
19616
  try {
19566
19617
  let chosenStream = visualStream;
19567
19618
  if (!params?.aboutToInitialize) {
19619
+ this.initializationId++;
19620
+ this.clearInitializationListeners?.();
19568
19621
  chosenStream ??= this.currentVisualStream;
19569
19622
  const tracks = chosenStream?.getTracks();
19570
19623
  if (tracks) tracks.forEach((track)=>{
@@ -19574,7 +19627,7 @@
19574
19627
  this.audioRecorder?.stop();
19575
19628
  this.audioRecorder = void 0;
19576
19629
  }
19577
- if (!params?.switchingFacingMode) this.paused = this.recording = false;
19630
+ if (!params?.preserveRecordingState) this.paused = this.recording = false;
19578
19631
  } catch (exc) {
19579
19632
  this.emit("ERROR", {
19580
19633
  exc
@@ -19672,6 +19725,7 @@
19672
19725
  originalAnimationFrameObject;
19673
19726
  samplesCount = 0;
19674
19727
  framesCount = 0;
19728
+ recordingAudioSampleRate;
19675
19729
  recordingStats;
19676
19730
  confirmedFrameNumber = 0;
19677
19731
  confirmedSampleNumber = 0;
@@ -19690,6 +19744,10 @@
19690
19744
  ctx;
19691
19745
  userMediaLoaded;
19692
19746
  userMediaLoading = false;
19747
+ mediaRequestId = 0;
19748
+ cameraRecovery;
19749
+ resolveCameraRecovery;
19750
+ resumeAfterCameraRecovery = false;
19693
19751
  submitting = false;
19694
19752
  unloaded;
19695
19753
  stopTime;
@@ -19953,7 +20011,9 @@
19953
20011
  this.pingInterval = void 0;
19954
20012
  }
19955
20013
  onAudioSample(audioSample) {
19956
- if (this.recordingTransportFailed) return;
20014
+ if (this.recordingTransportFailed || this.cameraRecovery) return;
20015
+ const sampleRate = this.userMedia?.getAudioSampleRate();
20016
+ if (sampleRate && sampleRate > 0) this.recordingAudioSampleRate ??= sampleRate;
19957
20017
  this.samplesCount++;
19958
20018
  this.mediaTransmissionStarted = true;
19959
20019
  const audioBuffer = audioSample.toBuffer();
@@ -19966,19 +20026,26 @@
19966
20026
  try {
19967
20027
  this.options.logger.debug(`Recorder: onUserMediaReady(${params ? string_pretty(params) : ""})`);
19968
20028
  const switchingFacingMode = params?.switchingFacingMode;
20029
+ if (params?.recovering) {
20030
+ if (!this.userMedia?.hasLiveStream()) return void this.failCameraRecovery(new Error("The restored camera has no live tracks."));
20031
+ if (void 0 !== this.recordingAudioSampleRate && this.userMedia.getAudioSampleRate() !== this.recordingAudioSampleRate) return void this.failCameraRecovery(new Error("The audio sample rate changed during recovery."));
20032
+ }
19969
20033
  this.userMediaLoading = this.blocking = this.unloaded = this.submitting = false;
19970
20034
  this.userMediaLoaded = true;
19971
20035
  this.clearUserMediaTimeout();
19972
- if (!switchingFacingMode) this.loop = this.createLoop();
20036
+ if (!switchingFacingMode && !params?.recovering) this.loop = this.createLoop();
19973
20037
  this.show();
19974
20038
  if (params?.recordWhenReady) this.record();
19975
20039
  this.emit("USER_MEDIA_READY", {
19976
20040
  switchingFacingMode: params?.switchingFacingMode,
20041
+ recovering: params?.recovering,
19977
20042
  paused: this.isPaused(),
19978
20043
  recordWhenReady: params?.recordWhenReady
19979
20044
  });
20045
+ if (params?.recovering) this.finishCameraRecovery(true);
19980
20046
  } catch (exc) {
19981
- this.emit("ERROR", {
20047
+ if (params?.recovering) this.failCameraRecovery(exc);
20048
+ else this.emit("ERROR", {
19982
20049
  exc
19983
20050
  });
19984
20051
  }
@@ -20349,6 +20416,7 @@
20349
20416
  return this.blocking;
20350
20417
  }
20351
20418
  userMediaErrorCallback(err, usedConstraints) {
20419
+ if (this.cameraRecovery) return void this.failCameraRecovery(err);
20352
20420
  if (this.unloaded) return void this.options.logger.debug(`Recorder: already unloaded. Not going to throw error ${string_pretty(err)}`);
20353
20421
  this.userMediaLoading = false;
20354
20422
  this.clearUserMediaTimeout();
@@ -20380,7 +20448,7 @@
20380
20448
  });
20381
20449
  }
20382
20450
  }
20383
- getUserMediaCallback(localStream, params) {
20451
+ getUserMediaCallback(localStream, params, requestId = this.mediaRequestId) {
20384
20452
  if (!this.userMedia) {
20385
20453
  localStream.getTracks().forEach((track)=>{
20386
20454
  track.stop();
@@ -20393,20 +20461,20 @@
20393
20461
  this.options.logger.debug(`Recorder: getUserMediaCallback(${params ? string_pretty(params) : ""})`);
20394
20462
  if (this.showUserMedia()) try {
20395
20463
  this.userMedia.init(localStream, ()=>{
20396
- if (!this.unloaded) this.onUserMediaReady(params);
20464
+ if (!this.unloaded && requestId === this.mediaRequestId) this.onUserMediaReady(params);
20397
20465
  }, this.onAudioSample.bind(this), (err)=>{
20398
- if (!this.unloaded) this.userMediaErrorCallback(err, {
20466
+ if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(err, {
20399
20467
  audio: isAudioEnabled(this.options)
20400
20468
  });
20401
- }, params?.switchingFacingMode);
20469
+ }, params);
20402
20470
  } catch (exc) {
20403
20471
  localStream.getTracks().forEach((track)=>{
20404
20472
  track.stop();
20405
20473
  });
20406
20474
  this.userMediaLoading = false;
20407
20475
  this.clearUserMediaTimeout();
20408
- this.emit("ERROR", {
20409
- exc
20476
+ this.userMediaErrorCallback(exc, {
20477
+ audio: isAudioEnabled(this.options)
20410
20478
  });
20411
20479
  }
20412
20480
  else {
@@ -20415,12 +20483,14 @@
20415
20483
  });
20416
20484
  this.userMediaLoading = false;
20417
20485
  this.clearUserMediaTimeout();
20486
+ this.cancelCameraRecovery();
20418
20487
  }
20419
20488
  }
20420
20489
  loadGenuineUserMedia(params) {
20421
20490
  this.options.logger.debug(`Recorder: loadGenuineUserMedia(${params ? string_pretty(params) : ""})`);
20422
20491
  this.emit("ASKING_WEBCAM_PERMISSION");
20423
20492
  this.userMediaLoading = true;
20493
+ const requestId = ++this.mediaRequestId;
20424
20494
  const constraints = {
20425
20495
  video: {
20426
20496
  frameRate: {
@@ -20434,18 +20504,18 @@
20434
20504
  this.options.logger.debug(string_summarize("Recorder: available webcam constraints are:", navigator.mediaDevices.getSupportedConstraints()));
20435
20505
  const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
20436
20506
  streamPromise.then((localStream)=>{
20437
- if (this.unloaded) return void localStream.getTracks().forEach((track)=>{
20507
+ if (this.unloaded || requestId !== this.mediaRequestId) return void localStream.getTracks().forEach((track)=>{
20438
20508
  track.stop();
20439
20509
  });
20440
- this.getUserMediaCallback(localStream, params);
20510
+ this.getUserMediaCallback(localStream, params, requestId);
20441
20511
  }).catch((reason)=>{
20442
- if (!this.unloaded) this.userMediaErrorCallback(reason, constraints);
20512
+ if (!this.unloaded && requestId === this.mediaRequestId) this.userMediaErrorCallback(reason, constraints);
20443
20513
  });
20444
20514
  }
20445
20515
  configureVideoConstraints(constraints, params) {
20446
20516
  const videoConstraints = "object" == typeof constraints.video ? constraints.video : void 0;
20447
20517
  if (!videoConstraints) return;
20448
- if (params?.switchingFacingMode) videoConstraints.facingMode = params.switchingFacingMode;
20518
+ if (params?.switchingFacingMode || params?.recovering) videoConstraints.facingMode = params.switchingFacingMode || this.facingMode;
20449
20519
  if (this.options.video.width) videoConstraints.width = {
20450
20520
  ideal: this.options.video.width
20451
20521
  };
@@ -20473,8 +20543,8 @@
20473
20543
  if (!this.userMediaLoaded) {
20474
20544
  this.userMediaLoading = false;
20475
20545
  const err = util_getBrowser(this.options).getNoAccessIssue();
20476
- this.emit("ERROR", {
20477
- err
20546
+ this.userMediaErrorCallback(err, {
20547
+ audio: isAudioEnabled(this.options)
20478
20548
  });
20479
20549
  }
20480
20550
  }, this.options.timeouts.userMedia);
@@ -20484,6 +20554,7 @@
20484
20554
  this.clearUserMediaTimeout();
20485
20555
  this.options.logger.debug("Recorder: failed to load genuine user media");
20486
20556
  this.userMediaLoading = false;
20557
+ if (this.cameraRecovery) return void this.failCameraRecovery(exc);
20487
20558
  const errorListeners = this.getListeners("ERROR");
20488
20559
  if (errorListeners?.length) this.emit("ERROR", {
20489
20560
  exc
@@ -20607,9 +20678,10 @@
20607
20678
  return this.recordingStats;
20608
20679
  }
20609
20680
  getAudioSampleRate() {
20610
- return this.userMedia?.getAudioSampleRate();
20681
+ return this.recordingAudioSampleRate ?? this.userMedia?.getAudioSampleRate();
20611
20682
  }
20612
20683
  stop(params) {
20684
+ this.cancelCameraRecovery();
20613
20685
  if (!this.userMedia) throw new Error("No user media defined, unable to stop");
20614
20686
  this.options.logger.debug(`Recorder: stop(${params ? string_pretty(params) : ""})`);
20615
20687
  this.recordingActive = false;
@@ -20635,7 +20707,7 @@
20635
20707
  if (this.userMedia) this.recordingStats.videoTrackLabel = this.userMedia.getVideoTrackLabel();
20636
20708
  if (isAudioEnabled(this.options) && this.userMedia) {
20637
20709
  this.recordingStats.samplesCount = this.samplesCount;
20638
- this.recordingStats.sampleRate = this.userMedia.getAudioSampleRate();
20710
+ this.recordingStats.sampleRate = this.recordingAudioSampleRate ?? this.userMedia.getAudioSampleRate();
20639
20711
  }
20640
20712
  this.writeCommand("stop", this.recordingStats, ()=>{
20641
20713
  this.emit("STOPPED", {
@@ -20690,6 +20762,7 @@
20690
20762
  this.built = this.connecting = this.connected = false;
20691
20763
  }
20692
20764
  reset() {
20765
+ this.cancelCameraRecovery();
20693
20766
  if (!this.unloaded) {
20694
20767
  this.options.logger.debug("Recorder: reset()");
20695
20768
  this.emit("RESETTING");
@@ -20717,7 +20790,70 @@
20717
20790
  isReady() {
20718
20791
  return this.userMedia?.isReady();
20719
20792
  }
20793
+ finishCameraRecovery(restored) {
20794
+ this.clearUserMediaTimeout();
20795
+ const resolve = this.resolveCameraRecovery;
20796
+ const resumeRequested = restored && this.resumeAfterCameraRecovery;
20797
+ this.cameraRecovery = void 0;
20798
+ this.resolveCameraRecovery = void 0;
20799
+ this.resumeAfterCameraRecovery = false;
20800
+ resolve?.(restored);
20801
+ if (resumeRequested && this.isPaused()) this.resume();
20802
+ }
20803
+ cancelCameraRecovery() {
20804
+ if (!this.cameraRecovery) return;
20805
+ this.mediaRequestId++;
20806
+ this.userMediaLoading = false;
20807
+ this.userMediaLoaded = false;
20808
+ this.clearUserMediaTimeout();
20809
+ this.userMedia?.stop(void 0, {
20810
+ preserveRecordingState: true
20811
+ });
20812
+ this.finishCameraRecovery(false);
20813
+ }
20814
+ failCameraRecovery(exc) {
20815
+ this.cancelCameraRecovery();
20816
+ const err = error_createError({
20817
+ message: "Camera recovery failed",
20818
+ explanation: "Your recording is still paused. Check camera permissions, then try Resume again or stop to keep the recorded part.",
20819
+ exc,
20820
+ options: this.options
20821
+ });
20822
+ this.emit("CAMERA_RECOVERY_FAILED", {
20823
+ err
20824
+ });
20825
+ }
20826
+ recoverCamera() {
20827
+ if (this.cameraRecovery) return this.cameraRecovery;
20828
+ if (!this.built || this.unloaded || !this.userMedia || this.userMediaLoading || this.submitting || this.key || void 0 !== this.stopTimeout || this.recordingTransportFailed) return Promise.resolve(false);
20829
+ const liveStream = this.userMedia.hasLiveStream();
20830
+ if (liveStream && this.isRecording()) return Promise.resolve(true);
20831
+ if (!liveStream && this.recordingActive && !this.isPaused()) this.pause();
20832
+ const recovery = new Promise((resolve)=>{
20833
+ this.resolveCameraRecovery = resolve;
20834
+ });
20835
+ this.cameraRecovery = recovery;
20836
+ this.emit("CAMERA_RECOVERING");
20837
+ if (liveStream) {
20838
+ this.userMediaTimeout = window.setTimeout(()=>{
20839
+ this.failCameraRecovery(new Error("Camera playback recovery timed out."));
20840
+ }, this.options.timeouts.userMedia);
20841
+ this.userMedia.restorePlayback().then(()=>{
20842
+ if (this.cameraRecovery === recovery) this.finishCameraRecovery(true);
20843
+ }).catch((exc)=>{
20844
+ if (this.cameraRecovery === recovery) this.failCameraRecovery(exc);
20845
+ });
20846
+ } else {
20847
+ this.userMediaLoaded = false;
20848
+ this.loadUserMedia({
20849
+ recovering: true
20850
+ });
20851
+ }
20852
+ return recovery;
20853
+ }
20720
20854
  pause(params) {
20855
+ this.cancelCameraRecovery();
20856
+ this.resumeAfterDrain = false;
20721
20857
  if (params) this.options.logger.debug(`pause() at frame ${this.framesCount} with ${string_pretty(params)}`);
20722
20858
  else this.options.logger.debug(`pause() at frame ${this.framesCount}`);
20723
20859
  this.userMedia?.pause();
@@ -20726,10 +20862,15 @@
20726
20862
  this.sendPings();
20727
20863
  }
20728
20864
  resume() {
20865
+ if (this.cameraRecovery || !this.userMedia?.hasLiveStream()) {
20866
+ this.recoverCamera();
20867
+ this.resumeAfterCameraRecovery = Boolean(this.cameraRecovery);
20868
+ return;
20869
+ }
20729
20870
  this.options.logger.debug(`Recorder: resume() with frame ${this.framesCount}`);
20730
20871
  this.stopPings();
20731
20872
  this.emit("RESUMING");
20732
- this.userMedia?.resume();
20873
+ this.userMedia.resume();
20733
20874
  if (this.streamBackpressured) this.resumeAfterDrain = true;
20734
20875
  else this.loop.start();
20735
20876
  }
@@ -20784,7 +20925,7 @@
20784
20925
  return newLoop;
20785
20926
  }
20786
20927
  record() {
20787
- if (this.unloaded) return;
20928
+ if (this.unloaded || this.cameraRecovery) return;
20788
20929
  if (!this.connected) {
20789
20930
  this.options.logger.debug("Recorder: reconnecting before recording …");
20790
20931
  this.initSocket(()=>{
@@ -20857,6 +20998,7 @@
20857
20998
  this.recordingActive = true;
20858
20999
  this.recordingTransportFailed = false;
20859
21000
  this.mediaTransmissionStarted = false;
21001
+ this.recordingAudioSampleRate = void 0;
20860
21002
  this.userMedia.record();
20861
21003
  this.emit("RECORDING", {
20862
21004
  framesCount: this.framesCount
@@ -20918,7 +21060,7 @@
20918
21060
  }
20919
21061
  }
20920
21062
  switchFacingMode() {
20921
- if (!util_getBrowser(this.options).isMobile()) return;
21063
+ if (this.cameraRecovery || !util_getBrowser(this.options).isMobile()) return;
20922
21064
  if ("user" === this.facingMode) this.facingMode = "environment";
20923
21065
  else if ("environment" === this.facingMode) this.facingMode = "user";
20924
21066
  else this.options.logger.warn(`Recorder: unsupported facing mode ${string_pretty(this.facingMode)}`);
@@ -20929,12 +21071,14 @@
20929
21071
  initEvents() {
20930
21072
  this.options.logger.debug("Recorder: initEvents()");
20931
21073
  this.on("SUBMITTING", ()=>{
21074
+ this.cancelCameraRecovery();
20932
21075
  this.submitting = true;
20933
21076
  });
20934
21077
  this.on("SUBMITTED", ()=>{
20935
21078
  this.submitting = false;
20936
21079
  });
20937
21080
  this.on("BLOCKING", ()=>{
21081
+ this.cancelCameraRecovery();
20938
21082
  this.blocking = true;
20939
21083
  this.clearUserMediaTimeout();
20940
21084
  });
@@ -21362,6 +21506,7 @@
21362
21506
  notifier;
21363
21507
  visualsElement;
21364
21508
  built = false;
21509
+ cameraRecoveryFailed = false;
21365
21510
  constructor(container, options){
21366
21511
  super("Visuals", options);
21367
21512
  this.container = container;
@@ -21396,6 +21541,17 @@
21396
21541
  this.endWaiting();
21397
21542
  this.container.enableForm(false);
21398
21543
  });
21544
+ this.on("CAMERA_RECOVERING", ()=>{
21545
+ this.clearCameraRecoveryNotice();
21546
+ });
21547
+ this.on("CAMERA_RECOVERY_FAILED", ({ err })=>{
21548
+ this.cameraRecoveryFailed = true;
21549
+ this.options.logger.warn(err);
21550
+ if (this.options.displayErrors) this.notifier.notify(err.message, err.explanation, {
21551
+ problem: true
21552
+ });
21553
+ this.endWaiting();
21554
+ });
21399
21555
  this.on("PREVIEW", ()=>{
21400
21556
  this.endWaiting();
21401
21557
  });
@@ -21474,6 +21630,7 @@
21474
21630
  child.remove();
21475
21631
  }
21476
21632
  reset() {
21633
+ this.clearCameraRecoveryNotice();
21477
21634
  this.endWaiting();
21478
21635
  this.recorder.reset();
21479
21636
  }
@@ -21484,11 +21641,13 @@
21484
21641
  this.container.endWaiting();
21485
21642
  }
21486
21643
  stop(params) {
21644
+ this.clearCameraRecoveryNotice();
21487
21645
  this.recorder.stop(params);
21488
21646
  this.recorderInsides.hidePause();
21489
21647
  }
21490
21648
  back(keepHidden = false, cb) {
21491
21649
  this.options.logger.debug(`Visuals: back(keepHidden = ${keepHidden})`);
21650
+ this.cameraRecoveryFailed = false;
21492
21651
  this.replay.hide();
21493
21652
  this.notifier.hide();
21494
21653
  if (keepHidden) {
@@ -21510,6 +21669,7 @@
21510
21669
  if (!this.built) return;
21511
21670
  const e = params?.e;
21512
21671
  this.options.logger.debug(`Visuals: unload(${e ? string_pretty(e) : ""})`);
21672
+ this.cameraRecoveryFailed = false;
21513
21673
  this.recorder.unload(params);
21514
21674
  this.recorderInsides.unload();
21515
21675
  this.replay.unload(params);
@@ -21523,13 +21683,31 @@
21523
21683
  this.recorder.pause(params);
21524
21684
  this.recorderInsides.showPause();
21525
21685
  }
21686
+ clearCameraRecoveryNotice() {
21687
+ if (this.cameraRecoveryFailed) {
21688
+ this.notifier.hide();
21689
+ this.recorder.show();
21690
+ this.cameraRecoveryFailed = false;
21691
+ }
21692
+ }
21693
+ recoverCamera() {
21694
+ if (this.replay.isShown() || this.isNotifying() && !this.cameraRecoveryFailed) return Promise.resolve(false);
21695
+ this.clearCameraRecoveryNotice();
21696
+ return this.recorder.recoverCamera();
21697
+ }
21526
21698
  resume() {
21527
- if (this.recorderInsides.isCountingDown()) this.recorderInsides.resumeCountdown();
21528
- else this.recorder.resume();
21529
- this.recorderInsides.hidePause();
21699
+ this.clearCameraRecoveryNotice();
21700
+ if (this.recorderInsides.isCountingDown()) {
21701
+ this.recorderInsides.resumeCountdown();
21702
+ this.recorderInsides.hidePause();
21703
+ } else {
21704
+ this.recorder.resume();
21705
+ if (!this.recorder.isPaused()) this.recorderInsides.hidePause();
21706
+ }
21530
21707
  }
21531
21708
  pauseOrResume() {
21532
- if (this.isRecordable()) {
21709
+ if (this.cameraRecoveryFailed && this.recorder.isPaused()) this.resume();
21710
+ else if (this.isRecordable()) {
21533
21711
  if (this.isRecording()) this.pause();
21534
21712
  else if (this.recorder.isPaused()) this.resume();
21535
21713
  else if (this.recorder.isReady()) this.recorder.record();
@@ -21772,10 +21950,10 @@
21772
21950
  });
21773
21951
  if (!playerOnly) this.stopListeningToVisibility = this.visibility.onChange((visible)=>{
21774
21952
  if (this.built) if (visible) {
21775
- if (isAutoPauseEnabled(this.options) && this.isCountingDown()) this.resume();
21953
+ if (isAutoPauseEnabled(this.options)) this.recoverCamera();
21776
21954
  this.emit("VISIBLE");
21777
21955
  } else {
21778
- if (isAutoPauseEnabled(this.options) && (this.isCountingDown() || this.isRecording())) this.pause();
21956
+ if (isAutoPauseEnabled(this.options)) this.suspend();
21779
21957
  this.emit("INVISIBLE");
21780
21958
  }
21781
21959
  });
@@ -21963,6 +22141,13 @@
21963
22141
  pause(params) {
21964
22142
  this.visuals.pause(params);
21965
22143
  }
22144
+ suspend() {
22145
+ if (this.built && (this.isCountingDown() || this.isRecording() || this.isPaused())) this.pause();
22146
+ }
22147
+ recoverCamera() {
22148
+ if (!this.built || this.hasError || this.submitted) return Promise.resolve(false);
22149
+ return this.visuals.recoverCamera();
22150
+ }
21966
22151
  validate(event, force = false) {
21967
22152
  let runValidation = true;
21968
22153
  let valid = true;
@@ -22281,6 +22466,12 @@
22281
22466
  record() {
22282
22467
  this.container.record();
22283
22468
  }
22469
+ pause() {
22470
+ this.container.suspend();
22471
+ }
22472
+ recoverCamera() {
22473
+ return this.container.recoverCamera();
22474
+ }
22284
22475
  getLogLines() {
22285
22476
  if (this.options.logger.getLines) return this.options.logger.getLines();
22286
22477
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "videomail-client",
3
- "version": "16.0.3",
3
+ "version": "16.1.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",