speechrecorderng 3.4.5 → 3.6.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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/esm2020/lib/audio/audio_player.mjs +13 -26
  3. package/esm2020/lib/audio/capture/capture.mjs +244 -207
  4. package/esm2020/lib/audio/context.mjs +64 -2
  5. package/esm2020/lib/audio/net_audio_buffer.mjs +5 -9
  6. package/esm2020/lib/audio/playback/player.mjs +137 -96
  7. package/esm2020/lib/audio/ui/audio_display_control.mjs +1 -1
  8. package/esm2020/lib/speechrecorder/project/project.mjs +1 -1
  9. package/esm2020/lib/speechrecorder/recordings/basic_recording.service.mjs +9 -8
  10. package/esm2020/lib/speechrecorder/recordings/recordings.service.mjs +38 -37
  11. package/esm2020/lib/speechrecorder/session/audiorecorder.mjs +28 -77
  12. package/esm2020/lib/speechrecorder/session/basicrecorder.mjs +9 -2
  13. package/esm2020/lib/speechrecorder/session/recordingfile/recording-file-meta.component.mjs +3 -3
  14. package/esm2020/lib/speechrecorder/session/recordingfile/recording-file-navi.component.mjs +1 -1
  15. package/esm2020/lib/speechrecorder/session/recordingfile/recording-file-view.component.mjs +44 -47
  16. package/esm2020/lib/speechrecorder/session/recordingfile/recordingfile-service.mjs +11 -11
  17. package/esm2020/lib/speechrecorder/session/sessionmanager.mjs +66 -70
  18. package/esm2020/lib/speechrecorderng.component.mjs +8 -14
  19. package/esm2020/lib/spr.module.version.mjs +2 -2
  20. package/fesm2015/speechrecorderng.mjs +682 -610
  21. package/fesm2015/speechrecorderng.mjs.map +1 -1
  22. package/fesm2020/speechrecorderng.mjs +679 -610
  23. package/fesm2020/speechrecorderng.mjs.map +1 -1
  24. package/lib/audio/audio_player.d.ts +0 -1
  25. package/lib/audio/capture/capture.d.ts +5 -4
  26. package/lib/audio/context.d.ts +2 -0
  27. package/lib/audio/net_audio_buffer.d.ts +2 -4
  28. package/lib/audio/playback/player.d.ts +3 -2
  29. package/lib/speechrecorder/project/project.d.ts +1 -0
  30. package/lib/speechrecorder/recordings/basic_recording.service.d.ts +2 -2
  31. package/lib/speechrecorder/recordings/recordings.service.d.ts +8 -8
  32. package/lib/speechrecorder/session/basicrecorder.d.ts +3 -0
  33. package/lib/speechrecorder/session/recordingfile/recording-file-meta.component.d.ts +1 -0
  34. package/lib/speechrecorder/session/recordingfile/recordingfile-service.d.ts +2 -2
  35. package/lib/speechrecorder/session/sessionmanager.d.ts +1 -0
  36. package/lib/spr.module.version.d.ts +1 -1
  37. package/package.json +1 -1
@@ -1982,9 +1982,8 @@ class IndexedDbAudioBufferSourceNode extends AudioSourceNode {
1982
1982
  }
1983
1983
 
