speechrecorderng 2.18.13 → 2.19.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.
@@ -313,14 +313,295 @@ class AudioPlayer {
313
313
  }
314
314
  AudioPlayer.DEFAULT_BUFSIZE = 8192;
315
315
 
316
- class AudioStreamConstr {
316
+ class UserAgentComponent {
317
+ constructor(name, version, comment) {
318
+ this.name = name;
319
+ this.version = version;
320
+ this.comment = comment;
321
+ }
322
+ toString() {
323
+ let s = '[' + this.name + ']';
324
+ if (this.version) {
325
+ s = s + ' [' + this.version + ']';
326
+ }
327
+ if (this.comment) {
328
+ s = s + ' [' + this.comment + ']';
329
+ }
330
+ return s;
331
+ }
332
+ }
333
+ const NAME_FIREFOX = 'Firefox';
334
+ const NAME_CHROME = 'Chrome';
335
+ const NAME_SAFARI = 'Safari';
336
+ const NAME_EDGE = 'Edge';
337
+ var Browser$1;
338
+ (function (Browser) {
339
+ Browser["Firefox"] = "Firefox";
340
+ Browser["Chrome"] = "Chrome";
341
+ Browser["Safari"] = "Safari";
342
+ Browser["Edge"] = "Edge";
343
+ })(Browser$1 || (Browser$1 = {}));
344
+ const OS_WINDOWS = 'Windows';
345
+ const OS_ANDROID = 'Android';
346
+ var Platform$1;
347
+ (function (Platform) {
348
+ Platform["Windows"] = "Windows";
349
+ Platform["Android"] = "Android";
350
+ Platform["macOS"] = "MAC OS X";
351
+ })(Platform$1 || (Platform$1 = {}));
352
+ class UserAgent {
353
+ constructor(_detectedPlatform, _detectedBrowser) {
354
+ this._detectedPlatform = _detectedPlatform;
355
+ this._detectedBrowser = _detectedBrowser;
356
+ }
357
+ get detectedBrowser() {
358
+ return this._detectedBrowser;
359
+ }
360
+ get detectedPlatform() {
361
+ return this._detectedPlatform;
362
+ }
363
+ }
364
+ class UserAgentBuilder {
317
365
  constructor() {
318
- this.audio = true;
319
- this.video = false;
366
+ this.comps = new Array();
367
+ }
368
+ build() {
369
+ // // @ts-ignore
370
+ // if(navigator.userAgentData){
371
+ // // maybe we can use this in the future
372
+ // console.info("Browser provides userAgentData:");
373
+ //
374
+ // console.info("Brands:");
375
+ // // @ts-ignore
376
+ // navigator.userAgentData.brands.forEach((br=>{
377
+ // console.info(br.brand +" "+br.version);
378
+ // }))
379
+ // // @ts-ignore
380
+ // console.info("Platform: "+navigator.userAgentData.platform);
381
+ // // @ts-ignore
382
+ // console.info("Mobile:"+navigator.userAgentData.mobile);
383
+ // // @ts-ignore
384
+ // //console.info(navigator.userAgentData.toJSON());
385
+ // }else {
386
+ // console.info("Browser does not provide userAgentData.");
387
+ // }
388
+ let ua = navigator.userAgent;
389
+ this.comps = new Array();
390
+ let pp = 0;
391
+ while (pp < ua.length) {
392
+ //parse name/version
393
+ let name = null;
394
+ let version = null;
395
+ let comment;
396
+ let blPos = ua.indexOf(' ', pp);
397
+ let prt;
398
+ if (blPos == -1) {
399
+ prt = ua.substr(pp);
400
+ pp += prt.length;
401
+ }
402
+ else {
403
+ prt = ua.substr(pp, blPos - pp);
404
+ pp = blPos + 1;
405
+ }
406
+ let sepPos = prt.indexOf('/');
407
+ if (sepPos > 0) {
408
+ name = prt.substr(0, sepPos);
409
+ version = prt.substr(sepPos + 1);
410
+ }
411
+ else {
412
+ name = prt;
413
+ }
414
+ while (ua[pp] === ' ' && pp < ua.length) {
415
+ pp++;
416
+ }
417
+ if (ua[pp] === '(') {
418
+ pp++;
419
+ let commEnd = ua.indexOf(')', pp);
420
+ comment = ua.substr(pp, commEnd - pp);
421
+ pp = commEnd + 1;
422
+ }
423
+ while (ua[pp] === ' ' && pp < ua.length) {
424
+ pp++;
425
+ }
426
+ this.comps.push(new UserAgentComponent(name, version, comment));
427
+ }
428
+ let detPlatf = null;
429
+ if (this.runsOnOS(Platform$1.Android)) {
430
+ detPlatf = Platform$1.Android;
431
+ }
432
+ else if (this.runsOnOS(Platform$1.Windows)) {
433
+ detPlatf = Platform$1.Windows;
434
+ }
435
+ else if (this.runsOnOS(Platform$1.macOS)) {
436
+ detPlatf = Platform$1.macOS;
437
+ }
438
+ let detBr = null;
439
+ if (this.matchesBrowser(Browser$1.Firefox)) {
440
+ detBr = Browser$1.Firefox;
441
+ }
442
+ else if (this.matchesBrowser(Browser$1.Chrome)) {
443
+ detBr = Browser$1.Chrome;
444
+ }
445
+ else if (this.matchesBrowser(Browser$1.Safari)) {
446
+ detBr = Browser$1.Safari;
447
+ }
448
+ this.userAgent = new UserAgent(detPlatf, detBr);
449
+ }
450
+ matchesBrowser(browserName) {
451
+ for (let ci = 0; ci < this.comps.length; ci++) {
452
+ let bn = this.comps[ci].name;
453
+ let bnRe = new RegExp(browserName, 'i');
454
+ if (bn.match(bnRe)) {
455
+ return true;
456
+ }
457
+ }
458
+ return false;
459
+ }
460
+ runsOnOS(os) {
461
+ for (let ci = 0; ci < this.comps.length; ci++) {
462
+ let cc = this.comps[ci].comment;
463
+ if (cc) {
464
+ var osRe = new RegExp(os, 'i');
465
+ if (cc.match(osRe)) {
466
+ return true;
467
+ }
468
+ }
469
+ }
470
+ return false;
471
+ }
472
+ static userAgent() {
473
+ if (!this.instance) {
474
+ this.instance = new UserAgentBuilder();
475
+ }
476
+ this.instance.build();
477
+ return this.instance.userAgent;
320
478
  }
321
479
  }
480
+ UserAgentBuilder.instance = undefined;
481
+
482
+ var ConstraintType;
483
+ (function (ConstraintType) {
484
+ ConstraintType["Exact"] = "EXACT";
485
+ ConstraintType["Ideal"] = "IDEAL";
486
+ })(ConstraintType || (ConstraintType = {}));
487
+ ;
488
+ var Platform;
489
+ (function (Platform) {
490
+ Platform["Linux"] = "LINUX";
491
+ Platform["macOS"] = "MACOS";
492
+ Platform["Windows"] = "WINDOWS";
493
+ Platform["Android"] = "ANDROID";
494
+ })(Platform || (Platform = {}));
495
+ var BrowserBase;
496
+ (function (BrowserBase) {
497
+ BrowserBase["Chromium"] = "CHROMIUM";
498
+ })(BrowserBase || (BrowserBase = {}));
499
+ ;
500
+ var Browser;
501
+ (function (Browser) {
502
+ Browser["Firefox"] = "FIREFOX";
503
+ Browser["Chromium"] = "CHROMIUM";
504
+ Browser["Chrome"] = "CHROME";
505
+ Browser["Edge"] = "EDGE";
506
+ Browser["Opera"] = "OPERA";
507
+ })(Browser || (Browser = {}));
508
+ class ProjectUtil {
509
+ static audioChannelCount(project) {
510
+ let chs = ProjectUtil.DEFAULT_AUDIO_CHANNEL_COUNT;
511
+ if (project.mediaCaptureFormat) {
512
+ chs = project.mediaCaptureFormat.audioChannelCount;
513
+ }
514
+ else if (project.audioFormat) {
515
+ chs = project.audioFormat.channels;
516
+ }
517
+ return chs;
518
+ }
519
+ }
520
+ ProjectUtil.DEFAULT_AUDIO_CHANNEL_COUNT = 2;
521
+
522
+ const CHROME_ACTIVATE_ECHO_CANCELLATION_WITH_AGC = false;
523
+ const DEBUG_TRACE_LEVEL = 0;
524
+ const ENABLE_AUDIO_WORKLET = true;
525
+ // Super dirty way to load this module
526
+ // Copy content of interceptor_worklet.js to this string
527
+ const awpStr = "class AudioCaptureInterceptorProcessor extends AudioWorkletProcessor{\n" +
528
+ "\n" +
529
+ " BUFFER_QUANTUMS=64;\n" +
530
+ " QUANTUM_FRAME_LEN=128;\n" +
531
+ " BUFFER_FRAME_LEN=this.QUANTUM_FRAME_LEN*this.BUFFER_QUANTUMS;\n" +
532
+ " buffer=null;\n" +
533
+ " bufferPos=0;\n" +
534
+ " bufferPosBytes=0;\n" +
535
+ " constructor() {\n" +
536
+ " super();\n" +
537
+ "\n" +
538
+ " }\n" +
539
+ "\n" +
540
+ " process(\n" +
541
+ " inputs,\n" +
542
+ " outputs,\n" +
543
+ " parameters\n" +
544
+ " ){\n" +
545
+ "\n" +
546
+ " let inputsCnt=inputs.length;\n" +
547
+ " let channelCount=0;\n" +
548
+ " let inputLen=0;\n" +
549
+ " let inputLenBytes=0;\n" +
550
+ " if(inputsCnt>0) {\n" +
551
+ " let input0 = inputs[0];\n" +
552
+ " channelCount = input0.length;\n" +
553
+ " if (channelCount > 0) {\n" +
554
+ " let input0ch0=input0[0];\n" +
555
+ " inputLen=input0ch0.length;\n" +
556
+ " inputLenBytes=input0ch0.buffer.length;\n" +
557
+ " }\n" +
558
+ " }\n" +
559
+ " if (!this.buffer || this.buffer.length < channelCount) {\n" +
560
+ " this.buffer = new Array(channelCount);\n" +
561
+ " this.bufferPos = 0\n" +
562
+ " for (let bch = 0; bch < channelCount; bch++) {\n" +
563
+ " this.buffer[bch] = new Float32Array(this.BUFFER_FRAME_LEN);\n" +
564
+ " this.bufferPos = 0;\n" +
565
+ " this.bufferPosBytes=0;\n" +
566
+ " }\n" +
567
+ " }\n" +
568
+ " let bufAvail = this.BUFFER_FRAME_LEN - this.bufferPos;\n" +
569
+ " // check if buffer has to be transferred\n" +
570
+ " if (inputLen > bufAvail) {\n" +
571
+ " let ada=new Array(channelCount);\n" +
572
+ " for (let ch = 0; ch < channelCount; ch++) {\n" +
573
+ " ada[ch]=this.buffer[ch].buffer.slice(0);\n" +
574
+ " }\n" +
575
+ " this.port.postMessage({\n" +
576
+ " data: ada,\n" +
577
+ " chs: channelCount,\n" +
578
+ " len: this.bufferPos\n" +
579
+ " }, ada);\n" +
580
+ " // buffer transferred, reset\n" +
581
+ " this.bufferPos = 0;\n" +
582
+ " this.bufferPosBytes=0;\n" +
583
+ " }\n" +
584
+ "\n" +
585
+ " for(let ii=0;ii<inputsCnt;ii++) {\n" +
586
+ " for (let ch = 0; ch < channelCount; ch++) {\n" +
587
+ " // Mute outputs\n" +
588
+ " //outputs[ii][ch].fill(0);\n" +
589
+ " let chSamples = inputs[ii][ch];\n" +
590
+ " this.buffer[ch].set(chSamples,this.bufferPos);\n" +
591
+ " }\n" +
592
+ " this.bufferPos+=inputLen;\n" +
593
+ " this.bufferPosBytes+=inputLenBytes;\n" +
594
+ " }\n" +
595
+ " \n" +
596
+ " return true;\n" +
597
+ " }\n" +
598
+ "}\n" +
599
+ "\n" +
600
+ "registerProcessor('capture-interceptor',AudioCaptureInterceptorProcessor);\n";
322
601
  class AudioCapture {
323
602
  constructor(context) {
603
+ this.agcStatus = null;
604
+ this.bufferingNode = null;
324
605
  this.audioOutStream = null;
325
606
  this.disconnectStreams = true;
326
607
  this._opened = false;
@@ -368,7 +649,7 @@ class AudioCapture {
368
649
  if (!labelsAvailable) {
369
650
  //console.debug("Media device enumeration: No labels.")
370
651
  if (retry) {
371
- //console.debug("Starting dummy session to request audio permissions...")
652
+ console.info("Starting dummy session to request audio permissions...");
372
653
  this.dummySession().then((s) => {
373
654
  // and stop it immediately
374
655
  if (s) {
@@ -433,12 +714,12 @@ class AudioCapture {
433
714
  console.log("Audio device: Id: " + di.deviceId + " groupId: " + di.groupId + " label: " + di.label + " kind: " + di.kind);
434
715
  }
435
716
  }
436
- open(channelCount, selDeviceId) {
717
+ open(channelCount, selDeviceId, autoGainControlConfigs) {
437
718
  this.context.resume().then(() => {
438
- this._open(channelCount, selDeviceId);
719
+ this._open(channelCount, selDeviceId, autoGainControlConfigs);
439
720
  });
440
721
  }
441
- _open(channelCount, selDeviceId) {
722
+ _open(channelCount, selDeviceId, autoGainControlConfigs) {
442
723
  this.channelCount = channelCount;
443
724
  this.framesRecorded = 0;
444
725
  //var msc = new AudioStreamConstr();
@@ -454,7 +735,48 @@ class AudioCapture {
454
735
  // TODO test if input is unprocessed
455
736
  let msc;
456
737
  console.info('User agent: ' + navigator.userAgent);
457
- if (navigator.userAgent.match(".*Edge.*")) {
738
+ let ua = UserAgentBuilder.userAgent();
739
+ // ua.components.forEach((c)=>{
740
+ // console.info("UA_Comp: "+c.toString());
741
+ // })
742
+ let agcCfg = null;
743
+ let autoGainControl = false;
744
+ let chromeEchoCancellation = false;
745
+ if (autoGainControlConfigs) {
746
+ for (let agcc of autoGainControlConfigs) {
747
+ if (agcc.platform === Platform.Android && ua.detectedPlatform === Platform$1.Android) {
748
+ agcCfg = agcc;
749
+ break;
750
+ }
751
+ if (agcc.platform === Platform.Windows && ua.detectedPlatform === Platform$1.Windows) {
752
+ agcCfg = agcc;
753
+ break;
754
+ }
755
+ }
756
+ if (agcCfg) {
757
+ // TODO use EXACT/IDEAL constraint
758
+ autoGainControl = agcCfg.value;
759
+ if (CHROME_ACTIVATE_ECHO_CANCELLATION_WITH_AGC) {
760
+ chromeEchoCancellation = agcCfg.value;
761
+ }
762
+ // TODO query real AGC status
763
+ this.agcStatus = agcCfg.value;
764
+ }
765
+ else {
766
+ this.agcStatus = false;
767
+ }
768
+ }
769
+ // default
770
+ msc = {
771
+ audio: {
772
+ deviceId: selDeviceId,
773
+ echoCancellation: false,
774
+ channelCount: channelCount,
775
+ autoGainControl: autoGainControl
776
+ },
777
+ video: false
778
+ };
779
+ if (ua.detectedBrowser === Browser$1.Edge) {
458
780
  // Microsoft Edge sends unmodified audio
459
781
  // The constraint can follow the specification
460
782
  console.info("Setting media track constraints for Microsoft Edge.");
@@ -462,12 +784,13 @@ class AudioCapture {
462
784
  audio: {
463
785
  deviceId: selDeviceId,
464
786
  echoCancellation: false,
465
- channelCount: channelCount
787
+ channelCount: channelCount,
788
+ autoGainControl: autoGainControl
466
789
  },
467
790
  video: false
468
791
  };
469
792
  }
470
- else if (navigator.userAgent.match(".*Chrome.*")) {
793
+ else if (ua.detectedBrowser === Browser$1.Chrome) {
471
794
  // Google Chrome: we need to switch of each of the preprocessing units including the
472
795
  console.info("Setting media track constraints for Google Chrome.");
473
796
  // Chrome 60 -> 61 changed
@@ -475,47 +798,38 @@ class AudioCapture {
475
798
  // Requires at least Chrome 61
476
799
  msc = {
477
800
  audio: {
478
- "deviceId": selDeviceId,
479
- "channelCount": channelCount,
480
- "echoCancellation": false,
481
- "autoGainControl": false,
482
- "googEchoCancellation": false,
483
- "googExperimentalEchoCancellation": false,
484
- "googAutoGainControl": false,
485
- "googTypingNoiseDetection": false,
486
- "googNoiseSuppression": false,
487
- "googHighpassFilter": false,
488
- "googBeamforming": false
801
+ deviceId: selDeviceId,
802
+ channelCount: channelCount,
803
+ echoCancellation: { exact: chromeEchoCancellation },
804
+ autoGainControl: { exact: autoGainControl },
805
+ sampleSize: { min: 16 },
489
806
  },
490
807
  video: false,
491
808
  };
492
809
  }
493
- else if (navigator.userAgent.match(".*Firefox.*")) {
810
+ else if (ua.detectedBrowser === Browser$1.Firefox) {
494
811
  console.info("Setting media track constraints for Mozilla Firefox.");
495
812
  // Firefox
496
813
  msc = {
497
814
  audio: {
498
- "deviceId": selDeviceId,
499
- "channelCount": channelCount,
500
- "echoCancellation": false,
501
- "mozEchoCancellation": false,
502
- "autoGainControl": false,
503
- "mozAutoGainControl": false,
504
- "noiseSuppression": false,
505
- "mozNoiseSuppression": false
815
+ deviceId: selDeviceId,
816
+ channelCount: channelCount,
817
+ echoCancellation: false,
818
+ autoGainControl: autoGainControl,
819
+ noiseSuppression: false
506
820
  },
507
821
  video: false,
508
822
  };
509
823
  }
510
- else if (navigator.userAgent.match(".*Safari.*")) {
824
+ else if (ua.detectedBrowser === Browser$1.Safari) {
511
825
  console.info("Setting media track constraints for Safari browser.");
512
826
  console.info("Apply workaround for Safari: Avoid disconnect of streams.");
513
827
  this.disconnectStreams = false;
514
828
  msc = {
515
829
  audio: {
516
- "deviceId": selDeviceId,
517
- "channelCount": channelCount,
518
- "echoCancellation": false
830
+ deviceId: selDeviceId,
831
+ channelCount: channelCount,
832
+ echoCancellation: false
519
833
  },
520
834
  video: false,
521
835
  };
@@ -523,6 +837,7 @@ class AudioCapture {
523
837
  else {
524
838
  // TODO default constraints or error Browser not supported
525
839
  }
840
+ console.debug("Audio capture, AGC: " + this.agcStatus);
526
841
  let ump = navigator.mediaDevices.getUserMedia(msc);
527
842
  ump.then((s) => {
528
843
  this.stream = s;
@@ -530,6 +845,11 @@ class AudioCapture {
530
845
  for (let i = 0; i < aTracks.length; i++) {
531
846
  let aTrack = aTracks[i];
532
847
  console.info("Track audio info: id: " + aTrack.id + " kind: " + aTrack.kind + " label: \"" + aTrack.label + "\"");
848
+ let mtrSts = aTrack.getSettings();
849
+ console.info("Track audio settings: Ch cnt: " + mtrSts.channelCount + ", AGC: " + mtrSts.autoGainControl + ", Echo cancell.: " + mtrSts.echoCancellation);
850
+ if (mtrSts.autoGainControl) {
851
+ this.agcStatus = mtrSts.autoGainControl;
852
+ }
533
853
  }
534
854
  let vTracks = s.getVideoTracks();
535
855
  for (let i = 0; i < vTracks.length; i++) {
@@ -539,6 +859,7 @@ class AudioCapture {
539
859
  this.mediaStream = this.context.createMediaStreamSource(s);
540
860
  // stream channel count ( is always 2 !)
541
861
  let streamChannelCount = this.mediaStream.channelCount;
862
+ console.info("Stream channel count: " + streamChannelCount);
542
863
  // is not set!!
543
864
  //this.currentSampleRate = this.mediaStream.sampleRate;
544
865
  this.currentSampleRate = this.context.sampleRate;
@@ -548,43 +869,117 @@ class AudioCapture {
548
869
  }
549
870
  // W3C -> new name is createScriptProcessor
550
871
  //
551
- // TODO Again deprecated, but AudioWorker not yet implemented in stable releases (June 2016)
872
+ // Again deprecated, but AudioWorker not yet implemented in stable releases (June 2016)
552
873
  // AudioWorker is now AudioWorkletProcessor ... (May 2017)
553
874
  // Update 12-2020:
554
875
  // The ScriptProcessorNode Interface - DEPRECATED
555
- if (!this.context.createScriptProcessor) {
556
- //console.debug("Audio script processor NOT implemented.")
876
+ // Update 06-2021
877
+ // AudioWorkletProcessor is here to stay. Web Audio API has now Recommendation status !
878
+ if (ENABLE_AUDIO_WORKLET && this.context.audioWorklet) {
879
+ //const workletFileName = ('file-loader!./interceptor_worklet.js');
880
+ //const workletFileName = 'http://localhost:4200/assets/interceptor_worklet.js';
881
+ //console.log(awpStr);
882
+ let audioWorkletModuleBlob = new Blob([awpStr], { type: 'text/javascript' });
883
+ let audioWorkletModuleBlobUrl = window.URL.createObjectURL(audioWorkletModuleBlob);
884
+ this.context.audioWorklet.addModule(audioWorkletModuleBlobUrl).then(() => {
885
+ const awn = new AudioWorkletNode(this.context, 'capture-interceptor');
886
+ awn.onprocessorerror = (ev) => {
887
+ let msg = 'Unknwon error';
888
+ if (ev instanceof ErrorEvent) {
889
+ msg = ev.message;
890
+ }
891
+ console.error("Capture audio worklet error: " + msg);
892
+ if (this.listener) {
893
+ this.listener.error(msg);
894
+ }
895
+ };
896
+ let awnPt = awn.port;
897
+ if (awnPt) {
898
+ awnPt.onmessage = (ev) => {
899
+ if (this.capturing) {
900
+ let dt = ev.data;
901
+ let chs = dt.chs;
902
+ let adaLen = dt.data.length;
903
+ if (DEBUG_TRACE_LEVEL > 8) {
904
+ console.debug('Received data from worklet: ' + chs + ' ' + dt.len + ' Data chs: ' + adaLen);
905
+ }
906
+ //let chunkLen = adaLen / chs;
907
+ let chunkLen = adaLen;
908
+ let chunk = new Array(chs);
909
+ for (let ch = 0; ch < chs; ch++) {
910
+ if (this.data && this.data[ch]) {
911
+ let adaPos = ch * chunkLen;
912
+ if (dt.data[ch]) {
913
+ let fa = new Float32Array(dt.data[ch]);
914
+ this.data[ch].push(fa);
915
+ chunk[ch] = fa;
916
+ // Use samples of channel 0 to count frames (samples)
917
+ if (ch == 0) {
918
+ this.framesRecorded += fa.length;
919
+ }
920
+ }
921
+ else {
922
+ if (DEBUG_TRACE_LEVEL > 8) {
923
+ console.debug('Channel ' + ch + ' data not set!!');
924
+ }
925
+ }
926
+ }
927
+ }
928
+ if (this.audioOutStream) {
929
+ this.audioOutStream.write(chunk);
930
+ }
931
+ }
932
+ };
933
+ }
934
+ this.bufferingNode = awn;
935
+ this._opened = true;
936
+ if (this.listener) {
937
+ this.listener.opened();
938
+ }
939
+ }).catch((error) => {
940
+ console.log('Could not add module ' + error);
941
+ });
557
942
  }
558
- else {
559
- // The ScriptProcessorNode Interface - DEPRECATED
560
- //console.debug("Audio script processor implemented!!");
943
+ else if (this.context.createScriptProcessor) {
944
+ // The ScriptProcessorNode Interface - DEPRECATED Only as fallback
561
945
  // TODO should we use streamChannelCount or channelCount here ?
562
- this.bufferingNode = this.context.createScriptProcessor(AudioCapture.BUFFER_SIZE, streamChannelCount, streamChannelCount);
946
+ let scriptProcessorNode = this.context.createScriptProcessor(AudioCapture.BUFFER_SIZE, streamChannelCount, streamChannelCount);
947
+ this.bufferingNode = scriptProcessorNode;
563
948
  let c = 0;
564
- this.bufferingNode.onaudioprocess = (e) => {
565
- if (this.capturing) {
566
- // TODO use chCnt
567
- let inBuffer = e.inputBuffer;
568
- let duration = inBuffer.duration;
569
- // only process requested count of channels
570
- let currentBuffers = new Array(channelCount);
571
- for (let ch = 0; ch < channelCount; ch++) {
572
- let chSamples = inBuffer.getChannelData(ch);
573
- let chSamplesCopy = chSamples.slice(0);
574
- currentBuffers[ch] = chSamplesCopy.slice(0);
575
- this.data[ch].push(chSamplesCopy);
576
- this.framesRecorded += chSamplesCopy.length;
577
- }
578
- c++;
579
- if (this.audioOutStream) {
580
- this.audioOutStream.write(currentBuffers);
949
+ if (scriptProcessorNode.onaudioprocess) {
950
+ scriptProcessorNode.onaudioprocess = (e) => {
951
+ if (this.capturing) {
952
+ let inBuffer = e.inputBuffer;
953
+ let duration = inBuffer.duration;
954
+ // only process requested count of channels
955
+ let currentBuffers = new Array(channelCount);
956
+ for (let ch = 0; ch < channelCount; ch++) {
957
+ let chSamples = inBuffer.getChannelData(ch);
958
+ let chSamplesCopy = chSamples.slice(0);
959
+ currentBuffers[ch] = chSamplesCopy.slice(0);
960
+ this.data[ch].push(chSamplesCopy);
961
+ if (DEBUG_TRACE_LEVEL > 8) {
962
+ console.debug("Process " + chSamplesCopy.length + " samples.");
963
+ }
964
+ this.framesRecorded += chSamplesCopy.length;
965
+ }
966
+ c++;
967
+ if (this.audioOutStream) {
968
+ this.audioOutStream.write(currentBuffers);
969
+ }
581
970
  }
971
+ };
972
+ this._opened = true;
973
+ if (this.listener) {
974
+ this.listener.opened();
582
975
  }
583
- };
976
+ }
977
+ else {
978
+ this.listener.error('Browser does not support audio processing (ScriptProcessor.onaudioprocess method not found)!');
979
+ }
584
980
  }
585
- this._opened = true;
586
- if (this.listener) {
587
- this.listener.opened();
981
+ else {
982
+ this.listener.error('Browser does not support audio processing (neither AudioWorkletProcessor nor ScriptProcessor)!');
588
983
  }
589
984
  }, (e) => {
590
985
  console.error(e + " Error name: " + e.name);
@@ -611,14 +1006,16 @@ class AudioCapture {
611
1006
  this.audioOutStream.nextStream();
612
1007
  }
613
1008
  this.capturing = true;
614
- this.mediaStream.connect(this.bufferingNode);
615
- this.bufferingNode.connect(this.context.destination);
1009
+ if (this.bufferingNode) {
1010
+ this.mediaStream.connect(this.bufferingNode);
1011
+ this.bufferingNode.connect(this.context.destination);
1012
+ }
616
1013
  if (this.listener) {
617
1014
  this.listener.started();
618
1015
  }
619
1016
  }
620
1017
  stop() {
621
- if (this.disconnectStreams) {
1018
+ if (this.disconnectStreams && this.bufferingNode) {
622
1019
  this.mediaStream.disconnect(this.bufferingNode);
623
1020
  this.bufferingNode.disconnect(this.context.destination);
624
1021
  }
@@ -1788,6 +2185,8 @@ class LevelBarDisplay {
1788
2185
  this.peakDbLevelStr = "-___ dB";
1789
2186
  this.peakDbLvl = MIN_DB_LEVEL;
1790
2187
  this._displayLevelInfos = null;
2188
+ this._agc = undefined;
2189
+ this.agcString = 'n/a';
1791
2190
  this.onShowRecordingDetails = new EventEmitter();
1792
2191
  this.onDownloadRecording = new EventEmitter();
1793
2192
  this.playStartEnabled = false;
@@ -1796,6 +2195,20 @@ class LevelBarDisplay {
1796
2195
  this.destroyed = false;
1797
2196
  this.warnDbLevel = DEFAULT_WARN_DB_LEVEL;
1798
2197
  }
2198
+ set agc(agc) {
2199
+ this._agc = agc;
2200
+ if (this._agc === undefined || this._agc === null) {
2201
+ this.agcString = 'n/a';
2202
+ }
2203
+ else {
2204
+ if (this._agc === true) {
2205
+ this.agcString = 'On';
2206
+ }
2207
+ else {
2208
+ this.agcString = 'Off';
2209
+ }
2210
+ }
2211
+ }
1799
2212
  ngOnDestroy() {
1800
2213
  this.destroyed = true;
1801
2214
  }
@@ -1845,7 +2258,7 @@ class LevelBarDisplay {
1845
2258
  }
1846
2259
  }
1847
2260
  LevelBarDisplay.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.10", ngImport: i0, type: LevelBarDisplay, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1848
- LevelBarDisplay.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.10", type: LevelBarDisplay, selector: "spr-recordingitemdisplay", inputs: { streamingMode: "streamingMode", audioSignalCollapsed: "audioSignalCollapsed", enableDownload: "enableDownload", playStartAction: "playStartAction", playStopAction: "playStopAction", displayAudioBuffer: "displayAudioBuffer", displayLevelInfos: "displayLevelInfos" }, outputs: { onShowRecordingDetails: "onShowRecordingDetails", onDownloadRecording: "onDownloadRecording" }, viewQueries: [{ propertyName: "liveLevel", first: true, predicate: LevelBar, descendants: true, static: true }], ngImport: i0, template: `
2261
+ LevelBarDisplay.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.10", type: LevelBarDisplay, selector: "spr-recordingitemdisplay", inputs: { streamingMode: "streamingMode", audioSignalCollapsed: "audioSignalCollapsed", enableDownload: "enableDownload", agc: "agc", playStartAction: "playStartAction", playStopAction: "playStopAction", displayAudioBuffer: "displayAudioBuffer", displayLevelInfos: "displayLevelInfos" }, outputs: { onShowRecordingDetails: "onShowRecordingDetails", onDownloadRecording: "onDownloadRecording" }, viewQueries: [{ propertyName: "liveLevel", first: true, predicate: LevelBar, descendants: true, static: true }], ngImport: i0, template: `
1849
2262
  <audio-levelbar [streamingMode]="streamingMode" [displayLevelInfos]="_displayLevelInfos"></audio-levelbar>
1850
2263
  <button matTooltip="Start playback" (click)="playStartAction?.perform()"
1851
2264
  [disabled]="playStartAction?.disabled"
@@ -1866,7 +2279,8 @@ LevelBarDisplay.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", versio
1866
2279
  <mat-icon>file_download</mat-icon>
1867
2280
  </button>
1868
2281
  <div style="min-width: 14ch;padding:2px"><table border="0"><tr><td>Peak:</td><td><span matTooltip="Peak level"
1869
- [style.color]="(peakDbLvl > warnDbLevel)?'red':'black'">{{peakDbLvl | number:'1.1-1'}} dB </span></td></tr></table></div>
2282
+ [style.color]="(peakDbLvl > warnDbLevel)?'red':'black'">{{peakDbLvl | number:'1.1-1'}} dB </span></td></tr>
2283
+ <tr *ngIf="_agc"><td>AGC:</td><td><span matTooltip="Auto gain control">{{agcString}}</span></td></tr></table></div>
1870
2284
  `, isInline: true, styles: [":host {\n flex: 0; /* only required vertical space */\n width: 100%;\n background: darkgray;\n padding: 4px;\n box-sizing: border-box;\n height: 100px;\n min-height: 100px;\n display: flex; /* flex container: left level bar, right decimal peak level value */\n flex-direction: row;\n flex-wrap: nowrap; /* wrap could completely destroy the layout */\n }", "audio-levelbar {\n flex: 1;\n box-sizing: border-box;\n }", "span {\n flex: 0;\n font-weight: bold;\n display: inline-block;\n white-space: nowrap;\n box-sizing: border-box;\n }"], components: [{ type: LevelBar, selector: "audio-levelbar", inputs: ["streamingMode", "displayLevelInfos"] }, { type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], directives: [{ type: i3.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], pipes: { "number": i4.DecimalPipe } });
1871
2285
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImport: i0, type: LevelBarDisplay, decorators: [{
1872
2286
  type: Component,
@@ -1893,7 +2307,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImpo
1893
2307
  <mat-icon>file_download</mat-icon>
1894
2308
  </button>
1895
2309
  <div style="min-width: 14ch;padding:2px"><table border="0"><tr><td>Peak:</td><td><span matTooltip="Peak level"
1896
- [style.color]="(peakDbLvl > warnDbLevel)?'red':'black'">{{peakDbLvl | number:'1.1-1'}} dB </span></td></tr></table></div>
2310
+ [style.color]="(peakDbLvl > warnDbLevel)?'red':'black'">{{peakDbLvl | number:'1.1-1'}} dB </span></td></tr>
2311
+ <tr *ngIf="_agc"><td>AGC:</td><td><span matTooltip="Auto gain control">{{agcString}}</span></td></tr></table></div>
1897
2312
  `,
1898
2313
  styles: [`:host {
1899
2314
  flex: 0; /* only required vertical space */
@@ -1926,6 +2341,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImpo
1926
2341
  type: Input
1927
2342
  }], enableDownload: [{
1928
2343
  type: Input
2344
+ }], agc: [{
2345
+ type: Input
1929
2346
  }], onShowRecordingDetails: [{
1930
2347
  type: Output
1931
2348
  }], onDownloadRecording: [{
@@ -6001,12 +6418,13 @@ class Float32ArrayChunkerOutStream {
6001
6418
  toFill = avail;
6002
6419
  }
6003
6420
  let sliceEnd = copied + toFill;
6004
- for (let ch = 0; ch < this._channels; ch++) {
6005
- let cpPrt = buffers[ch].slice(copied, sliceEnd);
6006
- let prtLen = cpPrt.length;
6007
- let buf = this.bufs[ch];
6008
- let bufLen = buf.length;
6009
- buf.set(cpPrt, this.filled);
6421
+ // Firefox on Android sends only the first channel
6422
+ for (let ch = 0; ch < buffersLen; ch++) {
6423
+ if (buffers[ch]) {
6424
+ let cpPrt = buffers[ch].slice(copied, sliceEnd);
6425
+ let buf = this.bufs[ch];
6426
+ buf.set(cpPrt, this.filled);
6427
+ }
6010
6428
  }
6011
6429
  copied += toFill;
6012
6430
  avail -= toFill;
@@ -6790,6 +7208,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImpo
6790
7208
  type: Input
6791
7209
  }] } });
6792
7210
 
7211
+ const FORCE_REQUEST_AUDIO_PERMISSIONS = false;
6793
7212
  const RECFILE_API_CTX = 'recfile';
6794
7213
  const MAX_RECORDING_TIME_MS = 1000 * 60 * 60 * 60; // 1 hour
6795
7214
  const DEFAULT_PRE_REC_DELAY = 1000;
@@ -6841,6 +7260,9 @@ class SessionManager {
6841
7260
  this.selCaptureDeviceId = null;
6842
7261
  this.levelMeasure = new LevelMeasure();
6843
7262
  this.streamLevelMeasure = new StreamLevelMeasure();
7263
+ this.userAgent = UserAgentBuilder.userAgent();
7264
+ console.debug("Detected platform: " + this.userAgent.detectedPlatform);
7265
+ console.debug("Detected browser: " + this.userAgent.detectedBrowser);
6844
7266
  if (this.config && this.config.enableUploadRecordings !== undefined) {
6845
7267
  this.enableUploadRecordings = this.config.enableUploadRecordings;
6846
7268
  }
@@ -7004,6 +7426,9 @@ class SessionManager {
7004
7426
  set audioDevices(audioDevices) {
7005
7427
  this._audioDevices = audioDevices;
7006
7428
  }
7429
+ set autoGainControlConfigs(autoGainControlConfigs) {
7430
+ this._autoGainControlConfigs = autoGainControlConfigs;
7431
+ }
7007
7432
  update(e) {
7008
7433
  if (e.type == EventType.STARTED) {
7009
7434
  this.playStartAction.disabled = true;
@@ -7082,7 +7507,7 @@ class SessionManager {
7082
7507
  else {
7083
7508
  console.log("Open session with default audio device for " + this._channelCount + " channels");
7084
7509
  }
7085
- this.ac.open(this._channelCount, this._selectedDeviceId);
7510
+ this.ac.open(this._channelCount, this._selectedDeviceId, this._autoGainControlConfigs);
7086
7511
  }
7087
7512
  else {
7088
7513
  this.ac.start();
@@ -7325,9 +7750,22 @@ class SessionManager {
7325
7750
  this.sessionService.patchSessionObserver(this._session, body).subscribe();
7326
7751
  }
7327
7752
  }
7753
+ // Check browser compatibility
7754
+ if (this.userAgent.detectedBrowser === Browser$1.Safari && this._channelCount > 1) {
7755
+ let eMsg = "Error: Safari browser does not support stereo recordings.";
7756
+ console.error(eMsg);
7757
+ this.dialog.open(MessageDialog, {
7758
+ data: {
7759
+ type: 'error',
7760
+ title: 'Browser not supported',
7761
+ msg: eMsg,
7762
+ advice: "Please use a supported browser, e.g. Mozilla Firefox."
7763
+ }
7764
+ });
7765
+ }
7328
7766
  //console.log("Session ID: "+this._session.session+ " status: "+this._session.status)
7329
7767
  this._selectedDeviceId = undefined;
7330
- if (!this.readonly && this.ac) {
7768
+ if (!this.readonly && this.ac && (FORCE_REQUEST_AUDIO_PERMISSIONS || (this._audioDevices && this._audioDevices.length > 0))) {
7331
7769
  this.statusMsg = 'Requesting audio permissions...';
7332
7770
  this.statusAlertType = 'info';
7333
7771
  this.ac.deviceInfos((mdis) => {
@@ -7444,6 +7882,7 @@ class SessionManager {
7444
7882
  }
7445
7883
  });
7446
7884
  }
7885
+ // Safari does not list playback devices
7447
7886
  if (!audioPlayDeviceAvail) {
7448
7887
  // Firefox does not enumerate audiooutput devices
7449
7888
  // Do not show this warning, because it would always appear on Firefox
@@ -7451,7 +7890,8 @@ class SessionManager {
7451
7890
  // It is already implemneted but kept behind a preference setting https://bugzilla.mozilla.org/show_bug.cgi?id=1152401
7452
7891
  // Output devices are listed if about:config media.setsinkid.enabled=true
7453
7892
  // but default setting is false
7454
- if (!navigator.userAgent.match(".*Firefox.*")) {
7893
+ // Same problem with Safari
7894
+ if (!(this.userAgent.detectedBrowser === Browser$1.Safari || this.userAgent.detectedBrowser === Browser$1.Firefox)) {
7455
7895
  // no device found
7456
7896
  this.statusMsg = 'WARNING: No audio playback device available!';
7457
7897
  this.statusAlertType = 'warn';
@@ -7876,7 +8316,9 @@ SessionManager.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version
7876
8316
  [playStopAction]="controlAudioPlayer?.stopAction"
7877
8317
  [streamingMode]="isRecording()"
7878
8318
  [displayLevelInfos]="displayLevelInfos"
7879
- [displayAudioBuffer]="displayAudioClip?.buffer" [audioSignalCollapsed]="audioSignalCollapsed"
8319
+ [displayAudioBuffer]="displayAudioClip?.buffer"
8320
+ [agc]="this.ac?.agcStatus"
8321
+ [audioSignalCollapsed]="audioSignalCollapsed"
7880
8322
  (onShowRecordingDetails)="audioSignalCollapsed=!audioSignalCollapsed"
7881
8323
  (onDownloadRecording)="downloadRecording()"
7882
8324
  [enableDownload]="enableDownloadRecordings"></spr-recordingitemdisplay>
@@ -7885,7 +8327,7 @@ SessionManager.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version
7885
8327
  [statusAlertType]="statusAlertType" [uploadProgress]="uploadProgress"
7886
8328
  [uploadStatus]="uploadStatus" [ready]="dataSaved && !isActive()" [processing]="processingRecording" [navigationEnabled]="items==null || items.length>1"></app-sprcontrolpanel>
7887
8329
 
7888
- `, isInline: true, styles: [":host {\n flex: 2;\n background: lightgrey;\n display: flex; /* Vertical flex container: Bottom transport panel, above prompting panel */\n flex-direction: column;\n margin: 0;\n padding: 0;\n min-height: 0px;\n\n /* Prevents horizontal scroll bar on swipe right */\n overflow: hidden;\n }"], components: [{ type: WarningBar, selector: "app-warningbar", inputs: ["warningText", "show"] }, { type: Prompting, selector: "app-sprprompting", inputs: ["projectName", "startStopSignalState", "promptItem", "showPrompt", "items", "selectedItemIdx", "transportActions", "enableDownload", "audioSignalCollapsed", "displayAudioClip", "playStartAction", "playSelectionAction", "autoPlayOnSelectToggleAction", "playStopAction"], outputs: ["onItemSelect", "onNextItem", "onPrevItem"] }, { type: i7.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "mode", "value", "bufferValue"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { type: LevelBarDisplay, selector: "spr-recordingitemdisplay", inputs: ["streamingMode", "audioSignalCollapsed", "enableDownload", "playStartAction", "playStopAction", "displayAudioBuffer", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }, { type: ControlPanel, selector: "app-sprcontrolpanel", inputs: ["readonly", "transportActions", "processing", "statusMsg", "statusAlertType", "statusWaiting", "uploadStatus", "uploadProgress", "currentRecording", "enableUploadRecordings", "navigationEnabled", "ready"] }], directives: [{ type: i6.DefaultShowHideDirective, selector: " [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]", inputs: ["fxShow", "fxShow.print", "fxShow.xs", "fxShow.sm", "fxShow.md", "fxShow.lg", "fxShow.xl", "fxShow.lt-sm", "fxShow.lt-md", "fxShow.lt-lg", "fxShow.lt-xl", "fxShow.gt-xs", "fxShow.gt-sm", "fxShow.gt-md", "fxShow.gt-lg", "fxHide", "fxHide.print", "fxHide.xs", "fxHide.sm", "fxHide.md", "fxHide.lg", "fxHide.xl", "fxHide.lt-sm", "fxHide.lt-md", "fxHide.lt-lg", "fxHide.lt-xl", "fxHide.gt-xs", "fxHide.gt-sm", "fxHide.gt-md", "fxHide.gt-lg"] }] });
8330
+ `, isInline: true, styles: [":host {\n flex: 2;\n background: lightgrey;\n display: flex; /* Vertical flex container: Bottom transport panel, above prompting panel */\n flex-direction: column;\n margin: 0;\n padding: 0;\n min-height: 0px;\n\n /* Prevents horizontal scroll bar on swipe right */\n overflow: hidden;\n }"], components: [{ type: WarningBar, selector: "app-warningbar", inputs: ["warningText", "show"] }, { type: Prompting, selector: "app-sprprompting", inputs: ["projectName", "startStopSignalState", "promptItem", "showPrompt", "items", "selectedItemIdx", "transportActions", "enableDownload", "audioSignalCollapsed", "displayAudioClip", "playStartAction", "playSelectionAction", "autoPlayOnSelectToggleAction", "playStopAction"], outputs: ["onItemSelect", "onNextItem", "onPrevItem"] }, { type: i7.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "mode", "value", "bufferValue"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { type: LevelBarDisplay, selector: "spr-recordingitemdisplay", inputs: ["streamingMode", "audioSignalCollapsed", "enableDownload", "agc", "playStartAction", "playStopAction", "displayAudioBuffer", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }, { type: ControlPanel, selector: "app-sprcontrolpanel", inputs: ["readonly", "transportActions", "processing", "statusMsg", "statusAlertType", "statusWaiting", "uploadStatus", "uploadProgress", "currentRecording", "enableUploadRecordings", "navigationEnabled", "ready"] }], directives: [{ type: i6.DefaultShowHideDirective, selector: " [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]", inputs: ["fxShow", "fxShow.print", "fxShow.xs", "fxShow.sm", "fxShow.md", "fxShow.lg", "fxShow.xl", "fxShow.lt-sm", "fxShow.lt-md", "fxShow.lt-lg", "fxShow.lt-xl", "fxShow.gt-xs", "fxShow.gt-sm", "fxShow.gt-md", "fxShow.gt-lg", "fxHide", "fxHide.print", "fxHide.xs", "fxHide.sm", "fxHide.md", "fxHide.lg", "fxHide.xl", "fxHide.lt-sm", "fxHide.lt-md", "fxHide.lt-lg", "fxHide.lt-xl", "fxHide.gt-xs", "fxHide.gt-sm", "fxHide.gt-md", "fxHide.gt-lg"] }] });
7889
8331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImport: i0, type: SessionManager, decorators: [{
7890
8332
  type: Component,
7891
8333
  args: [{
@@ -7913,7 +8355,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImpo
7913
8355
  [playStopAction]="controlAudioPlayer?.stopAction"
7914
8356
  [streamingMode]="isRecording()"
7915
8357
  [displayLevelInfos]="displayLevelInfos"
7916
- [displayAudioBuffer]="displayAudioClip?.buffer" [audioSignalCollapsed]="audioSignalCollapsed"
8358
+ [displayAudioBuffer]="displayAudioClip?.buffer"
8359
+ [agc]="this.ac?.agcStatus"
8360
+ [audioSignalCollapsed]="audioSignalCollapsed"
7917
8361
  (onShowRecordingDetails)="audioSignalCollapsed=!audioSignalCollapsed"
7918
8362
  (onDownloadRecording)="downloadRecording()"
7919
8363
  [enableDownload]="enableDownloadRecordings"></spr-recordingitemdisplay>
@@ -7957,20 +8401,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImpo
7957
8401
  args: ['window:keydown', ['$event']]
7958
8402
  }] } });
7959
8403
 
7960
- class ProjectUtil {
7961
- static audioChannelCount(project) {
7962
- let chs = ProjectUtil.DEFAULT_AUDIO_CHANNEL_COUNT;
7963
- if (project.mediaCaptureFormat) {
7964
- chs = project.mediaCaptureFormat.audioChannelCount;
7965
- }
7966
- else if (project.audioFormat) {
7967
- chs = project.audioFormat.channels;
7968
- }
7969
- return chs;
7970
- }
7971
- }
7972
- ProjectUtil.DEFAULT_AUDIO_CHANNEL_COUNT = 2;
7973
-
7974
8404
  /**
7975
8405
  * Created by klausj on 17.06.2017.
7976
8406
  */
@@ -8293,6 +8723,7 @@ class SpeechrecorderngComponent {
8293
8723
  this.sm.audioDevices = project.audioDevices;
8294
8724
  chCnt = ProjectUtil.audioChannelCount(project);
8295
8725
  console.info("Project requested recording channel count: " + chCnt);
8726
+ this.sm.autoGainControlConfigs = project.autoGainControlConfigs;
8296
8727
  }
8297
8728
  else {
8298
8729
  console.error("Empty project configuration!");
@@ -9711,7 +10142,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.10", ngImpo
9711
10142
  }]
9712
10143
  }] });
9713
10144
 
9714
- const VERSION = '2.18.13';
10145
+ const VERSION = '2.19.0';
9715
10146
 
9716
10147
  /*
9717
10148
  * Public API Surface of speechrecorderng