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
@@ -1973,9 +1973,8 @@ class IndexedDbAudioBufferSourceNode extends AudioSourceNode {
1973
1973
  }
1974
1974
 
1975
1975
  class NetAudioBuffer extends BasicAudioSource {
1976
- constructor(_audioContext, _recFileService, _baseUrl, _channelCount, _sampleRate, _chunkFrameLen, _frameLen, _uuid = null, _orgFetchChunkFrameLen = _chunkFrameLen) {
1976
+ constructor(_recFileService, _baseUrl, _channelCount, _sampleRate, _chunkFrameLen, _frameLen, _uuid = null, _orgFetchChunkFrameLen = _chunkFrameLen) {
1977
1977
  super();
1978
- this._audioContext = _audioContext;
1979
1978
  this._recFileService = _recFileService;
1980
1979
  this._baseUrl = _baseUrl;
1981
1980
  this._channelCount = _channelCount;
@@ -1993,9 +1992,6 @@ class NetAudioBuffer extends BasicAudioSource {
1993
1992
  get recFileService() {
1994
1993
  return this._recFileService;
1995
1994
  }
1996
- get audioContext() {
1997
- return this._audioContext;
1998
- }
1999
1995
  get baseUrl() {
2000
1996
  return this._baseUrl;
2001
1997
  }
@@ -2033,8 +2029,8 @@ class NetAudioBuffer extends BasicAudioSource {
2033
2029
  toString() {
2034
2030
  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();
2035
2031
  }
2036
- static fromChunkAudioBuffer(aCtx, recordingsService, baseUrl, ab, frameLen, orgFetchChunkFrameLen = ab.length) {
2037
- let nab = new NetAudioBuffer(aCtx, recordingsService, baseUrl, ab.numberOfChannels, ab.sampleRate, ab.length, frameLen, null, orgFetchChunkFrameLen);
2032
+ static fromChunkAudioBuffer(recordingsService, baseUrl, ab, frameLen, orgFetchChunkFrameLen = ab.length) {
2033
+ let nab = new NetAudioBuffer(recordingsService, baseUrl, ab.numberOfChannels, ab.sampleRate, ab.length, frameLen, null, orgFetchChunkFrameLen);
2038
2034
  nab.ready();
2039
2035
  return nab;
2040
2036
  }
@@ -2077,7 +2073,7 @@ class NetRandomAccessAudioStream {
2077
2073
  }
2078
2074
  chunk(baseUrl, ci, cb, errCb) {
2079
2075
  let startFrame = ci * this._netAb.orgFetchChunkFrameLen;
2080
- this._netAb.recFileService.chunkAudioRequest(this._netAb.audioContext, baseUrl, startFrame, this._netAb.orgFetchChunkFrameLen).subscribe({
2076
+ this._netAb.recFileService.chunkAudioRequest(baseUrl, startFrame, this._netAb.orgFetchChunkFrameLen).subscribe({
2081
2077
  next: (chDl) => {
2082
2078
  if (chDl) {
2083
2079
  const ab = chDl.decodedAudioBuffer;
@@ -2479,6 +2475,85 @@ class NetAudioBufferSourceNode extends AudioSourceNode {
2479
2475
  }
2480
2476
  }
2481
2477
 
2478
+ class AudioContextProvider {
2479
+ static audioContextInstance() {
2480
+ if (!this._audioContext) {
2481
+ let debugFail = false;
2482
+ if (!window.AudioContext || typeof window.AudioContext !== 'function' || debugFail) {
2483
+ this._audioContext = null;
2484
+ throw new Error('Browser does not support Web Audio API!');
2485
+ }
2486
+ else {
2487
+ console.debug("Get new audio context...");
2488
+ this._audioContext = new window.AudioContext();
2489
+ console.debug("Created new audio context.");
2490
+ this._audioContext.addEventListener('statechange', () => {
2491
+ console.debug("Audio context state changed: " + this._audioContext?.state);
2492
+ });
2493
+ console.debug("Created new audio context with state: " + this._audioContext?.state);
2494
+ }
2495
+ }
2496
+ return this._audioContext;
2497
+ }
2498
+ // public static audioContextInstanceRunning(audioContext?:AudioContext):Promise<AudioContext>{
2499
+ //
2500
+ // return new Promise<AudioContext>((resolve,reject)=>{
2501
+ // let aCtx=audioContext?audioContext:AudioContextProvider.audioContextInstance();
2502
+ // if(aCtx) {
2503
+ // if(aCtx.state==='closed') {
2504
+ // reject(new Error('Audio context already closed.'));
2505
+ // }else if(aCtx.state==='running') {
2506
+ // resolve(aCtx);
2507
+ // }else{
2508
+ // aCtx.resume().then(() => {
2509
+ // if(aCtx) {
2510
+ // resolve(aCtx);
2511
+ // }else{
2512
+ // reject(new Error('Could not get audio context'));
2513
+ // }
2514
+ // }).catch(() => {
2515
+ // reject(new Error('Could not resume audio context'));
2516
+ // })
2517
+ // }
2518
+ // }else{
2519
+ // reject(new Error('Could not get audio context from browser'));
2520
+ // }
2521
+ // });
2522
+ // }
2523
+ // public static decodeAudioData(data:ArrayBuffer,audioContext?:AudioContext):Promise<AudioBuffer>{
2524
+ // return new Promise<AudioBuffer>((resolve,reject)=>{
2525
+ // // decodeAudioData requires an audio context in running state
2526
+ // AudioContextProvider.audioContextInstanceRunning(audioContext).then(
2527
+ // (aCtx)=>{
2528
+ // // Do not use Promise version, which does not work with Safari 13
2529
+ // aCtx.decodeAudioData(data,decodedData => {
2530
+ // resolve(decodedData);
2531
+ // },(reason) => {
2532
+ // reject(reason);
2533
+ // });
2534
+ // }
2535
+ // ).catch((reason)=>{
2536
+ // reject(reason);
2537
+ // })
2538
+ // })
2539
+ // }
2540
+ static decodeAudioData(data) {
2541
+ return new Promise((resolve, reject) => {
2542
+ if (!this._offlineAudioContext) {
2543
+ this._offlineAudioContext = new OfflineAudioContext(1, 44100, 44100);
2544
+ }
2545
+ // Do not use Promise version, which does not work with Safari 13
2546
+ this._offlineAudioContext.decodeAudioData(data, decodedData => {
2547
+ resolve(decodedData);
2548
+ }, (reason) => {
2549
+ reject(reason);
2550
+ });
2551
+ });
2552
+ }
2553
+ }
2554
+ AudioContextProvider._audioContext = null;
2555
+ AudioContextProvider._offlineAudioContext = null;
2556
+
2482
2557
  var EventType;
2483
2558
  (function (EventType) {
2484
2559
  EventType[EventType["CLOSED"] = 0] = "CLOSED";
@@ -2502,8 +2577,9 @@ class AudioPlayerEvent {
2502
2577
  }
2503
2578
  }
2504
2579
  class AudioPlayer {
2505
- constructor(context, listener) {
2580
+ constructor(listener) {
2506
2581
  this.running = false;
2582
+ this.context = null;
2507
2583
  this.ready = false;
2508
2584
  this._audioClip = null;
2509
2585
  this._audioSource = null;
@@ -2511,7 +2587,6 @@ class AudioPlayer {
2511
2587
  this.sourceAudioWorkletNode = null;
2512
2588
  this.playStartTime = null;
2513
2589
  this.timerVar = null;
2514
- this.context = context;
2515
2590
  this.listener = listener;
2516
2591
  this.bufSize = AudioPlayer.DEFAULT_BUFSIZE;
2517
2592
  this.n = navigator;
@@ -2528,15 +2603,23 @@ class AudioPlayer {
2528
2603
  this._stopAction = new Action('Stop');
2529
2604
  this._stopAction.disabled = true;
2530
2605
  this._stopAction.onAction = () => this.stop();
2531
- this.context.addEventListener('statechange', (ev) => {
2532
- if (this.context.state !== 'running') {
2533
- this.stop();
2534
- }
2535
- });
2536
2606
  }
2537
2607
  get autoPlayOnSelectToggleAction() {
2538
2608
  return this._autoPlayOnSelectToggleAction;
2539
2609
  }
2610
+ _audioContext() {
2611
+ if (!this.context) {
2612
+ this.context = AudioContextProvider.audioContextInstance();
2613
+ if (this.context) {
2614
+ this.context.addEventListener('statechange', (ev) => {
2615
+ if (this.context && this.context.state !== 'running') {
2616
+ this.stop();
2617
+ }
2618
+ });
2619
+ }
2620
+ }
2621
+ return this.context;
2622
+ }
2540
2623
  get startAction() {
2541
2624
  return this._startAction;
2542
2625
  }
@@ -2555,9 +2638,9 @@ class AudioPlayer {
2555
2638
  chs = audioDataHolder.numberOfChannels;
2556
2639
  if (chs > 0) {
2557
2640
  length = audioDataHolder.frameLen;
2558
- if (chs > this.context.destination.maxChannelCount) {
2559
- // TODO exception
2560
- }
2641
+ //if (chs > this.context.destination.maxChannelCount) {
2642
+ // // TODO exception
2643
+ //}
2561
2644
  }
2562
2645
  this.audioSource = audioDataHolder.audioSource;
2563
2646
  audioClip.addSelectionObserver((ac) => {
@@ -2577,16 +2660,11 @@ class AudioPlayer {
2577
2660
  set audioSource(value) {
2578
2661
  this.stop();
2579
2662
  this._audioSource = value;
2580
- if (this._audioSource && this.context) {
2581
- if (this._audioSource instanceof AudioBufferSource) {
2582
- this.ready = true;
2583
- this.updateStartActions();
2584
- if (this.listener) {
2585
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2586
- }
2587
- }
2588
- else {
2589
- this._loadSourceWorkletAndInitStart();
2663
+ if (this._audioSource) {
2664
+ this.ready = true;
2665
+ this.updateStartActions();
2666
+ if (this.listener) {
2667
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2590
2668
  }
2591
2669
  }
2592
2670
  else {
@@ -2598,24 +2676,27 @@ class AudioPlayer {
2598
2676
  }
2599
2677
  }
2600
2678
  _loadSourceWorkletAndInitStart() {
2601
- AudioSourceWorkletModuleLoader.loadModule(this.context).then(() => {
2602
- //console.debug("Player ready. ( by Player::_loadSourceWorkletAndInitStart()");
2603
- this.ready = true;
2604
- this.updateStartActions();
2605
- if (this.listener) {
2606
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2607
- }
2608
- }).catch((error) => {
2609
- this.ready = false;
2610
- this.updateStartActions();
2611
- if (this.listener) {
2612
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
2613
- }
2614
- console.error('Could not add module ' + error);
2615
- });
2679
+ if (this.context) {
2680
+ AudioSourceWorkletModuleLoader.loadModule(this.context).then(() => {
2681
+ //console.debug("Player ready. ( by Player::_loadSourceWorkletAndInitStart()");
2682
+ this.ready = true;
2683
+ this.updateStartActions();
2684
+ if (this.listener) {
2685
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
2686
+ }
2687
+ }).catch((error) => {
2688
+ this.ready = false;
2689
+ this.updateStartActions();
2690
+ if (this.listener) {
2691
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
2692
+ }
2693
+ console.error('Could not add module ' + error);
2694
+ });
2695
+ }
2616
2696
  }
2617
2697
  _startAudioSourceWorkletNode() {
2618
- if (this.sourceAudioWorkletNode) {
2698
+ this._loadSourceWorkletAndInitStart();
2699
+ if (this.context && this.sourceAudioWorkletNode) {
2619
2700
  this.sourceAudioWorkletNode.onprocessorerror = (ev) => {
2620
2701
  let msg = 'Unknwon error';
2621
2702
  if (ev instanceof ErrorEvent) {
@@ -2642,16 +2723,25 @@ class AudioPlayer {
2642
2723
  }
2643
2724
  }
2644
2725
  start() {
2645
- if (!this._startAction.disabled && !this.running) {
2646
- if (this.context.state !== 'running') {
2647
- this.context.resume().then(() => {
2726
+ this._audioContext();
2727
+ if (this.context) {
2728
+ if (!this._startAction.disabled && !this.running) {
2729
+ if (this.context.state === 'suspended') {
2730
+ this.context.resume().then(() => {
2731
+ this._start();
2732
+ }).catch((reason) => {
2733
+ console.error(reason.message());
2734
+ throw reason;
2735
+ });
2736
+ }
2737
+ else if (this.context.state === 'closed') {
2738
+ const msg = 'Error: Cannot start playback. Audio context is already closed!';
2739
+ console.error(msg);
2740
+ throw new Error(msg);
2741
+ }
2742
+ else {
2648
2743
  this._start();
2649
- }).catch((reason) => {
2650
- console.error('Could not resume audio context: ' + reason);
2651
- });
2652
- }
2653
- else {
2654
- this._start();
2744
+ }
2655
2745
  }
2656
2746
  }
2657
2747
  }
@@ -2676,9 +2766,12 @@ class AudioPlayer {
2676
2766
  }
2677
2767
  }
2678
2768
  startSelectionDisabled() {
2679
- return !(this._audioClip && this.context && !this.startAction.disabled && this._audioClip.selection);
2769
+ return !(this._audioClip && !this.startAction.disabled && this._audioClip.selection);
2680
2770
  }
2681
2771
  _start(playSelection = false) {
2772
+ if (!this.context) {
2773
+ throw new Error("Could not get audio context!");
2774
+ }
2682
2775
  if (this._audioSource instanceof AudioBufferSource) {
2683
2776
  this.sourceBufferNode = this.context.createBufferSource();
2684
2777
  this.sourceBufferNode.buffer = this._audioSource.audioBuffer;
@@ -2709,68 +2802,90 @@ class AudioPlayer {
2709
2802
  }
2710
2803
  }
2711
2804
  else if (this._audioSource instanceof ArrayAudioBuffer || this._audioSource instanceof IndexedDbAudioBuffer || this._audioSource instanceof NetAudioBuffer) {
2712
- this.playStartTime = null;
2713
- if (this._audioSource instanceof ArrayAudioBuffer) {
2714
- const aabsn = new ArrayAudioBufferSourceNode(this.context);
2715
- aabsn.arrayAudioBuffer = this._audioSource;
2716
- this.sourceAudioWorkletNode = aabsn;
2717
- }
2718
- else if (this._audioSource instanceof IndexedDbAudioBuffer) {
2719
- const iasn = new IndexedDbAudioBufferSourceNode(this.context);
2720
- iasn.inddbAudioBuffer = this._audioSource;
2721
- this.sourceAudioWorkletNode = iasn;
2722
- }
2723
- else if (this._audioSource instanceof NetAudioBuffer) {
2724
- const nabsn = new NetAudioBufferSourceNode(this.context);
2725
- nabsn.netAudioBuffer = this._audioSource;
2726
- this.sourceAudioWorkletNode = nabsn;
2727
- }
2728
- if (this.sourceAudioWorkletNode) {
2729
- this.sourceAudioWorkletNode.onprocessorerror = (ev) => {
2730
- let msg = 'Unknwon error';
2731
- if (ev instanceof ErrorEvent) {
2732
- msg = ev.message;
2805
+ AudioSourceWorkletModuleLoader.loadModule(this.context).then(() => {
2806
+ this.playStartTime = null;
2807
+ if (this.context) {
2808
+ if (this._audioSource instanceof ArrayAudioBuffer) {
2809
+ const aabsn = new ArrayAudioBufferSourceNode(this.context);
2810
+ aabsn.arrayAudioBuffer = this._audioSource;
2811
+ this.sourceAudioWorkletNode = aabsn;
2733
2812
  }
2734
- console.error("Audio source worklet error: " + msg);
2735
- if (this.listener) {
2736
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.ERROR));
2813
+ else if (this._audioSource instanceof IndexedDbAudioBuffer) {
2814
+ const iasn = new IndexedDbAudioBufferSourceNode(this.context);
2815
+ iasn.inddbAudioBuffer = this._audioSource;
2816
+ this.sourceAudioWorkletNode = iasn;
2817
+ }
2818
+ else if (this._audioSource instanceof NetAudioBuffer) {
2819
+ const nabsn = new NetAudioBufferSourceNode(this.context);
2820
+ nabsn.netAudioBuffer = this._audioSource;
2821
+ this.sourceAudioWorkletNode = nabsn;
2822
+ }
2823
+ if (this.sourceAudioWorkletNode) {
2824
+ this.sourceAudioWorkletNode.onprocessorerror = (ev) => {
2825
+ let msg = 'Unknwon error';
2826
+ if (ev instanceof ErrorEvent) {
2827
+ msg = ev.message;
2828
+ }
2829
+ console.error("Audio source worklet error: " + msg);
2830
+ if (this.listener) {
2831
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.ERROR));
2832
+ }
2833
+ };
2834
+ this.sourceAudioWorkletNode.connect(this.context.destination); // this already starts playing
2835
+ this.sourceAudioWorkletNode.onended = () => this.onended();
2836
+ this.running = true;
2837
+ const ac = this._audioClip;
2838
+ let offset = 0;
2839
+ if (playSelection && ac && ac.selection) {
2840
+ const s = ac.selection;
2841
+ const sr = ac.audioDataHolder.sampleRate;
2842
+ offset = s.leftFrame / sr;
2843
+ const stopPosInsecs = s.rightFrame / sr;
2844
+ const dur = stopPosInsecs - offset;
2845
+ this.sourceAudioWorkletNode.start(0, offset, dur);
2846
+ }
2847
+ else {
2848
+ this.sourceAudioWorkletNode.start();
2849
+ }
2850
+ //this.playStartTime = this.context.currentTime - offset;
2851
+ this._startAction.disabled = true;
2852
+ this._startSelectionAction.disabled = true;
2853
+ this._stopAction.disabled = false;
2854
+ if (this.listener) {
2855
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
2856
+ }
2737
2857
  }
2738
- };
2739
- this.sourceAudioWorkletNode.connect(this.context.destination); // this already starts playing
2740
- this.sourceAudioWorkletNode.onended = () => this.onended();
2741
- this.running = true;
2742
- const ac = this._audioClip;
2743
- let offset = 0;
2744
- if (playSelection && ac && ac.selection) {
2745
- const s = ac.selection;
2746
- const sr = ac.audioDataHolder.sampleRate;
2747
- offset = s.leftFrame / sr;
2748
- const stopPosInsecs = s.rightFrame / sr;
2749
- const dur = stopPosInsecs - offset;
2750
- this.sourceAudioWorkletNode.start(0, offset, dur);
2751
- }
2752
- else {
2753
- this.sourceAudioWorkletNode.start();
2754
2858
  }
2755
- //this.playStartTime = this.context.currentTime - offset;
2756
- this._startAction.disabled = true;
2757
- this._startSelectionAction.disabled = true;
2758
- this._stopAction.disabled = false;
2859
+ }).catch((error) => {
2860
+ console.error(error.message);
2861
+ this.ready = false;
2862
+ this.updateStartActions();
2759
2863
  if (this.listener) {
2760
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
2864
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
2761
2865
  }
2762
- }
2866
+ throw error;
2867
+ });
2763
2868
  }
2764
2869
  }
2765
2870
  startSelected() {
2871
+ this._audioContext();
2872
+ if (!this.context) {
2873
+ throw new Error("Could not get audio context!");
2874
+ }
2766
2875
  if (!this._startAction.disabled && !this.running) {
2767
- if (this.context.state !== 'running') {
2876
+ if (this.context.state === 'suspended') {
2768
2877
  this.context.resume().then(() => {
2769
2878
  this._start(true);
2770
2879
  }).catch((reason) => {
2771
- console.error('Could not resume audio context: ' + reason);
2880
+ console.error(reason.message);
2881
+ throw reason;
2772
2882
  });
2773
2883
  }
2884
+ else if (this.context.state === 'closed') {
2885
+ const msg = 'Error: Cannot start playback of selection. Audio context is already closed!';
2886
+ console.error(msg);
2887
+ throw new Error(msg);
2888
+ }
2774
2889
  else {
2775
2890
  this._start(true);
2776
2891
  }
@@ -2807,7 +2922,7 @@ class AudioPlayer {
2807
2922
  }
2808
2923
  get playPositionTime() {
2809
2924
  let ppt = null;
2810
- if (this.playStartTime !== null) {
2925
+ if (this.context && this.playStartTime !== null) {
2811
2926
  ppt = this.context.currentTime - this.playStartTime;
2812
2927
  }
2813
2928
  else if (this.sourceAudioWorkletNode) {
@@ -3149,8 +3264,10 @@ const awpStr = "class AudioCaptureInterceptorProcessor extends AudioWorkletProce
3149
3264
  "\n" +
3150
3265
  "registerProcessor('capture-interceptor',AudioCaptureInterceptorProcessor);\n";
3151
3266
  class AudioCapture {
3152
- constructor(context) {
3267
+ //private context:AudioContext|null=null;
3268
+ constructor() {
3153
3269
  this._maxAutoNetMemStoreSamples = AudioCapture.DEFAULT_MAX_NET_AUTO_MEM_STORE_SAMPLES;
3270
+ this.context = null;
3154
3271
  this._recUUID = null;
3155
3272
  this.agcStatus = null;
3156
3273
  this.bufferingNode = null;
@@ -3165,13 +3282,7 @@ class AudioCapture {
3165
3282
  this.persisted = true;
3166
3283
  this.persistError = null;
3167
3284
  this.inddbAudioBuffer = null;
3168
- this.context = context;
3169
3285
  this.n = navigator;
3170
- this.context.addEventListener('statechange', () => {
3171
- if (this.context.state !== 'running') {
3172
- this.close();
3173
- }
3174
- });
3175
3286
  }
3176
3287
  get maxAutoNetMemStoreSamples() {
3177
3288
  return this._maxAutoNetMemStoreSamples;
@@ -3200,6 +3311,19 @@ class AudioCapture {
3200
3311
  get opened() {
3201
3312
  return this._opened;
3202
3313
  }
3314
+ _audioContext() {
3315
+ if (!this.context) {
3316
+ this.context = AudioContextProvider.audioContextInstance();
3317
+ if (this.context) {
3318
+ this.context.addEventListener('statechange', () => {
3319
+ if (this.context && this.context.state !== 'running') {
3320
+ this.close();
3321
+ }
3322
+ });
3323
+ }
3324
+ }
3325
+ return this.context;
3326
+ }
3203
3327
  initData() {
3204
3328
  if (!this._recUUID) {
3205
3329
  this._recUUID = UUID.generate();
@@ -3314,119 +3438,137 @@ class AudioCapture {
3314
3438
  }
3315
3439
  }
3316
3440
  addCaptureInterceptor() {
3317
- const awn = new AudioWorkletNode(this.context, 'capture-interceptor');
3318
- awn.onprocessorerror = (ev) => {
3319
- let msg = 'Unknwon error';
3320
- if (ev instanceof ErrorEvent) {
3321
- msg = ev.message;
3322
- }
3323
- console.error("Capture audio worklet error: " + msg);
3324
- if (this.listener) {
3325
- this.listener.error(msg);
3326
- }
3327
- };
3328
- let awnPt = awn.port;
3329
- if (awnPt) {
3330
- awnPt.onmessage = (ev) => {
3331
- if (this.capturing) {
3332
- let dt = ev.data;
3333
- let chs = dt.chs;
3334
- let adaLen = dt.data.length;
3335
- if (DEBUG_TRACE_LEVEL > 8) {
3336
- console.debug('Received data from worklet: ' + chs + ' ' + dt.len + ' Data chs: ' + adaLen);
3337
- }
3338
- let chunk = new Array(chs);
3339
- const samples = this.framesRecorded * chs;
3340
- if ((AudioStorageType.MEM_ENTIRE_AUTO_NET_CHUNKED === this.audioStorageType || AudioStorageType.MEM_CHUNKED_AUTO_NET_CHUNKED === this.audioStorageType) && this.data && samples > this._maxAutoNetMemStoreSamples) {
3341
- this.data = null;
3342
- }
3343
- //console.debug("Data initialized: "+(this.data!=null));
3344
- for (let ch = 0; ch < chs; ch++) {
3345
- //console.debug("Data ch initialized: "+(this.data !=null && this.data[ch] !=null));
3346
- if (ch < this.channelCount) {
3347
- if (dt.data[ch]) {
3348
- let fa = new Float32Array(dt.data[ch]);
3349
- if (this.data && this.data[ch]) {
3350
- this.data[ch].push(fa);
3351
- }
3352
- chunk[ch] = fa;
3353
- // Use samples of channel 0 to count frames (samples)
3354
- if (ch == 0) {
3355
- this.framesRecorded += fa.length;
3356
- }
3357
- }
3441
+ if (this.context) {
3442
+ const awn = new AudioWorkletNode(this.context, 'capture-interceptor');
3443
+ awn.onprocessorerror = (ev) => {
3444
+ let msg = 'Unknwon error';
3445
+ if (ev instanceof ErrorEvent) {
3446
+ msg = ev.message;
3447
+ }
3448
+ console.error("Capture audio worklet error: " + msg);
3449
+ if (this.listener) {
3450
+ this.listener.error(msg);
3451
+ }
3452
+ };
3453
+ let awnPt = awn.port;
3454
+ if (awnPt) {
3455
+ awnPt.onmessage = (ev) => {
3456
+ if (this.capturing) {
3457
+ let dt = ev.data;
3458
+ let chs = dt.chs;
3459
+ let adaLen = dt.data.length;
3460
+ if (DEBUG_TRACE_LEVEL > 8) {
3461
+ console.debug('Received data from worklet: ' + chs + ' ' + dt.len + ' Data chs: ' + adaLen);
3358
3462
  }
3359
- }
3360
- if (this.audioOutStream) {
3361
- try {
3362
- this.audioOutStream.write(chunk);
3363
- // // Random test error:
3364
- // if(Math.random()>0.98) {
3365
- // throw new Error('Test');
3366
- // }
3463
+ let chunk = new Array(chs);
3464
+ const samples = this.framesRecorded * chs;
3465
+ if ((AudioStorageType.MEM_ENTIRE_AUTO_NET_CHUNKED === this.audioStorageType || AudioStorageType.MEM_CHUNKED_AUTO_NET_CHUNKED === this.audioStorageType) && this.data && samples > this._maxAutoNetMemStoreSamples) {
3466
+ this.data = null;
3367
3467
  }
3368
- catch (err) {
3369
- if (err instanceof Error) {
3370
- this.persistError = err;
3371
- }
3372
- else {
3373
- this.persistError = new Error('Error handling recorded audio data');
3468
+ //console.debug("Data initialized: "+(this.data!=null));
3469
+ for (let ch = 0; ch < chs; ch++) {
3470
+ //console.debug("Data ch initialized: "+(this.data !=null && this.data[ch] !=null));
3471
+ if (ch < this.channelCount) {
3472
+ if (dt.data[ch]) {
3473
+ let fa = new Float32Array(dt.data[ch]);
3474
+ if (this.data && this.data[ch]) {
3475
+ this.data[ch].push(fa);
3476
+ }
3477
+ chunk[ch] = fa;
3478
+ // Use samples of channel 0 to count frames (samples)
3479
+ if (ch == 0) {
3480
+ this.framesRecorded += fa.length;
3481
+ }
3482
+ }
3374
3483
  }
3375
- console.error("Capture error: " + err);
3484
+ }
3485
+ if (this.audioOutStream) {
3376
3486
  try {
3377
- this.stop();
3378
- }
3379
- catch (err2) {
3380
- console.error("Capture next error (ignored): " + err2);
3487
+ this.audioOutStream.write(chunk);
3488
+ // // Random test error:
3489
+ // if(Math.random()>0.98) {
3490
+ // throw new Error('Test');
3491
+ // }
3381
3492
  }
3382
- finally {
3383
- if (this.listener) {
3384
- let errExpl = '';
3385
- if (err instanceof DOMException) {
3386
- errExpl = ': ' + err.name + ': ' + err.message;
3387
- }
3388
- this.listener.error("Could not handle recorded audio data" + errExpl, "Please try to record again.");
3493
+ catch (err) {
3494
+ if (err instanceof Error) {
3495
+ this.persistError = err;
3389
3496
  }
3390
3497
  else {
3391
- this.close();
3498
+ this.persistError = new Error('Error handling recorded audio data');
3499
+ }
3500
+ console.error("Capture error: " + err);
3501
+ try {
3502
+ this.stop();
3503
+ }
3504
+ catch (err2) {
3505
+ console.error("Capture next error (ignored): " + err2);
3506
+ }
3507
+ finally {
3508
+ if (this.listener) {
3509
+ let errExpl = '';
3510
+ if (err instanceof DOMException) {
3511
+ errExpl = ': ' + err.name + ': ' + err.message;
3512
+ }
3513
+ this.listener.error("Could not handle recorded audio data" + errExpl, "Please try to record again.");
3514
+ }
3515
+ else {
3516
+ this.close();
3517
+ }
3392
3518
  }
3393
3519
  }
3394
3520
  }
3521
+ if (AudioStorageType.DB_CHUNKED === this._audioStorageType && this._persistentAudioStorageTarget) {
3522
+ this.store();
3523
+ }
3395
3524
  }
3396
- if (AudioStorageType.DB_CHUNKED === this._audioStorageType && this._persistentAudioStorageTarget) {
3397
- this.store();
3398
- }
3399
- }
3400
- };
3401
- }
3402
- // Tried to fix that Safari does not record the second channel
3403
- // Does not help
3404
- //awn.channelCount=this.channelCount;
3405
- //awn.channelCountMode='explicit';
3406
- //console.debug('Channel count explicitly set to '+this.channelCount);
3407
- this.bufferingNode = awn;
3408
- //this.bufferingNode.channelCount=this.channelCount;
3409
- this._opened = true;
3410
- if (this.listener) {
3411
- this.listener.opened();
3525
+ };
3526
+ }
3527
+ // Tried to fix that Safari does not record the second channel
3528
+ // Does not help
3529
+ //awn.channelCount=this.channelCount;
3530
+ //awn.channelCountMode='explicit';
3531
+ //console.debug('Channel count explicitly set to '+this.channelCount);
3532
+ this.bufferingNode = awn;
3533
+ //this.bufferingNode.channelCount=this.channelCount;
3534
+ this._opened = true;
3535
+ if (this.listener) {
3536
+ this.listener.opened();
3537
+ }
3412
3538
  }
3413
3539
  }
3414
- open(channelCount, selDeviceId, autoGainControlConfigs) {
3540
+ open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation) {
3415
3541
  //console.debug("Capture open: ctx state: "+this.context.state);
3416
- if (this.context.state !== 'running') {
3542
+ this.context = this._audioContext();
3543
+ if (!this.context) {
3544
+ throw new Error("Could not get audio context!");
3545
+ }
3546
+ if (this.context.state === 'suspended') {
3417
3547
  //console.debug("Capture open: Resume context");
3418
3548
  this.context.resume().then(() => {
3419
3549
  //console.debug("Capture open (ctx resumed): ctx state: "+this.context.state);
3420
- this._open(channelCount, selDeviceId, autoGainControlConfigs);
3550
+ this._open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation);
3551
+ }).catch((err) => {
3552
+ console.error(err.message);
3553
+ throw err;
3421
3554
  });
3422
3555
  }
3556
+ else if (this.context.state === 'closed') {
3557
+ const msg = 'Error on start capture: The audio context is already closed.';
3558
+ console.error(msg);
3559
+ throw new Error(msg);
3560
+ }
3423
3561
  else {
3424
- this._open(channelCount, selDeviceId, autoGainControlConfigs);
3562
+ this._open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation);
3425
3563
  }
3426
3564
  }
3427
- _open(channelCount, selDeviceId, autoGainControlConfigs) {
3565
+ _open(channelCount, selDeviceId, autoGainControlConfigs, allowEchoCancellation) {
3428
3566
  this.channelCount = channelCount;
3429
3567
  this.framesRecorded = 0;
3568
+ this.context = this._audioContext();
3569
+ if (!this.context) {
3570
+ throw new Error("Could not get audio context!");
3571
+ }
3430
3572
  //var msc = new AudioStreamConstr();
3431
3573
  // var msc={};
3432
3574
  //msc.video = false;
@@ -3534,7 +3676,7 @@ class AudioCapture {
3534
3676
  audio: {
3535
3677
  deviceId: selDeviceId,
3536
3678
  channelCount: channelCount,
3537
- //echoCancellation: false
3679
+ echoCancellation: allowEchoCancellation ? undefined : false
3538
3680
  },
3539
3681
  video: false,
3540
3682
  };
@@ -3545,106 +3687,109 @@ class AudioCapture {
3545
3687
  console.debug("Audio capture, AGC: " + this.agcStatus);
3546
3688
  let ump = navigator.mediaDevices.getUserMedia(msc);
3547
3689
  ump.then((s) => {
3548
- this.stream = s;
3549
- let aTracks = s.getAudioTracks();
3550
- for (let i = 0; i < aTracks.length; i++) {
3551
- let aTrack = aTracks[i];
3552
- console.info("Track audio info: id: " + aTrack.id + " kind: " + aTrack.kind + " label: \"" + aTrack.label + "\"");
3553
- let mtrSts = aTrack.getSettings();
3554
- // Typescript lib.dom.ts MediaTrackSettings.channelCount is missing
3555
- // https://github.com/mdn/browser-compat-data/blob/5493d8f937e05b2ddbd41b99f5bdfad4a1f2ed85/api/MediaTrackSettings.json
3556
- //@ts-ignore
3557
- console.info("Track audio settings: Ch cnt: " + mtrSts.channelCount + ", AGC: " + mtrSts.autoGainControl + ", Echo cancell.: " + mtrSts.echoCancellation);
3558
- if (mtrSts.autoGainControl) {
3559
- this.agcStatus = mtrSts.autoGainControl;
3560
- }
3561
- }
3562
- let vTracks = s.getVideoTracks();
3563
- for (let i = 0; i < vTracks.length; i++) {
3564
- let vTrack = vTracks[i];
3565
- console.info("Track video info: id: " + vTrack.id + " kind: " + vTrack.kind + " label: " + vTrack.label);
3566
- }
3567
- this.mediaStream = this.context.createMediaStreamSource(s);
3568
- // stream channel count ( is always 2 !)
3569
- let streamChannelCount = this.mediaStream.channelCount;
3570
- console.info("Stream channel count: " + streamChannelCount);
3571
- // is not set!!
3572
- //this.currentSampleRate = this.mediaStream.sampleRate;
3573
- this.currentSampleRate = this.context.sampleRate;
3574
- console.info("Source audio node: channels: " + streamChannelCount + " samplerate: " + this.currentSampleRate);
3575
- if (this.audioOutStream) {
3576
- this.audioOutStream.setFormat(this.channelCount, this.currentSampleRate);
3577
- }
3578
- // W3C -> new name is createScriptProcessor
3579
- //
3580
- // Again deprecated, but AudioWorker not yet implemented in stable releases (June 2016)
3581
- // AudioWorker is now AudioWorkletProcessor ... (May 2017)
3582
- // Update 12-2020:
3583
- // The ScriptProcessorNode Interface - DEPRECATED
3584
- // Update 06-2021
3585
- // AudioWorkletProcessor is here to stay. Web Audio API has now Recommendation status !
3586
- if (this.context.audioWorklet) {
3587
- //const workletFileName = ('file-loader!./interceptor_worklet.js');
3588
- //const workletFileName = 'http://localhost:4200/assets/interceptor_worklet.js';
3589
- //console.log(awpStr);
3590
- if (AudioCapture.captureInterceptorModuleRegistered) {
3591
- // Required capture interceptor module already registered
3592
- this.addCaptureInterceptor();
3593
- }
3594
- else {
3595
- // Register capture interceptor module
3596
- let audioWorkletModuleBlob = new Blob([awpStr], { type: 'text/javascript' });
3597
- let audioWorkletModuleBlobUrl = window.URL.createObjectURL(audioWorkletModuleBlob);
3598
- this.context.audioWorklet.addModule(audioWorkletModuleBlobUrl).then(() => {
3599
- AudioCapture.captureInterceptorModuleRegistered = true;
3690
+ if (this.context) {
3691
+ this.stream = s;
3692
+ let aTracks = s.getAudioTracks();
3693
+ for (let i = 0; i < aTracks.length; i++) {
3694
+ let aTrack = aTracks[i];
3695
+ console.info("Track audio info: id: " + aTrack.id + " kind: " + aTrack.kind + " label: \"" + aTrack.label + "\"");
3696
+ let mtrSts = aTrack.getSettings();
3697
+ // Typescript lib.dom.ts MediaTrackSettings.channelCount is missing
3698
+ // https://github.com/mdn/browser-compat-data/blob/5493d8f937e05b2ddbd41b99f5bdfad4a1f2ed85/api/MediaTrackSettings.json
3699
+ //@ts-ignore
3700
+ console.info("Track audio settings: Ch cnt: " + mtrSts.channelCount + ", AGC: " + mtrSts.autoGainControl + ", Echo cancell.: " + mtrSts.echoCancellation);
3701
+ if (mtrSts.autoGainControl) {
3702
+ this.agcStatus = mtrSts.autoGainControl;
3703
+ }
3704
+ console.debug("Echo cancellation: " + mtrSts.echoCancellation);
3705
+ }
3706
+ let vTracks = s.getVideoTracks();
3707
+ for (let i = 0; i < vTracks.length; i++) {
3708
+ let vTrack = vTracks[i];
3709
+ console.info("Track video info: id: " + vTrack.id + " kind: " + vTrack.kind + " label: " + vTrack.label);
3710
+ }
3711
+ this.mediaStream = this.context.createMediaStreamSource(s);
3712
+ // stream channel count ( is always 2 !)
3713
+ let streamChannelCount = this.mediaStream.channelCount;
3714
+ console.info("Stream channel count: " + streamChannelCount);
3715
+ // is not set!!
3716
+ //this.currentSampleRate = this.mediaStream.sampleRate;
3717
+ this.currentSampleRate = this.context.sampleRate;
3718
+ console.info("Source audio node: channels: " + streamChannelCount + " samplerate: " + this.currentSampleRate);
3719
+ if (this.audioOutStream) {
3720
+ this.audioOutStream.setFormat(this.channelCount, this.currentSampleRate);
3721
+ }
3722
+ // W3C -> new name is createScriptProcessor
3723
+ //
3724
+ // Again deprecated, but AudioWorker not yet implemented in stable releases (June 2016)
3725
+ // AudioWorker is now AudioWorkletProcessor ... (May 2017)
3726
+ // Update 12-2020:
3727
+ // The ScriptProcessorNode Interface - DEPRECATED
3728
+ // Update 06-2021
3729
+ // AudioWorkletProcessor is here to stay. Web Audio API has now Recommendation status !
3730
+ if (this.context.audioWorklet) {
3731
+ //const workletFileName = ('file-loader!./interceptor_worklet.js');
3732
+ //const workletFileName = 'http://localhost:4200/assets/interceptor_worklet.js';
3733
+ //console.log(awpStr);
3734
+ if (AudioCapture.captureInterceptorModuleRegistered) {
3735
+ // Required capture interceptor module already registered
3600
3736
  this.addCaptureInterceptor();
3601
- }).catch((error) => {
3602
- console.log('Could not add module ' + error);
3603
- });
3737
+ }
3738
+ else {
3739
+ // Register capture interceptor module
3740
+ let audioWorkletModuleBlob = new Blob([awpStr], { type: 'text/javascript' });
3741
+ let audioWorkletModuleBlobUrl = window.URL.createObjectURL(audioWorkletModuleBlob);
3742
+ this.context.audioWorklet.addModule(audioWorkletModuleBlobUrl).then(() => {
3743
+ AudioCapture.captureInterceptorModuleRegistered = true;
3744
+ this.addCaptureInterceptor();
3745
+ }).catch((error) => {
3746
+ console.log('Could not add module ' + error);
3747
+ });
3748
+ }
3604
3749
  }
3605
- }
3606
- else if (this.context.createScriptProcessor) {
3607
- // The ScriptProcessorNode Interface - DEPRECATED Only as fallback
3608
- // TODO should we use streamChannelCount or channelCount here ?
3609
- let scriptProcessorNode = this.context.createScriptProcessor(AudioCapture.BUFFER_SIZE, streamChannelCount, streamChannelCount);
3610
- this.bufferingNode = scriptProcessorNode;
3611
- let c = 0;
3612
- if (scriptProcessorNode.onaudioprocess) {
3613
- scriptProcessorNode.onaudioprocess = (e) => {
3614
- if (this.capturing) {
3615
- let inBuffer = e.inputBuffer;
3616
- // only process requested count of channels
3617
- let currentBuffers = new Array(channelCount);
3618
- for (let ch = 0; ch < channelCount; ch++) {
3619
- let chSamples = inBuffer.getChannelData(ch);
3620
- let chSamplesCopy = chSamples.slice(0);
3621
- currentBuffers[ch] = chSamplesCopy.slice(0);
3622
- if (this.data) {
3623
- this.data[ch].push(chSamplesCopy);
3750
+ else if (this.context.createScriptProcessor) {
3751
+ // The ScriptProcessorNode Interface - DEPRECATED Only as fallback
3752
+ // TODO should we use streamChannelCount or channelCount here ?
3753
+ let scriptProcessorNode = this.context.createScriptProcessor(AudioCapture.BUFFER_SIZE, streamChannelCount, streamChannelCount);
3754
+ this.bufferingNode = scriptProcessorNode;
3755
+ let c = 0;
3756
+ if (scriptProcessorNode.onaudioprocess) {
3757
+ scriptProcessorNode.onaudioprocess = (e) => {
3758
+ if (this.capturing) {
3759
+ let inBuffer = e.inputBuffer;
3760
+ // only process requested count of channels
3761
+ let currentBuffers = new Array(channelCount);
3762
+ for (let ch = 0; ch < channelCount; ch++) {
3763
+ let chSamples = inBuffer.getChannelData(ch);
3764
+ let chSamplesCopy = chSamples.slice(0);
3765
+ currentBuffers[ch] = chSamplesCopy.slice(0);
3766
+ if (this.data) {
3767
+ this.data[ch].push(chSamplesCopy);
3768
+ }
3769
+ if (DEBUG_TRACE_LEVEL > 8) {
3770
+ console.debug("Process " + chSamplesCopy.length + " samples.");
3771
+ }
3772
+ this.framesRecorded += chSamplesCopy.length;
3624
3773
  }
3625
- if (DEBUG_TRACE_LEVEL > 8) {
3626
- console.debug("Process " + chSamplesCopy.length + " samples.");
3774
+ c++;
3775
+ if (this.audioOutStream) {
3776
+ this.audioOutStream.write(currentBuffers);
3627
3777
  }
3628
- this.framesRecorded += chSamplesCopy.length;
3629
- }
3630
- c++;
3631
- if (this.audioOutStream) {
3632
- this.audioOutStream.write(currentBuffers);
3633
3778
  }
3779
+ };
3780
+ this._opened = true;
3781
+ if (this.listener) {
3782
+ this.listener.opened();
3634
3783
  }
3635
- };
3636
- this._opened = true;
3637
- if (this.listener) {
3638
- this.listener.opened();
3784
+ }
3785
+ else {
3786
+ this.listener.error('Browser does not support audio processing (ScriptProcessor.onaudioprocess method not found)!');
3639
3787
  }
3640
3788
  }
3641
3789
  else {
3642
- this.listener.error('Browser does not support audio processing (ScriptProcessor.onaudioprocess method not found)!');
3790
+ this.listener.error('Browser does not support audio processing (neither AudioWorkletProcessor nor ScriptProcessor)!');
3643
3791
  }
3644
3792
  }
3645
- else {
3646
- this.listener.error('Browser does not support audio processing (neither AudioWorkletProcessor nor ScriptProcessor)!');
3647
- }
3648
3793
  }, (e) => {
3649
3794
  console.error(e + " Error name: " + e.name);
3650
3795
  if (this.listener) {
@@ -3665,36 +3810,42 @@ class AudioCapture {
3665
3810
  });
3666
3811
  }
3667
3812
  _start() {
3668
- this.initData();
3669
- if (this.audioOutStream) {
3670
- this.audioOutStream.nextStream();
3671
- }
3672
- this.capturing = true;
3673
- if (this.bufferingNode) {
3674
- this.mediaStream.connect(this.bufferingNode);
3675
- this.bufferingNode.connect(this.context.destination);
3676
- }
3677
- if (this.listener) {
3678
- this.listener.started();
3813
+ if (this.context) {
3814
+ this.initData();
3815
+ if (this.audioOutStream) {
3816
+ this.audioOutStream.nextStream();
3817
+ }
3818
+ this.capturing = true;
3819
+ if (this.bufferingNode) {
3820
+ this.mediaStream.connect(this.bufferingNode);
3821
+ this.bufferingNode.connect(this.context.destination);
3822
+ }
3823
+ if (this.listener) {
3824
+ this.listener.started();
3825
+ }
3679
3826
  }
3680
3827
  }
3681
3828
  start() {
3682
- const aSt = this.context.state;
3683
- if (aSt === 'running') {
3684
- this._start();
3685
- }
3686
- else {
3687
- console.debug("Capture start: audio context not running, state: " + aSt + ", resuming...");
3688
- this.context.resume().then(() => {
3689
- console.debug("Capture start: audio context resumed, starting...");
3829
+ if (this.context) {
3830
+ const aSt = this.context.state;
3831
+ if (aSt === 'running') {
3690
3832
  this._start();
3691
- });
3833
+ }
3834
+ else {
3835
+ console.debug("Capture start: audio context not running, state: " + aSt + ", resuming...");
3836
+ this.context.resume().then(() => {
3837
+ console.debug("Capture start: audio context resumed, starting...");
3838
+ this._start();
3839
+ });
3840
+ }
3692
3841
  }
3693
3842
  }
3694
3843
  stop() {
3695
3844
  if (this.disconnectStreams && this.bufferingNode) {
3696
3845
  this.mediaStream.disconnect(this.bufferingNode);
3697
- this.bufferingNode.disconnect(this.context.destination);
3846
+ if (this.context) {
3847
+ this.bufferingNode.disconnect(this.context.destination);
3848
+ }
3698
3849
  }
3699
3850
  try {
3700
3851
  if (this.audioOutStream) {
@@ -3817,7 +3968,7 @@ class AudioCapture {
3817
3968
  }
3818
3969
  audioBuffer() {
3819
3970
  let ab = null;
3820
- if (this.data) {
3971
+ if (this.context && this.data) {
3821
3972
  let frameLen = 0;
3822
3973
  let ch0Data = this.data[0];
3823
3974
  for (let ch0Chk of ch0Data) {
@@ -9001,7 +9152,7 @@ let BasicRecordingService = class BasicRecordingService {
9001
9152
  withCredentials: this.withCredentials
9002
9153
  });
9003
9154
  }
9004
- chunkAudioRequest(aCtx, baseAudioUrl, startFrame = 0, frameLength) {
9155
+ chunkAudioRequest(baseAudioUrl, startFrame = 0, frameLength) {
9005
9156
  let ausps = new URLSearchParams();
9006
9157
  ausps.set('startFrame', startFrame.toString());
9007
9158
  ausps.set('frameLength', frameLength.toString());
@@ -9033,12 +9184,12 @@ let BasicRecordingService = class BasicRecordingService {
9033
9184
  // console.error("Could not read WAVE format of original download chunk!");
9034
9185
  // }
9035
9186
  if (pcmFmt && orgFl) {
9036
- aCtx.decodeAudioData(resp.body, ab => {
9187
+ AudioContextProvider.decodeAudioData(resp.body).then((ab) => {
9037
9188
  //console.debug("Decoded audio chunk frames: "+ab.length);
9038
9189
  let chDl = new ChunkDownload(pcmFmt, orgFl, ab);
9039
9190
  observer.next(chDl);
9040
9191
  observer.complete();
9041
- }, error => {
9192
+ }).catch(error => {
9042
9193
  //if(error instanceof HttpErrorResponse) {
9043
9194
  // if (error.status == 404) {
9044
9195
  // // Interpret not as an error, the file ist not recorded yet
@@ -9074,7 +9225,7 @@ let BasicRecordingService = class BasicRecordingService {
9074
9225
  });
9075
9226
  return obs;
9076
9227
  }
9077
- chunkAudioRequestToNetAudioBuffer(aCtx, baseAudioUrl, startFrame = 0, orgSampleRate, seconds, frames) {
9228
+ chunkAudioRequestToNetAudioBuffer(baseAudioUrl, startFrame = 0, orgSampleRate, seconds, frames) {
9078
9229
  //let audioUrl=baseAudioUrl+'?startFrame='+startFrame+'&frameLength='+frameLength;
9079
9230
  //let audioUrl=new URL(baseAudioUrl);
9080
9231
  // if(orgSampleRate!=null && frameLength%orgSampleRate>0){
@@ -9098,7 +9249,7 @@ let BasicRecordingService = class BasicRecordingService {
9098
9249
  if (resp.body) {
9099
9250
  //console.debug("chunkAudioRequestTonetAb: subscriber.closed: "+subscriber.closed);
9100
9251
  //console.debug("chunkAudioRequestTonetAb: Audio file bytes: "+resp.body.byteLength);
9101
- aCtx.decodeAudioData(resp.body, ab => {
9252
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9102
9253
  //console.debug("chunkAudioRequestTonetAb: Decoded audio chunk frames for netAb: "+ab.length);
9103
9254
  //console.debug("chunkAudioRequestTonetAb: Create netAb ab from chunk ab...");
9104
9255
  if (frames === null) {
@@ -9112,7 +9263,7 @@ let BasicRecordingService = class BasicRecordingService {
9112
9263
  //console.debug("Platform sr: "+ab.sampleRate+", file sr: "+orgSampleRate+", decoded/org frame length: "+fl+"/"+frames+", ab.length: "+ab.length);
9113
9264
  }
9114
9265
  }
9115
- let nab = NetAudioBuffer.fromChunkAudioBuffer(aCtx, this, baseAudioUrl, ab, fl, frameLength);
9266
+ let nab = NetAudioBuffer.fromChunkAudioBuffer(this, baseAudioUrl, ab, fl, frameLength);
9116
9267
  //let rp=new ReadyProvider();
9117
9268
  //nab.readyProvider=rp;
9118
9269
  //rp.ready();
@@ -9124,7 +9275,7 @@ let BasicRecordingService = class BasicRecordingService {
9124
9275
  subscriber.next(nab);
9125
9276
  subscriber.complete();
9126
9277
  }
9127
- }, error => {
9278
+ }).catch(error => {
9128
9279
  console.error('chunkAudioRequestToNetAb: error: ' + error);
9129
9280
  //if(error instanceof HttpErrorResponse) {
9130
9281
  subscriber.error(error);
@@ -9232,7 +9383,7 @@ class RecordingService extends BasicRecordingService {
9232
9383
  withCredentials: this.withCredentials
9233
9384
  });
9234
9385
  }
9235
- chunkAudioRequestToIndDb(aCtx, persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame = 0, orgSampleRate, seconds) {
9386
+ chunkAudioRequestToIndDb(persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame = 0, orgSampleRate, seconds) {
9236
9387
  //let audioUrl=baseAudioUrl+'?startFrame='+startFrame+'&frameLength='+frameLength;
9237
9388
  //let audioUrl=new URL(baseAudioUrl);
9238
9389
  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.
@@ -9251,7 +9402,7 @@ class RecordingService extends BasicRecordingService {
9251
9402
  if (resp.body) {
9252
9403
  //console.debug("chunkAudioRequestToIndDb: subscriber.closed: "+subscriber.closed);
9253
9404
  //console.debug("chunkAudioRequestToIndDb: Audio file bytes: "+resp.body.byteLength);
9254
- aCtx.decodeAudioData(resp.body, ab => {
9405
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9255
9406
  //console.debug("chunkAudioRequestToIndDb: Decoded audio chunk frames for inddb: "+ab.length);
9256
9407
  if (!inddbAudioBuffer) {
9257
9408
  //console.debug("chunkAudioRequestToIndDb: Create inddb ab from chunk ab...");
@@ -9295,7 +9446,7 @@ class RecordingService extends BasicRecordingService {
9295
9446
  }
9296
9447
  });
9297
9448
  }
9298
- }, error => {
9449
+ }).catch(error => {
9299
9450
  console.error('chunkAudioRequestToIndDb: error: ' + error);
9300
9451
  //if(error instanceof HttpErrorResponse) {
9301
9452
  subscriber.error(error);
@@ -9303,8 +9454,9 @@ class RecordingService extends BasicRecordingService {
9303
9454
  });
9304
9455
  }
9305
9456
  else {
9306
- console.error('chunkAudioRequestToIndDb: Fetching audio file: response has no body');
9307
- subscriber.error('chunkAudioRequestToIndDb: Fetching audio file: response has no body');
9457
+ const errMsg = 'chunkAudioRequestToIndDb: Fetching audio file: response has no body';
9458
+ console.error(errMsg);
9459
+ subscriber.error(new Error(errMsg));
9308
9460
  }
9309
9461
  },
9310
9462
  error: (error) => {
@@ -9316,13 +9468,13 @@ class RecordingService extends BasicRecordingService {
9316
9468
  });
9317
9469
  return obs;
9318
9470
  }
9319
- chunkedAudioRequestToArrayBuffer(aCtx, baseAudioUrl, orgSampleRate, seconds) {
9471
+ chunkedAudioRequestToArrayBuffer(baseAudioUrl, orgSampleRate, seconds) {
9320
9472
  let obs = new Observable(subscriber => {
9321
9473
  let arrayAudioBuffer = null;
9322
9474
  let startFrame = 0;
9323
9475
  let frameLength = orgSampleRate * Math.round(seconds); // Important: multiple of original sample rate to prevent numeric rounding errors on resampling.
9324
9476
  //console.debug("Chunk audio request startFrame 0");
9325
- let subscr = this.chunkAudioRequest(aCtx, baseAudioUrl, startFrame, frameLength).pipe(expand(value => {
9477
+ let subscr = this.chunkAudioRequest(baseAudioUrl, startFrame, frameLength).pipe(expand(value => {
9326
9478
  if (subscriber.closed) {
9327
9479
  subscr.unsubscribe();
9328
9480
  }
@@ -9341,7 +9493,7 @@ class RecordingService extends BasicRecordingService {
9341
9493
  startFrame += frameLength;
9342
9494
  //console.debug("Next start frame: "+startFrame);
9343
9495
  //console.debug("chunkedAudioRequest: expand() subscriber.closed: "+subscriber.closed);
9344
- return this.chunkAudioRequest(aCtx, baseAudioUrl, startFrame, frameLength);
9496
+ return this.chunkAudioRequest(baseAudioUrl, startFrame, frameLength);
9345
9497
  }
9346
9498
  }
9347
9499
  else {
@@ -9411,14 +9563,14 @@ class RecordingService extends BasicRecordingService {
9411
9563
  });
9412
9564
  return obs;
9413
9565
  }
9414
- chunkedInddbAudioRequest(aCtx, persistentAudioStorageTarget, baseAudioUrl, orgSampleRate, seconds) {
9566
+ chunkedInddbAudioRequest(persistentAudioStorageTarget, baseAudioUrl, orgSampleRate, seconds) {
9415
9567
  let obs = new Observable(subscriber => {
9416
9568
  let inddbAudioBuffer = null;
9417
9569
  let startFrame = 0;
9418
9570
  //let frameLength = DEFAULT_CHUNKED_DOWNLOAD_FRAMELENGTH;
9419
9571
  let frameLength = orgSampleRate * Math.round(seconds);
9420
9572
  //console.debug("chunkedInddbAudioRequest: Chunk audio request for inddb. startFrame: "+startFrame);
9421
- let subscr = this.chunkAudioRequestToIndDb(aCtx, persistentAudioStorageTarget, null, baseAudioUrl, startFrame, orgSampleRate, seconds).pipe(expand(iab => {
9573
+ let subscr = this.chunkAudioRequestToIndDb(persistentAudioStorageTarget, null, baseAudioUrl, startFrame, orgSampleRate, seconds).pipe(expand(iab => {
9422
9574
  // console.debug("chunkedInddbAudioRequest (pipe/expand): Got inddb ab: "+iab);
9423
9575
  if (subscriber.closed) {
9424
9576
  subscr.unsubscribe();
@@ -9439,7 +9591,7 @@ class RecordingService extends BasicRecordingService {
9439
9591
  startFrame += frameLength;
9440
9592
  //console.debug("Next start frame: "+startFrame);
9441
9593
  //console.debug("chunkedInddbAudioRequest: expand() subscriber.closed: "+subscriber.closed);
9442
- return this.chunkAudioRequestToIndDb(aCtx, persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame, orgSampleRate, seconds);
9594
+ return this.chunkAudioRequestToIndDb(persistentAudioStorageTarget, inddbAudioBuffer, baseAudioUrl, startFrame, orgSampleRate, seconds);
9443
9595
  }
9444
9596
  // } else {
9445
9597
  // return EMPTY;
@@ -9631,7 +9783,7 @@ class RecordingService extends BasicRecordingService {
9631
9783
  // //let recUrl=new URL(recUrlStr);
9632
9784
  // return this.chunkedInddbAudioRequest(aCtx,recUrlStr);
9633
9785
  // }
9634
- fetchRecordingFileAudioBuffer(aCtx, projectName, recordingFile) {
9786
+ fetchRecordingFileAudioBuffer(projectName, recordingFile) {
9635
9787
  let wobs = new Observable(observer => {
9636
9788
  let recFileId = recordingFile.recordingFileId;
9637
9789
  if (!recFileId) {
@@ -9643,10 +9795,10 @@ class RecordingService extends BasicRecordingService {
9643
9795
  next: resp => {
9644
9796
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9645
9797
  if (resp.body) {
9646
- aCtx.decodeAudioData(resp.body, ab => {
9798
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9647
9799
  observer.next(ab);
9648
9800
  observer.complete();
9649
- }, error => {
9801
+ }).catch(error => {
9650
9802
  observer.error(error);
9651
9803
  observer.complete();
9652
9804
  });
@@ -9691,7 +9843,7 @@ class RecordingService extends BasicRecordingService {
9691
9843
  //console.log("Fetched audio file. HTTP response status: "+resp.status+", type: "+resp.type+", byte length: "+ resp.body.byteLength);
9692
9844
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9693
9845
  if (resp.body) {
9694
- aCtx.decodeAudioData(resp.body, ab => {
9846
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9695
9847
  let abs = new AudioBufferSource(ab);
9696
9848
  let adh = new AudioDataHolder(abs);
9697
9849
  RecordingFileUtils.setAudioData(recordingFile, adh);
@@ -9705,7 +9857,7 @@ class RecordingService extends BasicRecordingService {
9705
9857
  observer.next(recordingFile);
9706
9858
  observer.complete();
9707
9859
  }
9708
- }, error => {
9860
+ }).catch(error => {
9709
9861
  observer.error(error);
9710
9862
  observer.complete();
9711
9863
  });
@@ -9738,7 +9890,7 @@ class RecordingService extends BasicRecordingService {
9738
9890
  });
9739
9891
  return wobs;
9740
9892
  }
9741
- fetchSprRecordingFileAudioBuffer(aCtx, projectName, recordingFile) {
9893
+ fetchSprRecordingFileAudioBuffer(projectName, recordingFile) {
9742
9894
  let wobs = new Observable(observer => {
9743
9895
  if (recordingFile.session) {
9744
9896
  let obs = this.fetchSprAudiofile(projectName, recordingFile.session, recordingFile.itemCode, recordingFile.version);
@@ -9746,11 +9898,11 @@ class RecordingService extends BasicRecordingService {
9746
9898
  next: resp => {
9747
9899
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9748
9900
  if (resp.body) {
9749
- aCtx.decodeAudioData(resp.body, ab => {
9901
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
9750
9902
  //RecordingFileUtils.setAudioData(recordingFile,new AudioDataHolder(ab,null));
9751
9903
  observer.next(ab);
9752
9904
  observer.complete();
9753
- }, error => {
9905
+ }).catch(error => {
9754
9906
  observer.error(error);
9755
9907
  observer.complete();
9756
9908
  });
@@ -9778,14 +9930,14 @@ class RecordingService extends BasicRecordingService {
9778
9930
  });
9779
9931
  return wobs;
9780
9932
  }
9781
- fetchSprRecordingFileArrayAudioBuffer(aCtx, projectName, recordingFile) {
9933
+ fetchSprRecordingFileArrayAudioBuffer(projectName, recordingFile) {
9782
9934
  let wobs = new Observable(observer => {
9783
9935
  if (recordingFile.session) {
9784
9936
  let baseUrl = this.sprAudioFileUrl(projectName, recordingFile);
9785
9937
  if (baseUrl) {
9786
9938
  if (recordingFile.samplerate) {
9787
9939
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9788
- let obs = this.chunkedAudioRequestToArrayBuffer(aCtx, baseUrl, recordingFile.samplerate, lengthInSeconds);
9940
+ let obs = this.chunkedAudioRequestToArrayBuffer(baseUrl, recordingFile.samplerate, lengthInSeconds);
9789
9941
  //let obs = this.fetchSprAudiofileArrayBuffer(aCtx,projectName, recordingFile.session, recordingFile.itemCode, recordingFile.version);
9790
9942
  let subscr = obs.subscribe({
9791
9943
  next: aab => {
@@ -9822,14 +9974,14 @@ class RecordingService extends BasicRecordingService {
9822
9974
  });
9823
9975
  return wobs;
9824
9976
  }
9825
- fetchRecordingFileArrayAudioBuffer(aCtx, projectName, recordingFile) {
9977
+ fetchRecordingFileArrayAudioBuffer(projectName, recordingFile) {
9826
9978
  let wobs = new Observable(observer => {
9827
9979
  if (recordingFile.session) {
9828
9980
  let baseUrl = this.audioFileUrl(projectName, recordingFile);
9829
9981
  if (baseUrl) {
9830
9982
  if (recordingFile.samplerate) {
9831
9983
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9832
- let obs = this.chunkedAudioRequestToArrayBuffer(aCtx, baseUrl, recordingFile.samplerate, lengthInSeconds);
9984
+ let obs = this.chunkedAudioRequestToArrayBuffer(baseUrl, recordingFile.samplerate, lengthInSeconds);
9833
9985
  //let obs = this.fetchSprAudiofileArrayBuffer(aCtx,projectName, recordingFile.session, recordingFile.itemCode, recordingFile.version);
9834
9986
  let subscr = obs.subscribe({
9835
9987
  next: aab => {
@@ -9866,14 +10018,14 @@ class RecordingService extends BasicRecordingService {
9866
10018
  });
9867
10019
  return wobs;
9868
10020
  }
9869
- fetchSprRecordingFileIndDbAudioBuffer(aCtx, persistentAudioStorageTarget, projectName, recordingFile) {
10021
+ fetchSprRecordingFileIndDbAudioBuffer(persistentAudioStorageTarget, projectName, recordingFile) {
9870
10022
  let wobs = new Observable(observer => {
9871
10023
  if (recordingFile.session) {
9872
10024
  let baseUrl = this.sprAudioFileUrl(projectName, recordingFile);
9873
10025
  if (baseUrl) {
9874
10026
  if (recordingFile.samplerate) {
9875
10027
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9876
- let obs = this.chunkedInddbAudioRequest(aCtx, persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
10028
+ let obs = this.chunkedInddbAudioRequest(persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
9877
10029
  let subscr = obs.subscribe({
9878
10030
  next: aab => {
9879
10031
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -9909,14 +10061,14 @@ class RecordingService extends BasicRecordingService {
9909
10061
  });
9910
10062
  return wobs;
9911
10063
  }
9912
- fetchRecordingFileIndDbAudioBuffer(aCtx, persistentAudioStorageTarget, projectName, recordingFile) {
10064
+ fetchRecordingFileIndDbAudioBuffer(persistentAudioStorageTarget, projectName, recordingFile) {
9913
10065
  let wobs = new Observable(observer => {
9914
10066
  if (recordingFile.session) {
9915
10067
  let baseUrl = this.audioFileUrl(projectName, recordingFile);
9916
10068
  if (baseUrl) {
9917
10069
  if (recordingFile.samplerate) {
9918
10070
  let lengthInSeconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9919
- let obs = this.chunkedInddbAudioRequest(aCtx, persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
10071
+ let obs = this.chunkedInddbAudioRequest(persistentAudioStorageTarget, baseUrl, recordingFile.samplerate, lengthInSeconds);
9920
10072
  let subscr = obs.subscribe({
9921
10073
  next: aab => {
9922
10074
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -9952,14 +10104,14 @@ class RecordingService extends BasicRecordingService {
9952
10104
  });
9953
10105
  return wobs;
9954
10106
  }
9955
- fetchSprRecordingFileNetAudioBuffer(aCtx, projectName, recordingFile) {
10107
+ fetchSprRecordingFileNetAudioBuffer(projectName, recordingFile) {
9956
10108
  let wobs = new Observable(observer => {
9957
10109
  if (recordingFile.session) {
9958
10110
  let baseUrl = this.sprAudioFileUrl(projectName, recordingFile);
9959
10111
  if (baseUrl) {
9960
10112
  let seconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
9961
10113
  if (recordingFile.samplerate) {
9962
- let obs = this.chunkAudioRequestToNetAudioBuffer(aCtx, baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
10114
+ let obs = this.chunkAudioRequestToNetAudioBuffer(baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
9963
10115
  let subscr = obs.subscribe({
9964
10116
  next: aab => {
9965
10117
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -10000,14 +10152,14 @@ class RecordingService extends BasicRecordingService {
10000
10152
  });
10001
10153
  return wobs;
10002
10154
  }
10003
- fetchRecordingFileNetAudioBuffer(aCtx, projectName, recordingFile) {
10155
+ fetchRecordingFileNetAudioBuffer(projectName, recordingFile) {
10004
10156
  let wobs = new Observable(observer => {
10005
10157
  if (recordingFile.session) {
10006
10158
  let baseUrl = this.audioFileUrl(projectName, recordingFile);
10007
10159
  if (baseUrl) {
10008
10160
  let seconds = RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
10009
10161
  if (recordingFile.samplerate) {
10010
- let obs = this.chunkAudioRequestToNetAudioBuffer(aCtx, baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
10162
+ let obs = this.chunkAudioRequestToNetAudioBuffer(baseUrl, 0, recordingFile.samplerate, seconds, recordingFile.frames);
10011
10163
  let subscr = obs.subscribe({
10012
10164
  next: aab => {
10013
10165
  //console.debug("fetchSprRecordingFileIndDbAudioBuffer: observer.closed: "+observer.closed);
@@ -10057,7 +10209,7 @@ class RecordingService extends BasicRecordingService {
10057
10209
  //console.log("Fetched audio file. HTTP response status: "+resp.status+", type: "+resp.type+", byte length: "+ resp.body.byteLength);
10058
10210
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
10059
10211
  if (resp.body) {
10060
- aCtx.decodeAudioData(resp.body, ab => {
10212
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
10061
10213
  let abs = new AudioBufferSource(ab);
10062
10214
  RecordingFileUtils.setAudioData(recordingFile, new AudioDataHolder(abs));
10063
10215
  if (this.debugDelay > 0) {
@@ -10070,7 +10222,7 @@ class RecordingService extends BasicRecordingService {
10070
10222
  observer.next(recordingFile);
10071
10223
  observer.complete();
10072
10224
  }
10073
- }, error => {
10225
+ }).catch(error => {
10074
10226
  observer.error(error);
10075
10227
  observer.complete();
10076
10228
  });
@@ -10102,9 +10254,8 @@ class RecordingService extends BasicRecordingService {
10102
10254
  let wobs = new Observable(observer => {
10103
10255
  let obs = this.fetchSprAudiofile(projectName, sessId, itemcode, version);
10104
10256
  obs.subscribe({ next: resp => {
10105
- // Do not use Promise version, which does not work with Safari 13
10106
10257
  if (resp.body) {
10107
- aCtx.decodeAudioData(resp.body, ab => {
10258
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
10108
10259
  let abs = new AudioBufferSource(ab);
10109
10260
  let adh = new AudioDataHolder(abs);
10110
10261
  let rf = new SprRecordingFile(sessId, itemcode, version, adh);
@@ -10118,7 +10269,7 @@ class RecordingService extends BasicRecordingService {
10118
10269
  observer.next(rf);
10119
10270
  observer.complete();
10120
10271
  }
10121
- });
10272
+ }).catch((reason) => { observer.error(reason); });
10122
10273
  }
10123
10274
  else {
10124
10275
  observer.error();
@@ -10153,23 +10304,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
10153
10304
  args: [SPEECHRECORDER_CONFIG]
10154
10305
  }] }]; } });
10155
10306
 
10156
- class AudioContextProvider {
10157
- static audioContextInstance() {
10158
- if (!this._audioContext) {
10159
- let debugFail = false;
10160
- if (!window.AudioContext || typeof window.AudioContext !== 'function' || debugFail) {
10161
- throw new Error('Browser does not support Web Audio API!');
10162
- this._audioContext = null;
10163
- }
10164
- else {
10165
- this._audioContext = new window.AudioContext();
10166
- }
10167
- }
10168
- return this._audioContext;
10169
- }
10170
- }
10171
- AudioContextProvider._audioContext = null;
10172
-
10173
10307
  class Item {
10174
10308
  constructor(_promptAsString, _training, _recording) {
10175
10309
  this._promptAsString = _promptAsString;
@@ -12040,6 +12174,7 @@ let BasicRecorder = class BasicRecorder extends ResponsiveComponent {
12040
12174
  this._wakeLock = false;
12041
12175
  this._selectedDeviceId = undefined;
12042
12176
  this._channelCount = 2;
12177
+ this._allowEchoCancellation = false;
12043
12178
  this._session = null;
12044
12179
  this._recordingFile = null;
12045
12180
  this.startedDate = null;
@@ -12081,6 +12216,12 @@ let BasicRecorder = class BasicRecorder extends ResponsiveComponent {
12081
12216
  this.streamLevelMeasure = new StreamLevelMeasure();
12082
12217
  this.selCaptureDeviceId = null;
12083
12218
  }
12219
+ get allowEchoCancellation() {
12220
+ return this._allowEchoCancellation;
12221
+ }
12222
+ set allowEchoCancellation(value) {
12223
+ this._allowEchoCancellation = value;
12224
+ }
12084
12225
  get maxAutoNetMemStoreSamples() {
12085
12226
  return this._maxAutoNetMemStoreSamples;
12086
12227
  }
@@ -12465,7 +12606,7 @@ let BasicRecorder = class BasicRecorder extends ResponsiveComponent {
12465
12606
  else {
12466
12607
  console.log("Open session with default audio device for " + this._channelCount + " channels");
12467
12608
  }
12468
- this.ac.open(this._channelCount, this._selectedDeviceId, this._autoGainControlConfigs);
12609
+ this.ac.open(this._channelCount, this._selectedDeviceId, this._autoGainControlConfigs, this._allowEchoCancellation);
12469
12610
  }
12470
12611
  else {
12471
12612
  this.ac.start();
@@ -12821,6 +12962,7 @@ class SessionManager extends BasicRecorder {
12821
12962
  this.bpo = bpo;
12822
12963
  this.renderer = renderer;
12823
12964
  this.recFileService = recFileService;
12965
+ this.offlineAudioContext = null;
12824
12966
  this.enableUploadRecordings = true;
12825
12967
  this.enableDownloadRecordings = false;
12826
12968
  this.status = 0 /* BLOCKED */;
@@ -12877,37 +13019,58 @@ class SessionManager extends BasicRecorder {
12877
13019
  this.transportActions.nextAction.disabled = true;
12878
13020
  this.transportActions.pauseAction.disabled = true;
12879
13021
  this.playStartAction.disabled = true;
12880
- let context = null;
12881
- try {
12882
- context = AudioContextProvider.audioContextInstance();
12883
- }
12884
- catch (err) {
12885
- this.status = 9 /* ERROR */;
12886
- let errMsg = 'Unknown error';
12887
- if (err instanceof Error) {
12888
- errMsg = err.message;
12889
- }
12890
- this.statusMsg = 'ERROR: ' + errMsg;
12891
- this.statusAlertType = 'error';
12892
- this.dialog.open(MessageDialog, {
12893
- data: {
12894
- type: 'error',
12895
- title: 'Error',
12896
- msg: errMsg,
12897
- advice: 'Please use a supported browser.',
12898
- }
12899
- });
12900
- return;
12901
- }
12902
- if (context) {
12903
- console.info("State of audio context: " + context.state);
13022
+ // let context:AudioContext|null=null;
13023
+ // try {
13024
+ // context = AudioContextProvider.audioContextInstance();
13025
+ // } catch (err) {
13026
+ // this.status = Status.ERROR;
13027
+ // let errMsg = 'Unknown error';
13028
+ // if(err instanceof Error){
13029
+ // errMsg=err.message;
13030
+ // }
13031
+ // this.statusMsg = 'ERROR: ' + errMsg;
13032
+ // this.statusAlertType = 'error';
13033
+ // this.dialog.open(MessageDialog, {
13034
+ // data: {
13035
+ // type: 'error',
13036
+ // title: 'Error',
13037
+ // msg: errMsg,
13038
+ // advice: 'Please use a supported browser.',
13039
+ // }
13040
+ // });
13041
+ // return;
13042
+ // }
13043
+ // if(context) {
13044
+ // console.info("State of audio context: " + context.state)
13045
+ // }else{
13046
+ // console.info("No audio context available!");
13047
+ // }
13048
+ // if (!context || !navigator.mediaDevices) {
13049
+ // this.status = Status.ERROR;
13050
+ // let errMsg = 'Browser does not support Media streams!';
13051
+ // this.statusMsg = 'ERROR: ' + errMsg;
13052
+ // this.statusAlertType = 'error';
13053
+ // this.dialog.open(MessageDialog, {
13054
+ // data: {
13055
+ // type: 'error',
13056
+ // title: 'Error',
13057
+ // msg: errMsg,
13058
+ // advice: 'Please use a supported browser.',
13059
+ // }
13060
+ // });
13061
+ // return;
13062
+ // } else {
13063
+ this.ac = new AudioCapture();
13064
+ if (this.ac) {
13065
+ this.transportActions.startAction.onAction = () => this.startItem();
13066
+ this.ac.listener = this;
13067
+ this.configureStreamCaptureStream();
13068
+ // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
13069
+ //this.ac.listDevices();
12904
13070
  }
12905
13071
  else {
12906
- console.info("No audio context available!");
12907
- }
12908
- if (!context || !navigator.mediaDevices) {
12909
- this.status = 9 /* ERROR */;
12910
- let errMsg = 'Browser does not support Media streams!';
13072
+ this.transportActions.startAction.disabled = true;
13073
+ let errMsg = 'Browser does not support Media/Audio API!';
12911
13074
  this.statusMsg = 'ERROR: ' + errMsg;
12912
13075
  this.statusAlertType = 'error';
12913
13076
  this.dialog.open(MessageDialog, {
@@ -12920,38 +13083,13 @@ class SessionManager extends BasicRecorder {
12920
13083
  });
12921
13084
  return;
12922
13085
  }
12923
- else {
12924
- this.ac = new AudioCapture(context);
12925
- if (this.ac) {
12926
- this.transportActions.startAction.onAction = () => this.startItem();
12927
- this.ac.listener = this;
12928
- this.configureStreamCaptureStream();
12929
- // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
12930
- //this.ac.listDevices();
12931
- }
12932
- else {
12933
- this.transportActions.startAction.disabled = true;
12934
- let errMsg = 'Browser does not support Media/Audio API!';
12935
- this.statusMsg = 'ERROR: ' + errMsg;
12936
- this.statusAlertType = 'error';
12937
- this.dialog.open(MessageDialog, {
12938
- data: {
12939
- type: 'error',
12940
- title: 'Error',
12941
- msg: errMsg,
12942
- advice: 'Please use a supported browser.',
12943
- }
12944
- });
12945
- return;
12946
- }
12947
- this.transportActions.stopAction.onAction = () => this.stopItem();
12948
- this.transportActions.nextAction.onAction = () => this.stopItem();
12949
- this.transportActions.pauseAction.onAction = () => this.pauseItem();
12950
- this.transportActions.fwdAction.onAction = () => this.nextItem();
12951
- this.transportActions.fwdNextAction.onAction = () => this.nextUnrecordedItem();
12952
- this.transportActions.bwdAction.onAction = () => this.prevItem();
12953
- this.playStartAction.onAction = () => this.controlAudioPlayer?.start();
12954
- }
13086
+ this.transportActions.stopAction.onAction = () => this.stopItem();
13087
+ this.transportActions.nextAction.onAction = () => this.stopItem();
13088
+ this.transportActions.pauseAction.onAction = () => this.pauseItem();
13089
+ this.transportActions.fwdAction.onAction = () => this.nextItem();
13090
+ this.transportActions.fwdNextAction.onAction = () => this.nextUnrecordedItem();
13091
+ this.transportActions.bwdAction.onAction = () => this.prevItem();
13092
+ this.playStartAction.onAction = () => this.controlAudioPlayer?.start();
12955
13093
  this.startStopSignalState = 4 /* OFF */;
12956
13094
  }
12957
13095
  onKeyPress(ke) {
@@ -13258,7 +13396,7 @@ class SessionManager extends BasicRecorder {
13258
13396
  }
13259
13397
  else {
13260
13398
  //console.debug("Fetch audio and store to indexed db...");
13261
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileIndDbAudioBuffer(this._controlAudioPlayer.context, this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
13399
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileIndDbAudioBuffer(this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
13262
13400
  next: (iab) => {
13263
13401
  //console.debug("Sessionmanager: Received inddb audio buffer: "+iab);
13264
13402
  nextIab = iab;
@@ -13300,7 +13438,7 @@ class SessionManager extends BasicRecorder {
13300
13438
  // Fetch chunked audio buffer from network
13301
13439
  let nextNetAb = null;
13302
13440
  //console.debug("Fetch chunked audio from network");
13303
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileNetAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
13441
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileNetAudioBuffer(this._session.project, rf).subscribe({
13304
13442
  next: (netAb) => {
13305
13443
  //console.debug("Sessionmanager: Received net audio buffer: "+netAb);
13306
13444
  nextNetAb = netAb;
@@ -13342,7 +13480,7 @@ class SessionManager extends BasicRecorder {
13342
13480
  // Fetch chunked array audio buffer
13343
13481
  let nextAab = null;
13344
13482
  //console.debug("Fetch audio and store to (chunked) array buffer...");
13345
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileArrayAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
13483
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileArrayAudioBuffer(this._session.project, rf).subscribe({
13346
13484
  next: (aab) => {
13347
13485
  nextAab = aab;
13348
13486
  },
@@ -13380,7 +13518,7 @@ class SessionManager extends BasicRecorder {
13380
13518
  else {
13381
13519
  // Fetch regular audio buffer
13382
13520
  //console.debug("Fetch audio and store to audio buffer...");
13383
- this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
13521
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileAudioBuffer(this._session.project, rf).subscribe({
13384
13522
  next: (ab) => {
13385
13523
  this.liveLevelDisplayState = State.READY;
13386
13524
  let fabDh = null;
@@ -13767,7 +13905,7 @@ class SessionManager extends BasicRecorder {
13767
13905
  const sr = this.ac.currentSampleRate;
13768
13906
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
13769
13907
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
13770
- let netAb = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13908
+ let netAb = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13771
13909
  as = netAb;
13772
13910
  if (this.uploadSet) {
13773
13911
  //let rp=new ReadyProvider();
@@ -13814,7 +13952,7 @@ class SessionManager extends BasicRecorder {
13814
13952
  const sr = this.ac.currentSampleRate;
13815
13953
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
13816
13954
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
13817
- const netAb = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13955
+ const netAb = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
13818
13956
  as = netAb;
13819
13957
  if (this.uploadSet) {
13820
13958
  this.uploadSet.onDone = (uploadSet) => {
@@ -13852,8 +13990,8 @@ class SessionManager extends BasicRecorder {
13852
13990
  // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
13853
13991
  // TODO duplicate conversion for manual download
13854
13992
  //console.log("Build wav writer...");
13855
- this.processingRecording = true;
13856
13993
  if (ab) {
13994
+ this.processingRecording = true;
13857
13995
  let ww = new WavWriter();
13858
13996
  //new REST API URL
13859
13997
  let apiEndPoint = '';
@@ -14361,18 +14499,10 @@ class SpeechrecorderngComponent extends RecorderComponent {
14361
14499
  console.error(errMsg);
14362
14500
  }
14363
14501
  ngOnInit() {
14364
- try {
14365
- let audioContext = AudioContextProvider.audioContextInstance();
14366
- if (audioContext) {
14367
- this.controlAudioPlayer = new AudioPlayer(audioContext, this);
14368
- }
14369
- this.sm.controlAudioPlayer = this.controlAudioPlayer;
14370
- this.sm.statusAlertType = 'info';
14371
- this.sm.statusMsg = 'Player initialized.';
14372
- }
14373
- catch (err) {
14374
- this.handleError(err);
14375
- }
14502
+ this.controlAudioPlayer = new AudioPlayer(this);
14503
+ this.sm.controlAudioPlayer = this.controlAudioPlayer;
14504
+ this.sm.statusAlertType = 'info';
14505
+ this.sm.statusMsg = 'Player initialized.';
14376
14506
  }
14377
14507
  ngAfterViewInit() {
14378
14508
  // let wakeLockSupp=('wakeLock' in navigator);
@@ -14629,6 +14759,9 @@ class SpeechrecorderngComponent extends RecorderComponent {
14629
14759
  chCnt = ProjectUtil.audioChannelCount(project);
14630
14760
  console.info("Project requested recording channel count: " + chCnt);
14631
14761
  this.sm.autoGainControlConfigs = project.autoGainControlConfigs;
14762
+ if (project.allowEchoCancellation !== undefined) {
14763
+ this.sm.allowEchoCancellation = project.allowEchoCancellation;
14764
+ }
14632
14765
  if (project.chunkedRecording === true) {
14633
14766
  console.debug("Enable chunked upload: chunkSize: " + BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS);
14634
14767
  this.sm.uploadChunkSizeSeconds = BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS;
@@ -14725,10 +14858,8 @@ class AudioDisplayPlayer {
14725
14858
  this.ref = ref;
14726
14859
  this.eRef = eRef;
14727
14860
  this._audioUrl = null;
14728
- this.aCtx = null;
14729
14861
  this._audioClip = null;
14730
14862
  this.currentLoader = null;
14731
- //console.log("constructor: "+this.ac);
14732
14863
  this.parentE = this.eRef.nativeElement;
14733
14864
  this.playStartAction = new Action("Start");
14734
14865
  this.playSelectionAction = new Action("Play selected");
@@ -14736,26 +14867,18 @@ class AudioDisplayPlayer {
14736
14867
  this.status = "Player created.";
14737
14868
  }
14738
14869
  ngOnInit() {
14739
- //console.log("OnInit: "+this.ac);
14740
14870
  this.zoomSelectedAction = this.audioDisplayScrollPane.zoomSelectedAction;
14741
14871
  this.zoomFitToPanelAction = this.audioDisplayScrollPane.zoomFitToPanelAction;
14742
14872
  this.zoomOutAction = this.audioDisplayScrollPane.zoomOutAction;
14743
14873
  this.zoomInAction = this.audioDisplayScrollPane.zoomInAction;
14744
- try {
14745
- this.aCtx = AudioContextProvider.audioContextInstance();
14746
- if (this.aCtx) {
14747
- this.ap = new AudioPlayer(this.aCtx, this);
14748
- }
14749
- }
14750
- catch (err) {
14751
- if (err instanceof Error) {
14752
- this.status = err.message;
14753
- }
14754
- }
14874
+ this.ap = new AudioPlayer(this);
14755
14875
  }
14756
14876
  ngAfterViewInit() {
14757
- if (this.aCtx && this.ap) {
14758
- this.playStartAction.onAction = () => this.ap?.start();
14877
+ if (this.ap) {
14878
+ this.playStartAction.onAction = () => {
14879
+ console.debug("Start action, player: " + this.ap);
14880
+ this.ap?.start();
14881
+ };
14759
14882
  this.playSelectionAction.onAction = () => this.ap?.startSelected();
14760
14883
  this.playStopAction.onAction = () => this.ap?.stop();
14761
14884
  }
@@ -14820,15 +14943,12 @@ class AudioDisplayPlayer {
14820
14943
  //console.debug("Loaded");
14821
14944
  this.status = 'Audio file loaded.';
14822
14945
  //console.debug("Received data ", data.byteLength);
14823
- // Do not use Promise version, which does not work with Safari 13
14824
- if (this.aCtx) {
14825
- this.aCtx.decodeAudioData(data, (audioBuffer) => {
14826
- //console.debug("Audio Buffer Samplerate: ", audioBuffer.sampleRate)
14827
- let as = new AudioBufferSource(audioBuffer);
14828
- let adh = new AudioDataHolder(as);
14829
- this.audioClip = new AudioClip(adh);
14830
- });
14831
- }
14946
+ AudioContextProvider.decodeAudioData(data).then(audioBuffer => {
14947
+ //console.debug("Audio Buffer Samplerate: ", audioBuffer.sampleRate)
14948
+ let as = new AudioBufferSource(audioBuffer);
14949
+ let adh = new AudioDataHolder(as);
14950
+ this.audioClip = new AudioClip(adh);
14951
+ });
14832
14952
  }
14833
14953
  set audioData(audioData) {
14834
14954
  this.audioDisplayScrollPane.audioData = audioData;
@@ -15088,14 +15208,13 @@ class RecordingFileService extends BasicRecordingService {
15088
15208
  });
15089
15209
  }
15090
15210
  // TODO test
15091
- fetchAndApplyRecordingFile(aCtx, recordingFile) {
15211
+ fetchAndApplyRecordingFile(recordingFile) {
15092
15212
  let wobs = new Observable(observer => {
15093
15213
  if (recordingFile.recordingFileId) {
15094
15214
  let obs = this.fetchAudiofile(recordingFile.recordingFileId);
15095
15215
  obs.subscribe({ next: resp => {
15096
- // Do not use Promise version, which does not work with Safari 13
15097
15216
  if (resp.body) {
15098
- aCtx.decodeAudioData(resp.body, ab => {
15217
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
15099
15218
  let as = new AudioBufferSource(ab);
15100
15219
  RecordingFileUtils.setAudioData(recordingFile, new AudioDataHolder(as));
15101
15220
  if (this.debugDelay > 0) {
@@ -15108,7 +15227,7 @@ class RecordingFileService extends BasicRecordingService {
15108
15227
  observer.next(recordingFile);
15109
15228
  observer.complete();
15110
15229
  }
15111
- });
15230
+ }).catch(reason => { observer.error(reason); });
15112
15231
  }
15113
15232
  else {
15114
15233
  observer.error('Received no audio data!');
@@ -15148,7 +15267,7 @@ class RecordingFileService extends BasicRecordingService {
15148
15267
  next: resp => {
15149
15268
  // Do not use Promise version, which does not work with Safari 13
15150
15269
  if (resp.body) {
15151
- aCtx.decodeAudioData(resp.body, ab => {
15270
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
15152
15271
  if (rf) {
15153
15272
  let as = new AudioBufferSource(ab);
15154
15273
  RecordingFileUtils.setAudioData(rf, new AudioDataHolder(as));
@@ -15166,7 +15285,7 @@ class RecordingFileService extends BasicRecordingService {
15166
15285
  observer.next(rf);
15167
15286
  observer.complete();
15168
15287
  }
15169
- });
15288
+ }).catch(reason => { observer.error(reason); });
15170
15289
  }
15171
15290
  else {
15172
15291
  observer.error('Received no audio data');
@@ -15190,7 +15309,7 @@ class RecordingFileService extends BasicRecordingService {
15190
15309
  });
15191
15310
  return wobs;
15192
15311
  }
15193
- fetchSprRecordingFile(aCtx, recordingFileId) {
15312
+ fetchSprRecordingFile(recordingFileId) {
15194
15313
  let wobs = new Observable(observer => {
15195
15314
  let rf = null;
15196
15315
  let rfDescrObs = this.sprRecordingFileDescrObserver(recordingFileId);
@@ -15207,7 +15326,7 @@ class RecordingFileService extends BasicRecordingService {
15207
15326
  // TODO use download storage type depending on sample count of file
15208
15327
  if (rf && rf.samplerate && sampleCnt != null && sampleCnt > this._maxAutoNetMemStoreSamples) {
15209
15328
  const baseUrl = this.recoFileUrl(recordingFileId);
15210
- const obNetAb = this.chunkAudioRequestToNetAudioBuffer(aCtx, baseUrl, 0, rf?.samplerate, BasicRecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS, rf.frames);
15329
+ const obNetAb = this.chunkAudioRequestToNetAudioBuffer(baseUrl, 0, rf?.samplerate, BasicRecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS, rf.frames);
15211
15330
  obNetAb.subscribe({
15212
15331
  next: (nab) => {
15213
15332
  let adh = new AudioDataHolder(nab);
@@ -15238,7 +15357,7 @@ class RecordingFileService extends BasicRecordingService {
15238
15357
  rfAudioObs.subscribe({ next: resp => {
15239
15358
  // Do not use Promise version, which does not work with Safari 13
15240
15359
  if (resp.body) {
15241
- aCtx.decodeAudioData(resp.body, ab => {
15360
+ AudioContextProvider.decodeAudioData(resp.body).then(ab => {
15242
15361
  if (rf) {
15243
15362
  let as = new AudioBufferSource(ab);
15244
15363
  let adh = new AudioDataHolder(as);
@@ -15257,7 +15376,7 @@ class RecordingFileService extends BasicRecordingService {
15257
15376
  observer.next(rf);
15258
15377
  observer.complete();
15259
15378
  }
15260
- });
15379
+ }).catch(reason => { observer.error(reason); });
15261
15380
  }
15262
15381
  else {
15263
15382
  observer.error('Received no audio data');
@@ -15415,9 +15534,9 @@ class RecordingFileMetaComponent {
15415
15534
  constructor() {
15416
15535
  this.sessionId = null;
15417
15536
  this._recordingFile = null;
15418
- this.stateLoading = false;
15419
15537
  this.itemCode = null;
15420
15538
  this.uuid = null;
15539
+ this.stateLoading = false;
15421
15540
  }
15422
15541
  get recordingFile() {
15423
15542
  return this._recordingFile;
@@ -15525,7 +15644,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
15525
15644
  </mat-card-content>
15526
15645
  </mat-card>
15527
15646
  ` }]
15528
- }], propDecorators: { sessionId: [{
15647
+ }], ctorParameters: function () { return []; }, propDecorators: { sessionId: [{
15529
15648
  type: Input
15530
15649
  }], stateLoading: [{
15531
15650
  type: Input
@@ -15546,7 +15665,6 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
15546
15665
  this.ref = ref;
15547
15666
  this.eRef = eRef;
15548
15667
  this.dialog = dialog;
15549
- //protected _recordingFileId: string | number = null;
15550
15668
  this.sessionId = null;
15551
15669
  this.sessionIdFromRoute = null;
15552
15670
  this.availRecFiles = null;
@@ -15555,9 +15673,11 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
15555
15673
  this.recordingFileVersion = null;
15556
15674
  this.routedByQueryParam = false;
15557
15675
  this.posInList = null;
15558
- this.audioFetching = false;
15559
- this.naviInfoLoading = false;
15560
15676
  this.parentE = this.eRef.nativeElement;
15677
+ // TODO Should be initialized with false, but this causes in debug mode:
15678
+ // 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
15679
+ this.audioFetching = true;
15680
+ this.naviInfoLoading = false;
15561
15681
  this.firstAction = new Action('First');
15562
15682
  this.firstAction.onAction = () => {
15563
15683
  this.posInList = 0;
@@ -15723,54 +15843,51 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
15723
15843
  this.recordingFile = null;
15724
15844
  this.posInList = null;
15725
15845
  this.updateActions();
15726
- let audioContext = AudioContextProvider.audioContextInstance();
15727
- if (audioContext) {
15728
- this.audioFetching = true;
15729
- this.recordingFileService.fetchSprRecordingFile(audioContext, rfId).subscribe({
15730
- next: value => {
15731
- this.audioFetching = false;
15732
- this.status = 'Audio file loaded.';
15733
- let clip = null;
15734
- this.recordingFile = value;
15735
- if (this.recordingFile) {
15736
- let ab = this.recordingFile.audioDataHolder;
15737
- if (ab) {
15738
- clip = new AudioClip(ab);
15739
- let esffsr = null;
15740
- let eeffsr = null;
15741
- let esr = null;
15742
- if (clip.audioDataHolder != null) {
15743
- esr = ab.sampleRate;
15744
- if (esr != null) {
15745
- esffsr = RecordingFileUtil.editStartFrameForSampleRate(this.recordingFile, esr);
15746
- eeffsr = RecordingFileUtil.editEndFrameForSampleRate(this.recordingFile, esr);
15747
- }
15748
- let sel = null;
15749
- if (esffsr != null) {
15750
- if (eeffsr != null) {
15751
- sel = new Selection(ab.sampleRate, esffsr, eeffsr);
15752
- }
15753
- else {
15754
- //let ch0 = ab.getChannelData(0);
15755
- let frameLength = ab.frameLen;
15756
- sel = new Selection(esr, esffsr, frameLength);
15757
- }
15846
+ this.audioFetching = true;
15847
+ this.recordingFileService.fetchSprRecordingFile(rfId).subscribe({
15848
+ next: value => {
15849
+ this.audioFetching = false;
15850
+ this.status = 'Audio file loaded.';
15851
+ let clip = null;
15852
+ this.recordingFile = value;
15853
+ if (this.recordingFile) {
15854
+ let ab = this.recordingFile.audioDataHolder;
15855
+ if (ab) {
15856
+ clip = new AudioClip(ab);
15857
+ let esffsr = null;
15858
+ let eeffsr = null;
15859
+ let esr = null;
15860
+ if (clip.audioDataHolder != null) {
15861
+ esr = ab.sampleRate;
15862
+ if (esr != null) {
15863
+ esffsr = RecordingFileUtil.editStartFrameForSampleRate(this.recordingFile, esr);
15864
+ eeffsr = RecordingFileUtil.editEndFrameForSampleRate(this.recordingFile, esr);
15865
+ }
15866
+ let sel = null;
15867
+ if (esffsr != null) {
15868
+ if (eeffsr != null) {
15869
+ sel = new Selection(ab.sampleRate, esffsr, eeffsr);
15758
15870
  }
15759
- else if (eeffsr != null) {
15760
- sel = new Selection(esr, 0, eeffsr);
15871
+ else {
15872
+ //let ch0 = ab.getChannelData(0);
15873
+ let frameLength = ab.frameLen;
15874
+ sel = new Selection(esr, esffsr, frameLength);
15761
15875
  }
15762
- clip.selection = sel;
15763
15876
  }
15877
+ else if (eeffsr != null) {
15878
+ sel = new Selection(esr, 0, eeffsr);
15879
+ }
15880
+ clip.selection = sel;
15764
15881
  }
15765
15882
  }
15766
- this.audioClip = clip;
15767
- this.loadedRecfile();
15768
- }, error: error1 => {
15769
- this.audioFetching = false;
15770
- this.status = 'Error loading audio file!';
15771
15883
  }
15772
- });
15773
- }
15884
+ this.audioClip = clip;
15885
+ this.loadedRecfile();
15886
+ }, error: error1 => {
15887
+ this.audioFetching = false;
15888
+ this.status = 'Error loading audio file!';
15889
+ }
15890
+ });
15774
15891
  }
15775
15892
  loadedRecfile() {
15776
15893
  if (this.recordingFile && !this.sessionId) {
@@ -16380,38 +16497,17 @@ class AudioRecorder extends BasicRecorder {
16380
16497
  this.transportActions.nextAction.disabled = true;
16381
16498
  this.transportActions.pauseAction.disabled = true;
16382
16499
  this.playStartAction.disabled = true;
16383
- //this.audioLoaded=false;
16384
- let context = null;
16385
- try {
16386
- context = AudioContextProvider.audioContextInstance();
16387
- }
16388
- catch (err) {
16389
- this.status = 5 /* ERROR */;
16390
- let errMsg = 'Unknown error';
16391
- if (err instanceof Error) {
16392
- errMsg = err.message;
16393
- }
16394
- this.statusMsg = 'ERROR: ' + errMsg;
16395
- this.statusAlertType = 'error';
16396
- this.dialog.open(MessageDialog, {
16397
- data: {
16398
- type: 'error',
16399
- title: 'Error',
16400
- msg: errMsg,
16401
- advice: 'Please use a supported browser.',
16402
- }
16403
- });
16404
- return;
16405
- }
16406
- if (context) {
16407
- console.info("State of audio context: " + context.state);
16500
+ this.ac = new AudioCapture();
16501
+ if (this.ac) {
16502
+ this.transportActions.startAction.onAction = () => this.startItem();
16503
+ this.ac.listener = this;
16504
+ this.configureStreamCaptureStream();
16505
+ // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
16506
+ //this.ac.listDevices();
16408
16507
  }
16409
16508
  else {
16410
- console.info("No audio context available!");
16411
- }
16412
- if (!context || !navigator.mediaDevices) {
16413
- this.status = 5 /* ERROR */;
16414
- let errMsg = 'Browser does not support Media streams!';
16509
+ this.transportActions.startAction.disabled = true;
16510
+ let errMsg = 'Browser does not support Media/Audio API!';
16415
16511
  this.statusMsg = 'ERROR: ' + errMsg;
16416
16512
  this.statusAlertType = 'error';
16417
16513
  this.dialog.open(MessageDialog, {
@@ -16424,36 +16520,10 @@ class AudioRecorder extends BasicRecorder {
16424
16520
  });
16425
16521
  return;
16426
16522
  }
16427
- else {
16428
- //this.controlAudioPlayer = new AudioPlayer(context, this);
16429
- this.ac = new AudioCapture(context);
16430
- if (this.ac) {
16431
- this.transportActions.startAction.onAction = () => this.startItem();
16432
- this.ac.listener = this;
16433
- this.configureStreamCaptureStream();
16434
- // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
16435
- //this.ac.listDevices();
16436
- }
16437
- else {
16438
- this.transportActions.startAction.disabled = true;
16439
- let errMsg = 'Browser does not support Media/Audio API!';
16440
- this.statusMsg = 'ERROR: ' + errMsg;
16441
- this.statusAlertType = 'error';
16442
- this.dialog.open(MessageDialog, {
16443
- data: {
16444
- type: 'error',
16445
- title: 'Error',
16446
- msg: errMsg,
16447
- advice: 'Please use a supported browser.',
16448
- }
16449
- });
16450
- return;
16451
- }
16452
- this.transportActions.stopAction.onAction = () => this.stopItem();
16453
- this.transportActions.nextAction.onAction = () => this.stopItem();
16454
- //this.transportActions.pauseAction.onAction = () => this.pauseItem();
16455
- this.playStartAction.onAction = () => this.controlAudioPlayer?.start();
16456
- }
16523
+ this.transportActions.stopAction.onAction = () => this.stopItem();
16524
+ this.transportActions.nextAction.onAction = () => this.stopItem();
16525
+ //this.transportActions.pauseAction.onAction = () => this.pauseItem();
16526
+ this.playStartAction.onAction = () => this.controlAudioPlayer?.start();
16457
16527
  this.uploader.listener = (ue) => {
16458
16528
  this.uploadUpdate(ue);
16459
16529
  };
@@ -16562,6 +16632,9 @@ class AudioRecorder extends BasicRecorder {
16562
16632
  chCnt = ProjectUtil.audioChannelCount(project);
16563
16633
  console.info("Project requested recording channel count: " + chCnt);
16564
16634
  this.autoGainControlConfigs = project.autoGainControlConfigs;
16635
+ if (project.allowEchoCancellation !== undefined) {
16636
+ this.allowEchoCancellation = project.allowEchoCancellation;
16637
+ }
16565
16638
  if (project.chunkedRecording === true) {
16566
16639
  this.uploadChunkSizeSeconds = BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS;
16567
16640
  }
@@ -16762,7 +16835,7 @@ class AudioRecorder extends BasicRecorder {
16762
16835
  }
16763
16836
  else {
16764
16837
  //console.debug("Fetch audio and store to indexed db...");
16765
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileIndDbAudioBuffer(this._controlAudioPlayer.context, this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
16838
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileIndDbAudioBuffer(this._persistentAudioStorageTarget, this._session.project, rf).subscribe({
16766
16839
  next: (iab) => {
16767
16840
  //console.debug("Sessionmanager: Received inddb audio buffer: "+iab);
16768
16841
  nextIab = iab;
@@ -16805,7 +16878,7 @@ class AudioRecorder extends BasicRecorder {
16805
16878
  // Fetch chunked audio buffer from network
16806
16879
  let nextNetAb = null;
16807
16880
  //console.debug("Fetch chunked audio from network");
16808
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileNetAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
16881
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileNetAudioBuffer(this._session.project, rf).subscribe({
16809
16882
  next: (netAb) => {
16810
16883
  //console.debug("Sessionmanager: Received net audio buffer: "+netAb);
16811
16884
  nextNetAb = netAb;
@@ -16850,7 +16923,7 @@ class AudioRecorder extends BasicRecorder {
16850
16923
  // Fetch chunked array audio buffer
16851
16924
  let nextAab = null;
16852
16925
  //console.debug("Fetch audio and store to (chunked) array buffer...");
16853
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileArrayAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
16926
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileArrayAudioBuffer(this._session.project, rf).subscribe({
16854
16927
  next: (aab) => {
16855
16928
  nextAab = aab;
16856
16929
  },
@@ -16887,7 +16960,7 @@ class AudioRecorder extends BasicRecorder {
16887
16960
  });
16888
16961
  }
16889
16962
  else {
16890
- this.audioFetchSubscription = this.recFileService.fetchRecordingFileAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
16963
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileAudioBuffer(this._session.project, rf).subscribe({
16891
16964
  next: ab => {
16892
16965
  this.liveLevelDisplayState = State.READY;
16893
16966
  let fabDh = null;
@@ -17026,7 +17099,7 @@ class AudioRecorder extends BasicRecorder {
17026
17099
  this.transportActions.pauseAction.disabled = true;
17027
17100
  this.statusAlertType = 'info';
17028
17101
  this.statusMsg = 'Recorded.';
17029
- let ad = null;
17102
+ let ab = null;
17030
17103
  if (this.ac) {
17031
17104
  let adh = null;
17032
17105
  let as = null;
@@ -17058,7 +17131,7 @@ class AudioRecorder extends BasicRecorder {
17058
17131
  const sr = this.ac.currentSampleRate;
17059
17132
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
17060
17133
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
17061
- let netAs = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17134
+ let netAs = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17062
17135
  as = netAs;
17063
17136
  if (this.uploadSet) {
17064
17137
  this.uploadSet.onDone = (uploadSet) => {
@@ -17105,7 +17178,7 @@ class AudioRecorder extends BasicRecorder {
17105
17178
  const sr = this.ac.currentSampleRate;
17106
17179
  const chFl = sr * RecordingService.DEFAULT_CHUNKED_DOWNLOAD_SECONDS;
17107
17180
  //console.debug("stopped(): rfID: "+this._recordingFile?.recordingFileId+", net ab url: " + burl+", frames: "+this.ac.framesRecorded+", sample rate: "+sr);
17108
- let netAs = new NetAudioBuffer(this.ac.context, this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17181
+ let netAs = new NetAudioBuffer(this.recFileService, burl, this.ac.channelCount, sr, chFl, this.ac.framesRecorded, rUUID, chFl);
17109
17182
  as = netAs;
17110
17183
  if (this.uploadSet) {
17111
17184
  this.uploadSet.onDone = (uploadSet) => {
@@ -17124,7 +17197,7 @@ class AudioRecorder extends BasicRecorder {
17124
17197
  as = this.ac.audioBufferArray();
17125
17198
  }
17126
17199
  else {
17127
- let ab = this.ac.audioBuffer();
17200
+ ab = this.ac.audioBuffer();
17128
17201
  if (ab) {
17129
17202
  as = new AudioBufferSource(ab);
17130
17203
  }
@@ -17144,7 +17217,7 @@ class AudioRecorder extends BasicRecorder {
17144
17217
  RecordingFileUtils.setAudioData(rf, adh);
17145
17218
  this.recorderCombiPane.addRecFile(rf);
17146
17219
  // Upload if upload enabled and not in chunked upload mode
17147
- if (this.enableUploadRecordings && this._uploadChunkSizeSeconds === null && rf != null && ad != null) {
17220
+ if (this.enableUploadRecordings && this._uploadChunkSizeSeconds === null && AudioStorageType.MEM_ENTIRE === this._clientAudioStorageType && rf != null && ab != null) {
17148
17221
  let apiEndPoint = '';
17149
17222
  if (this.config && this.config.apiEndPoint) {
17150
17223
  apiEndPoint = this.config.apiEndPoint;
@@ -17159,7 +17232,7 @@ class AudioRecorder extends BasicRecorder {
17159
17232
  // TODO duplicate conversion for manual download
17160
17233
  this.processingRecording = true;
17161
17234
  let ww = new WavWriter();
17162
- ww.writeAsync(ad, (wavFile) => {
17235
+ ww.writeAsync(ab, (wavFile) => {
17163
17236
  this.postRecordingMultipart(wavFile, recUrl, rf);
17164
17237
  this.processingRecording = false;
17165
17238
  this.updateWakeLock();
@@ -17395,11 +17468,7 @@ class AudioRecorderComponent extends RecorderComponent {
17395
17468
  this.uploader = uploader;
17396
17469
  }
17397
17470
  ngOnInit() {
17398
- //super.ngOnInit();
17399
- let audioContext = AudioContextProvider.audioContextInstance();
17400
- if (audioContext) {
17401
- this.controlAudioPlayer = new AudioPlayer(audioContext, this.ar);
17402
- }
17471
+ this.controlAudioPlayer = new AudioPlayer(this.ar);
17403
17472
  this.ar.controlAudioPlayer = this.controlAudioPlayer;
17404
17473
  //TODO Duplicate code in SpeechRecorderComponent
17405
17474
  window.addEventListener('beforeunload', (e) => {
@@ -17567,7 +17636,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
17567
17636
  }]
17568
17637
  }] });
17569
17638
 
17570
- const VERSION = '3.4.5';
17639
+ const VERSION = '3.6.0';
17571
17640
 
17572
17641
  /*
17573
17642
  * Public API Surface of speechrecorderng