1984
1984
  class NetAudioBuffer extends BasicAudioSource {
1985
- constructor(_audioContext, _recFileService, _baseUrl, _channelCount, _sampleRate, _chunkFrameLen, _frameLen, _uuid = null, _orgFetchChunkFrameLen = _chunkFrameLen) {
1985
+ constructor(_recFileService, _baseUrl, _channelCount, _sampleRate, _chunkFrameLen, _frameLen, _uuid = null, _orgFetchChunkFrameLen = _chunkFrameLen) {
1986
1986
  super();
1987
- this._audioContext = _audioContext;
1988
1987
  this._recFileService = _recFileService;
1989
1988
  this._baseUrl = _baseUrl;
1990
1989
  this._channelCount = _channelCount;
@@ -2002,9 +2001,6 @@ class NetAudioBuffer extends BasicAudioSource {
2002
2001
  get recFileService() {
2003
2002
  return this._recFileService;
2004
2003
  }
2005
- get audioContext() {
2006
- return this._audioContext;
2007
- }
2008
2004
  get baseUrl() {
2009
2005
  return this._baseUrl;
2010
2006
  }
@@ -2042,8 +2038,8 @@ class NetAudioBuffer extends BasicAudioSource {
2042
2038
  toString() {
2043
2039
  return "Indexed db audio buffer. Channels: " + this.channelCount + ", sample rate: " + this.sampleRate + ", chunk frame length: " + this._chunkFrameLen + ", number of chunks: " + this.chunkCount + ", frame length: " + this.frameLen + ", sealed: " + this.sealed();
2044
2040
  }
2045
- static fromChunkAudioBuffer(aCtx, recordingsService, baseUrl, ab, frameLen, orgFetchChunkFrameLen = ab.length) {
2046
- let nab = new NetAudioBuffer(aCtx, recordingsService, baseUrl, ab.numberOfChannels, ab.sampleRate, ab.length, frameLen, null, orgFetchChunkFrameLen);
2041
+ static fromChunkAudioBuffer(recordingsService, baseUrl, ab, frameLen, orgFetchChunkFrameLen = ab.length) {
2042
+ let nab = new NetAudioBuffer(recordingsService, baseUrl, ab.numberOfChannels, ab.sampleRate, ab.length, frameLen, null, orgFetchChunkFrameLen);
2047
2043
  nab.ready();
2048
2044
  return nab;
2049
2045
  }
@@ -2086,7 +2082,7 @@ class NetRandomAccessAudioStream {
2086
2082
  }
2087
2083
  chunk(baseUrl, ci, cb, errCb) {
2088
2084
  let startFrame = ci * this._netAb.orgFetchChunkFrameLen;
2089
- this._netAb.recFileService.chunkAudioRequest(this._netAb.audioContext, baseUrl, startFrame, this._netAb.orgFetchChunkFrameLen).subscribe({
2085
+ this._netAb.recFileService.chunkAudioRequest(baseUrl, startFrame, this._netAb.orgFetchChunkFrameLen).subscribe({
2090
2086
  next: (chDl) => {
2091
2087
  if (chDl) {
2092
2088
  const ab = chDl.decodedAudioBuffer;
@@ -2493,6 +2489,87 @@ class NetAudioBufferSourceNode extends AudioSourceNode {
2493
2489
  }
2494
2490
  }
2495
2491
 
2492
+ class AudioContextProvider {
2493
+ static audioContextInstance() {
2494
+ var _a;
2495
+ if (!this._audioContext) {
2496
+ let debugFail = false;
2497
+ if (!window.AudioContext || typeof window.AudioContext !== 'function' || debugFail) {
2498
+ this._audioContext = null;
2499
+ throw new Error('Browser does not support Web Audio API!');
2500
+ }
2501
+ else {
2502
+ console.debug("Get new audio context...");
2503
+ this._audioContext = new window.AudioContext();
2504
+ console.debug("Created new audio context.");
2505
+ this._audioContext.addEventListener('statechange', () => {
2506
+ var _a;
2507
+ console.debug("Audio context state changed: " + ((_a = this._audioContext) === null || _a === void 0 ? void 0 : _a.state));
2508
+ });
2509
+ console.debug("Created new audio context with state: " + ((_a = this._audioContext) === null || _a === void 0 ? void 0 : _a.state));
2510
+ }
2511
+ }
2512
+ return this._audioContext;
2513
+ }
2514
+ // public static audioContextInstanceRunning(audioContext?:AudioContext):Promise<AudioContext>{
2515
+ //
2516
+ // return new Promise<AudioContext>((resolve,reject)=>{
2517
+ // let aCtx=audioContext?audioContext:AudioContextProvider.audioContextInstance();
2518
+ // if(aCtx) {
2519
+ // if(aCtx.state==='closed') {
2520
+ // reject(new Error('Audio context already closed.'));
2521
+ // }else if(aCtx.state==='running') {
2522
+ // resolve(aCtx);
2523
+ // }else{
2524
+ // aCtx.resume().then(() => {
2525
+ // if(aCtx) {
2526
+ // resolve(aCtx);
2527
+ // }else{
2528
+ // reject(new Error('Could not get audio context'));
2529
+ // }
2530
+ // }).catch(() => {
2531
+ // reject(new Error('Could not resume audio context'));
2532
+ // })
2533
+ // }
2534
+ // }else{
2535
+ // reject(new Error('Could not get audio context from browser'));
2536
+ // }
2537
+ // });
2538
+ // }
2539
+ // public static decodeAudioData(data:ArrayBuffer,audioContext?:AudioContext):Promise<AudioBuffer>{
2540
+ // return new Promise<AudioBuffer>((resolve,reject)=>{
2541
+ // // decodeAudioData requires an audio context in running state
2542
+ // AudioContextProvider.audioContextInstanceRunning(audioContext).then(
2543
+ // (aCtx)=>{
2544
+ // // Do not use Promise version, which does not work with Safari 13
2545
+ // aCtx.decodeAudioData(data,decodedData => {
2546
+ // resolve(decodedData);
2547
+ // },(reason) => {
2548
+ // reject(reason);
2549
+ // });
2550
+ // }
2551
+ // ).catch((reason)=>{
2552
+ // reject(reason);
2553
+ // })
2554
+ // })
2555
+ // }
2556
+ static decodeAudioData(data) {
2557
+ return new Promise((resolve, reject) => {
2558
+ if (!this._offlineAudioContext) {
2559
+ this._offlineAudioContext = new OfflineAudioContext(1, 44100, 44100);
2560
+ }
2561
+ // Do not use Promise version, which does not work with Safari 13
2562
+ this._offlineAudioContext.decodeAudioData(data, decodedData => {
2563
+ resolve(decodedData);
2564
+ }, (reason) => {
2565
+ reject(reason);
2566
+ });
2567
+ });
2568
+ }
2569
+ }
2570
+ AudioContextProvider._audioContext = null;
2571
+ AudioContextProvider._offlineAudioContext = null;
2572
+
2496
2573
  var EventType;
2497
2574
  (function (EventType) {
2498
2575
  EventType[EventType["CLOSED"] = 0] = "CLOSED";
@@ -2516,8 +2593,9 @@ class AudioPlayerEvent {
2516
2593
  }
2517
2594
  }
2518
2595
  class AudioPlayer {
2519
- constructor(context, listener) {
2596
+ constructor(listener) {
2520
2597
  this.running = false;
2598
+ this.context = null;
2521
2599
  this.ready = false;
2522
2600
  this._audioClip = null;
2523
2601
  this._audioSource = null;
@@ -2525,7 +2603,6 @@ class AudioPlayer {
2525
2603
  this.sourceAudioWorkletNode = null;
2526
2604
  this.playStartTime = null;
2527
2605
  this.timerVar = null;
2528
- this.context = context;
2529
2606
  this.listener = listener;
2530
2607
  this.bufSize = AudioPlayer.DEFAULT_BUFSIZE;
2531
2608
  this.n = navigator;
@@ -2542,15 +2619,23 @@ class AudioPlayer {
2542
2619
  this._stopAction = new Action('Stop');
2543
2620
  this._stopAction.disabled = true;
2544
2621
  this._stopAction.onAction = () => this.stop();
2545
- this.context.addEventListener('statechange', (ev) => {
2546
- if (this.context.state !== 'running') {
2547
- this.stop();
2548
- }
2549
- });
2550
2622
  }
2551
2623
  get autoPlayOnSelectToggleAction() {
2552
2624
  return this._autoPlayOnSelectToggleAction;
2553
2625
  }
2626
+ _audioContext() {
2627
+ if (!this.context) {
2628
+ this.context = AudioContextProvider.audioContextInstance();
2629
+ if (this.context) {
2630
+ this.context.addEventListener('statechange', (ev) => {
2631
+ if (this.context && this.context.state !== 'running') {
2632
+ this.stop();
2633
+ }
2634
+ });
2635
+ }
2636
+ }
2637
+ return this.context;
2638
+ }
2554
2639
  get startAction() {
2555
2640
  return this._startAction;
2556
2641
  }
@@ -2569,9 +2654,9 @@ class AudioPlayer {
2569
2654
  chs = audioDataHolder.numberOfChannels;
2570
2655
  if (chs > 0) {
2571
2656
  length = audioDataHolder.frameLen;
2572
- if (chs > this.context.destination.maxChannelCount) {
2573
- // TODO exception
2574
- }
2657
+ //if (chs > this.context.destination.maxChannelCount) {
2658
+ // // TODO exception
2659
+ //}
2575
2660
  }
2576
2661
  this.audioSource = audioDataHolder.audioSource;
2577
2662
  audioClip.addSelectionObserver((ac) => {
@@ -2591,16 +2676,11 @@ class AudioPlayer {
2591
2676
  set audioSource(value) {
2592
2677
  this.stop();
2593
2678
  this._audioSource = value;
2594
- if (this._audioSource && this.context) {
2595
- if (this._audioSource instanceof AudioBufferSource) {
2596
- this.ready = true;
2597
- this.updateStartActions();
2598
- if (this.listener) {
2599
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2600
- }
2601
- }
2602
- else {
2603
- this._loadSourceWorkletAndInitStart();
2679
+ if (this._audioSource) {
2680
+ this.ready = true;
2681
+ this.updateStartActions();
2682
+ if (this.listener) {
2683
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2604
2684
  }
2605
2685
  }
2606
2686
  else {
@@ -2612,24 +2692,27 @@ class AudioPlayer {
2612
2692
  }
2613
2693
  }
2614
2694
  _loadSourceWorkletAndInitStart() {
2615
- AudioSourceWorkletModuleLoader.loadModule(this.context).then(() => {
2616
- //console.debug("Player ready. ( by Player::_loadSourceWorkletAndInitStart()");
2617
- this.ready = true;
2618
- this.updateStartActions();
2619
- if (this.listener) {
2620
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2621
- }
2622
- }).catch((error) => {
2623
- this.ready = false;
2624
- this.updateStartActions();
2625
- if (this.listener) {
2626
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
2627
- }
2628
- console.error('Could not add module ' + error);
2629
- });
2695
+ if (this.context) {
2696
+ AudioSourceWorkletModuleLoader.loadModule(this.context).then(() => {
2697
+ //console.debug("Player ready. ( by Player::_loadSourceWorkletAndInitStart()");
2698
+ this.ready = true;
2699
+ this.updateStartActions();
2700
+ if (this.listener) {
2701
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2702
+ }
2703
+ }).catch((error) => {
2704
+ this.ready = false;
2705
+ this.updateStartActions();
2706
+ if (this.listener) {
2707
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
2708
+ }
2709
+ console.error('Could not add module ' + error);
2710
+ });
2711
+ }
2630
2712
  }
2631
2713
  _startAudioSourceWorkletNode() {
2632
- if (this.sourceAudioWorkletNode) {
2714
+ this._loadSourceWorkletAndInitStart();
2715
+ if (this.context && this.sourceAudioWorkletNode) {
2633
2716
  this.sourceAudioWorkletNode.onprocessorerror = (ev) => {
2634
2717
  let msg = 'Unknwon error';
2635
2718
  if (ev instanceof ErrorEvent) {
@@ -2656,16 +2739,25 @@ class AudioPlayer {
2656
2739
  }
2657
2740
  }
2658
2741
  start() {
2659
- if (!this._startAction.disabled && !this.running) {
2660
- if (this.context.state !== 'running') {
2661
- this.context.resume().then(() => {
2742
+ this._audioContext();
2743
+ if (this.context) {
2744
+ if (!this._startAction.disabled && !this.running) {
2745
+ if (this.context.state === 'suspended') {
2746
+ this.context.resume().then(() => {
2747
+ this._start();
2748
+ }).catch((reason) => {
2749
+ console.error(reason.message());
2750
+ throw reason;
2751
+ });
2752
+ }
2753
+ else if (this.context.state === 'closed') {
2754
+ const msg = 'Error: Cannot start playback. Audio context is already closed!';
2755
+ console.error(msg);
2756
+ throw new Error(msg);
2757
+ }
2758
+ else {
2662
2759
  this._start();
2663
- }).catch((reason) => {
2664
- console.error('Could not resume audio context: ' + reason);
2665
- });
2666
- }
2667
- else {
2668
- this._start();
2760
+ }
2669
2761
  }
2670
2762
  }
2671
2763
  }
@@ -2690,9 +2782,12 @@ class AudioPlayer {
2690
2782
  }
2691
2783
  }
2692
2784
  startSelectionDisabled() {
2693
- return !(this._audioClip && this.context && !this.startAction.disabled && this._audioClip.selection);
2785
+ return !(this._audioClip && !this.startAction.disabled && this._audioClip.selection);
2694
2786
  }
2695
2787
  _start(playSelection = false) {
2788
+ if (!this.context) {
2789
+ throw new Error("Could not get audio context!");
2790
+ }
2696
2791
  if (this._audioSource instanceof AudioBufferSource) {
2697
2792
  this.sourceBufferNode = this.context.createBufferSource();
2698
2793
  this.sourceBufferNode.buffer = this._audioSource.audioBuffer;
@@ -2723,68 +2818,90 @@ class AudioPlayer {
2723
2818
  }
2724
2819
  }
2725
2820
  else if (this._audioSource instanceof ArrayAudioBuffer || this._audioSource instanceof IndexedDbAudioBuffer || this._audioSource instanceof NetAudioBuffer) {
2726
- this.playStartTime = null;
2727
- if (this._audioSource instanceof ArrayAudioBuffer) {
2728
- const aabsn = new ArrayAudioBufferSourceNode(this.context);
2729
- aabsn.arrayAudioBuffer = this._audioSource;
2730
- this.sourceAudioWorkletNode = aabsn;
2731
- }
2732
- else if (this._audioSource instanceof IndexedDbAudioBuffer) {
2733
- const iasn = new IndexedDbAudioBufferSourceNode(this.context);
2734
- iasn.inddbAudioBuffer = this._audioSource;
2735
- this.sourceAudioWorkletNode = iasn;
2736
- }
2737
- else if (this._audioSource instanceof NetAudioBuffer) {
2738
- const nabsn = new NetAudioBufferSourceNode(this.context);
2739
- nabsn.netAudioBuffer = this._audioSource;
2740
- this.sourceAudioWorkletNode = nabsn;
2741
- }
2742
- if (this.sourceAudioWorkletNode) {
2743
- this.sourceAudioWorkletNode.onprocessorerror = (ev) => {
2744
- let msg = 'Unknwon error';
2745
- if (ev instanceof ErrorEvent) {
2746
- msg = ev.message;
2821
+ AudioSourceWorkletModuleLoader.loadModule(this.context).then(() => {
2822
+ this.playStartTime = null;
2823
+ if (this.context) {
2824
+ if (this._audioSource instanceof ArrayAudioBuffer) {
2825
+ const aabsn = new ArrayAudioBufferSourceNode(this.context);
2826
+ aabsn.arrayAudioBuffer = this._audioSource;
2827
+ this.sourceAudioWorkletNode = aabsn;
2747
2828
  }
2748
- console.error("Audio source worklet error: " + msg);
2749
- if (this.listener) {
2750
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.ERROR));
2829
+ else if (this._audioSource instanceof IndexedDbAudioBuffer) {
2830
+ const iasn = new IndexedDbAudioBufferSourceNode(this.context);
2831
+ iasn.inddbAudioBuffer = this._audioSource;
2832
+ this.sourceAudioWorkletNode = iasn;
2833
+ }
2834
+ else if (this._audioSource instanceof NetAudioBuffer) {
2835
+ const nabsn = new NetAudioBufferSourceNode(this.context);
2836
+ nabsn.netAudioBuffer = this._audioSource;
2837
+ this.sourceAudioWorkletNode = nabsn;
2838
+ }
2839
+ if (this.sourceAudioWorkletNode) {
2840
+ this.sourceAudioWorkletNode.onprocessorerror = (ev) => {
2841
+ let msg = 'Unknwon error';
2842
+ if (ev instanceof ErrorEvent) {
2843
+ msg = ev.message;
2844
+ }
2845
+ console.error("Audio source worklet error: " + msg);
2846
+ if (this.listener) {
2847
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.ERROR));
2848
+ }
2849
+ };
2850
+ this.sourceAudioWorkletNode.connect(this.context.destination); // this already starts playing
2851
+ this.sourceAudioWorkletNode.onended = () => this.onended();
2852
+ this.running = true;
2853
+ const ac = this._audioClip;
2854
+ let offset = 0;
2855
+ if (playSelection && ac && ac.selection) {
2856
+ const s = ac.selection;
2857
+ const sr = ac.audioDataHolder.sampleRate;
2858
+ offset = s.leftFrame / sr;
2859
+ const stopPosInsecs = s.rightFrame / sr;
2860
+ const dur = stopPosInsecs - offset;
2861
+ this.sourceAudioWorkletNode.start(0, offset, dur);
2862
+ }
2863
+ else {
2864
+ this.sourceAudioWorkletNode.start();
2865
+ }
2866
+ //this.playStartTime = this.context.currentTime - offset;
2867
+ this._startAction.disabled = true;
2868
+ this._startSelectionAction.disabled = true;
2869
+ this._stopAction.disabled = false;
2870
+ if (this.listener) {
2871
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
2872
+ }
2751
2873
  }
2752
- };
2753
- this.sourceAudioWorkletNode.connect(this.context.destination); // this already starts playing
2754
- this.sourceAudioWorkletNode.onended = () => this.onended();
2755
- this.running = true;
2756
- const ac = this._audioClip;
2757
- let offset = 0;
2758
- if (playSelection && ac && ac.selection) {
2759
- const s = ac.selection;
2760
- const sr = ac.audioDataHolder.sampleRate;
2761
- offset = s.leftFrame / sr;
2762
- const stopPosInsecs = s.rightFrame / sr;
2763
- const dur = stopPosInsecs - offset;
2764
- this.sourceAudioWorkletNode.start(0, offset, dur);
2765
- }
2766
- else {
2767
- this.sourceAudioWorkletNode.start();
2768
2874
  }
2769
- //this.playStartTime = this.context.currentTime - offset;
2770
- this._startAction.disabled = true;
2771
- this._startSelectionAction.disabled = true;
2772
- this._stopAction.disabled = false;
2875
+ }).catch((error) => {
2876
+ console.error(error.message);
2877
+ this.ready = false;
2878
+ this.updateStartActions();
2773
2879
  if (this.listener) {
2774
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
2880
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
2775
2881
  }
2776
- }
2882
+ throw error;
2883
+ });
2777
2884
  }
2778
2885
  }
2779
2886
  startSelected() {
2887
+ this._audioContext();
2888
+ if (!this.context) {
2889
+ throw new Error("Could not get audio context!");
2890
+ }
2780
2891
  if (!this._startAction.disabled && !this.running) {
2781
- if (this.context.state !== 'running') {
2892
+ if (this.context.state === 'suspended') {
2782
2893
  this.context.resume().then(() => {
2783
2894
  this._start(true);
2784
2895
  }).catch((reason) => {
2785
- console.error('Could not resume audio context: ' + reason);
2896
+ console.error(reason.message);
2897
+ throw reason;
2786
2898
  });
2787
2899
  }
2900
+ else if (this.context.state === 'closed') {
2901
+ const msg = 'Error: Cannot start playback of selection. Audio context is already closed!';
2902
+ console.error(msg);
2903
+ throw new Error(msg);
2904
+ }
2788
2905
  else {
2789
2906
  this._start(true);
2790
2907
  }
@@ -2821,7 +2938,7 @@ class AudioPlayer {
2821
2938
  }
2822
2939
  get playPositionTime() {
2823
2940
  let ppt = null;
2824
- if (this.playStartTime !== null) {
2941
+ if (this.context && this.playStartTime !== null) {
2825
2942
  ppt = this.context.currentTime - this.playStartTime;
2826
2943
  }
2827
2944
  else if (this.sourceAudioWorkletNode) {
@@ -3163,8 +3280,10 @@ const awpStr = "class AudioCaptureInterceptorProcessor extends AudioWorkletProce
3163
3280
  "\n" +
3164
3281
  "registerProcessor('capture-interceptor',AudioCaptureInterceptorProcessor);\n";
3165
3282
  class AudioCapture {
3166
- constructor(context) {
3283
+ //private context:AudioContext|null=null;
3284
+ constructor() {
3167
3285
  this._maxAutoNetMemStoreSamples = AudioCapture.DEFAULT_MAX_NET_AUTO_MEM_STORE_SAMPLES;
3286
+ this.context = null;
3168
3287
  this._recUUID = null;
3169
3288
  this.agcStatus = null;
3170
3289
  this.bufferingNode = null;
@@ -3179,13 +3298,7 @@ class AudioCapture {
3179
3298
  this.persisted = true;
3180
3299
  this.persistError = null;
3181
3300
  this.inddbAudioBuffer = null;
3182
- this.context = context;
3183
3301
  this.n = navigator;
3184
- this.context.addEventListener('statechange', () => {
3185
- if (this.context.state !== 'running') {
3186
- this.close();
3187
- }
3188
- });
3189
3302
  }
3190
3303
  get maxAutoNetMemStoreSamples() {
3191
3304
  return this._maxAutoNetMemStoreSamples;
@@ -3214,6 +3327,19 @@ class AudioCapture {
3214
3327
  get opened() {
3215
3328
  return this._opened;
3216
3329
  }
3330
+ _audioContext() {
3331
+ if (!this.context) {
3332
+ this.context = AudioContextProvider.audioContextInstance();
3333
+ if (this.context) {
3334
+ this.context.addEventListener('statechange', () => {
3335
+ if (this.context && this.context.state !== 'running') {
3336
+ this.close();
3337
+ }
3338
+ });
3339
+ }
3340
+ }
3341
+ return this.context;
3342
+ }
3217
3343
  initData() {
3218
3344
  if (!this._recUUID) {
3219
3345
  this._recUUID = UUID.generate();
@@ -3328,119 +3454,137 @@ class AudioCapture {
3328
3454
  }
3329
3455
  }
3330
3456
  addCaptureInterceptor() {
3331
- const awn = new AudioWorkletNode(this.context, 'capture-interceptor');
3332
- awn.onprocessorerror = (ev) => {
3333
- let msg = 'Unknwon error';
3334
- if (ev instanceof ErrorEvent) {
3335
- msg = ev.message;
3336
- }
3337
- console.error("Capture audio worklet error: " + msg);
3338
- if (this.listener) {
3339
- this.listener.error(msg);
3340
- }
3341
- };
3342
- let awnPt = awn.port;
3343
- if (awnPt) {
3344
- awnPt.onmessage = (ev) => {
3345
- if (this.capturing) {
3346
- let dt = ev.data;
3347
- let chs = dt.chs;
3348
- let adaLen = dt.data.length;
3349
- if (DEBUG_TRACE_LEVEL > 8) {
3350
- console.debug('Received data from worklet: ' + chs + ' ' + dt.len + ' Data chs: ' + adaLen);
3351
- }
3352
- let chunk = new Array(chs);
3353
- const samples = this.framesRecorded * chs;
3354
- if ((AudioStorageType.MEM_ENTIRE_AUTO_NET_CHUNKED === this.audioStorageType || AudioStorageType.MEM_CHUNKED_AUTO_NET_CHUNKED === this.audioStorageType) && this.data && samples > this._maxAutoNetMemStoreSamples) {
3355
- this.data = null;
3356
- }
3357
- //console.debug("Data initialized: "+(this.data!=null));
3358
- for (let ch = 0; ch < chs; ch++) {
3359
- //console.debug("Data ch initialized: "+(this.data !=null && this.data[ch] !=null));
3360
- if (ch < this.channelCount) {
3361
- if (dt.data[ch]) {
3362
- let fa = new Float32Array(dt.data[ch]);
3363
- if (this.data && this.data[ch]) {
3364
- this.data[ch].push(fa);
3365
- }
3366
- chunk[ch] = fa;
3367
- // Use samples of channel 0 to count frames (samples)
3368
- if (ch == 0) {
3369
- this.framesRecorded += fa.length;
3370
- }
3371
- }
3457
+ if (this.context) {
3458
+ const awn = new AudioWorkletNode(this.context, 'capture-interceptor');
3459
+ awn.onprocessorerror = (ev) => {
3460
+ let msg = 'Unknwon error';
3461
+ if (ev instanceof ErrorEvent) {
3462
+ msg = ev.message;
3463
+ }
3464
+ console.error("Capture audio worklet error: " + msg);
3465
+ if (this.listener) {
3466
+ this.listener.error(msg);
3467
+ }
3468
+ };
3469
+ let awnPt = awn.port;
3470
+ if (awnPt) {
3471
+ awnPt.onmessage = (ev) => {
3472
+ if (this.capturing) {
3473
+ let dt = ev.data;
3474
+ let chs = dt.chs;
3475
+ let adaLen = dt.data.length;
3476
+ if (DEBUG_TRACE_LEVEL > 8) {
3477
+ console.debug('Received data from worklet: ' + chs + ' ' + dt.len + ' Data chs: ' + adaLen);
3372
3478
  }
3373
- }
3374
- if (this.audioOutStream) {
3375
- try {
3376
- this.audioOutStream.write(chunk);
3377
- // // Random test error:
3378
- // if(Math.random()>0.98) {
3379
- // throw new Error('Test');
3380
- // }
3479
+ let chunk = new Array(chs);
3480
+ const samples = this.framesRecorded * chs;
3481
+ if ((AudioStorageType.MEM_ENTIRE_AUTO_NET_CHUNKED === this.audioStorageType || AudioStorageType.MEM_CHUNKED_AUTO_NET_CHUNKED === this.audioStorageType) && this.data && samples > this._maxAutoNetMemStoreSamples) {
3482
+ this.data = null;
3381
3483
  }
3382
- catch (err) {
3383
- if (err instanceof Error) {
3384
- this.persistError = err;
3385
- }
3386
- else {
3387
- this.persistError = new Error('Error handling recorded audio data');
3484
+ //console.debug("Data initialized: "+(this.data!=null));
3485
+ for (let ch = 0; ch < chs; ch++) {
3486
+ //console.debug("Data ch initialized: "+(this.data !=null && this.data[ch] !=null));
3487
+ if (ch < this.channelCount) {
3488
+ if (dt.data[ch]) {
3489
+ let fa = new Float32Array(dt.data[ch]);
3490
+ if (this.data && this.data[ch]) {
3491
+ this.data[ch].push(fa);
3492
+ }
3493
+ chunk[ch] = fa;
3494
+ // Use samples of channel 0 to count frames (samples)
3495
+ if (ch == 0) {
3496
+ this.framesRecorded += fa.length;
3497
+ }
3498
+ }
3388
3499
  }
3389
- console.error("Capture error: " + err);
3500
+ }
3501
+ if (this.audioOutStream) {
3390
3502
  try {
3391
- this.stop();
3392
- }
3393
- catch (err2) {
3394
- console.error("Capture next error (ignored): " + err2);
3503
+ this.audioOutStream.write(chunk);
3504
+ // // Random test error:
3505
+ // if(Math.random()>0.98) {
3506
+ // throw new Error('Test');
3507
+ // }
3395
3508
  }
3396
- finally {
3397
- if (this.listener) {
3398
- let errExpl = '';
3399
- if (err instanceof DOMException) {
3400
- errExpl = ': ' + err.name + ': ' + err.message;
3401
- }
3402
- this.listener.error("Could not handle recorded audio data" + errExpl, "Please try to record again.");
3509
+ catch (err) {
3510
+ if (err instanceof Error) {
3511
+ this.persistError = err;
3403
3512
  }
3404
3513
  else {
3405
- this.close();
3514
+ this.persistError = new Error('Error handling recorded audio data');
3515
+ }
3516
+ console.error("Capture error: " + err);
3517
+ try {
3518
+ this.stop();
3519
+ }
3520
+ catch (err2) {
3521
+ console.error("Capture next error (ignored): " + err2);
3522
+ }
3523
+ finally {
3524
+ if (this.listener) {
3525
+ let errExpl = '';
3526
+ if (err instanceof DOMException) {
3527
+ errExpl = ': ' + err.name + ': ' + err.message;
3528
+ }
3529
+ this.listener.error("Could not handle recorded audio data" + errExpl, "Please try to record again.");
3530
+ }
3531
+ else {
3532
+ this.close();
3533
+ }
3406
3534
  }
3407
3535
  }
3408
3536
  }
3537
+ if (AudioStorageType.DB_CHUNKED === this._audioStorageType && this._persistentAudioStorageTarget) {
3538
+ this.store();
3539
+ }
3409
3540
  }
3410
- if (AudioStorageType.DB_CHUNKED === this._audioStorageType && this._persistentAudioStorageTarget) {
3411
- this.store();
3412
- }
3413
- }
3414
- };
3415
- }
3416
- // Tried to fix that Safari does not record the second channel
3417
- // Does not help
3418
- //awn.channelCount=this.channelCount;
3419
- //awn.channelCountMode='explicit';
3420
- //console.debug('Channel count explicitly set to '+this.channelCount);
3421
- this.bufferingNode = awn;
3422
- //this.bufferingNode.channelCount=this.channelCount;
3423
- this._opened = true;
3424
- if (this.listener) {
3425
- this.listener.opened();
3541
+ };
3542
+ }
3543
+ // Tried to fix that Safari does not record the second channel
3544
+ // Does not help
3545
+ //awn.channelCount=this.channelCount;
3546
+ //awn.channelCountMode='explicit';
3547
+ //console.debug('Channel count explicitly set to '+this.channelCount);
3548
+ this.bufferingNode = awn;
3549
+ //this.bufferingNode.channelCount=this.channelCount;
3550
+ this._opened = true;
3551
+ if (this.listener) {
3552
+ this.listener.opened();
3553
+ }
3426
3554
  }
3427
3555
  }
3428
- open(channelCount, selDeviceId, autoGainControlConfigs) {
3556
+ open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation) {
3429
3557
  //console.debug("Capture open: ctx state: "+this.context.state);
3430
- if (this.context.state !== 'running') {
3558
+ this.context = this._audioContext();
3559
+ if (!this.context) {
3560
+ throw new Error("Could not get audio context!");
3561
+ }
3562
+ if (this.context.state === 'suspended') {
3431
3563
  //console.debug("Capture open: Resume context");
3432
3564
  this.context.resume().then(() => {
3433
3565
  //console.debug("Capture open (ctx resumed): ctx state: "+this.context.state);
3434
- this._open(channelCount, selDeviceId, autoGainControlConfigs);
3566
+ this._open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation);
3567
+ }).catch((err) => {
3568
+ console.error(err.message);
3569
+ throw err;
3435
3570
  });
3436
3571
  }
3572
+ else if (this.context.state === 'closed') {
3573
+ const msg = 'Error on start capture: The audio context is already closed.';
3574
+ console.error(msg);
3575
+ throw new Error(msg);
3576
+ }
3437
3577
  else {
3438
- this._open(channelCount, selDeviceId, autoGainControlConfigs);
3578
+ this._open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation);
3439
3579
  }
3440
3580
  }
3441
- _open(channelCount, selDeviceId, autoGainControlConfigs) {
3581
+ _open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation) {
3442
3582
  this.channelCount = channelCount;
3443
3583
  this.framesRecorded = 0;
3584
+ this.context = this._audioContext();
3585
+ if (!this.context) {
3586
+ throw new Error("Could not get audio context!");
3587
+ }
3444
3588
  //var msc = new AudioStreamConstr();
3445
3589
  // var msc={};
3446
3590
  //msc.video = false;
@@ -3548,7 +3692,7 @@ class AudioCapture {
3548
3692
  audio: {
3549
3693
  deviceId: selDeviceId,
3550
3694
  channelCount: channelCount,
3551
- //echoCancellation: false
3695
+ echoCancellation: allowEchoCancellation ? undefined : false
3552
3696
  },
3553
3697
  video: false,
3554
3698
  };
@@ -3559,106 +3703,109 @@ class AudioCapture {
3559
3703
  console.debug("Audio capture, AGC: " + this.agcStatus);
3560
3704
  let ump = navigator.mediaDevices.getUserMedia(msc);
3561
3705
  ump.then((s) => {
3562
- this.stream = s;
3563
- let aTracks = s.getAudioTracks();
3564
- for (let i = 0; i < aTracks.length; i++) {
3565
- let aTrack = aTracks[i];
3566
- console.info("Track audio info: id: " + aTrack.id + " kind: " + aTrack.kind + " label: \"" + aTrack.label + "\"");
3567
- let mtrSts = aTrack.getSettings();
3568
- // Typescript lib.dom.ts MediaTrackSettings.channelCount is missing
3569
- // https://github.com/mdn/browser-compat-data/blob/5493d8f937e05b2ddbd41b99f5bdfad4a1f2ed85/api/MediaTrackSettings.json
3570
- //@ts-ignore
3571
- console.info("Track audio settings: Ch cnt: " + mtrSts.channelCount + ", AGC: " + mtrSts.autoGainControl + ", Echo cancell.: " + mtrSts.echoCancellation);
3572
- if (mtrSts.autoGainControl) {
3573
- this.agcStatus = mtrSts.autoGainControl;
3574
- }
3575
- }
3576
- let vTracks = s.getVideoTracks();
3577
- for (let i = 0; i < vTracks.length; i++) {
3578
- let vTrack = vTracks[i];
3579
- console.info("Track video info: id: " + vTrack.id + " kind: " + vTrack.kind + " label: " + vTrack.label);
3580
- }
3581
- this.mediaStream = this.context.createMediaStreamSource(s);
3582
- // stream channel count ( is always 2 !)
3583
- let streamChannelCount = this.mediaStream.channelCount;
3584
- console.info("Stream channel count: " + streamChannelCount);
3585
- // is not set!!
3586
- //this.currentSampleRate = this.mediaStream.sampleRate;
3587
- this.currentSampleRate = this.context.sampleRate;
3588
- console.info("Source audio node: channels: " + streamChannelCount + " samplerate: " + this.currentSampleRate);
3589
- if (this.audioOutStream) {
3590
- this.audioOutStream.setFormat(this.channelCount, this.currentSampleRate);
3591
- }
3592
- // W3C -> new name is createScriptProcessor
3593
- //
3594
- // Again deprecated, but AudioWorker not yet implemented in stable releases (June 2016)
3595
- // AudioWorker is now AudioWorkletProcessor ... (May 2017)
3596
- // Update 12-2020:
3597
- // The ScriptProcessorNode Interface - DEPRECATED
3598
- // Update 06-2021
3599
- // AudioWorkletProcessor is here to stay. Web Audio API has now Recommendation status !
3600
- if (this.context.audioWorklet) {
3601
- //const workletFileName = ('file-loader!./interceptor_worklet.js');
3602
- //const workletFileName = 'http://localhost:4200/assets/interceptor_worklet.js';
3603
- //console.log(awpStr);
3604
- if (AudioCapture.captureInterceptorModuleRegistered) {
3605
- // Required capture interceptor module already registered
3606
- this.addCaptureInterceptor();
3607
- }
3608
- else {
3609
- // Register capture interceptor module
3610
- let audioWorkletModuleBlob = new Blob([awpStr], { type: 'text/javascript' });
3611
- let audioWorkletModuleBlobUrl = window.URL.createObjectURL(audioWorkletModuleBlob);
3612
- this.context.audioWorklet.addModule(audioWorkletModuleBlobUrl).then(() => {
3613
- AudioCapture.captureInterceptorModuleRegistered = true;
3706
+ if (this.context) {
3707
+ this.stream = s;
3708
+ let aTracks = s.getAudioTracks();
3709
+ for (let i = 0; i < aTracks.length; i++) {
3710
+ let aTrack = aTracks[i];
3711
+ console.info("Track audio info: id: " + aTrack.id + " kind: " + aTrack.kind + " label: \"" + aTrack.label + "\"");
3712
+ let mtrSts = aTrack.getSettings();
3713
+ // Typescript lib.dom.ts MediaTrackSettings.channelCount is missing
3714
+ // https://github.com/mdn/browser-compat-data/blob/5493d8f937e05b2ddbd41b99f5bdfad4a1f2ed85/api/MediaTrackSettings.json
3715
+ //@ts-ignore
3716
+ console.info("Track audio settings: Ch cnt: " + mtrSts.channelCount + ", AGC: " + mtrSts.autoGainControl + ", Echo cancell.: " + mtrSts.echoCancellation);
3717
+ if (mtrSts.autoGainControl) {
3718
+ this.agcStatus = mtrSts.autoGainControl;
3719
+ }
3720
+ console.debug("Echo cancellation: " + mtrSts.echoCancellation);
3721
+ }
3722
+ let vTracks = s.getVideoTracks();
3723
+ for (let i = 0; i < vTracks.length; i++) {
3724
+ let vTrack = vTracks[i];
3725
+ console.info("Track video info: id: " + vTrack.id + " kind: " + vTrack.kind + " label: " + vTrack.label);
3726
+ }
3727
+ this.mediaStream = this.context.createMediaStreamSource(s);
3728
+ // stream channel count ( is always 2 !)
3729
+ let streamChannelCount = this.mediaStream.channelCount;
3730
+ console.info("Stream channel count: " + streamChannelCount);
3731
+ // is not set!!
3732
+ //this.currentSampleRate = this.mediaStream.sampleRate;
3733
+ this.currentSampleRate = this.context.sampleRate;
3734
+ console.info("Source audio node: channels: " + streamChannelCount + " samplerate: " + this.currentSampleRate);
3735
+ if (this.audioOutStream) {
3736
+ this.audioOutStream.setFormat(this.channelCount, this.currentSampleRate);
3737
+ }
3738
+ // W3C -> new name is createScriptProcessor
3739
+ //
3740
+ // Again deprecated, but AudioWorker not yet implemented in stable releases (June 2016)
3741
+ // AudioWorker is now AudioWorkletProcessor ... (May 2017)
3742
+ // Update 12-2020:
3743
+ // The ScriptProcessorNode Interface - DEPRECATED
3744
+ // Update 06-2021
3745
+ // AudioWorkletProcessor is here to stay. Web Audio API has now Recommendation status !
3746
+ if (this.context.audioWorklet) {
3747
+ //const workletFileName = ('file-loader!./interceptor_worklet.js');
3748
+ //const workletFileName = 'http://localhost:4200/assets/interceptor_worklet.js';
3749
+ //console.log(awpStr);
3750
+ if (AudioCapture.captureInterceptorModuleRegistered) {
3751
+ // Required capture interceptor module already registered
3614
3752
  this.addCaptureInterceptor();
3615
- }).catch((error) => {
3616
- console.log('Could not add module ' + error);
3617
- });
3753
+ }
3754
+ else {
3755
+ // Register capture interceptor module
3756
+ let audioWorkletModuleBlob = new Blob([awpStr], { type: 'text/javascript' });
3757
+ let audioWorkletModuleBlobUrl = window.URL.createObjectURL(audioWorkletModuleBlob);
3758
+ this.context.audioWorklet.addModule(audioWorkletModuleBlobUrl).then(() => {
3759
+ AudioCapture.captureInterceptorModuleRegistered = true;
3760
+ this.addCaptureInterceptor();
3761
+ }).catch((error) => {
3762
+ console.log('Could not add module ' + error);
3763
+ });
3764
+ }
3618
3765
  }
3619
- }
3620
- else if (this.context.createScriptProcessor) {
3621
- // The ScriptProcessorNode Interface - DEPRECATED Only as fallback
3622
- // TODO should we use streamChannelCount or channelCount here ?
3623
- let scriptProcessorNode = this.context.createScriptProcessor(AudioCapture.BUFFER_SIZE, streamChannelCount, streamChannelCount);
3624
- this.bufferingNode = scriptProcessorNode;
3625
- let c = 0;
3626
- if (scriptProcessorNode.onaudioprocess) {
3627
- scriptProcessorNode.onaudioprocess = (e) => {
3628
- if (this.capturing) {
3629
- let inBuffer = e.inputBuffer;
3630
- // only process requested count of channels
3631
- let currentBuffers = new Array(channelCount);
3632
- for (let ch = 0; ch < channelCount; ch++) {
3633
- let chSamples = inBuffer.getChannelData(ch);
3634
- let chSamplesCopy = chSamples.slice(0);
3635
- currentBuffers[ch] = chSamplesCopy.slice(0);
3636
- if (this.data) {
3637
- this.data[ch].push(chSamplesCopy);
3766
+ else if (this.context.createScriptProcessor) {
3767
+ // The ScriptProcessorNode Interface - DEPRECATED Only as fallback
3768
+ // TODO should we use streamChannelCount or channelCount here ?
3769
+ let scriptProcessorNode = this.context.createScriptProcessor(AudioCapture.BUFFER_SIZE, streamChannelCount, streamChannelCount);
3770
+ this.bufferingNode = scriptProcessorNode;
3771
+ let c = 0;
3772
+ if (scriptProcessorNode.onaudioprocess) {
3773
+ scriptProcessorNode.onaudioprocess = (e) => {
3774
+ if (this.capturing) {
3775
+ let inBuffer = e.inputBuffer;
3776
+ // only process requested count of channels
3777
+ let currentBuffers = new Array(channelCount);
3778
+ for (let ch = 0; ch < channelCount; ch++) {
3779
+ let chSamples = inBuffer.getChannelData(ch);
3780
+ let chSamplesCopy = chSamples.slice(0);
3781
+ currentBuffers[ch] = chSamplesCopy.slice(0);
3782
+ if (this.data) {
3783
+ this.data[ch].push(chSamplesCopy);
3784
+ }
3785
+ if (DEBUG_TRACE_LEVEL > 8) {
3786
+ console.debug("Process " + chSamplesCopy.length + " samples.");
3787
+ }
3788
+ this.framesRecorded += chSamplesCopy.length;
3638
3789
  }
3639
- if (DEBUG_TRACE_LEVEL > 8) {
3640
- console.debug("Process " + chSamplesCopy.length + " samples.");
3790
+ c++;
3791
+ if (this.audioOutStream) {
3792
+ this.audioOutStream.write(currentBuffers);
3641
3793
  }
3642
- this.framesRecorded += chSamplesCopy.length;
3643
- }
3644
- c++;
3645
- if (this.audioOutStream) {
3646
- this.audioOutStream.write(currentBuffers);
3647
3794
  }
3795
+ };
3796
+ this._opened = true;
3797
+ if (this.listener) {
3798
+ this.listener.opened();
3648
3799
  }
3649
- };
3650
- this._opened = true;
3651
- if (this.listener) {
3652
- this.listener.opened();
3800
+ }
3801
+ else {
3802
+ this.listener.error('Browser does not support audio processing (ScriptProcessor.onaudioprocess method not found)!');
3653
3803
  }
3654
3804
  }
3655
3805
  else {
3656
- this.listener.error('Browser does not support audio processing (ScriptProcessor.onaudioprocess method not found)!');
3806
+ this.listener.error('Browser does not support audio processing (neither AudioWorkletProcessor nor ScriptProcessor)!');
3657
3807
  }
3658
3808
  }
3659
- else {
3660
- this.listener.error('Browser does not support audio processing (neither AudioWorkletProcessor nor ScriptProcessor)!');
3661
- }
3662
3809
  }, (e) => {
3663
3810
  console.error(e + " Error name: " + e.name);
3664
3811
  if (this.listener) {
@@ -3679,36 +3826,42 @@ class AudioCapture {
3679
3826
  });
3680
3827
  }
3681
3828
  _start() {
3682
- this.initData();
3683
- if (this.audioOutStream) {
3684
- this.audioOutStream.nextStream();
3685
- }
3686
- this.capturing = true;
3687
- if (this.bufferingNode) {
3688
- this.mediaStream.connect(this.bufferingNode);
3689
- this.bufferingNode.connect(this.context.destination);
3690
- }
3691
- if (this.listener) {
3692
- this.listener.started();
3829
+ if (this.context) {
3830
+ this.initData();
3831
+ if (this.audioOutStream) {
3832
+ this.audioOutStream.nextStream();
3833
+ }
3834
+ this.capturing = true;
3835
+ if (this.bufferingNode) {
3836
+ this.mediaStream.connect(this.bufferingNode);
3837
+ this.bufferingNode.connect(this.context.destination);
3838
+ }
3839
+ if (this.listener) {
3840
+ this.listener.started();
3841
+ }
3693
3842
  }
3694
3843
  }
3695
3844
  start() {
3696
- const aSt = this.context.state;
3697
- if (aSt === 'running') {
3698
- this._start();
3699
- }
3700
- else {
3701
- console.debug("Capture start: audio context not running, state: " + aSt + ", resuming...");
3702
- this.context.resume().then(() => {
3703
- console.debug("Capture start: audio context resumed, starting...");
3845
+ if (this.context) {
3846
+ const aSt = this.context.state;
3847
+ if (aSt === 'running') {
3704
3848
  this._start();
3705
- });
3849
+ }
3850
+ else {
3851
+ console.debug("Capture start: audio context not running, state: " + aSt + ", resuming...");
3852
+ this.context.resume().then(() => {
3853
+ console.debug("Capture start: audio context resumed, starting...");
3854
+ this._start();
3855
+ });
3856
+ }
3706
3857
  }
3707
3858
  }
3708
3859
  stop() {
3709
3860
  if (this.disconnectStreams && this.bufferingNode) {
3710
3861
  this.mediaStream.disconnect(this.bufferingNode);
3711
- this.bufferingNode.disconnect(this.context.destination);
3862
+ if (this.context) {
3863
+ this.bufferingNode.disconnect(this.context.destination);
3864
+ }
3712
3865
  }
3713
3866
  try {
3714
3867
  if (this.audioOutStream) {
@@ -3831,7 +3984,7 @@ class AudioCapture {
3831
3984
  }
3832
3985
  audioBuffer() {
3833
3986
  let ab = null;
3834
- if (this.data) {
3987
+ if (this.context && this.data) {
3835
3988
  let frameLen = 0;
3836
3989
  let ch0Data = this.data[0];
3837
3990
  for (let ch0Chk of ch0Data) {
@@ -9027,7 +9180,7 @@ let BasicRecordingService = class BasicRecordingService {
9027
9180
  withCredentials: this.withCredentials
9028
9181
  });
9029
9182
  }
9030
- chunkAudioRequest(aCtx, baseAudioUrl, startFrame = 0, frameLength) {
9183
+ chunkAudioRequest(baseAudioUrl, startFrame = 0, frameLength) {
9031
9184
  let ausps = new URLSearchParams();
9032
9185
  ausps.set('startFrame', startFrame.toString());
9033
9186
  ausps.set('frameLength', frameLength.toString());
@@ -9059,12 +9212,12 @@ let BasicRecordingService = class BasicRecordingService {
9059
9212
  // console.error("Could not read WAVE format of original download chunk!");
9060
9213
  // }
9061
9214
  if (pcmFmt && orgFl) {
9062
- aCtx.decodeAudioData(resp.body, ab => {
9215
+ AudioContextProvider.decodeAudioData(resp.body).then((ab) => {
9063
9216
  //console.debug("Decoded audio chunk frames: "+ab.length);
9064
9217
  let chDl = new ChunkDownload(pcmFmt, orgFl, ab);
9065
9218
  observer.next(chDl);
9066
9219
  observer.complete();
9067
- }, error => {
9220
+ }).catch(error => {
9068
9221
  //if(error instanceof HttpErrorResponse) {
9069
9222
  // if (error.status == 404) {
9070
9223
  // // Interpret not as an error, the file ist not recorded yet
@@ -9100,7 +9253,7 @@ let BasicRecordingService = class BasicRecordingService {
9100
9253
  });
9101
9254
  return obs;
9102
9255
  }
9103
- chunkAudioRequestToNetAudioBuffer(aCtx, baseAudioUrl, startFrame = 0, orgSampleRate, seconds, frames) {
9256
+ chunkAudioRequestToNetAudioBuffer(baseAudioUrl, startFrame = 0, orgSampleRate, seconds, frames) {
9104
9257
  //let audioUrl=baseAudioUrl+'?startFrame='+startFrame+'&frameLength='+frameLength;
9105
9258
  //let audioUrl=new URL(baseAudioUrl);
9106
9259
  // if(orgSampleRate!=null && frameLength%orgSampleRate>0){
@@ -9124,7 +9277,7 @@ let BasicRecordingService = class BasicRecordingService {
9124
9277
  if (resp.body) {
9125
9278
  //console.debug("chunkAudioRequestTonetAb: subscriber.closed: "+subscriber.closed);
9126
9279
  //console.debug("chunkAudioRequestTonetAb: Audio file bytes: "+resp.body.byteLength);
9127
- aCtx.decodeAudioData(resp.body, ab => {
9280
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9128
9281
  //console.debug("chunkAudioRequestTonetAb: Decoded audio chunk frames for netAb: "+ab.length);
9129
9282
  //console.debug("chunkAudioRequestTonetAb: Create netAb ab from chunk ab...");
9130
9283
  if (frames === null) {
@@ -9138,7 +9291,7 @@ let BasicRecordingService = class BasicRecordingService {
9138
9291
  //console.debug("Platform sr: "+ab.sampleRate+", file sr: "+orgSampleRate+", decoded/org frame length: "+fl+"/"+frames+", ab.length: "+ab.length);
9139
9292
  }
9140
9293
  }
9141
- let nab = NetAudioBuffer.fromChunkAudioBuffer(aCtx, this, baseAudioUrl, ab, fl, frameLength);
9294
+ let nab = NetAudioBuffer.fromChunkAudioBuffer(this, baseAudioUrl, ab, fl, frameLength);
9142
9295
  //let rp=new ReadyProvider();
9143
9296
  //nab.readyProvider=rp;
9144
9297
  //rp.ready();
@@ -9150,7 +9303,7 @@ let BasicRecordingService = class BasicRecordingService {
9150
9303
  subscriber.next(nab);
9151
9304
  subscriber.complete();
9152
9305
  }
9153
- }, error => {
9306
+ }).catch(error => {
9154
9307
  console.error('chunkAudioRequestToNetAb: error: ' + error);
9155
9308
  //if(error instanceof HttpErrorResponse) {
9156
9309
  subscriber.error(error);
@@ -9258,7 +9411,7 @@ class RecordingService extends BasicRecordingService {
9258
9411
  withCredentials: this.withCredentials
9259
9412
  });
9260
9413
  }
9261
- chunkAudioRequestToIndDb(aCtx, persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame = 0, orgSampleRate, seconds) {
9414
+ chunkAudioRequestToIndDb(persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame = 0, orgSampleRate, seconds) {
9262
9415
  //let audioUrl=baseAudioUrl+'?startFrame='+startFrame+'&frameLength='+frameLength;
9263
9416
  //let audioUrl=new URL(baseAudioUrl);
9264
9417
  let frameLength = orgSampleRate * Math.round(seconds); // Important: multiple of original sample rate to prevent numeric rounding errors on resampling. 10 seconds is a good value for ind db storage.
@@ -9277,7 +9430,7 @@ class RecordingService extends BasicRecordingService {
9277
9430
  if (resp.body) {
9278
9431
  //console.debug("chunkAudioRequestToIndDb: subscriber.closed: "+subscriber.closed);
9279
9432
  //console.debug("chunkAudioRequestToIndDb: Audio file bytes: "+resp.body.byteLength);
9280
- aCtx.decodeAudioData(resp.body, ab => {
9433
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9281
9434
  //console.debug("chunkAudioRequestToIndDb: Decoded audio chunk frames for inddb: "+ab.length);
9282
9435
  if (!inddbAudioBuffer) {
9283
9436
  //console.debug("chunkAudioRequestToIndDb: Create inddb ab from chunk ab...");
@@ -9321,7 +9474,7 @@ class RecordingService extends BasicRecordingService {
9321
9474
  }
9322
9475
  });
9323
9476
  }
9324
- }, error => {
9477
+ }).catch(error => {
9325
9478
  console.error('chunkAudioRequestToIndDb: error: ' + error);
9326
9479
  //if(error instanceof HttpErrorResponse) {
9327
9480
  subscriber.error(error);
@@ -9329,8 +9482,9 @@ class RecordingService extends BasicRecordingService {
9329
9482
  });
9330
9483
  }
9331
9484
  else {
9332
- console.error('chunkAudioRequestToIndDb: Fetching audio file: response has no body');
9333
- subscriber.error('chunkAudioRequestToIndDb: Fetching audio file: response has no body');
9485
+ const errMsg = 'chunkAudioRequestToIndDb: Fetching audio file: response has no body';
9486
+ console.error(errMsg);
9487
+ subscriber.error(new Error(errMsg));
9334
9488
  }
9335
9489
  },
9336
9490
  error: (error) => {
@@ -9342,13 +9496,13 @@ class RecordingService extends BasicRecordingService {
9342
9496
  });
9343
9497
  return obs;
9344
9498
  }
9345
- chunkedAudioRequestToArrayBuffer(aCtx, baseAudioUrl, orgSampleRate, seconds) {
9499
+ chunkedAudioRequestToArrayBuffer(baseAudioUrl, orgSampleRate, seconds) {
9346
9500
  let obs = new Observable(subscriber => {
9347
9501
  let arrayAudioBuffer = null;
9348
9502
  let startFrame = 0;
9349
9503
  let frameLength = orgSampleRate * Math.round(seconds); // Important: multiple of original sample rate to prevent numeric rounding errors on resampling.
9350
9504
  //console.debug("Chunk audio request startFrame 0");
9351
- let subscr = this.chunkAudioRequest(aCtx, baseAudioUrl, startFrame, frameLength).pipe(expand(value => {
9505
+ let subscr = this.chunkAudioRequest(baseAudioUrl, startFrame, frameLength).pipe(expand(value => {
9352
9506
  if (subscriber.closed) {
9353
9507
  subscr.unsubscribe();
9354
9508
  }
@@ -9367,7 +9521,7 @@ class RecordingService extends BasicRecordingService {
9367
9521
  startFrame += frameLength;
9368
9522
  //console.debug("Next start frame: "+startFrame);
9369
9523
  //console.debug("chunkedAudioRequest: expand() subscriber.closed: "+subscriber.closed);
9370
- return this.chunkAudioRequest(aCtx, baseAudioUrl, startFrame, frameLength);
9524
+ return this.chunkAudioRequest(baseAudioUrl, startFrame, frameLength);
9371
9525
  }
9372
9526
  }
9373
9527
  else {
@@ -9437,14 +9591,14 @@ class RecordingService extends BasicRecordingService {
9437
9591
  });
9438
9592
  return obs;
9439
9593
  }
9440
- chunkedInddbAudioRequest(aCtx, persistentAudioStorageTarget, baseAudioUrl, orgSampleRate, seconds) {
9594
+ chunkedInddbAudioRequest(persistentAudioStorageTarget, baseAudioUrl, orgSampleRate, seconds) {
9441
9595
  let obs = new Observable(subscriber => {
9442
9596
  let inddbAudioBuffer = null;
9443
9597
  let startFrame = 0;
9444
9598
  //let frameLength = DEFAULT_CHUNKED_DOWNLOAD_FRAMELENGTH;
9445
9599
  let frameLength = orgSampleRate * Math.round(seconds);
9446
9600
  //console.debug("chunkedInddbAudioRequest: Chunk audio request for inddb. startFrame: "+startFrame);
9447
- let subscr = this.chunkAudioRequestToIndDb(aCtx, persistentAudioStorageTarget, null, baseAudioUrl, startFrame, orgSampleRate, seconds).pipe(expand(iab => {
9601
+ let subscr = this.chunkAudioRequestToIndDb(persistentAudioStorageTarget, null, baseAudioUrl, startFrame, orgSampleRate, seconds).pipe(expand(iab => {
9448
9602
  // console.debug("chunkedInddbAudioRequest (pipe/expand): Got inddb ab: "+iab);
9449
9603
  if (subscriber.closed) {
9450
9604
  subscr.unsubscribe();
@@ -9465,7 +9619,7 @@ class RecordingService extends BasicRecordingService {
9465
9619
  startFrame += frameLength;
9466
9620
  //console.debug("Next start frame: "+startFrame);
9467
9621
  //console.debug("chunkedInddbAudioRequest: expand() subscriber.closed: "+subscriber.closed);
9468
- return this.chunkAudioRequestToIndDb(aCtx, persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame, orgSampleRate, seconds);
9622
+ return this.chunkAudioRequestToIndDb(persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame, orgSampleRate, seconds);
9469
9623
  }
9470
9624
  // } else {
9471
9625
  // return EMPTY;
@@ -9657,7 +9811,7 @@ class RecordingService extends BasicRecordingService {
9657
9811
  // //let recUrl=new URL(recUrlStr);
9658
9812
  // return this.chunkedInddbAudioRequest(aCtx,recUrlStr);
9659
9813
  // }
9660
- fetchRecordingFileAudioBuffer(aCtx, projectName, recordingFile) {
9814
+ fetchRecordingFileAudioBuffer(projectName, recordingFile) {
9661
9815
  let wobs = new Observable(observer => {
9662
9816
  let recFileId = recordingFile.recordingFileId;
9663
9817
  if (!recFileId) {
@@ -9669,10 +9823,10 @@ class RecordingService extends BasicRecordingService {
9669
9823
  next: resp => {
9670
9824
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9671
9825
  if (resp.body) {
9672
- aCtx.decodeAudioData(resp.body, ab => {
9826
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9673
9827
  observer.next(ab);
9674
9828
  observer.complete();
9675
- }, error => {
9829
+ }).catch(error => {
9676
9830
  observer.error(error);
9677
9831
  observer.complete();
9678
9832
  });
@@ -9717,7 +9871,7 @@ class RecordingService extends BasicRecordingService {
9717
9871
  //console.log("Fetched audio file. HTTP response status: "+resp.status+", type: "+resp.type+", byte length: "+ resp.body.byteLength);
9718
9872
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9719
9873
  if (resp.body) {
9720
- aCtx.decodeAudioData(resp.body, ab => {
9874
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9721
9875
  let abs = new AudioBufferSource(ab);
9722
9876
  let adh = new AudioDataHolder(abs);
9723
9877
  RecordingFileUtils.setAudioData(recordingFile, adh);
@@ -9731,7 +9885,7 @@ class RecordingService extends BasicRecordingService {
9731
9885
  observer.next(recordingFile);
9732
9886
  observer.complete();
9733
9887
  }
9734
- }, error => {
9888
+ }).catch(error => {
9735
9889
  observer.error(error);
9736
9890
  observer.complete();
9737
9891
  });
@@ -9764,7 +9918,7 @@ class RecordingService extends BasicRecordingService {
9764
9918
  });
9765
9919
  return wobs;
9766
9920
  }
9767
- fetchSprRecordingFileAudioBuffer(aCtx, projectName, recordingFile) {
9921
+ fetchSprRecordingFileAudioBuffer(projectName, recordingFile) {
9768
9922
  let wobs = new Observable(observer => {
9769
9923
  if (recordingFile.session) {
9770
9924
  let obs = this.fetchSprAudiofile(projectName, recordingFile.session, recordingFile.itemCode, recordingFile.version);
@@ -9772,11 +9926,11 @@ class RecordingService extends BasicRecordingService {
9772
9926
  next: resp => {
9773
9927
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9774
9928
  if (resp.body) {
9775
- aCtx.decodeAudioData(resp.body, ab => {
9929
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9776
9930
  //RecordingFileUtils.setAudioData(recordingFile,new AudioDataHolder(ab,null));
9777
9931
  observer.next(ab);
9778
9932
  observer.complete();
9779
- }, error => {
9933
+ }).catch(error => {
9780
9934
  observer.error(error);
9781
9935
  observer.complete();
9782
9936
  });
@@ -9804,14 +9958,14 @@ class RecordingService extends BasicRecordingService {
9804
9958
  });
9805
9959
  return wobs;
9806
9960
  }
9807
- fetchSprRecordingFileArrayAudioBuffer(aCtx, projectName, recordingFile) {
9961
+ fetchSprRecordingFileArrayAudioBuffer(projectName, recordingFile) {
9808
9962
  let wobs = new Observable(observer => {
9809
9963
  if (recordingFile.session) {
9810
9964
  let baseUrl = this.sprAudioFileUrl(projectName, recordingFile);
9811
9965
  if (baseUrl) {
9812
9966
  if (recordingFile.samplerate) {
9813
9967
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9814
- let obs = this.chunkedAudioRequestToArrayBuffer(aCtx, baseUrl, recordingFile.samplerate, lengthInSeconds);
9968
+ let obs = this.chunkedAudioRequestToArrayBuffer(baseUrl, recordingFile.samplerate, lengthInSeconds);
9815
9969
  //let obs = this.fetchSprAudiofileArrayBuffer(aCtx,projectName, recordingFile.session, recordingFile.itemCode, recordingFile.version);
9816
9970
  let subscr = obs.subscribe({
9817
9971
  next: aab => {
@@ -9848,14 +10002,14 @@ class RecordingService extends BasicRecordingService {
9848
10002
  });
9849
10003
  return wobs;
9850
10004
  }
9851
- fetchRecordingFileArrayAudioBuffer(aCtx, projectName, recordingFile) {
10005
+ fetchRecordingFileArrayAudioBuffer(projectName, recordingFile) {
9852
10006
  let wobs = new Observable(observer => {
9853
10007
  if (recordingFile.session) {
9854
10008
  let baseUrl = this.audioFileUrl(projectName, recordingFile);
9855
10009
  if (baseUrl) {
9856
10010
  if (recordingFile.samplerate) {
9857
10011
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9858
- let obs = this.chunkedAudioRequestToArrayBuffer(aCtx, baseUrl, recordingFile.samplerate, lengthInSeconds);
10012
+ let obs = this.chunkedAudioRequestToArrayBuffer(baseUrl, recordingFile.samplerate, lengthInSeconds);
9859
10013
  //let obs = this.fetchSprAudiofileArrayBuffer(aCtx,projectName, recordingFile.session, recordingFile.itemCode, recordingFile.version);
9860
10014
  let subscr = obs.subscribe({
9861
10015
  next: aab => {
@@ -9892,14 +10046,14 @@ class RecordingService extends BasicRecordingService {
9892
10046
  });
9893
10047
  return wobs;
9894
10048
  }
9895
- fetchSprRecordingFileIndDbAudioBuffer(aCtx, persistentAudioStorageTarget, projectName, recordingFile) {
10049
+ fetchSprRecordingFileIndDbAudioBuffer(persistentAudioStorageTarget, projectName, recordingFile) {
9896
10050
  let wobs = new Observable(observer => {
9897
10051
  if (recordingFile.session) {
9898
10052
  let baseUrl = this.sprAudioFileUrl(projectName, recordingFile);
9899
10053
  if (baseUrl) {
9900
10054
  if (recordingFile.samplerate) {
9901
10055
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9902
- let obs = this.chunkedInddbAudioRequest(aCtx, persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
10056
+ let obs = this.chunkedInddbAudioRequest(persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
9903
10057
  let subscr = obs.subscribe({
9904
10058
  next: aab => {
9905
10059
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -9935,14 +10089,14 @@ class RecordingService extends BasicRecordingService {
9935
10089
  });
9936
10090
  return wobs;
9937
10091
  }
9938
- fetchRecordingFileIndDbAudioBuffer(aCtx, persistentAudioStorageTarget, projectName, recordingFile) {
10092
+ fetchRecordingFileIndDbAudioBuffer(persistentAudioStorageTarget, projectName, recordingFile) {
9939
10093
  let wobs = new Observable(observer => {
9940
10094
  if (recordingFile.session) {
9941
10095
  let baseUrl = this.audioFileUrl(projectName, recordingFile);
9942
10096
  if (baseUrl) {
9943
10097
  if (recordingFile.samplerate) {
9944
10098
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9945
- let obs = this.chunkedInddbAudioRequest(aCtx, persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
10099
+ let obs = this.chunkedInddbAudioRequest(persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
9946
10100
  let subscr = obs.subscribe({
9947
10101
  next: aab => {
9948
10102
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -9978,14 +10132,14 @@ class RecordingService extends BasicRecordingService {
9978
10132
  });
9979
10133
  return wobs;
9980
10134
  }
9981
- fetchSprRecordingFileNetAudioBuffer(aCtx, projectName, recordingFile) {
10135
+ fetchSprRecordingFileNetAudioBuffer(projectName, recordingFile) {
9982
10136
  let wobs = new Observable(observer => {
9983
10137
  if (recordingFile.session) {
9984
10138
  let baseUrl = this.sprAudioFileUrl(projectName, recordingFile);
9985
10139
  if (baseUrl) {
9986
10140
  let seconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9987
10141
  if (recordingFile.samplerate) {
9988
- let obs = this.chunkAudioRequestToNetAudioBuffer(aCtx, baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
10142
+ let obs = this.chunkAudioRequestToNetAudioBuffer(baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
9989
10143
  let subscr = obs.subscribe({
9990
10144
  next: aab => {
9991
10145
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -10026,14 +10180,14 @@ class RecordingService extends BasicRecordingService {
10026
10180
  });
10027
10181
  return wobs;
10028
10182
  }
10029
- fetchRecordingFileNetAudioBuffer(aCtx, projectName, recordingFile) {
10183
+ fetchRecordingFileNetAudioBuffer(projectName, recordingFile) {
10030
10184
  let wobs = new Observable(observer => {
10031
10185
  if (recordingFile.session) {
10032
10186
  let baseUrl = this.audioFileUrl(projectName, recordingFile);
10033
10187
  if (baseUrl) {
10034
10188
  let seconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
10035
10189
  if (recordingFile.samplerate) {
10036
- let obs = this.chunkAudioRequestToNetAudioBuffer(aCtx, baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
10190
+ let obs = this.chunkAudioRequestToNetAudioBuffer(baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
10037
10191
  let subscr = obs.subscribe({
10038
10192
  next: aab => {
10039
10193
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -10083,7 +10237,7 @@ class RecordingService extends BasicRecordingService {
10083
10237
  //console.log("Fetched audio file. HTTP response status: "+resp.status+", type: "+resp.type+", byte length: "+ resp.body.byteLength);
10084
10238
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
10085
10239
  if (resp.body) {
10086
- aCtx.decodeAudioData(resp.body, ab => {
10240
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
10087
10241
  let abs = new AudioBufferSource(ab);
10088
10242
  RecordingFileUtils.setAudioData(recordingFile, new AudioDataHolder(abs));
10089
10243
  if (this.debugDelay > 0) {
@@ -10096,7 +10250,7 @@ class RecordingService extends BasicRecordingService {
10096
10250
  observer.next(recordingFile);
10097
10251
  observer.complete();
10098
10252
  }
10099
- }, error => {
10253
+ }).catch(error => {
10100
10254
  observer.error(error);
10101
10255
  observer.complete();
10102
10256
  });
@@ -10128,9 +10282,8 @@ class RecordingService extends BasicRecordingService {
10128
10282
  let wobs = new Observable(observer => {
10129
10283
  let obs = this.fetchSprAudiofile(projectName, sessId, itemcode, version);
10130
10284
  obs.subscribe({ next: resp => {
10131
- // Do not use Promise version, which does not work with Safari 13
10132
10285
  if (resp.body) {
10133
- aCtx.decodeAudioData(resp.body, ab => {
10286
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
10134
10287
  let abs = new AudioBufferSource(ab);
10135
10288
  let adh = new AudioDataHolder(abs);
10136
10289
  let rf = new SprRecordingFile(sessId, itemcode, version, adh);
@@ -10144,7 +10297,7 @@ class RecordingService extends BasicRecordingService {
10144
10297
  observer.next(rf);
10145
10298
  observer.complete();
10146
10299
  }
10147
- });
10300
+ }).catch((reason) => { observer.error(reason); });
10148
10301
  }
10149
10302
  else {
10150
10303
  observer.error();
@@ -10181,23 +10334,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
10181
10334
  }] }];
10182
10335
  } });
10183
10336
 
10184
- class AudioContextProvider {
10185
- static audioContextInstance() {
10186
- if (!this._audioContext) {
10187
- let debugFail = false;
10188
- if (!window.AudioContext || typeof window.AudioContext !== 'function' || debugFail) {
10189
- throw new Error('Browser does not support Web Audio API!');
10190
- this._audioContext = null;
10191
- }
10192
- else {
10193
- this._audioContext = new window.AudioContext();
10194
- }
10195
- }
10196
- return this._audioContext;
10197
- }
10198
- }
10199
- AudioContextProvider._audioContext = null;
10200
-
10201
10337
  class Item {
10202
10338
  constructor(_promptAsString, _training, _recording) {
10203
10339
  this._promptAsString = _promptAsString;
@@ -12073,6 +12209,7 @@ let BasicRecorder = class BasicRecorder extends ResponsiveComponent {
12073
12209
  this._wakeLock = false;
12074
12210
  this._selectedDeviceId = undefined;
12075
12211
  this._channelCount = 2;
12212
+ this._allowEchoCancellation = false;
12076
12213
  this._session = null;
12077
12214
  this._recordingFile = null;
12078
12215
  this.startedDate = null;
@@ -12114,6 +12251,12 @@ let BasicRecorder = class BasicRecorder extends ResponsiveComponent {
12114
12251
  this.streamLevelMeasure = new StreamLevelMeasure();
12115
12252
  this.selCaptureDeviceId = null;
12116
12253
  }
12254
+ get allowEchoCancellation() {
12255
+ return this._allowEchoCancellation;
12256
+ }
12257
+ set allowEchoCancellation(value) {
12258
+ this._allowEchoCancellation = value;
12259
+ }
12117
12260
  get maxAutoNetMemStoreSamples() {
12118
12261
  return this._maxAutoNetMemStoreSamples;
12119
12262
  }
@@ -12499,7 +12642,7 @@ let BasicRecorder = class BasicRecorder extends ResponsiveComponent {
12499
12642
  else {
12500
12643
  console.log("Open session with default audio device for " + this._channelCount + " channels");
12501
12644
  }
12502
- this.ac.open(this._channelCount, this._selectedDeviceId, this._autoGainControlConfigs);
12645
+ this.ac.open(this._channelCount, this._selectedDeviceId, this._autoGainControlConfigs, this._allowEchoCancellation);
12503
12646
  }
12504
12647
  else {
12505
12648
  this.ac.start();
@@ -12859,6 +13002,7 @@ class SessionManager extends BasicRecorder {
12859
13002
  this.bpo = bpo;
12860
13003
  this.renderer = renderer;
12861
13004
  this.recFileService = recFileService;
13005
+ this.offlineAudioContext = null;
12862
13006
  this.enableUploadRecordings = true;
12863
13007
  this.enableDownloadRecordings = false;
12864
13008
  this.status = 0 /* BLOCKED */;
@@ -12915,37 +13059,58 @@ class SessionManager extends BasicRecorder {
12915
13059
  this.transportActions.nextAction.disabled = true;
12916
13060
  this.transportActions.pauseAction.disabled = true;
12917
13061
  this.playStartAction.disabled = true;
12918
- let context = null;
12919
- try {
12920
- context = AudioContextProvider.audioContextInstance();
12921
- }
12922
- catch (err) {
12923
- this.status = 9 /* ERROR */;
12924
- let errMsg = 'Unknown error';
12925
- if (err instanceof Error) {
12926
- errMsg = err.message;
12927
- }
12928
- this.statusMsg = 'ERROR: ' + errMsg;
12929
- this.statusAlertType = 'error';
12930
- this.dialog.open(MessageDialog, {
12931
- data: {
12932
- type: 'error',
12933
- title: 'Error',
12934
- msg: errMsg,
12935
- advice: 'Please use a supported browser.',
12936
- }
12937
- });
12938
- return;
12939
- }
12940
- if (context) {
12941
- console.info("State of audio context: " + context.state);
13062
+ // let context:AudioContext|null=null;
13063
+ // try {
13064
+ // context = AudioContextProvider.audioContextInstance();
13065
+ // } catch (err) {
13066
+ // this.status = Status.ERROR;
13067
+ // let errMsg = 'Unknown error';
13068
+ // if(err instanceof Error){
13069
+ // errMsg=err.message;
13070
+ // }
13071
+ // this.statusMsg = 'ERROR: ' + errMsg;
13072
+ // this.statusAlertType = 'error';
13073
+ // this.dialog.open(MessageDialog, {
13074
+ // data: {
13075
+ // type: 'error',
13076
+ // title: 'Error',
13077
+ // msg: errMsg,
13078
+ // advice: 'Please use a supported browser.',
13079
+ // }
13080
+ // });
13081
+ // return;
13082
+ // }
13083
+ // if(context) {
13084
+ // console.info("State of audio context: " + context.state)
13085
+ // }else{
13086
+ // console.info("No audio context available!");
13087
+ // }
13088
+ // if (!context || !navigator.mediaDevices) {
13089
+ // this.status = Status.ERROR;
13090
+ // let errMsg = 'Browser does not support Media streams!';
13091
+ // this.statusMsg = 'ERROR: ' + errMsg;
13092
+ // this.statusAlertType = 'error';
13093
+ // this.dialog.open(MessageDialog, {
13094
+ // data: {
13095
+ // type: 'error',
13096
+ // title: 'Error',
13097
+ // msg: errMsg,
13098
+ // advice: 'Please use a supported browser.',
13099
+ // }
13100
+ // });
13101
+ // return;
13102
+ // } else {
13103
+ this.ac = new AudioCapture();
13104
+ if (this.ac) {
13105
+ this.transportActions.startAction.onAction = () => this.startItem();
13106
+ this.ac.listener = this;
13107
+ this.configureStreamCaptureStream();
13108
+ // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
13109
+ //this.ac.listDevices();
12942
13110
  }
12943
13111
  else {
12944
- console.info("No audio context available!");
12945
- }
12946
- if (!context || !navigator.mediaDevices) {
12947
- this.status = 9 /* ERROR */;
12948
- let errMsg = 'Browser does not support Media streams!';
13112
+ this.transportActions.startAction.disabled = true;
13113
+ let errMsg = 'Browser does not support Media/Audio API!';
12949
13114
  this.statusMsg = 'ERROR: ' + errMsg;
12950
13115
  this.statusAlertType = 'error';
12951
13116
  this.dialog.open(MessageDialog, {
@@ -12958,38 +13123,13 @@ class SessionManager extends BasicRecorder {
12958
13123
  });
12959
13124
  return;
12960
13125
  }
12961
- else {
12962
- this.ac = new AudioCapture(context);
12963
- if (this.ac) {
12964
- this.transportActions.startAction.onAction = () => this.startItem();
12965
- this.ac.listener = this;
12966
- this.configureStreamCaptureStream();
12967
- // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
12968
- //this.ac.listDevices();
12969
- }
12970
- else {
12971
- this.transportActions.startAction.disabled = true;
12972
- let errMsg = 'Browser does not support Media/Audio API!';
12973
- this.statusMsg = 'ERROR: ' + errMsg;
12974
- this.statusAlertType = 'error';
12975
- this.dialog.open(MessageDialog, {
12976
- data: {
12977
- type: 'error',
12978
- title: 'Error',
12979
- msg: errMsg,
12980
- advice: 'Please use a supported browser.',
12981
- }
12982
- });
12983
- return;
12984
- }
12985
- this.transportActions.stopAction.onAction = () => this.stopItem();
12986
- this.transportActions.nextAction.onAction = () => this.stopItem();
12987
- this.transportActions.pauseAction.onAction = () => this.pauseItem();
12988
- this.transportActions.fwdAction.onAction = () => this.nextItem();
12989
- this.transportActions.fwdNextAction.onAction = () => this.nextUnrecordedItem();
12990
- this.transportActions.bwdAction.onAction = () => this.prevItem();
12991
- this.playStartAction.onAction = () => { var _a; return (_a = this.controlAudioPlayer) === null || _a === void 0 ? void 0 : _a.start(); };
12992
- }
13126
+ this.transportActions.stopAction.onAction = () => this.stopItem();
13127
+ this.transportActions.nextAction.onAction = () => this.stopItem();
13128
+ this.transportActions.pauseAction.onAction = () => this.pauseItem();
13129
+ this.transportActions.fwdAction.onAction = () => this.nextItem();
13130
+ this.transportActions.fwdNextAction.onAction = () => this.nextUnrecordedItem();
13131
+ this.transportActions.bwdAction.onAction = () => this.prevItem();
13132
+ this.playStartAction.onAction = () => { var _a; return (_a = this.controlAudioPlayer) === null || _a === void 0 ? void 0 : _a.start(); };
12993
13133
  this.startStopSignalState = 4 /* OFF */;
12994
13134
  }
12995
13135
  onKeyPress(ke) {
@@ -13296,7 +13436,7 @@ class SessionManager extends BasicRecorder {
13296
13436
  }
13297
13437
  else {
13298
13438
  //console.debug("Fetch audio and store to indexed db...");
13299
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileIndDbAudioBuffer(this._controlAudioPlayer.context, this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
13439
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileIndDbAudioBuffer(this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
13300
13440
  next: (iab) => {
13301
13441
  //console.debug("Sessionmanager: Received inddb audio buffer: "+iab);
13302
13442
  nextIab = iab;
@@ -13338,7 +13478,7 @@ class SessionManager extends BasicRecorder {
13338
13478
  // Fetch chunked audio buffer from network
13339
13479
  let nextNetAb = null;
13340
13480
  //console.debug("Fetch chunked audio from network");
13341
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileNetAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
13481
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileNetAudioBuffer(this._session.project, rf).subscribe({
13342
13482
  next: (netAb) => {
13343
13483
  //console.debug("Sessionmanager: Received net audio buffer: "+netAb);
13344
13484
  nextNetAb = netAb;
@@ -13380,7 +13520,7 @@ class SessionManager extends BasicRecorder {
13380
13520
  // Fetch chunked array audio buffer
13381
13521
  let nextAab = null;
13382
13522
  //console.debug("Fetch audio and store to (chunked) array buffer...");
13383
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileArrayAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
13523
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileArrayAudioBuffer(this._session.project, rf).subscribe({
13384
13524
  next: (aab) => {
13385
13525
  nextAab = aab;
13386
13526
  },
@@ -13418,7 +13558,7 @@ class SessionManager extends BasicRecorder {
13418
13558
  else {
13419
13559
  // Fetch regular audio buffer
13420
13560
  //console.debug("Fetch audio and store to audio buffer...");
13421
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
13561
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileAudioBuffer(this._session.project, rf).subscribe({
13422
13562
  next: (ab) => {
13423
13563
  this.liveLevelDisplayState = State.READY;
13424
13564
  let fabDh = null;
@@ -13806,7 +13946,7 @@ class SessionManager extends BasicRecorder {
13806
13946
  const sr = this.ac.currentSampleRate;
13807
13947
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
13808
13948
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
13809
- let netAb = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13949
+ let netAb = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13810
13950
  as = netAb;
13811
13951
  if (this.uploadSet) {
13812
13952
  //let rp=new ReadyProvider();
@@ -13853,7 +13993,7 @@ class SessionManager extends BasicRecorder {
13853
13993
  const sr = this.ac.currentSampleRate;
13854
13994
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
13855
13995
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
13856
- const netAb = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13996
+ const netAb = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13857
13997
  as = netAb;
13858
13998
  if (this.uploadSet) {
13859
13999
  this.uploadSet.onDone = (uploadSet) => {
@@ -13891,8 +14031,8 @@ class SessionManager extends BasicRecorder {
13891
14031
  // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
13892
14032
  // TODO duplicate conversion for manual download
13893
14033
  //console.log("Build wav writer...");
13894
- this.processingRecording = true;
13895
14034
  if (ab) {
14035
+ this.processingRecording = true;
13896
14036
  let ww = new WavWriter();
13897
14037
  //new REST API URL
13898
14038
  let apiEndPoint = '';
@@ -14405,18 +14545,10 @@ class SpeechrecorderngComponent extends RecorderComponent {
14405
14545
  console.error(errMsg);
14406
14546
  }
14407
14547
  ngOnInit() {
14408
- try {
14409
- let audioContext = AudioContextProvider.audioContextInstance();
14410
- if (audioContext) {
14411
- this.controlAudioPlayer = new AudioPlayer(audioContext, this);
14412
- }
14413
- this.sm.controlAudioPlayer = this.controlAudioPlayer;
14414
- this.sm.statusAlertType = 'info';
14415
- this.sm.statusMsg = 'Player initialized.';
14416
- }
14417
- catch (err) {
14418
- this.handleError(err);
14419
- }
14548
+ this.controlAudioPlayer = new AudioPlayer(this);
14549
+ this.sm.controlAudioPlayer = this.controlAudioPlayer;
14550
+ this.sm.statusAlertType = 'info';
14551
+ this.sm.statusMsg = 'Player initialized.';
14420
14552
  }
14421
14553
  ngAfterViewInit() {
14422
14554
  // let wakeLockSupp=('wakeLock' in navigator);
@@ -14673,6 +14805,9 @@ class SpeechrecorderngComponent extends RecorderComponent {
14673
14805
  chCnt = ProjectUtil.audioChannelCount(project);
14674
14806
  console.info("Project requested recording channel count: " + chCnt);
14675
14807
  this.sm.autoGainControlConfigs = project.autoGainControlConfigs;
14808
+ if (project.allowEchoCancellation !== undefined) {
14809
+ this.sm.allowEchoCancellation = project.allowEchoCancellation;
14810
+ }
14676
14811
  if (project.chunkedRecording === true) {
14677
14812
  console.debug("Enable chunked upload: chunkSize: " + BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS);
14678
14813
  this.sm.uploadChunkSizeSeconds = BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS;
@@ -14769,10 +14904,8 @@ class AudioDisplayPlayer {
14769
14904
  this.ref = ref;
14770
14905
  this.eRef = eRef;
14771
14906
  this._audioUrl = null;
14772
- this.aCtx = null;
14773
14907
  this._audioClip = null;
14774
14908
  this.currentLoader = null;
14775
- //console.log("constructor: "+this.ac);
14776
14909
  this.parentE = this.eRef.nativeElement;
14777
14910
  this.playStartAction = new Action("Start");
14778
14911
  this.playSelectionAction = new Action("Play selected");
@@ -14780,26 +14913,19 @@ class AudioDisplayPlayer {
14780
14913
  this.status = "Player created.";
14781
14914
  }
14782
14915
  ngOnInit() {
14783
- //console.log("OnInit: "+this.ac);
14784
14916
  this.zoomSelectedAction = this.audioDisplayScrollPane.zoomSelectedAction;
14785
14917
  this.zoomFitToPanelAction = this.audioDisplayScrollPane.zoomFitToPanelAction;
14786
14918
  this.zoomOutAction = this.audioDisplayScrollPane.zoomOutAction;
14787
14919
  this.zoomInAction = this.audioDisplayScrollPane.zoomInAction;
14788
- try {
14789
- this.aCtx = AudioContextProvider.audioContextInstance();
14790
- if (this.aCtx) {
14791
- this.ap = new AudioPlayer(this.aCtx, this);
14792
- }
14793
- }
14794
- catch (err) {
14795
- if (err instanceof Error) {
14796
- this.status = err.message;
14797
- }
14798
- }
14920
+ this.ap = new AudioPlayer(this);
14799
14921
  }
14800
14922
  ngAfterViewInit() {
14801
- if (this.aCtx && this.ap) {
14802
- this.playStartAction.onAction = () => { var _a; return (_a = this.ap) === null || _a === void 0 ? void 0 : _a.start(); };
14923
+ if (this.ap) {
14924
+ this.playStartAction.onAction = () => {
14925
+ var _a;
14926
+ console.debug("Start action, player: " + this.ap);
14927
+ (_a = this.ap) === null || _a === void 0 ? void 0 : _a.start();
14928
+ };
14803
14929
  this.playSelectionAction.onAction = () => { var _a; return (_a = this.ap) === null || _a === void 0 ? void 0 : _a.startSelected(); };
14804
14930
  this.playStopAction.onAction = () => { var _a; return (_a = this.ap) === null || _a === void 0 ? void 0 : _a.stop(); };
14805
14931
  }
@@ -14864,15 +14990,12 @@ class AudioDisplayPlayer {
14864
14990
  //console.debug("Loaded");
14865
14991
  this.status = 'Audio file loaded.';
14866
14992
  //console.debug("Received data ", data.byteLength);
14867
- // Do not use Promise version, which does not work with Safari 13
14868
- if (this.aCtx) {
14869
- this.aCtx.decodeAudioData(data, (audioBuffer) => {
14870
- //console.debug("Audio Buffer Samplerate: ", audioBuffer.sampleRate)
14871
- let as = new AudioBufferSource(audioBuffer);
14872
- let adh = new AudioDataHolder(as);
14873
- this.audioClip = new AudioClip(adh);
14874
- });
14875
- }
14993
+ AudioContextProvider.decodeAudioData(data).then(audioBuffer => {
14994
+ //console.debug("Audio Buffer Samplerate: ", audioBuffer.sampleRate)
14995
+ let as = new AudioBufferSource(audioBuffer);
14996
+ let adh = new AudioDataHolder(as);
14997
+ this.audioClip = new AudioClip(adh);
14998
+ });
14876
14999
  }
14877
15000
  set audioData(audioData) {
14878
15001
  this.audioDisplayScrollPane.audioData = audioData;
@@ -15132,14 +15255,13 @@ class RecordingFileService extends BasicRecordingService {
15132
15255
  });
15133
15256
  }
15134
15257
  // TODO test
15135
- fetchAndApplyRecordingFile(aCtx, recordingFile) {
15258
+ fetchAndApplyRecordingFile(recordingFile) {
15136
15259
  let wobs = new Observable(observer => {
15137
15260
  if (recordingFile.recordingFileId) {
15138
15261
  let obs = this.fetchAudiofile(recordingFile.recordingFileId);
15139
15262
  obs.subscribe({ next: resp => {
15140
- // Do not use Promise version, which does not work with Safari 13
15141
15263
  if (resp.body) {
15142
- aCtx.decodeAudioData(resp.body, ab => {
15264
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
15143
15265
  let as = new AudioBufferSource(ab);
15144
15266
  RecordingFileUtils.setAudioData(recordingFile, new AudioDataHolder(as));
15145
15267
  if (this.debugDelay > 0) {
@@ -15152,7 +15274,7 @@ class RecordingFileService extends BasicRecordingService {
15152
15274
  observer.next(recordingFile);
15153
15275
  observer.complete();
15154
15276
  }
15155
- });
15277
+ }).catch(reason => { observer.error(reason); });
15156
15278
  }
15157
15279
  else {
15158
15280
  observer.error('Received no audio data!');
@@ -15192,7 +15314,7 @@ class RecordingFileService extends BasicRecordingService {
15192
15314
  next: resp => {
15193
15315
  // Do not use Promise version, which does not work with Safari 13
15194
15316
  if (resp.body) {
15195
- aCtx.decodeAudioData(resp.body, ab => {
15317
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
15196
15318
  if (rf) {
15197
15319
  let as = new AudioBufferSource(ab);
15198
15320
  RecordingFileUtils.setAudioData(rf, new AudioDataHolder(as));
@@ -15210,7 +15332,7 @@ class RecordingFileService extends BasicRecordingService {
15210
15332
  observer.next(rf);
15211
15333
  observer.complete();
15212
15334
  }
15213
- });
15335
+ }).catch(reason => { observer.error(reason); });
15214
15336
  }
15215
15337
  else {
15216
15338
  observer.error('Received no audio data');
@@ -15234,7 +15356,7 @@ class RecordingFileService extends BasicRecordingService {
15234
15356
  });
15235
15357
  return wobs;
15236
15358
  }
15237
- fetchSprRecordingFile(aCtx, recordingFileId) {
15359
+ fetchSprRecordingFile(recordingFileId) {
15238
15360
  let wobs = new Observable(observer => {
15239
15361
  let rf = null;
15240
15362
  let rfDescrObs = this.sprRecordingFileDescrObserver(recordingFileId);
@@ -15251,7 +15373,7 @@ class RecordingFileService extends BasicRecordingService {
15251
15373
  // TODO use download storage type depending on sample count of file
15252
15374
  if (rf && rf.samplerate && sampleCnt != null && sampleCnt > this._maxAutoNetMemStoreSamples) {
15253
15375
  const baseUrl = this.recoFileUrl(recordingFileId);
15254
- const obNetAb = this.chunkAudioRequestToNetAudioBuffer(aCtx, baseUrl, 0, rf === null || rf === void 0 ? void 0 : rf.samplerate, BasicRecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS, rf.frames);
15376
+ const obNetAb = this.chunkAudioRequestToNetAudioBuffer(baseUrl, 0, rf === null || rf === void 0 ? void 0 : rf.samplerate, BasicRecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS, rf.frames);
15255
15377
  obNetAb.subscribe({
15256
15378
  next: (nab) => {
15257
15379
  let adh = new AudioDataHolder(nab);
@@ -15282,7 +15404,7 @@ class RecordingFileService extends BasicRecordingService {
15282
15404
  rfAudioObs.subscribe({ next: resp => {
15283
15405
  // Do not use Promise version, which does not work with Safari 13
15284
15406
  if (resp.body) {
15285
- aCtx.decodeAudioData(resp.body, ab => {
15407
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
15286
15408
  if (rf) {
15287
15409
  let as = new AudioBufferSource(ab);
15288
15410
  let adh = new AudioDataHolder(as);
@@ -15301,7 +15423,7 @@ class RecordingFileService extends BasicRecordingService {
15301
15423
  observer.next(rf);
15302
15424
  observer.complete();
15303
15425
  }
15304
- });
15426
+ }).catch(reason => { observer.error(reason); });
15305
15427
  }
15306
15428
  else {
15307
15429
  observer.error('Received no audio data');
@@ -15461,9 +15583,9 @@ class RecordingFileMetaComponent {
15461
15583
  constructor() {
15462
15584
  this.sessionId = null;
15463
15585
  this._recordingFile = null;
15464
- this.stateLoading = false;
15465
15586
  this.itemCode = null;
15466
15587
  this.uuid = null;
15588
+ this.stateLoading = false;
15467
15589
  }
15468
15590
  get recordingFile() {
15469
15591
  return this._recordingFile;
@@ -15572,7 +15694,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
15572
15694
  </mat-card-content>
15573
15695
  </mat-card>
15574
15696
  ` }]
15575
- }], propDecorators: { sessionId: [{
15697
+ }], ctorParameters: function () { return []; }, propDecorators: { sessionId: [{
15576
15698
  type: Input
15577
15699
  }], stateLoading: [{
15578
15700
  type: Input
@@ -15593,7 +15715,6 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
15593
15715
  this.ref = ref;
15594
15716
  this.eRef = eRef;
15595
15717
  this.dialog = dialog;
15596
- //protected _recordingFileId: string | number = null;
15597
15718
  this.sessionId = null;
15598
15719
  this.sessionIdFromRoute = null;
15599
15720
  this.availRecFiles = null;
@@ -15602,9 +15723,11 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
15602
15723
  this.recordingFileVersion = null;
15603
15724
  this.routedByQueryParam = false;
15604
15725
  this.posInList = null;
15605
- this.audioFetching = false;
15606
- this.naviInfoLoading = false;
15607
15726
  this.parentE = this.eRef.nativeElement;
15727
+ // TODO Should be initialized with false, but this causes in debug mode:
15728
+ // ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'. Find more at https://angular.io/errors/NG0100
15729
+ this.audioFetching = true;
15730
+ this.naviInfoLoading = false;
15608
15731
  this.firstAction = new Action('First');
15609
15732
  this.firstAction.onAction = () => {
15610
15733
  this.posInList = 0;
@@ -15772,54 +15895,51 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
15772
15895
  this.recordingFile = null;
15773
15896
  this.posInList = null;
15774
15897
  this.updateActions();
15775
- let audioContext = AudioContextProvider.audioContextInstance();
15776
- if (audioContext) {
15777
- this.audioFetching = true;
15778
- this.recordingFileService.fetchSprRecordingFile(audioContext, rfId).subscribe({
15779
- next: value => {
15780
- this.audioFetching = false;
15781
- this.status = 'Audio file loaded.';
15782
- let clip = null;
15783
- this.recordingFile = value;
15784
- if (this.recordingFile) {
15785
- let ab = this.recordingFile.audioDataHolder;
15786
- if (ab) {
15787
- clip = new AudioClip(ab);
15788
- let esffsr = null;
15789
- let eeffsr = null;
15790
- let esr = null;
15791
- if (clip.audioDataHolder != null) {
15792
- esr = ab.sampleRate;
15793
- if (esr != null) {
15794
- esffsr = RecordingFileUtil.editStartFrameForSampleRate(this.recordingFile, esr);
15795
- eeffsr = RecordingFileUtil.editEndFrameForSampleRate(this.recordingFile, esr);
15796
- }
15797
- let sel = null;
15798
- if (esffsr != null) {
15799
- if (eeffsr != null) {
15800
- sel = new Selection(ab.sampleRate, esffsr, eeffsr);
15801
- }
15802
- else {
15803
- //let ch0 = ab.getChannelData(0);
15804
- let frameLength = ab.frameLen;
15805
- sel = new Selection(esr, esffsr, frameLength);
15806
- }
15898
+ this.audioFetching = true;
15899
+ this.recordingFileService.fetchSprRecordingFile(rfId).subscribe({
15900
+ next: value => {
15901
+ this.audioFetching = false;
15902
+ this.status = 'Audio file loaded.';
15903
+ let clip = null;
15904
+ this.recordingFile = value;
15905
+ if (this.recordingFile) {
15906
+ let ab = this.recordingFile.audioDataHolder;
15907
+ if (ab) {
15908
+ clip = new AudioClip(ab);
15909
+ let esffsr = null;
15910
+ let eeffsr = null;
15911
+ let esr = null;
15912
+ if (clip.audioDataHolder != null) {
15913
+ esr = ab.sampleRate;
15914
+ if (esr != null) {
15915
+ esffsr = RecordingFileUtil.editStartFrameForSampleRate(this.recordingFile, esr);
15916
+ eeffsr = RecordingFileUtil.editEndFrameForSampleRate(this.recordingFile, esr);
15917
+ }
15918
+ let sel = null;
15919
+ if (esffsr != null) {
15920
+ if (eeffsr != null) {
15921
+ sel = new Selection(ab.sampleRate, esffsr, eeffsr);
15807
15922
  }
15808
- else if (eeffsr != null) {
15809
- sel = new Selection(esr, 0, eeffsr);
15923
+ else {
15924
+ //let ch0 = ab.getChannelData(0);
15925
+ let frameLength = ab.frameLen;
15926
+ sel = new Selection(esr, esffsr, frameLength);
15810
15927
  }
15811
- clip.selection = sel;
15812
15928
  }
15929
+ else if (eeffsr != null) {
15930
+ sel = new Selection(esr, 0, eeffsr);
15931
+ }
15932
+ clip.selection = sel;
15813
15933
  }
15814
15934
  }
15815
- this.audioClip = clip;
15816
- this.loadedRecfile();
15817
- }, error: error1 => {
15818
- this.audioFetching = false;
15819
- this.status = 'Error loading audio file!';
15820
15935
  }
15821
- });
15822
- }
15936
+ this.audioClip = clip;
15937
+ this.loadedRecfile();
15938
+ }, error: error1 => {
15939
+ this.audioFetching = false;
15940
+ this.status = 'Error loading audio file!';
15941
+ }
15942
+ });
15823
15943
  }
15824
15944
  loadedRecfile() {
15825
15945
  if (this.recordingFile && !this.sessionId) {
@@ -16431,38 +16551,17 @@ class AudioRecorder extends BasicRecorder {
16431
16551
  this.transportActions.nextAction.disabled = true;
16432
16552
  this.transportActions.pauseAction.disabled = true;
16433
16553
  this.playStartAction.disabled = true;
16434
- //this.audioLoaded=false;
16435
- let context = null;
16436
- try {
16437
- context = AudioContextProvider.audioContextInstance();
16438
- }
16439
- catch (err) {
16440
- this.status = 5 /* ERROR */;
16441
- let errMsg = 'Unknown error';
16442
- if (err instanceof Error) {
16443
- errMsg = err.message;
16444
- }
16445
- this.statusMsg = 'ERROR: ' + errMsg;
16446
- this.statusAlertType = 'error';
16447
- this.dialog.open(MessageDialog, {
16448
- data: {
16449
- type: 'error',
16450
- title: 'Error',
16451
- msg: errMsg,
16452
- advice: 'Please use a supported browser.',
16453
- }
16454
- });
16455
- return;
16456
- }
16457
- if (context) {
16458
- console.info("State of audio context: " + context.state);
16554
+ this.ac = new AudioCapture();
16555
+ if (this.ac) {
16556
+ this.transportActions.startAction.onAction = () => this.startItem();
16557
+ this.ac.listener = this;
16558
+ this.configureStreamCaptureStream();
16559
+ // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
16560
+ //this.ac.listDevices();
16459
16561
  }
16460
16562
  else {
16461
- console.info("No audio context available!");
16462
- }
16463
- if (!context || !navigator.mediaDevices) {
16464
- this.status = 5 /* ERROR */;
16465
- let errMsg = 'Browser does not support Media streams!';
16563
+ this.transportActions.startAction.disabled = true;
16564
+ let errMsg = 'Browser does not support Media/Audio API!';
16466
16565
  this.statusMsg = 'ERROR: ' + errMsg;
16467
16566
  this.statusAlertType = 'error';
16468
16567
  this.dialog.open(MessageDialog, {
@@ -16475,36 +16574,10 @@ class AudioRecorder extends BasicRecorder {
16475
16574
  });
16476
16575
  return;
16477
16576
  }
16478
- else {
16479
- //this.controlAudioPlayer = new AudioPlayer(context, this);
16480
- this.ac = new AudioCapture(context);
16481
- if (this.ac) {
16482
- this.transportActions.startAction.onAction = () => this.startItem();
16483
- this.ac.listener = this;
16484
- this.configureStreamCaptureStream();
16485
- // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
16486
- //this.ac.listDevices();
16487
- }
16488
- else {
16489
- this.transportActions.startAction.disabled = true;
16490
- let errMsg = 'Browser does not support Media/Audio API!';
16491
- this.statusMsg = 'ERROR: ' + errMsg;
16492
- this.statusAlertType = 'error';
16493
- this.dialog.open(MessageDialog, {
16494
- data: {
16495
- type: 'error',
16496
- title: 'Error',
16497
- msg: errMsg,
16498
- advice: 'Please use a supported browser.',
16499
- }
16500
- });
16501
- return;
16502
- }
16503
- this.transportActions.stopAction.onAction = () => this.stopItem();
16504
- this.transportActions.nextAction.onAction = () => this.stopItem();
16505
- //this.transportActions.pauseAction.onAction = () => this.pauseItem();
16506
- this.playStartAction.onAction = () => { var _a; return (_a = this.controlAudioPlayer) === null || _a === void 0 ? void 0 : _a.start(); };
16507
- }
16577
+ this.transportActions.stopAction.onAction = () => this.stopItem();
16578
+ this.transportActions.nextAction.onAction = () => this.stopItem();
16579
+ //this.transportActions.pauseAction.onAction = () => this.pauseItem();
16580
+ this.playStartAction.onAction = () => { var _a; return (_a = this.controlAudioPlayer) === null || _a === void 0 ? void 0 : _a.start(); };
16508
16581
  this.uploader.listener = (ue) => {
16509
16582
  this.uploadUpdate(ue);
16510
16583
  };
@@ -16613,6 +16686,9 @@ class AudioRecorder extends BasicRecorder {
16613
16686
  chCnt = ProjectUtil.audioChannelCount(project);
16614
16687
  console.info("Project requested recording channel count: " + chCnt);
16615
16688
  this.autoGainControlConfigs = project.autoGainControlConfigs;
16689
+ if (project.allowEchoCancellation !== undefined) {
16690
+ this.allowEchoCancellation = project.allowEchoCancellation;
16691
+ }
16616
16692
  if (project.chunkedRecording === true) {
16617
16693
  this.uploadChunkSizeSeconds = BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS;
16618
16694
  }
@@ -16813,7 +16889,7 @@ class AudioRecorder extends BasicRecorder {
16813
16889
  }
16814
16890
  else {
16815
16891
  //console.debug("Fetch audio and store to indexed db...");
16816
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileIndDbAudioBuffer(this._controlAudioPlayer.context, this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
16892
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileIndDbAudioBuffer(this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
16817
16893
  next: (iab) => {
16818
16894
  //console.debug("Sessionmanager: Received inddb audio buffer: "+iab);
16819
16895
  nextIab = iab;
@@ -16856,7 +16932,7 @@ class AudioRecorder extends BasicRecorder {
16856
16932
  // Fetch chunked audio buffer from network
16857
16933
  let nextNetAb = null;
16858
16934
  //console.debug("Fetch chunked audio from network");
16859
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileNetAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
16935
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileNetAudioBuffer(this._session.project, rf).subscribe({
16860
16936
  next: (netAb) => {
16861
16937
  //console.debug("Sessionmanager: Received net audio buffer: "+netAb);
16862
16938
  nextNetAb = netAb;
@@ -16901,7 +16977,7 @@ class AudioRecorder extends BasicRecorder {
16901
16977
  // Fetch chunked array audio buffer
16902
16978
  let nextAab = null;
16903
16979
  //console.debug("Fetch audio and store to (chunked) array buffer...");
16904
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileArrayAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
16980
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileArrayAudioBuffer(this._session.project, rf).subscribe({
16905
16981
  next: (aab) => {
16906
16982
  nextAab = aab;
16907
16983
  },
@@ -16938,7 +17014,7 @@ class AudioRecorder extends BasicRecorder {
16938
17014
  });
16939
17015
  }
16940
17016
  else {
16941
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
17017
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileAudioBuffer(this._session.project, rf).subscribe({
16942
17018
  next: ab => {
16943
17019
  this.liveLevelDisplayState = State.READY;
16944
17020
  let fabDh = null;
@@ -17078,7 +17154,7 @@ class AudioRecorder extends BasicRecorder {
17078
17154
  this.transportActions.pauseAction.disabled = true;
17079
17155
  this.statusAlertType = 'info';
17080
17156
  this.statusMsg = 'Recorded.';
17081
- let ad = null;
17157
+ let ab = null;
17082
17158
  if (this.ac) {
17083
17159
  let adh = null;
17084
17160
  let as = null;
@@ -17110,7 +17186,7 @@ class AudioRecorder extends BasicRecorder {
17110
17186
  const sr = this.ac.currentSampleRate;
17111
17187
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
17112
17188
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
17113
- let netAs = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17189
+ let netAs = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17114
17190
  as = netAs;
17115
17191
  if (this.uploadSet) {
17116
17192
  this.uploadSet.onDone = (uploadSet) => {
@@ -17157,7 +17233,7 @@ class AudioRecorder extends BasicRecorder {
17157
17233
  const sr = this.ac.currentSampleRate;
17158
17234
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
17159
17235
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
17160
- let netAs = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17236
+ let netAs = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17161
17237
  as = netAs;
17162
17238
  if (this.uploadSet) {
17163
17239
  this.uploadSet.onDone = (uploadSet) => {
@@ -17176,7 +17252,7 @@ class AudioRecorder extends BasicRecorder {
17176
17252
  as = this.ac.audioBufferArray();
17177
17253
  }
17178
17254
  else {
17179
- let ab = this.ac.audioBuffer();
17255
+ ab = this.ac.audioBuffer();
17180
17256
  if (ab) {
17181
17257
  as = new AudioBufferSource(ab);
17182
17258
  }
@@ -17196,7 +17272,7 @@ class AudioRecorder extends BasicRecorder {
17196
17272
  RecordingFileUtils.setAudioData(rf, adh);
17197
17273
  this.recorderCombiPane.addRecFile(rf);
17198
17274
  // Upload if upload enabled and not in chunked upload mode
17199
- if (this.enableUploadRecordings && this._uploadChunkSizeSeconds === null && rf != null && ad != null) {
17275
+ if (this.enableUploadRecordings && this._uploadChunkSizeSeconds === null && AudioStorageType.MEM_ENTIRE === this._clientAudioStorageType && rf != null && ab != null) {
17200
17276
  let apiEndPoint = '';
17201
17277
  if (this.config && this.config.apiEndPoint) {
17202
17278
  apiEndPoint = this.config.apiEndPoint;
@@ -17211,7 +17287,7 @@ class AudioRecorder extends BasicRecorder {
17211
17287
  // TODO duplicate conversion for manual download
17212
17288
  this.processingRecording = true;
17213
17289
  let ww = new WavWriter();
17214
- ww.writeAsync(ad, (wavFile) => {
17290
+ ww.writeAsync(ab, (wavFile) => {
17215
17291
  this.postRecordingMultipart(wavFile, recUrl, rf);
17216
17292
  this.processingRecording = false;
17217
17293
  this.updateWakeLock();
@@ -17450,11 +17526,7 @@ class AudioRecorderComponent extends RecorderComponent {
17450
17526
  this.uploader = uploader;
17451
17527
  }
17452
17528
  ngOnInit() {
17453
- //super.ngOnInit();
17454
- let audioContext = AudioContextProvider.audioContextInstance();
17455
- if (audioContext) {
17456
- this.controlAudioPlayer = new AudioPlayer(audioContext, this.ar);
17457
- }
17529
+ this.controlAudioPlayer = new AudioPlayer(this.ar);
17458
17530
  this.ar.controlAudioPlayer = this.controlAudioPlayer;
17459
17531
  //TODO Duplicate code in SpeechRecorderComponent
17460
17532
  window.addEventListener('beforeunload', (e) => {
@@ -17622,7 +17694,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
17622
17694
  }]
17623
17695
  }] });
17624
17696
 
17625
- const VERSION = '3.4.5';
17697
+ const VERSION = '3.6.0';
17626
17698
 
17627
17699
  /*
17628
17700
  * Public API Surface of speechrecorderng