@bigbinary/neeto-media-recorder 2.7.51 → 2.7.53

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/core.js CHANGED
@@ -1,43 +1,13 @@
1
+ import { isEmpty, isNotNil, min, isNil, identity, pick, not } from 'ramda';
1
2
  import axios from 'axios';
2
3
  import { existsBy, isNotEmpty, isNot, noop } from '@bigbinary/neeto-cist';
3
4
  import { UPLOAD_STATUS, UPLOAD_EVENT, SCREEN_RECORDER_STATUS as SCREEN_RECORDER_STATUS$1, COUNTDOWN_TIME } from '@bigbinary/neeto-media-recorder/constants';
4
5
  import { useMultipartS3UploadStatus as useMultipartS3UploadStatus$1, getMultipartS3Uploader as getMultipartS3Uploader$1 } from '@bigbinary/neeto-media-recorder/core';
5
- import { isNotNil, min, isNil, identity, isEmpty, pick, not } from 'ramda';
6
6
  import platform from 'platform';
7
7
  import withImmutableActions from '@bigbinary/neeto-commons-frontend/react-utils/withImmutableActions';
8
8
  import { create as create$3 } from 'zustand';
9
9
  import { useSyncExternalStore } from 'react';
10
10
 
11
- function _arrayLikeToArray(r, a) {
12
- (null == a || a > r.length) && (a = r.length);
13
- for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
14
- return n;
15
- }
16
-
17
- function _arrayWithoutHoles(r) {
18
- if (Array.isArray(r)) return _arrayLikeToArray(r);
19
- }
20
-
21
- function _iterableToArray(r) {
22
- if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
23
- }
24
-
25
- function _unsupportedIterableToArray(r, a) {
26
- if (r) {
27
- if ("string" == typeof r) return _arrayLikeToArray(r, a);
28
- var t = {}.toString.call(r).slice(8, -1);
29
- return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
30
- }
31
- }
32
-
33
- function _nonIterableSpread() {
34
- throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
35
- }
36
-
37
- function _toConsumableArray(r) {
38
- return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
39
- }
40
-
41
11
  function asyncGeneratorStep(n, t, e, r, o, a, c) {
42
12
  try {
43
13
  var i = n[a](c),
@@ -494,6 +464,386 @@ try {
494
464
 
495
465
  var _regeneratorRuntime = /*@__PURE__*/getDefaultExportFromCjs(regenerator);
496
466
 
467
+ // Where a recording's opening audio is PUT. Uploading it is the whole handoff —
468
+ // the bucket triggers a lambda which titles the recording and reports it to the
469
+ // server, so there is no second call to make.
470
+ //
471
+ // A nil upload_url means the recording already has a title, which is the
472
+ // caller's signal to stop.
473
+ var show = function show(_ref) {
474
+ var recordingId = _ref.recordingId;
475
+ return axios.get("/api/v1/recordings/".concat(encodeURIComponent(recordingId), "/fast_title_upload_url"));
476
+ };
477
+ var fastTitleApi = {
478
+ show: show
479
+ };
480
+
481
+ var createPrefixedLogger = function createPrefixedLogger(prefix) {
482
+ return {
483
+ info: function info() {
484
+ var _logger;
485
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
486
+ args[_key] = arguments[_key];
487
+ }
488
+ return (_logger = logger).info.apply(_logger, [prefix].concat(args));
489
+ },
490
+ error: function error() {
491
+ var _logger2;
492
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
493
+ args[_key2] = arguments[_key2];
494
+ }
495
+ return (_logger2 = logger).error.apply(_logger2, [prefix].concat(args));
496
+ },
497
+ warn: function warn() {
498
+ var _logger3;
499
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
500
+ args[_key3] = arguments[_key3];
501
+ }
502
+ return (_logger3 = logger).warn.apply(_logger3, [prefix].concat(args));
503
+ },
504
+ debug: function debug() {
505
+ var _logger4;
506
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
507
+ args[_key4] = arguments[_key4];
508
+ }
509
+ return (_logger4 = logger).debug.apply(_logger4, [prefix].concat(args));
510
+ }
511
+ };
512
+ };
513
+
514
+ // How much of the opening to transcribe. Long enough that a recording has
515
+ // usually said something, short enough that the title is saved well before the
516
+ // user stops — which is the whole point, since a link pasted right after
517
+ // recording is what the unfurl cache captures.
518
+ //
519
+ // There is only ever one window. The title is built from whatever these seconds
520
+ // contain rather than waiting for a clearly stated topic, so a second attempt
521
+ // would rarely say anything new.
522
+ var FAST_TITLE_WINDOW_MS = 20000;
523
+
524
+ // Ordered by preference. Opus at a low bitrate is all the transcription API
525
+ // consumes, and keeps the window small enough that the upload never dominates.
526
+ //
527
+ // Independent of the video format: this records audio only, so a recording made
528
+ // in MP4 still captures its opening as Opus. The MP4 entries are the fallback
529
+ // for a browser whose MediaRecorder cannot do audio/webm at all — Safari.
530
+ var FAST_TITLE_MIME_TYPES = ["audio/webm;codecs=opus", "audio/ogg;codecs=opus", "audio/webm", "audio/mp4;codecs=mp4a.40.2", "audio/mp4"];
531
+ var FAST_TITLE_AUDIO_BITS_PER_SECOND = 24000;
532
+
533
+ function _classPrivateFieldInitSpec$2(e, t, a) { _checkPrivateRedeclaration$2(e, t), t.set(e, a); }
534
+ function _checkPrivateRedeclaration$2(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
535
+ var supportedMimeType = function supportedMimeType() {
536
+ return FAST_TITLE_MIME_TYPES.find(function (type) {
537
+ return MediaRecorder.isTypeSupported(type);
538
+ });
539
+ };
540
+
541
+ /**
542
+ * Titles a recording while it is still being made.
543
+ *
544
+ * Link unfurlers (Slack, X, Teams) fetch a page's metadata the first time
545
+ * somebody pastes the URL and cache it per URL, with no purge API worth relying
546
+ * on — so whatever title exists at that moment is the one that sticks. Titling
547
+ * after the upload finishes can lose that race. Titling during the recording
548
+ * does not: for anything longer than about half a minute the title is saved
549
+ * before the user presses stop.
550
+ *
551
+ * Runs a second, audio-only MediaRecorder over cloned audio tracks. When its
552
+ * first window closes it PUTs that audio to S3 and stops. The upload is the
553
+ * whole handoff: the bucket triggers a lambda which titles the recording and
554
+ * reports it to the server, so there is no result to wait for here.
555
+ *
556
+ * It shares no object with the real recorder, and every failure path is a no-op
557
+ * — the transcode lambda still titles the recording from its full transcript
558
+ * afterwards.
559
+ */
560
+ var _logger$2 = /*#__PURE__*/new WeakMap();
561
+ var _recorder = /*#__PURE__*/new WeakMap();
562
+ var _tracks = /*#__PURE__*/new WeakMap();
563
+ var _recordingId$1 = /*#__PURE__*/new WeakMap();
564
+ var _mimeType$2 = /*#__PURE__*/new WeakMap();
565
+ var _finished = /*#__PURE__*/new WeakMap();
566
+ var _reset = /*#__PURE__*/new WeakMap();
567
+ var _onWindowClosed = /*#__PURE__*/new WeakMap();
568
+ var _upload = /*#__PURE__*/new WeakMap();
569
+ var FastTitleRunner = /*#__PURE__*/function () {
570
+ function FastTitleRunner() {
571
+ var _this = this;
572
+ _classCallCheck(this, FastTitleRunner);
573
+ _classPrivateFieldInitSpec$2(this, _logger$2, {
574
+ writable: true,
575
+ value: createPrefixedLogger("[FastTitle]")
576
+ });
577
+ _classPrivateFieldInitSpec$2(this, _recorder, {
578
+ writable: true,
579
+ value: void 0
580
+ });
581
+ _classPrivateFieldInitSpec$2(this, _tracks, {
582
+ writable: true,
583
+ value: []
584
+ });
585
+ _classPrivateFieldInitSpec$2(this, _recordingId$1, {
586
+ writable: true,
587
+ value: null
588
+ });
589
+ _classPrivateFieldInitSpec$2(this, _mimeType$2, {
590
+ writable: true,
591
+ value: void 0
592
+ });
593
+ _classPrivateFieldInitSpec$2(this, _finished, {
594
+ writable: true,
595
+ value: false
596
+ });
597
+ _defineProperty(this, "setRecordingId", function (recordingId) {
598
+ _classPrivateFieldSet(_this, _recordingId$1, recordingId);
599
+ });
600
+ _defineProperty(this, "start", function (mediaStream) {
601
+ var _mediaStream$getAudio, _mediaStream$getAudio2;
602
+ if (_this.isRunning) return;
603
+ var audioTracks = (_mediaStream$getAudio = mediaStream === null || mediaStream === void 0 ? void 0 : (_mediaStream$getAudio2 = mediaStream.getAudioTracks) === null || _mediaStream$getAudio2 === void 0 ? void 0 : _mediaStream$getAudio2.call(mediaStream)) !== null && _mediaStream$getAudio !== void 0 ? _mediaStream$getAudio : [];
604
+ if (isEmpty(audioTracks)) {
605
+ _classPrivateFieldGet(_this, _logger$2).info("start -> no audio track, skipping");
606
+ return;
607
+ }
608
+ _classPrivateFieldSet(_this, _mimeType$2, supportedMimeType());
609
+ if (!_classPrivateFieldGet(_this, _mimeType$2)) {
610
+ _classPrivateFieldGet(_this, _logger$2).info("start -> no supported audio mime type, skipping");
611
+ return;
612
+ }
613
+ _classPrivateFieldGet(_this, _reset).call(_this);
614
+ try {
615
+ // Cloned rather than shared: a clone is an independent track, so a second
616
+ // MediaRecorder reading it cannot affect the one producing the actual
617
+ // recording. Clones must be stopped explicitly or they outlive the take.
618
+ _classPrivateFieldSet(_this, _tracks, audioTracks.map(function (track) {
619
+ return track.clone();
620
+ }));
621
+ _classPrivateFieldSet(_this, _recorder, new MediaRecorder(new MediaStream(_classPrivateFieldGet(_this, _tracks)), {
622
+ mimeType: _classPrivateFieldGet(_this, _mimeType$2),
623
+ audioBitsPerSecond: FAST_TITLE_AUDIO_BITS_PER_SECOND
624
+ }));
625
+ _classPrivateFieldGet(_this, _recorder).ondataavailable = _classPrivateFieldGet(_this, _onWindowClosed);
626
+ _classPrivateFieldGet(_this, _recorder).onerror = function (event) {
627
+ var _event$error;
628
+ _classPrivateFieldGet(_this, _logger$2).error("recorder error", event === null || event === void 0 ? void 0 : (_event$error = event.error) === null || _event$error === void 0 ? void 0 : _event$error.name);
629
+ _this.stop();
630
+ };
631
+ _classPrivateFieldGet(_this, _recorder).start(FAST_TITLE_WINDOW_MS);
632
+ _classPrivateFieldGet(_this, _logger$2).info("start -> recording opening audio", {
633
+ mimeType: _classPrivateFieldGet(_this, _mimeType$2),
634
+ trackCount: _classPrivateFieldGet(_this, _tracks).length
635
+ });
636
+ } catch (error) {
637
+ _classPrivateFieldGet(_this, _logger$2).error("start -> failed", error === null || error === void 0 ? void 0 : error.message);
638
+ _this.stop();
639
+ }
640
+ });
641
+ _defineProperty(this, "pause", function () {
642
+ var _classPrivateFieldGet2;
643
+ if (((_classPrivateFieldGet2 = _classPrivateFieldGet(_this, _recorder)) === null || _classPrivateFieldGet2 === void 0 ? void 0 : _classPrivateFieldGet2.state) === "recording") _classPrivateFieldGet(_this, _recorder).pause();
644
+ });
645
+ _defineProperty(this, "resume", function () {
646
+ var _classPrivateFieldGet3;
647
+ if (((_classPrivateFieldGet3 = _classPrivateFieldGet(_this, _recorder)) === null || _classPrivateFieldGet3 === void 0 ? void 0 : _classPrivateFieldGet3.state) === "paused") _classPrivateFieldGet(_this, _recorder).resume();
648
+ });
649
+ _defineProperty(this, "stop", function () {
650
+ _classPrivateFieldSet(_this, _finished, true);
651
+ try {
652
+ if (_classPrivateFieldGet(_this, _recorder) && _classPrivateFieldGet(_this, _recorder).state !== "inactive") {
653
+ // No final flush: audio sent now would arrive while the recording is
654
+ // already uploading, which is what the transcode lambda is for.
655
+ _classPrivateFieldGet(_this, _recorder).ondataavailable = null;
656
+ _classPrivateFieldGet(_this, _recorder).stop();
657
+ }
658
+ } catch (error) {
659
+ _classPrivateFieldGet(_this, _logger$2).error("stop -> error stopping recorder", error === null || error === void 0 ? void 0 : error.message);
660
+ }
661
+ _classPrivateFieldGet(_this, _tracks).forEach(function (track) {
662
+ return track.stop();
663
+ });
664
+ _classPrivateFieldGet(_this, _reset).call(_this);
665
+ });
666
+ _classPrivateFieldInitSpec$2(this, _reset, {
667
+ writable: true,
668
+ value: function value() {
669
+ _classPrivateFieldSet(_this, _recorder, null);
670
+ _classPrivateFieldSet(_this, _tracks, []);
671
+ _classPrivateFieldSet(_this, _finished, false);
672
+ }
673
+ });
674
+ _classPrivateFieldInitSpec$2(this, _onWindowClosed, {
675
+ writable: true,
676
+ value: function () {
677
+ var _value = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(event) {
678
+ var _event$data;
679
+ var _event$data$size, _event$data2, audio, recordingId;
680
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
681
+ while (1) switch (_context.prev = _context.next) {
682
+ case 0:
683
+ if (!_classPrivateFieldGet(_this, _finished)) {
684
+ _context.next = 2;
685
+ break;
686
+ }
687
+ return _context.abrupt("return");
688
+ case 2:
689
+ if (!(!((_event$data = event.data) !== null && _event$data !== void 0 && _event$data.size) || !_classPrivateFieldGet(_this, _recordingId$1))) {
690
+ _context.next = 6;
691
+ break;
692
+ }
693
+ _classPrivateFieldGet(_this, _logger$2).info("nothing to send, stopping", {
694
+ size: (_event$data$size = (_event$data2 = event.data) === null || _event$data2 === void 0 ? void 0 : _event$data2.size) !== null && _event$data$size !== void 0 ? _event$data$size : 0,
695
+ hasRecordingId: Boolean(_classPrivateFieldGet(_this, _recordingId$1))
696
+ });
697
+ _this.stop();
698
+ return _context.abrupt("return");
699
+ case 6:
700
+ // Stopped up front: this runs once either way, and stopping first means a
701
+ // slow upload cannot overlap a second window.
702
+ audio = event.data;
703
+ recordingId = _classPrivateFieldGet(_this, _recordingId$1);
704
+ _this.stop();
705
+ _context.prev = 9;
706
+ _context.next = 12;
707
+ return _classPrivateFieldGet(_this, _upload).call(_this, recordingId, audio);
708
+ case 12:
709
+ _context.next = 17;
710
+ break;
711
+ case 14:
712
+ _context.prev = 14;
713
+ _context.t0 = _context["catch"](9);
714
+ // Not fatal: the transcode lambda still titles the recording from its
715
+ // full transcript afterwards.
716
+ _classPrivateFieldGet(_this, _logger$2).error("upload failed", _context.t0 === null || _context.t0 === void 0 ? void 0 : _context.t0.message);
717
+ case 17:
718
+ case "end":
719
+ return _context.stop();
720
+ }
721
+ }, _callee, null, [[9, 14]]);
722
+ }));
723
+ function value(_x) {
724
+ return _value.apply(this, arguments);
725
+ }
726
+ return value;
727
+ }()
728
+ });
729
+ _classPrivateFieldInitSpec$2(this, _upload, {
730
+ writable: true,
731
+ value: function () {
732
+ var _value2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(recordingId, audio) {
733
+ var startedAt, _yield$fastTitleApi$s, uploadUrl, response;
734
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
735
+ while (1) switch (_context2.prev = _context2.next) {
736
+ case 0:
737
+ startedAt = performance.now(); // Already the response body: the host app's axios interceptors unwrap it,
738
+ // the same way generatePreSignedUrl's caller destructures its result.
739
+ _context2.next = 3;
740
+ return fastTitleApi.show({
741
+ recordingId: recordingId
742
+ });
743
+ case 3:
744
+ _yield$fastTitleApi$s = _context2.sent;
745
+ uploadUrl = _yield$fastTitleApi$s.uploadUrl;
746
+ if (uploadUrl) {
747
+ _context2.next = 8;
748
+ break;
749
+ }
750
+ _classPrivateFieldGet(_this, _logger$2).info("not wanted for this recording");
751
+ return _context2.abrupt("return");
752
+ case 8:
753
+ _context2.next = 10;
754
+ return fetch(uploadUrl, {
755
+ method: "PUT",
756
+ headers: {
757
+ "Content-Type": _classPrivateFieldGet(_this, _mimeType$2)
758
+ },
759
+ body: audio
760
+ });
761
+ case 10:
762
+ response = _context2.sent;
763
+ if (response.ok) {
764
+ _context2.next = 13;
765
+ break;
766
+ }
767
+ throw new Error("audio upload ".concat(response.status));
768
+ case 13:
769
+ // The last thing this client will say about the recording. Uploading is the
770
+ // whole handoff — the bucket triggers a lambda which transcribes, titles and
771
+ // reports the title to the server — so nothing further appears here whether
772
+ // it succeeds or finds nothing to title.
773
+ _classPrivateFieldGet(_this, _logger$2).info("upload succeeded -> handed off for titling", {
774
+ recordingId: recordingId,
775
+ bytes: audio.size,
776
+ mimeType: _classPrivateFieldGet(_this, _mimeType$2),
777
+ ms: Math.round(performance.now() - startedAt),
778
+ // Path only. The query string of a presigned URL carries its signature.
779
+ key: new URL(uploadUrl).pathname.replace(/^\//, "")
780
+ });
781
+ case 14:
782
+ case "end":
783
+ return _context2.stop();
784
+ }
785
+ }, _callee2);
786
+ }));
787
+ function value(_x2, _x3) {
788
+ return _value2.apply(this, arguments);
789
+ }
790
+ return value;
791
+ }()
792
+ });
793
+ }
794
+ _createClass(FastTitleRunner, [{
795
+ key: "isRunning",
796
+ get: function get() {
797
+ return Boolean(_classPrivateFieldGet(this, _recorder)) && !_classPrivateFieldGet(this, _finished);
798
+ }
799
+
800
+ // Set once the recording exists. It is created asynchronously and usually
801
+ // lands well before the first window closes.
802
+ /**
803
+ * @param {MediaStream} mediaStream the stream being recorded; its audio tracks
804
+ * are cloned so nothing here can disturb the real recorder
805
+ */
806
+ // Fires once, when the first window closes. Whatever it holds is the title's
807
+ // only source, so this uploads it and stops.
808
+ }]);
809
+ return FastTitleRunner;
810
+ }();
811
+ var runner = null;
812
+ var getFastTitleRunner = function getFastTitleRunner() {
813
+ if (!runner) runner = new FastTitleRunner();
814
+ return runner;
815
+ };
816
+
817
+ function _arrayLikeToArray(r, a) {
818
+ (null == a || a > r.length) && (a = r.length);
819
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
820
+ return n;
821
+ }
822
+
823
+ function _arrayWithoutHoles(r) {
824
+ if (Array.isArray(r)) return _arrayLikeToArray(r);
825
+ }
826
+
827
+ function _iterableToArray(r) {
828
+ if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
829
+ }
830
+
831
+ function _unsupportedIterableToArray(r, a) {
832
+ if (r) {
833
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
834
+ var t = {}.toString.call(r).slice(8, -1);
835
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
836
+ }
837
+ }
838
+
839
+ function _nonIterableSpread() {
840
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
841
+ }
842
+
843
+ function _toConsumableArray(r) {
844
+ return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
845
+ }
846
+
497
847
  const byteToHex = [];
498
848
  for (let i = 0; i < 256; ++i) {
499
849
  byteToHex.push((i + 0x100).toString(16).slice(1));
@@ -952,39 +1302,6 @@ var s3Api = {
952
1302
  presignedUpload: retryablePresignedUpload
953
1303
  };
954
1304
 
955
- var createPrefixedLogger = function createPrefixedLogger(prefix) {
956
- return {
957
- info: function info() {
958
- var _logger;
959
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
960
- args[_key] = arguments[_key];
961
- }
962
- return (_logger = logger).info.apply(_logger, [prefix].concat(args));
963
- },
964
- error: function error() {
965
- var _logger2;
966
- for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
967
- args[_key2] = arguments[_key2];
968
- }
969
- return (_logger2 = logger).error.apply(_logger2, [prefix].concat(args));
970
- },
971
- warn: function warn() {
972
- var _logger3;
973
- for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
974
- args[_key3] = arguments[_key3];
975
- }
976
- return (_logger3 = logger).warn.apply(_logger3, [prefix].concat(args));
977
- },
978
- debug: function debug() {
979
- var _logger4;
980
- for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
981
- args[_key4] = arguments[_key4];
982
- }
983
- return (_logger4 = logger).debug.apply(_logger4, [prefix].concat(args));
984
- }
985
- };
986
- };
987
-
988
1305
  /**
989
1306
  * Due to the custom axios interceptor for s3Api,
990
1307
  * it'll return false for cancelled requests instead of a cancelled error object.
@@ -1804,6 +2121,7 @@ var _playCountdownAudio = /*#__PURE__*/new WeakMap();
1804
2121
  var _mimeType = /*#__PURE__*/new WeakMap();
1805
2122
  var _dataReceiving = /*#__PURE__*/new WeakMap();
1806
2123
  var _recordSystemAudio = /*#__PURE__*/new WeakMap();
2124
+ var _hasSpeechSourceForTitle = /*#__PURE__*/new WeakMap();
1807
2125
  var _stopAllStreams = /*#__PURE__*/new WeakMap();
1808
2126
  var _playCountdownBeep = /*#__PURE__*/new WeakMap();
1809
2127
  var _checkCompatibility = /*#__PURE__*/new WeakMap();
@@ -2174,6 +2492,7 @@ var ScreenRecorder = /*#__PURE__*/function () {
2174
2492
  status: SCREEN_RECORDER_STATUS.paused
2175
2493
  });
2176
2494
  _classPrivateFieldGet(_this, _mediaRecorder).pause();
2495
+ getFastTitleRunner().pause();
2177
2496
  } else {
2178
2497
  _classPrivateFieldGet(_this, _logger).warn("pauseRecording -> ignored, recorder not in recording state", _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this));
2179
2498
  }
@@ -2187,6 +2506,7 @@ var ScreenRecorder = /*#__PURE__*/function () {
2187
2506
  status: SCREEN_RECORDER_STATUS.recording
2188
2507
  });
2189
2508
  _classPrivateFieldGet(_this, _mediaRecorder).resume();
2509
+ getFastTitleRunner().resume();
2190
2510
  } else {
2191
2511
  _classPrivateFieldGet(_this, _logger).warn("resumeRecording -> ignored, recorder not paused", _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this));
2192
2512
  }
@@ -2239,26 +2559,43 @@ var ScreenRecorder = /*#__PURE__*/function () {
2239
2559
  _classPrivateFieldGet(_this, _mediaRecorder).onstop = null;
2240
2560
  _classPrivateFieldGet(_this, _stopAllStreams).call(_this);
2241
2561
  });
2562
+ _classPrivateFieldInitSpec(this, _hasSpeechSourceForTitle, {
2563
+ writable: true,
2564
+ value: function value() {
2565
+ var _classPrivateFieldGet9, _classPrivateFieldGet10, _classPrivateFieldGet11, _classPrivateFieldGet12, _classPrivateFieldGet13;
2566
+ var _ref2 = (_classPrivateFieldGet9 = (_classPrivateFieldGet10 = _classPrivateFieldGet(_this, _micAudioStream)) === null || _classPrivateFieldGet10 === void 0 ? void 0 : (_classPrivateFieldGet11 = _classPrivateFieldGet10.getAudioTracks) === null || _classPrivateFieldGet11 === void 0 ? void 0 : _classPrivateFieldGet11.call(_classPrivateFieldGet10)) !== null && _classPrivateFieldGet9 !== void 0 ? _classPrivateFieldGet9 : [],
2567
+ _ref3 = _slicedToArray(_ref2, 1),
2568
+ micTrack = _ref3[0];
2569
+ var micLive = Boolean(micTrack) && micTrack.readyState !== "ended" && !micTrack.muted && micTrack.enabled;
2570
+ var hasSystemAudio = isNotEmpty((_classPrivateFieldGet12 = (_classPrivateFieldGet13 = _classPrivateFieldGet(_this, _stream)) === null || _classPrivateFieldGet13 === void 0 ? void 0 : _classPrivateFieldGet13.getAudioTracks()) !== null && _classPrivateFieldGet12 !== void 0 ? _classPrivateFieldGet12 : []);
2571
+ if (!micLive && !hasSystemAudio) {
2572
+ _classPrivateFieldGet(_this, _logger).info("fast title skipped -> mic is off and no system audio");
2573
+ return false;
2574
+ }
2575
+ return true;
2576
+ }
2577
+ });
2242
2578
  _classPrivateFieldInitSpec(this, _stopAllStreams, {
2243
2579
  writable: true,
2244
2580
  value: function value() {
2245
- var _classPrivateFieldGet9, _classPrivateFieldGet10, _classPrivateFieldGet11, _classPrivateFieldGet12, _classPrivateFieldGet13, _classPrivateFieldGet14;
2581
+ var _classPrivateFieldGet14, _classPrivateFieldGet15, _classPrivateFieldGet16, _classPrivateFieldGet17, _classPrivateFieldGet18, _classPrivateFieldGet19;
2582
+ getFastTitleRunner().stop();
2246
2583
  _classPrivateFieldGet(_this, _logger).info("stopAllStreams", {
2247
2584
  mediaStream: _classPrivateFieldGet(_this, _summarizeStream).call(_this, _classPrivateFieldGet(_this, _mediaStream)),
2248
2585
  stream: _classPrivateFieldGet(_this, _summarizeStream).call(_this, _classPrivateFieldGet(_this, _stream)),
2249
2586
  audioStream: _classPrivateFieldGet(_this, _summarizeStream).call(_this, _classPrivateFieldGet(_this, _audioStream)),
2250
2587
  micAudioStream: _classPrivateFieldGet(_this, _summarizeStream).call(_this, _classPrivateFieldGet(_this, _micAudioStream))
2251
2588
  });
2252
- _classPrivateFieldGet(_this, _mediaStream) && ((_classPrivateFieldGet9 = _classPrivateFieldGet(_this, _mediaStream)) === null || _classPrivateFieldGet9 === void 0 ? void 0 : _classPrivateFieldGet9.getTracks().forEach(function (track) {
2589
+ _classPrivateFieldGet(_this, _mediaStream) && ((_classPrivateFieldGet14 = _classPrivateFieldGet(_this, _mediaStream)) === null || _classPrivateFieldGet14 === void 0 ? void 0 : _classPrivateFieldGet14.getTracks().forEach(function (track) {
2253
2590
  return track.stop();
2254
2591
  }));
2255
- (_classPrivateFieldGet10 = _classPrivateFieldGet(_this, _stream)) === null || _classPrivateFieldGet10 === void 0 ? void 0 : _classPrivateFieldGet10.getTracks().forEach(function (track) {
2592
+ (_classPrivateFieldGet15 = _classPrivateFieldGet(_this, _stream)) === null || _classPrivateFieldGet15 === void 0 ? void 0 : _classPrivateFieldGet15.getTracks().forEach(function (track) {
2256
2593
  return track.stop();
2257
2594
  });
2258
- (_classPrivateFieldGet11 = _classPrivateFieldGet(_this, _audioStream)) === null || _classPrivateFieldGet11 === void 0 ? void 0 : (_classPrivateFieldGet12 = _classPrivateFieldGet11.getAudioTracks()) === null || _classPrivateFieldGet12 === void 0 ? void 0 : _classPrivateFieldGet12.forEach(function (track) {
2595
+ (_classPrivateFieldGet16 = _classPrivateFieldGet(_this, _audioStream)) === null || _classPrivateFieldGet16 === void 0 ? void 0 : (_classPrivateFieldGet17 = _classPrivateFieldGet16.getAudioTracks()) === null || _classPrivateFieldGet17 === void 0 ? void 0 : _classPrivateFieldGet17.forEach(function (track) {
2259
2596
  return track.stop();
2260
2597
  });
2261
- (_classPrivateFieldGet13 = _classPrivateFieldGet(_this, _micAudioStream)) === null || _classPrivateFieldGet13 === void 0 ? void 0 : (_classPrivateFieldGet14 = _classPrivateFieldGet13.getAudioTracks()) === null || _classPrivateFieldGet14 === void 0 ? void 0 : _classPrivateFieldGet14.forEach(function (track) {
2598
+ (_classPrivateFieldGet18 = _classPrivateFieldGet(_this, _micAudioStream)) === null || _classPrivateFieldGet18 === void 0 ? void 0 : (_classPrivateFieldGet19 = _classPrivateFieldGet18.getAudioTracks()) === null || _classPrivateFieldGet19 === void 0 ? void 0 : _classPrivateFieldGet19.forEach(function (track) {
2262
2599
  return track.stop();
2263
2600
  });
2264
2601
  }
@@ -2389,9 +2726,7 @@ var ScreenRecorder = /*#__PURE__*/function () {
2389
2726
  break;
2390
2727
  }
2391
2728
  _context2.prev = 41;
2392
- workletUrl = IS_EXTENSION ?
2393
- // eslint-disable-next-line no-undef
2394
- chrome.runtime.getURL("public/NoiseSuppressorWorklet.js") : "NoiseSuppressorWorklet.js";
2729
+ workletUrl = IS_EXTENSION ? chrome.runtime.getURL("public/NoiseSuppressorWorklet.js") : "NoiseSuppressorWorklet.js";
2395
2730
  _classPrivateFieldGet(_this, _logger).info("acquireMediaStream -> loading noise suppressor worklet", {
2396
2731
  workletUrl: workletUrl
2397
2732
  });
@@ -2447,10 +2782,10 @@ var ScreenRecorder = /*#__PURE__*/function () {
2447
2782
  // diagnostics) over to it; otherwise preserve the legacy behavior of
2448
2783
  // finalizing the recording immediately.
2449
2784
  stream.getVideoTracks()[0].addEventListener("ended", function () {
2450
- var _classPrivateFieldGet15, _classPrivateFieldGet16;
2451
- var hasUnexpectedEndHandler = isNotEmpty((_classPrivateFieldGet15 = _classPrivateFieldGet(_this, _callbacks)[SCREEN_RECORDER_EVENT.onUnexpectedEnd]) !== null && _classPrivateFieldGet15 !== void 0 ? _classPrivateFieldGet15 : []);
2785
+ var _classPrivateFieldGet20, _classPrivateFieldGet21;
2786
+ var hasUnexpectedEndHandler = isNotEmpty((_classPrivateFieldGet20 = _classPrivateFieldGet(_this, _callbacks)[SCREEN_RECORDER_EVENT.onUnexpectedEnd]) !== null && _classPrivateFieldGet20 !== void 0 ? _classPrivateFieldGet20 : []);
2452
2787
  _classPrivateFieldGet(_this, _logger).info("display video track ended event received", {
2453
- recorderState: (_classPrivateFieldGet16 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet16 === void 0 ? void 0 : _classPrivateFieldGet16.state,
2788
+ recorderState: (_classPrivateFieldGet21 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet21 === void 0 ? void 0 : _classPrivateFieldGet21.state,
2454
2789
  status: _classPrivateFieldGet(_this, _store).getState().status,
2455
2790
  hasUnexpectedEndHandler: hasUnexpectedEndHandler,
2456
2791
  stream: _classPrivateFieldGet(_this, _summarizeStream).call(_this, stream)
@@ -2542,9 +2877,9 @@ var ScreenRecorder = /*#__PURE__*/function () {
2542
2877
  _classPrivateFieldInitSpec(this, _fireCallbacks, {
2543
2878
  writable: true,
2544
2879
  value: function value(event) {
2545
- var _classPrivateFieldGet17;
2880
+ var _classPrivateFieldGet22;
2546
2881
  var args = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
2547
- (_classPrivateFieldGet17 = _classPrivateFieldGet(_this, _callbacks)[event]) === null || _classPrivateFieldGet17 === void 0 ? void 0 : _classPrivateFieldGet17.forEach(function (callback) {
2882
+ (_classPrivateFieldGet22 = _classPrivateFieldGet(_this, _callbacks)[event]) === null || _classPrivateFieldGet22 === void 0 ? void 0 : _classPrivateFieldGet22.forEach(function (callback) {
2548
2883
  callback.apply(void 0, _toConsumableArray(args));
2549
2884
  });
2550
2885
  }
@@ -2558,9 +2893,9 @@ var ScreenRecorder = /*#__PURE__*/function () {
2558
2893
  });
2559
2894
  _classPrivateFieldInitSpec(this, _onRecordingActive, {
2560
2895
  writable: true,
2561
- value: function value(_ref2) {
2562
- var _classPrivateFieldGet18;
2563
- var data = _ref2.data;
2896
+ value: function value(_ref4) {
2897
+ var _classPrivateFieldGet23;
2898
+ var data = _ref4.data;
2564
2899
  if (data.size <= 0) {
2565
2900
  _classPrivateFieldGet(_this, _logger).warn("ondataavailable -> empty chunk, ignoring", {
2566
2901
  chunkCount: _classPrivateFieldGet(_this, _mediaChunks).length
@@ -2578,7 +2913,7 @@ var ScreenRecorder = /*#__PURE__*/function () {
2578
2913
  chunkType: data.type,
2579
2914
  chunkCount: _classPrivateFieldGet(_this, _mediaChunks).length,
2580
2915
  totalBytes: totalBytes,
2581
- recorderState: (_classPrivateFieldGet18 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet18 === void 0 ? void 0 : _classPrivateFieldGet18.state
2916
+ recorderState: (_classPrivateFieldGet23 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet23 === void 0 ? void 0 : _classPrivateFieldGet23.state
2582
2917
  });
2583
2918
  _classPrivateFieldGet(_this, _fireCallbacks).call(_this, SCREEN_RECORDER_EVENT.onDataAvailable, [data]);
2584
2919
  }
@@ -2586,14 +2921,14 @@ var ScreenRecorder = /*#__PURE__*/function () {
2586
2921
  _classPrivateFieldInitSpec(this, _onRecordingStop, {
2587
2922
  writable: true,
2588
2923
  value: function value() {
2589
- var _classPrivateFieldGet19, _classPrivateFieldGet20;
2924
+ var _classPrivateFieldGet24, _classPrivateFieldGet25;
2590
2925
  var totalBytes = _classPrivateFieldGet(_this, _mediaChunks).reduce(function (sum, chunk) {
2591
2926
  var _chunk$size2;
2592
2927
  return sum + ((_chunk$size2 = chunk === null || chunk === void 0 ? void 0 : chunk.size) !== null && _chunk$size2 !== void 0 ? _chunk$size2 : 0);
2593
2928
  }, 0);
2594
2929
  _classPrivateFieldGet(_this, _logger).info("MediaRecorder.onstop fired", _objectSpread(_objectSpread({}, _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this)), {}, {
2595
2930
  totalBytes: totalBytes,
2596
- mediaStreamActive: (_classPrivateFieldGet19 = _classPrivateFieldGet(_this, _mediaStream)) === null || _classPrivateFieldGet19 === void 0 ? void 0 : _classPrivateFieldGet19.active
2931
+ mediaStreamActive: (_classPrivateFieldGet24 = _classPrivateFieldGet(_this, _mediaStream)) === null || _classPrivateFieldGet24 === void 0 ? void 0 : _classPrivateFieldGet24.active
2597
2932
  }));
2598
2933
  _classPrivateFieldGet(_this, _store).setState({
2599
2934
  status: SCREEN_RECORDER_STATUS.stopped
@@ -2602,7 +2937,7 @@ var ScreenRecorder = /*#__PURE__*/function () {
2602
2937
 
2603
2938
  // If mediaStream is active after stopping mediaRecorder, then that means that
2604
2939
  // we are restarting and not stopping.
2605
- if ((_classPrivateFieldGet20 = _classPrivateFieldGet(_this, _mediaStream)) !== null && _classPrivateFieldGet20 !== void 0 && _classPrivateFieldGet20.active) {
2940
+ if ((_classPrivateFieldGet25 = _classPrivateFieldGet(_this, _mediaStream)) !== null && _classPrivateFieldGet25 !== void 0 && _classPrivateFieldGet25.active) {
2606
2941
  _classPrivateFieldGet(_this, _logger).info("onRecordingStop -> mediaStream still active, transitioning to restarting");
2607
2942
  _classPrivateFieldGet(_this, _store).setState({
2608
2943
  status: SCREEN_RECORDER_STATUS.restarting
@@ -2686,10 +3021,10 @@ var ScreenRecorder = /*#__PURE__*/function () {
2686
3021
  _classPrivateFieldInitSpec(this, _summarizeRecorderState, {
2687
3022
  writable: true,
2688
3023
  value: function value() {
2689
- var _classPrivateFieldGet21;
3024
+ var _classPrivateFieldGet26;
2690
3025
  var extra = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
2691
3026
  return _objectSpread({
2692
- recorderState: (_classPrivateFieldGet21 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet21 === void 0 ? void 0 : _classPrivateFieldGet21.state,
3027
+ recorderState: (_classPrivateFieldGet26 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet26 === void 0 ? void 0 : _classPrivateFieldGet26.state,
2693
3028
  status: _classPrivateFieldGet(_this, _store).getState().status,
2694
3029
  elapsedTime: _classPrivateFieldGet(_this, _store).getState().elapsedTime,
2695
3030
  chunkCount: _classPrivateFieldGet(_this, _mediaChunks).length,
@@ -2707,7 +3042,7 @@ var ScreenRecorder = /*#__PURE__*/function () {
2707
3042
  }, _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this)));
2708
3043
  _classPrivateFieldSet(_this, _startRecordingTimeoutID, setTimeout(function () {
2709
3044
  try {
2710
- var _classPrivateFieldGet22;
3045
+ var _classPrivateFieldGet27;
2711
3046
  if (!_classPrivateFieldGet(_this, _mediaRecorder)) {
2712
3047
  _classPrivateFieldGet(_this, _logger).error("startMediaRecorder -> aborted, mediaRecorder is null", _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this));
2713
3048
  _classPrivateFieldGet(_this, _store).setState({
@@ -2728,7 +3063,7 @@ var ScreenRecorder = /*#__PURE__*/function () {
2728
3063
  });
2729
3064
  return;
2730
3065
  }
2731
- if (((_classPrivateFieldGet22 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet22 === void 0 ? void 0 : _classPrivateFieldGet22.state) === "recording") {
3066
+ if (((_classPrivateFieldGet27 = _classPrivateFieldGet(_this, _mediaRecorder)) === null || _classPrivateFieldGet27 === void 0 ? void 0 : _classPrivateFieldGet27.state) === "recording") {
2732
3067
  _classPrivateFieldGet(_this, _logger).warn("startMediaRecorder -> mediaRecorder already recording, skipping", _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this));
2733
3068
  return;
2734
3069
  }
@@ -2736,6 +3071,12 @@ var ScreenRecorder = /*#__PURE__*/function () {
2736
3071
  timesliceMs: 5000
2737
3072
  }, _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this)));
2738
3073
  _classPrivateFieldGet(_this, _mediaRecorder).start(5000);
3074
+
3075
+ // Titles the recording from its opening seconds, while it is still
3076
+ // being made. Skipped when there is nothing that could carry speech.
3077
+ if (_classPrivateFieldGet(_this, _hasSpeechSourceForTitle).call(_this)) {
3078
+ getFastTitleRunner().start(_classPrivateFieldGet(_this, _mediaStream));
3079
+ }
2739
3080
  _classPrivateFieldGet(_this, _logger).info("startMediaRecorder -> start() returned", _classPrivateFieldGet(_this, _summarizeRecorderState).call(_this));
2740
3081
  _classPrivateFieldGet(_this, _startTimer).call(_this);
2741
3082
  } catch (error) {
@@ -2776,8 +3117,8 @@ var ScreenRecorder = /*#__PURE__*/function () {
2776
3117
  _classPrivateFieldGet(_this, _fireCallbacks).call(_this, SCREEN_RECORDER_EVENT.onCountdownStart);
2777
3118
  _classPrivateFieldSet(_this, _countdownTimerId, setInterval(function () {
2778
3119
  var shouldBeep = false;
2779
- _classPrivateFieldGet(_this, _store).setState(function (_ref3) {
2780
- var countdownTimeLeft = _ref3.countdownTimeLeft;
3120
+ _classPrivateFieldGet(_this, _store).setState(function (_ref5) {
3121
+ var countdownTimeLeft = _ref5.countdownTimeLeft;
2781
3122
  if (countdownTimeLeft - ONE_SECOND > 0) {
2782
3123
  var next = countdownTimeLeft - ONE_SECOND;
2783
3124
  _classPrivateFieldGet(_this, _logger).info("startCountdown -> tick", {
@@ -2812,8 +3153,8 @@ var ScreenRecorder = /*#__PURE__*/function () {
2812
3153
  value: function value() {
2813
3154
  _classPrivateFieldSet(_this, _lastTimerUpdatedAt, Date.now());
2814
3155
  _classPrivateFieldSet(_this, _timerId, setInterval(function () {
2815
- _classPrivateFieldGet(_this, _store).setState(function (_ref4) {
2816
- var elapsedTime = _ref4.elapsedTime;
3156
+ _classPrivateFieldGet(_this, _store).setState(function (_ref6) {
3157
+ var elapsedTime = _ref6.elapsedTime;
2817
3158
  var now = Date.now();
2818
3159
  var newElapsedTime = elapsedTime + (now - _classPrivateFieldGet(_this, _lastTimerUpdatedAt));
2819
3160
  _classPrivateFieldSet(_this, _lastTimerUpdatedAt, now);
@@ -2884,10 +3225,10 @@ var ScreenRecorder = /*#__PURE__*/function () {
2884
3225
  }, {
2885
3226
  key: "displaySurface",
2886
3227
  get: function get() {
2887
- var _classPrivateFieldGet23, _classPrivateFieldGet24;
2888
- var _ref5 = (_classPrivateFieldGet23 = (_classPrivateFieldGet24 = _classPrivateFieldGet(this, _mediaStream)) === null || _classPrivateFieldGet24 === void 0 ? void 0 : _classPrivateFieldGet24.getVideoTracks()) !== null && _classPrivateFieldGet23 !== void 0 ? _classPrivateFieldGet23 : [],
2889
- _ref6 = _slicedToArray(_ref5, 1),
2890
- videoTrack = _ref6[0];
3228
+ var _classPrivateFieldGet28, _classPrivateFieldGet29;
3229
+ var _ref7 = (_classPrivateFieldGet28 = (_classPrivateFieldGet29 = _classPrivateFieldGet(this, _mediaStream)) === null || _classPrivateFieldGet29 === void 0 ? void 0 : _classPrivateFieldGet29.getVideoTracks()) !== null && _classPrivateFieldGet28 !== void 0 ? _classPrivateFieldGet28 : [],
3230
+ _ref8 = _slicedToArray(_ref7, 1),
3231
+ videoTrack = _ref8[0];
2891
3232
  return videoTrack === null || videoTrack === void 0 ? void 0 : videoTrack.getSettings().displaySurface;
2892
3233
  }
2893
3234
  }, {
@@ -2905,6 +3246,15 @@ var ScreenRecorder = /*#__PURE__*/function () {
2905
3246
  // The chime is suppressed (vs. natural completion which plays it) since the
2906
3247
  // user explicitly opted out of the count-in. Listeners hook onCountdownSkip
2907
3248
  // to clean up countdown-only side effects like scheduled beeps.
3249
+ // Whether this recording could contain speech worth titling from.
3250
+ //
3251
+ // A muted mic is the case that matters: with the mic off the fast-title path
3252
+ // would upload audio, spend a transcription and a completion, and get nothing
3253
+ // back. Checked against the raw mic rather than #audioStream, which is the mic
3254
+ // mixed with system audio and stays loud through a dead mic.
3255
+ //
3256
+ // System audio counts on its own — a recording narrated by the thing on screen
3257
+ // is exactly as titleable as one narrated by the user.
2908
3258
  // `delayMs` only exists to keep the start chime out of the recording; when no
2909
3259
  // chime plays (countdown skipped or disabled) we start immediately so the
2910
3260
  // recorder begins ticking without a perceptible gap.
@@ -2950,5 +3300,5 @@ var useMultipartS3UploadStatus = function useMultipartS3UploadStatus() {
2950
3300
  };
2951
3301
  useMultipartS3UploadStatus.triggerStatusChange = triggerStatusChange;
2952
3302
 
2953
- export { getMultipartS3Uploader, getSupportedMimeType, screenRecorder, useMultipartS3UploadStatus };
3303
+ export { getFastTitleRunner, getMultipartS3Uploader, getSupportedMimeType, screenRecorder, useMultipartS3UploadStatus };
2954
3304
  //# sourceMappingURL=core.js.map