speechrecorderng 2.24.0 → 2.25.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.
@@ -19,6 +19,7 @@ import * as i1$2 from '@angular/material/dialog';
19
19
  import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
20
20
  import * as i3$2 from '@angular/material/button';
21
21
  import { MatButtonModule } from '@angular/material/button';
22
+ import { __decorate, __param } from 'tslib';
22
23
  import * as i1$3 from '@angular/material/progress-spinner';
23
24
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
24
25
  import * as i8 from '@angular/flex-layout/flex';
@@ -1047,17 +1048,45 @@ class AudioCapture {
1047
1048
  this._opened = false;
1048
1049
  }
1049
1050
  audioBuffer() {
1050
- var frameLen = 0;
1051
- var ch0Data = this.data[0];
1052
- for (var ch0Chk of ch0Data) {
1051
+ let frameLen = 0;
1052
+ let ch0Data = this.data[0];
1053
+ for (let ch0Chk of ch0Data) {
1053
1054
  frameLen += ch0Chk.length;
1054
1055
  }
1055
- var ab = this.context.createBuffer(this.channelCount, frameLen, this.context.sampleRate);
1056
- for (var ch = 0; ch < this.channelCount; ch++) {
1057
- var chD = ab.getChannelData(ch);
1058
- var pos = 0;
1059
- for (var chChk of this.data[ch]) {
1060
- var bufLen = chChk.length;
1056
+ let ab;
1057
+ try {
1058
+ ab = this.context.createBuffer(this.channelCount, frameLen, this.context.sampleRate);
1059
+ }
1060
+ catch (err) {
1061
+ if (err instanceof DOMException) {
1062
+ if (err.name === 'NotSupportedError') {
1063
+ if (frameLen == 0) {
1064
+ // Empty buffers are not supported by Chromium
1065
+ // Create dummy buffer with one sample
1066
+ ab = this.context.createBuffer(this.channelCount, 1, this.context.sampleRate);
1067
+ }
1068
+ else {
1069
+ throw err;
1070
+ }
1071
+ }
1072
+ else {
1073
+ throw err;
1074
+ }
1075
+ }
1076
+ else if (err instanceof RangeError) {
1077
+ // Out of memory
1078
+ // TODO What to do ??
1079
+ throw err;
1080
+ }
1081
+ else {
1082
+ throw err;
1083
+ }
1084
+ }
1085
+ for (let ch = 0; ch < this.channelCount; ch++) {
1086
+ let chD = ab.getChannelData(ch);
1087
+ let pos = 0;
1088
+ for (let chChk of this.data[ch]) {
1089
+ let bufLen = chChk.length;
1061
1090
  chD.set(chChk, pos);
1062
1091
  pos += bufLen;
1063
1092
  }
@@ -5629,98 +5658,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
5629
5658
  type: Output
5630
5659
  }] } });
5631
5660
 
5632
- class Float32ArrayChunkerOutStream {
5633
- constructor(outStream) {
5634
- this.outStream = outStream;
5635
- this.bufs = new Array();
5636
- this._channels = 0;
5637
- this._chunkSize = 0;
5638
- this.filled = 0;
5639
- this.receivedFrames = 0;
5640
- this.sentFrames = 0;
5641
- }
5642
- createBuffers() {
5643
- this.bufs = new Array(this._channels);
5644
- for (let ch = 0; ch < this._channels; ch++) {
5645
- this.bufs[ch] = new Float32Array(this._chunkSize);
5646
- }
5647
- }
5648
- set chunkSize(chunkSize) {
5649
- this._chunkSize = chunkSize;
5650
- this.createBuffers();
5651
- }
5652
- set channels(channels) {
5653
- this._channels = channels;
5654
- this.createBuffers();
5655
- }
5656
- write(buffers) {
5657
- let copied = 0;
5658
- if (buffers.length > 0) {
5659
- let buffersLen = buffers[0].length;
5660
- this.receivedFrames += buffersLen;
5661
- let avail = buffersLen;
5662
- // Fill out buffers until all values copied
5663
- while (avail > 0) {
5664
- let toFill = this._chunkSize - this.filled;
5665
- if (toFill > avail) {
5666
- toFill = avail;
5667
- }
5668
- let sliceEnd = copied + toFill;
5669
- // Firefox on Android sends only the first channel
5670
- for (let ch = 0; ch < buffersLen; ch++) {
5671
- if (buffers[ch]) {
5672
- let cpPrt = buffers[ch].slice(copied, sliceEnd);
5673
- let buf = this.bufs[ch];
5674
- buf.set(cpPrt, this.filled);
5675
- }
5676
- }
5677
- copied += toFill;
5678
- avail -= toFill;
5679
- this.filled += toFill;
5680
- if (this.filled == this._chunkSize) {
5681
- this.outStream.write(this.bufs);
5682
- this.sentFrames += this.filled;
5683
- this.filled = 0;
5684
- }
5685
- }
5686
- }
5687
- return copied;
5688
- }
5689
- flush() {
5690
- if (this.filled > 0) {
5691
- let restBufs = new Array(this._channels);
5692
- for (let ch = 0; ch < this._channels; ch++) {
5693
- restBufs[ch] = this.bufs[ch].slice(0, this.filled);
5694
- }
5695
- this.outStream.write(restBufs);
5696
- this.outStream.flush();
5697
- this.sentFrames += this.filled;
5698
- this.filled = 0;
5699
- }
5700
- }
5701
- close() {
5702
- this.outStream.close();
5703
- }
5704
- }
5705
-
5706
- class SequenceAudioFloat32ChunkerOutStream extends Float32ArrayChunkerOutStream {
5707
- constructor(outStream, chunkDurationSeconds) {
5708
- super(outStream);
5709
- this.chunkDurationSeconds = chunkDurationSeconds;
5710
- this.sampleRate = null;
5711
- this.sequenceAudioFloat32OutStream = outStream;
5712
- }
5713
- setFormat(channels, sampleRate) {
5714
- this.channels = channels;
5715
- this.sampleRate = sampleRate;
5716
- this.chunkSize = Math.round(sampleRate * this.chunkDurationSeconds);
5717
- this.sequenceAudioFloat32OutStream.setFormat(channels, sampleRate);
5718
- }
5719
- nextStream() {
5720
- this.sequenceAudioFloat32OutStream.nextStream();
5721
- }
5722
- }
5723
-
5724
5661
  class SessionFinishedDialog {
5725
5662
  constructor(dialogRef, data) {
5726
5663
  this.dialogRef = dialogRef;
@@ -7247,23 +7184,209 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
7247
7184
  type: Input
7248
7185
  }] } });
7249
7186
 
7187
+ class Float32ArrayChunkerOutStream {
7188
+ constructor(outStream) {
7189
+ this.outStream = outStream;
7190
+ this.bufs = new Array();
7191
+ this._channels = 0;
7192
+ this._chunkSize = 0;
7193
+ this.filled = 0;
7194
+ this.receivedFrames = 0;
7195
+ this.sentFrames = 0;
7196
+ }
7197
+ createBuffers() {
7198
+ this.bufs = new Array(this._channels);
7199
+ for (let ch = 0; ch < this._channels; ch++) {
7200
+ this.bufs[ch] = new Float32Array(this._chunkSize);
7201
+ }
7202
+ }
7203
+ set chunkSize(chunkSize) {
7204
+ this._chunkSize = chunkSize;
7205
+ this.createBuffers();
7206
+ }
7207
+ get chunkSize() {
7208
+ return this._chunkSize;
7209
+ }
7210
+ set channels(channels) {
7211
+ this._channels = channels;
7212
+ this.createBuffers();
7213
+ }
7214
+ get channels() {
7215
+ return this._channels;
7216
+ }
7217
+ available() {
7218
+ return this._chunkSize - this.filled;
7219
+ }
7220
+ write(buffers) {
7221
+ let copied = 0;
7222
+ if (buffers.length > 0) {
7223
+ let buffersLen = buffers[0].length;
7224
+ this.receivedFrames += buffersLen;
7225
+ let avail = buffersLen;
7226
+ // Fill out buffers until all values copied
7227
+ while (avail > 0) {
7228
+ let toFill = this._chunkSize - this.filled;
7229
+ if (toFill > avail) {
7230
+ toFill = avail;
7231
+ }
7232
+ let sliceEnd = copied + toFill;
7233
+ // Firefox on Android sends only the first channel
7234
+ for (let ch = 0; ch < buffersLen; ch++) {
7235
+ if (buffers[ch]) {
7236
+ let cpPrt = buffers[ch].slice(copied, sliceEnd);
7237
+ let buf = this.bufs[ch];
7238
+ buf.set(cpPrt, this.filled);
7239
+ }
7240
+ }
7241
+ copied += toFill;
7242
+ avail -= toFill;
7243
+ this.filled += toFill;
7244
+ if (this.filled == this._chunkSize) {
7245
+ this.outStream.write(this.bufs);
7246
+ this.sentFrames += this.filled;
7247
+ this.filled = 0;
7248
+ }
7249
+ }
7250
+ }
7251
+ return copied;
7252
+ }
7253
+ flush() {
7254
+ if (this.filled > 0) {
7255
+ let restBufs = new Array(this._channels);
7256
+ for (let ch = 0; ch < this._channels; ch++) {
7257
+ restBufs[ch] = this.bufs[ch].slice(0, this.filled);
7258
+ }
7259
+ this.outStream.write(restBufs);
7260
+ this.outStream.flush();
7261
+ this.sentFrames += this.filled;
7262
+ this.filled = 0;
7263
+ }
7264
+ }
7265
+ close() {
7266
+ this.outStream.close();
7267
+ }
7268
+ }
7269
+
7270
+ class SequenceAudioFloat32ChunkerOutStream extends Float32ArrayChunkerOutStream {
7271
+ constructor(outStream, chunkDurationSeconds) {
7272
+ super(outStream);
7273
+ this.chunkDurationSeconds = chunkDurationSeconds;
7274
+ this.sampleRate = null;
7275
+ this.sequenceAudioFloat32OutStream = outStream;
7276
+ }
7277
+ setFormat(channels, sampleRate) {
7278
+ console.debug("SequenceAudioFloat32ChunkerOutStream:setFormat(channels: " + channels + ",sampleRate: " + sampleRate + ")");
7279
+ this.channels = channels;
7280
+ this.sampleRate = sampleRate;
7281
+ this.chunkSize = Math.round(sampleRate * this.chunkDurationSeconds);
7282
+ console.debug("SequenceAudioFloat32ChunkerOutStream: chunkSize: " + this.chunkSize);
7283
+ this.sequenceAudioFloat32OutStream.setFormat(channels, sampleRate);
7284
+ }
7285
+ nextStream() {
7286
+ this.sequenceAudioFloat32OutStream.nextStream();
7287
+ }
7288
+ }
7289
+ /**
7290
+ * Streams a SequenceAudioFloat32OutStream to multiple streams
7291
+ */
7292
+ class SequenceAudioFloat32OutStreamMultiplier {
7293
+ constructor() {
7294
+ this._sequenceAudioFloat32OutStreams = new Array();
7295
+ }
7296
+ get sequenceAudioFloat32OutStreams() {
7297
+ return this._sequenceAudioFloat32OutStreams;
7298
+ }
7299
+ setFormat(channels, sampleRate) {
7300
+ for (let os of this._sequenceAudioFloat32OutStreams) {
7301
+ os.setFormat(channels, sampleRate);
7302
+ }
7303
+ }
7304
+ nextStream() {
7305
+ for (let os of this._sequenceAudioFloat32OutStreams) {
7306
+ os.nextStream();
7307
+ }
7308
+ }
7309
+ close() {
7310
+ for (let os of this._sequenceAudioFloat32OutStreams) {
7311
+ os.close();
7312
+ }
7313
+ }
7314
+ flush() {
7315
+ for (let os of this._sequenceAudioFloat32OutStreams) {
7316
+ os.flush();
7317
+ }
7318
+ }
7319
+ write(buffers) {
7320
+ let toWrite = buffers.length;
7321
+ for (let os of this._sequenceAudioFloat32OutStreams) {
7322
+ os.write(buffers);
7323
+ }
7324
+ return toWrite;
7325
+ }
7326
+ }
7327
+
7250
7328
  const FORCE_REQUEST_AUDIO_PERMISSIONS = false;
7251
7329
  const RECFILE_API_CTX = 'recfile';
7252
7330
  const MAX_RECORDING_TIME_MS = 1000 * 60 * 60 * 60; // 1 hour
7253
7331
  const LEVEL_BAR_INTERVALL_SECONDS = 0.1; // 100ms
7254
- class BasicRecorder {
7255
- constructor(dialog, sessionService) {
7332
+ class ChunkManager {
7333
+ constructor(chunkAudioBufferReceiver) {
7334
+ this.chunkAudioBufferReceiver = chunkAudioBufferReceiver;
7335
+ this.channels = 0;
7336
+ this.sampleRate = -1;
7337
+ this.chunkIdx = 0;
7338
+ }
7339
+ set recordingFile(value) {
7340
+ this._rf = value;
7341
+ }
7342
+ close() {
7343
+ // Nothing to do
7344
+ }
7345
+ flush() {
7346
+ this.chunkAudioBufferReceiver.postAudioStreamEnd(this.chunkIdx);
7347
+ }
7348
+ nextStream() {
7349
+ // reset chunk counter
7350
+ this.chunkIdx = 0;
7351
+ this.chunkAudioBufferReceiver.postAudioStreamStart();
7352
+ }
7353
+ setFormat(channels, sampleRate) {
7354
+ this.channels = channels;
7355
+ this.sampleRate = sampleRate;
7356
+ }
7357
+ write(buffers) {
7358
+ let aCtx = AudioContextProvider.audioContextInstance();
7359
+ let bChs = buffers.length;
7360
+ let frameLen = 0;
7361
+ if (aCtx && bChs > 0) {
7362
+ frameLen = buffers[0].length;
7363
+ let ad = aCtx.createBuffer(this.channels, frameLen, this.sampleRate);
7364
+ for (let ch = 0; ch < this.channels; ch++) {
7365
+ ad.copyToChannel(buffers[ch], ch);
7366
+ }
7367
+ this.chunkAudioBufferReceiver.postChunkAudioBuffer(ad, this.chunkIdx);
7368
+ this.chunkIdx++;
7369
+ }
7370
+ return frameLen;
7371
+ }
7372
+ }
7373
+ let BasicRecorder = class BasicRecorder {
7374
+ constructor(dialog, sessionService, uploader, config) {
7256
7375
  this.dialog = dialog;
7257
7376
  this.sessionService = sessionService;
7377
+ this.uploader = uploader;
7378
+ this.config = config;
7258
7379
  this.statusMsg = '';
7259
7380
  this.statusWaiting = false;
7260
7381
  this.readonly = false;
7382
+ this.rfUuid = null;
7261
7383
  this.processingRecording = false;
7262
7384
  this.ac = null;
7263
7385
  this._wakeLock = false;
7264
7386
  this._selectedDeviceId = undefined;
7265
7387
  this._channelCount = 2;
7266
7388
  this._session = null;
7389
+ this.startedDate = null;
7267
7390
  this.uploadProgress = 100;
7268
7391
  this.uploadStatus = 'ok';
7269
7392
  this.audioSignalCollapsed = true;
@@ -7273,7 +7396,10 @@ class BasicRecorder {
7273
7396
  this.audioFetchSubscription = null;
7274
7397
  this.destroyed = false;
7275
7398
  this.navigationDisabled = true;
7399
+ // Default: Do not try to keep the device awake
7276
7400
  this.noSleep = null;
7401
+ // Default: Disabled chunked upload
7402
+ this._uploadChunkSizeSeconds = null;
7277
7403
  this.userAgent = UserAgentBuilder.userAgent();
7278
7404
  console.debug("Detected platform: " + this.userAgent.detectedPlatform);
7279
7405
  console.debug("Detected browser: " + this.userAgent.detectedBrowser);
@@ -7283,6 +7409,16 @@ class BasicRecorder {
7283
7409
  this.streamLevelMeasure = new StreamLevelMeasure();
7284
7410
  this.selCaptureDeviceId = null;
7285
7411
  }
7412
+ get uploadChunkSizeSeconds() {
7413
+ return this._uploadChunkSizeSeconds;
7414
+ }
7415
+ set uploadChunkSizeSeconds(value) {
7416
+ let oldValue = this.uploadChunkSizeSeconds;
7417
+ this._uploadChunkSizeSeconds = value;
7418
+ if (value !== oldValue) {
7419
+ this.configureStreamCaptureStream();
7420
+ }
7421
+ }
7286
7422
  get wakeLock() {
7287
7423
  return this._wakeLock;
7288
7424
  }
@@ -7296,12 +7432,14 @@ class BasicRecorder {
7296
7432
  }
7297
7433
  if (!this.noSleep.isEnabled) {
7298
7434
  this.noSleep.enable();
7435
+ console.debug("Enabled wake lock.");
7299
7436
  }
7300
7437
  }
7301
7438
  }
7302
7439
  disableWakeLockCond() {
7303
7440
  if (this.noSleep && this.noSleep.isEnabled) {
7304
7441
  this.noSleep.disable();
7442
+ console.debug("Disabled wake lock.");
7305
7443
  }
7306
7444
  }
7307
7445
  set audioDevices(audioDevices) {
@@ -7323,6 +7461,27 @@ class BasicRecorder {
7323
7461
  this.statusAlertType = 'info';
7324
7462
  this.statusMsg = 'Ready.';
7325
7463
  }
7464
+ configureStreamCaptureStream() {
7465
+ let outStream;
7466
+ if (this.uploadChunkSizeSeconds) {
7467
+ // Multiply the capture stream to...
7468
+ let sasm = new SequenceAudioFloat32OutStreamMultiplier();
7469
+ // ...upload audio data in chunks...
7470
+ let chMng = new ChunkManager(this); // The chunk manager connects the chunked audio stream to this class to upload the chunks
7471
+ let chOsUpload = new SequenceAudioFloat32ChunkerOutStream(chMng, this.uploadChunkSizeSeconds); // The audio stream chunker itself
7472
+ sasm.sequenceAudioFloat32OutStreams.push(chOsUpload);
7473
+ // ...and to measure the level
7474
+ let chOsLvlMeas = new SequenceAudioFloat32ChunkerOutStream(this.streamLevelMeasure, LEVEL_BAR_INTERVALL_SECONDS);
7475
+ sasm.sequenceAudioFloat32OutStreams.push(chOsLvlMeas);
7476
+ outStream = sasm;
7477
+ }
7478
+ else {
7479
+ outStream = new SequenceAudioFloat32ChunkerOutStream(this.streamLevelMeasure, LEVEL_BAR_INTERVALL_SECONDS);
7480
+ }
7481
+ if (this.ac) {
7482
+ this.ac.audioOutStream = outStream;
7483
+ }
7484
+ }
7326
7485
  start() {
7327
7486
  this.statusAlertType = 'info';
7328
7487
  this.statusMsg = 'Starting session...';
@@ -7529,6 +7688,73 @@ class BasicRecorder {
7529
7688
  });
7530
7689
  }
7531
7690
  }
7691
+ startItem() {
7692
+ this.startedDate = null;
7693
+ this.enableWakeLockCond();
7694
+ this.rfUuid = UUID.generate();
7695
+ this.transportActions.startAction.disabled = true;
7696
+ this.transportActions.pauseAction.disabled = true;
7697
+ if (this.readonly) {
7698
+ return;
7699
+ }
7700
+ }
7701
+ started() {
7702
+ if (!this.startedDate) {
7703
+ this.startedDate = new Date();
7704
+ }
7705
+ this.transportActions.startAction.disabled = true;
7706
+ }
7707
+ postRecording(wavFile, recUrl) {
7708
+ let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
7709
+ let ul = new Upload(wavBlob, recUrl);
7710
+ this.uploader.queueUpload(ul);
7711
+ }
7712
+ postAudioStreamStart() {
7713
+ if (this.rfUuid) {
7714
+ let apiEndPoint = '';
7715
+ if (this.config && this.config.apiEndPoint) {
7716
+ apiEndPoint = this.config.apiEndPoint;
7717
+ }
7718
+ if (apiEndPoint !== '') {
7719
+ apiEndPoint = apiEndPoint + '/';
7720
+ }
7721
+ let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
7722
+ let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.rfUuid + '/prepareChunksRequest';
7723
+ let fd = new FormData();
7724
+ // Note: At least one parameter must be set
7725
+ fd.set('uuid', this.rfUuid);
7726
+ if (!this.startedDate) {
7727
+ this.startedDate = new Date();
7728
+ }
7729
+ fd.set('startedDate', this.startedDate.toJSON());
7730
+ let ul = new Upload(fd, recUrl);
7731
+ this.uploader.queueUpload(ul);
7732
+ }
7733
+ else {
7734
+ console.error("Recording file UUID not set!");
7735
+ }
7736
+ }
7737
+ postAudioStreamEnd(chunkCount) {
7738
+ if (this.rfUuid) {
7739
+ let apiEndPoint = '';
7740
+ if (this.config && this.config.apiEndPoint) {
7741
+ apiEndPoint = this.config.apiEndPoint;
7742
+ }
7743
+ if (apiEndPoint !== '') {
7744
+ apiEndPoint = apiEndPoint + '/';
7745
+ }
7746
+ let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
7747
+ let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.rfUuid + '/concatChunksRequest';
7748
+ let fd = new FormData();
7749
+ fd.set('uuid', this.rfUuid);
7750
+ fd.set('chunkCount', chunkCount.toString());
7751
+ let ul = new Upload(fd, recUrl);
7752
+ this.uploader.queueUpload(ul);
7753
+ }
7754
+ else {
7755
+ console.error("Recording file UUID not set!");
7756
+ }
7757
+ }
7532
7758
  closed() {
7533
7759
  this.statusAlertType = 'info';
7534
7760
  this.statusMsg = 'Session closed.';
@@ -7545,7 +7771,11 @@ class BasicRecorder {
7545
7771
  }
7546
7772
  });
7547
7773
  }
7548
- }
7774
+ };
7775
+ BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS = 30;
7776
+ BasicRecorder = __decorate([
7777
+ __param(3, Inject(SPEECHRECORDER_CONFIG))
7778
+ ], BasicRecorder);
7549
7779
 
7550
7780
  /**
7551
7781
  * Created by klausj on 17.06.2017.
@@ -7855,7 +8085,7 @@ const DEFAULT_PRE_REC_DELAY = 1000;
7855
8085
  const DEFAULT_POST_REC_DELAY = 500;
7856
8086
  class SessionManager extends BasicRecorder {
7857
8087
  constructor(changeDetectorRef, renderer, dialog, sessionService, recFileService, uploader, config) {
7858
- super(dialog, sessionService);
8088
+ super(dialog, sessionService, uploader, config);
7859
8089
  this.changeDetectorRef = changeDetectorRef;
7860
8090
  this.renderer = renderer;
7861
8091
  this.dialog = dialog;
@@ -7958,7 +8188,7 @@ class SessionManager extends BasicRecorder {
7958
8188
  if (this.ac) {
7959
8189
  this.transportActions.startAction.onAction = () => this.startItem();
7960
8190
  this.ac.listener = this;
7961
- this.ac.audioOutStream = new SequenceAudioFloat32ChunkerOutStream(this.streamLevelMeasure, LEVEL_BAR_INTERVALL_SECONDS);
8191
+ this.configureStreamCaptureStream();
7962
8192
  // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
7963
8193
  //this.ac.listDevices();
7964
8194
  }
@@ -8104,12 +8334,7 @@ class SessionManager extends BasicRecorder {
8104
8334
  }
8105
8335
  }
8106
8336
  startItem() {
8107
- this.enableWakeLockCond();
8108
- this.transportActions.startAction.disabled = true;
8109
- this.transportActions.pauseAction.disabled = true;
8110
- if (this.readonly) {
8111
- return;
8112
- }
8337
+ super.startItem();
8113
8338
  this.transportActions.fwdAction.disabled = true;
8114
8339
  this.transportActions.fwdNextAction.disabled = true;
8115
8340
  this.transportActions.bwdAction.disabled = true;
@@ -8408,8 +8633,8 @@ class SessionManager extends BasicRecorder {
8408
8633
  }
8409
8634
  }
8410
8635
  started() {
8636
+ super.started();
8411
8637
  this.status = 2 /* PRE_RECORDING */;
8412
- this.transportActions.startAction.disabled = true;
8413
8638
  this.startStopSignalState = 1 /* PRERECORDING */;
8414
8639
  if (this._session) {
8415
8640
  if (this._session.status === "LOADED") {
@@ -8578,7 +8803,7 @@ class SessionManager extends BasicRecorder {
8578
8803
  rf = new SprRecordingFile(sessId, ic, it.recs.length, ad);
8579
8804
  it.recs.push(rf);
8580
8805
  }
8581
- if (this.enableUploadRecordings) {
8806
+ if (this.enableUploadRecordings && !this.uploadChunkSizeSeconds) {
8582
8807
  // TODO use SpeechRecorderconfig resp. RecfileService
8583
8808
  // convert asynchronously to 16-bit integer PCM
8584
8809
  // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
@@ -8657,6 +8882,24 @@ class SessionManager extends BasicRecorder {
8657
8882
  }
8658
8883
  this.changeDetectorRef.detectChanges();
8659
8884
  }
8885
+ postChunkAudioBuffer(audioBuffer, chunkIdx) {
8886
+ this.processingRecording = true;
8887
+ let ww = new WavWriter();
8888
+ //new REST API URL
8889
+ let apiEndPoint = '';
8890
+ if (this.config && this.config.apiEndPoint) {
8891
+ apiEndPoint = this.config.apiEndPoint;
8892
+ }
8893
+ if (apiEndPoint !== '') {
8894
+ apiEndPoint = apiEndPoint + '/';
8895
+ }
8896
+ let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
8897
+ let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.promptItem.itemcode + '/' + this.rfUuid + '/' + chunkIdx;
8898
+ ww.writeAsync(audioBuffer, (wavFile) => {
8899
+ this.postRecording(wavFile, recUrl);
8900
+ this.processingRecording = false;
8901
+ });
8902
+ }
8660
8903
  postRecording(wavFile, recUrl) {
8661
8904
  let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
8662
8905
  let ul = new Upload(wavBlob, recUrl);
@@ -8834,6 +9077,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
8834
9077
  }] } });
8835
9078
 
8836
9079
  class RecorderComponent {
9080
+ constructor(uploader) {
9081
+ this.uploader = uploader;
9082
+ this.dataSaved = true;
9083
+ }
8837
9084
  }
8838
9085
 
8839
9086
  /**
@@ -8884,7 +9131,7 @@ var Mode;
8884
9131
  })(Mode || (Mode = {}));
8885
9132
  class SpeechrecorderngComponent extends RecorderComponent {
8886
9133
  constructor(route, router, changeDetectorRef, sessionsService, projectService, scriptService, recFilesService, uploader) {
8887
- super();
9134
+ super(uploader);
8888
9135
  this.route = route;
8889
9136
  this.router = router;
8890
9137
  this.changeDetectorRef = changeDetectorRef;
@@ -8894,7 +9141,6 @@ class SpeechrecorderngComponent extends RecorderComponent {
8894
9141
  this.recFilesService = recFilesService;
8895
9142
  this.uploader = uploader;
8896
9143
  this._project = null;
8897
- this.dataSaved = true;
8898
9144
  }
8899
9145
  ngOnInit() {
8900
9146
  try {
@@ -8937,39 +9183,39 @@ class SpeechrecorderngComponent extends RecorderComponent {
8937
9183
  }
8938
9184
  }
8939
9185
  fetchSession(sessionId) {
8940
- //Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'statusMsg: Player initialized.'. Current value: 'statusMsg: Fetching session info...'.
8941
- // params.subscribe seems not to be asynchronous
8942
- // this.sm.statusAlertType='info';
8943
- // this.sm.statusMsg = 'Fetching session info...';
8944
- // this.sm.statusWaiting=true;
8945
9186
  let sessObs = this.sessionsService.sessionObserver(sessionId);
8946
9187
  if (sessObs) {
8947
- sessObs.subscribe(sess => {
8948
- this.setSession(sess);
8949
- this.sm.statusAlertType = 'info';
8950
- this.sm.statusMsg = 'Received session info.';
8951
- this.sm.statusWaiting = false;
8952
- if (sess.project) {
8953
- //console.debug("Session associated project: "+sess.project)
8954
- this.projectService.projectObservable(sess.project).subscribe(project => {
8955
- this.project = project;
9188
+ sessObs.subscribe({
9189
+ next: sess => {
9190
+ this.setSession(sess);
9191
+ this.sm.statusAlertType = 'info';
9192
+ this.sm.statusMsg = 'Received session info.';
9193
+ this.sm.statusWaiting = false;
9194
+ if (sess.project) {
9195
+ //console.debug("Session associated project: "+sess.project)
9196
+ this.projectService.projectObservable(sess.project).subscribe({
9197
+ next: (project) => {
9198
+ this.project = project;
9199
+ this.fetchScript(sess);
9200
+ }, error: (reason) => {
9201
+ this.sm.statusMsg = reason;
9202
+ this.sm.statusAlertType = 'error';
9203
+ this.sm.statusWaiting = false;
9204
+ console.error("Error fetching project config: " + reason);
9205
+ }
9206
+ });
9207
+ }
9208
+ else {
9209
+ console.info("Session has no associated project. Using default configuration.");
8956
9210
  this.fetchScript(sess);
8957
- }, reason => {
8958
- this.sm.statusMsg = reason;
8959
- this.sm.statusAlertType = 'error';
8960
- this.sm.statusWaiting = false;
8961
- console.error("Error fetching project config: " + reason);
8962
- });
9211
+ }
9212
+ },
9213
+ error: (reason) => {
9214
+ this.sm.statusMsg = reason;
9215
+ this.sm.statusAlertType = 'error';
9216
+ this.sm.statusWaiting = false;
9217
+ console.error("Error fetching session " + reason);
8963
9218
  }
8964
- else {
8965
- console.info("Session has no associated project. Using default configuration.");
8966
- this.fetchScript(sess);
8967
- }
8968
- }, (reason) => {
8969
- this.sm.statusMsg = reason;
8970
- this.sm.statusAlertType = 'error';
8971
- this.sm.statusWaiting = false;
8972
- console.error("Error fetching session " + reason);
8973
9219
  });
8974
9220
  }
8975
9221
  }
@@ -8978,19 +9224,21 @@ class SpeechrecorderngComponent extends RecorderComponent {
8978
9224
  this.sm.statusAlertType = 'info';
8979
9225
  this.sm.statusMsg = 'Fetching recording script...';
8980
9226
  this.sm.statusWaiting = true;
8981
- this.scriptService.scriptObservable(sess.script).subscribe(script => {
8982
- this.sm.statusAlertType = 'info';
8983
- this.sm.statusMsg = 'Received recording script.';
8984
- this.sm.statusWaiting = false;
8985
- this.setScript(script);
8986
- this.sm.session = sess;
8987
- this.fetchRecordings(sess, this.script);
8988
- }, reason => {
8989
- let errMsg = "Error fetching recording script: " + reason;
8990
- console.error(errMsg);
8991
- this.sm.statusMsg = errMsg;
8992
- this.sm.statusAlertType = 'error';
8993
- this.sm.statusWaiting = false;
9227
+ this.scriptService.scriptObservable(sess.script).subscribe({
9228
+ next: (script) => {
9229
+ this.sm.statusAlertType = 'info';
9230
+ this.sm.statusMsg = 'Received recording script.';
9231
+ this.sm.statusWaiting = false;
9232
+ this.setScript(script);
9233
+ this.sm.session = sess;
9234
+ this.fetchRecordings(sess, this.script);
9235
+ }, error: (reason) => {
9236
+ let errMsg = "Error fetching recording script: " + reason;
9237
+ console.error(errMsg);
9238
+ this.sm.statusMsg = errMsg;
9239
+ this.sm.statusAlertType = 'error';
9240
+ this.sm.statusWaiting = false;
9241
+ }
8994
9242
  });
8995
9243
  }
8996
9244
  else {
@@ -9007,29 +9255,31 @@ class SpeechrecorderngComponent extends RecorderComponent {
9007
9255
  let prNm = null;
9008
9256
  if (this.project) {
9009
9257
  let rfsObs = this.recFilesService.recordingFileDescrList(this.project.name, sess.sessionId);
9010
- rfsObs.subscribe((rfs) => {
9011
- this.sm.statusAlertType = 'info';
9012
- this.sm.statusMsg = 'Received infos of recordings.';
9013
- this.sm.statusWaiting = false;
9014
- if (rfs) {
9015
- if (rfs instanceof Array) {
9016
- rfs.forEach((rf) => {
9017
- //console.debug("Already recorded: " + rf+ " "+rf.recording.itemcode);
9018
- this.sm.addRecordingFileByDescriptor(rf);
9019
- });
9258
+ rfsObs.subscribe({
9259
+ next: (rfs) => {
9260
+ this.sm.statusAlertType = 'info';
9261
+ this.sm.statusMsg = 'Received infos of recordings.';
9262
+ this.sm.statusWaiting = false;
9263
+ if (rfs) {
9264
+ if (rfs instanceof Array) {
9265
+ rfs.forEach((rf) => {
9266
+ //console.debug("Already recorded: " + rf+ " "+rf.recording.itemcode);
9267
+ this.sm.addRecordingFileByDescriptor(rf);
9268
+ });
9269
+ }
9270
+ else {
9271
+ console.error('Expected type array for list of already recorded files ');
9272
+ }
9020
9273
  }
9021
9274
  else {
9022
- console.error('Expected type array for list of already recorded files ');
9275
+ //console.debug("Recording file list: " + rfs);
9023
9276
  }
9277
+ }, error: (err) => {
9278
+ // we start the session anyway
9279
+ this.startSession();
9280
+ }, complete: () => {
9281
+ this.startSession();
9024
9282
  }
9025
- else {
9026
- //console.debug("Recording file list: " + rfs);
9027
- }
9028
- }, () => {
9029
- // we start the session anyway
9030
- this.startSession();
9031
- }, () => {
9032
- this.startSession();
9033
9283
  });
9034
9284
  }
9035
9285
  else {
@@ -9052,36 +9302,10 @@ class SpeechrecorderngComponent extends RecorderComponent {
9052
9302
  return this.dataSaved && !this.sm.isActive();
9053
9303
  }
9054
9304
  init() {
9055
- var n = navigator;
9056
- //var getUserMediaFnct= n.getUserMedia || n.webkitGetUserMedia ||
9057
- // n.mozGetUserMedia || n.msGetUserMedia;
9058
- var w = window;
9059
- // TODO test onyl !!
9060
- // let debugFail=true;
9061
- // AudioContext = w.AudioContext || w.webkitAudioContext;
9062
- // if (typeof AudioContext !== 'function' || debugFail) {
9063
- // this.sm.statusAlertType='error';
9064
- // this.sm.statusMsg = 'ERROR: Browser does not support Web Audio API!';
9065
- // return false;
9066
- // } else {
9067
- // var context = new AudioContext();
9068
- //
9069
- // if (typeof navigator.mediaDevices.getUserMedia !== 'function') {
9070
- // this.sm.statusAlertType='error';
9071
- // this.sm.statusMsg= 'ERROR: Browser does not support Media streams!';
9072
- // return false;
9073
- // } else {
9074
- //
9075
- //this.sm = new SessionManager(new SimpleTrafficLight(), this.uploader);
9076
- //this.sm.init();
9077
- //this.ap = new AudioPlayer(context,this);
9078
- //this.sm.listener=this;
9079
- // }
9080
- // }
9305
+ //TODO Duplicate code in AudioRecorderComponent
9081
9306
  this.uploader.listener = (ue) => {
9082
9307
  this.uploadUpdate(ue);
9083
9308
  };
9084
- //TODO Duplicate code in AudioRecorderComponent
9085
9309
  window.addEventListener('beforeunload', (e) => {
9086
9310
  console.debug("Before page unload event");
9087
9311
  if (this.ready()) {
@@ -9170,6 +9394,13 @@ class SpeechrecorderngComponent extends RecorderComponent {
9170
9394
  chCnt = ProjectUtil.audioChannelCount(project);
9171
9395
  console.info("Project requested recording channel count: " + chCnt);
9172
9396
  this.sm.autoGainControlConfigs = project.autoGainControlConfigs;
9397
+ if (project.chunkedRecording === true) {
9398
+ console.debug("Enable chunked upload: chunkSize: " + BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS);
9399
+ this.sm.uploadChunkSizeSeconds = BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS;
9400
+ }
9401
+ else {
9402
+ this.sm.uploadChunkSizeSeconds = null;
9403
+ }
9173
9404
  }
9174
9405
  else {
9175
9406
  console.error("Empty project configuration!");
@@ -9206,50 +9437,7 @@ class SpeechrecorderngComponent extends RecorderComponent {
9206
9437
  pLoader.send();
9207
9438
  }
9208
9439
  }
9209
- // loadScript(callback: ()=>any){
9210
- // var scrLoader = new XMLHttpRequest();
9211
- // let hn:string =window.location.hostname;
9212
- // let pn:string =window.location.pathname;
9213
- // let pr:string =window.location.protocol;
9214
- // let po:string =window.location.port;
9215
- //
9216
- // let sessionId:string=this.session.sessionId;
9217
- //
9218
- // let scrUrl:string | null = null;
9219
- //
9220
- // if (hn === 'localhost' || this.mode===Mode.DEMO) {
9221
- // // local debug mode
9222
- // // FF caches
9223
- // scrUrl = 'test/' + this.session.script.toString() + '.json?' + new Date().getTime();
9224
- //
9225
- // } else {
9226
- // let scrPath:string = '/wikispeech/session/scripts/servlet';
9227
- // let scrQu:string = 'sessionId=' + sessionId;
9228
- // scrUrl = pr + '//' + hn + ':' + po + scrPath;
9229
- // if (scrQu) {
9230
- // scrUrl = scrUrl + '?' + scrQu;
9231
- // }
9232
- // }
9233
- //
9234
- // scrLoader.open("GET",scrUrl , true);
9235
- // scrLoader.setRequestHeader('Accept','application/json');
9236
- // scrLoader.responseType = "json";
9237
- // scrLoader.onload = (e) => {
9238
- //
9239
- // this.script = scrLoader.response;
9240
- //
9241
- //
9242
- // this.sm.script = this.script;
9243
- //
9244
- // callback();
9245
- // }
9246
- // scrLoader.onerror = (e) => {
9247
- // console.log("Error downloading recording script data ...");
9248
- // }
9249
- // scrLoader.send();
9250
- // }
9251
9440
  start() {
9252
- //this.configure();
9253
9441
  }
9254
9442
  audioPlayerUpdate(e) {
9255
9443
  if (EventType.STARTED === e.type) {
@@ -10929,13 +11117,12 @@ class Item {
10929
11117
  }
10930
11118
  }
10931
11119
  class AudioRecorder extends BasicRecorder {
10932
- constructor(changeDetectorRef, renderer, route, dialog, projectService, sessionService, recFileService, uploader, config) {
10933
- super(dialog, sessionService);
11120
+ constructor(changeDetectorRef, renderer, route, dialog, sessionService, recFileService, uploader, config) {
11121
+ super(dialog, sessionService, uploader, config);
10934
11122
  this.changeDetectorRef = changeDetectorRef;
10935
11123
  this.renderer = renderer;
10936
11124
  this.route = route;
10937
11125
  this.dialog = dialog;
10938
- this.projectService = projectService;
10939
11126
  this.sessionService = sessionService;
10940
11127
  this.recFileService = recFileService;
10941
11128
  this.uploader = uploader;
@@ -10946,7 +11133,6 @@ class AudioRecorder extends BasicRecorder {
10946
11133
  this.enableDownloadRecordings = false;
10947
11134
  this.status = 0 /* BLOCKED */;
10948
11135
  this.dataSaved = true;
10949
- this.startedDate = null;
10950
11136
  this.maxRecTimerId = null;
10951
11137
  this.maxRecTimerRunning = false;
10952
11138
  this._promptIndex = null;
@@ -11037,7 +11223,7 @@ class AudioRecorder extends BasicRecorder {
11037
11223
  if (this.ac) {
11038
11224
  this.transportActions.startAction.onAction = () => this.startItem();
11039
11225
  this.ac.listener = this;
11040
- this.ac.audioOutStream = new SequenceAudioFloat32ChunkerOutStream(this.streamLevelMeasure, LEVEL_BAR_INTERVALL_SECONDS);
11226
+ this.configureStreamCaptureStream();
11041
11227
  // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
11042
11228
  //this.ac.listDevices();
11043
11229
  }
@@ -11064,23 +11250,6 @@ class AudioRecorder extends BasicRecorder {
11064
11250
  this.uploader.listener = (ue) => {
11065
11251
  this.uploadUpdate(ue);
11066
11252
  };
11067
- let wakeLockSupp = ('wakeLock' in navigator);
11068
- // if(wakeLockSupp) {
11069
- // let wakeLock = null;
11070
- // try {
11071
- // //@ts-ignore
11072
- // wakeLock = navigator.wakeLock.request('screen');
11073
- //
11074
- // //statusElem.textContent = 'Wake Lock is active!';
11075
- // } catch (err) {
11076
- // // The Wake Lock request has failed - usually system related, such as battery.
11077
- // console.error('Wakelock failed'+err)
11078
- // }
11079
- // }else{
11080
- // let noSleep=new NoSleep();
11081
- // noSleep.enable();
11082
- //
11083
- // }
11084
11253
  }
11085
11254
  onKeyPress(ke) {
11086
11255
  if (ke.key == ' ') {
@@ -11133,36 +11302,36 @@ class AudioRecorder extends BasicRecorder {
11133
11302
  let prNm = null;
11134
11303
  if (this.project) {
11135
11304
  let rfsObs = this.recFileService.recordingFileList(this.project.name, sess.sessionId);
11136
- rfsObs.subscribe((rfs) => {
11137
- this.statusAlertType = 'info';
11138
- this.statusMsg = 'Received infos of recordings.';
11139
- this.statusWaiting = false;
11140
- if (rfs) {
11141
- if (rfs instanceof Array) {
11142
- rfs.forEach((rf) => {
11143
- if (rf.startedDate) {
11144
- rf._startedAsDateObj = new Date(rf.startedDate);
11145
- }
11146
- if (rf.date) {
11147
- rf._dateAsDateObj = new Date(rf.date);
11148
- }
11149
- this.recorderCombiPane.push(rf);
11150
- });
11305
+ rfsObs.subscribe({ next: (rfs) => {
11306
+ this.statusAlertType = 'info';
11307
+ this.statusMsg = 'Received infos of recordings.';
11308
+ this.statusWaiting = false;
11309
+ if (rfs) {
11310
+ if (rfs instanceof Array) {
11311
+ rfs.forEach((rf) => {
11312
+ if (rf.startedDate) {
11313
+ rf._startedAsDateObj = new Date(rf.startedDate);
11314
+ }
11315
+ if (rf.date) {
11316
+ rf._dateAsDateObj = new Date(rf.date);
11317
+ }
11318
+ this.recorderCombiPane.push(rf);
11319
+ });
11320
+ }
11321
+ else {
11322
+ console.error('Expected type array for list of already recorded files ');
11323
+ }
11151
11324
  }
11152
11325
  else {
11153
- console.error('Expected type array for list of already recorded files ');
11326
+ //console.debug("Recording file list: " + rfs);
11154
11327
  }
11155
- }
11156
- else {
11157
- //console.debug("Recording file list: " + rfs);
11158
- }
11159
- }, () => {
11160
- // Failed fetching existing, but we start the session anyway
11161
- this.start();
11162
- }, () => {
11163
- // Normal start
11164
- this.start();
11165
- });
11328
+ }, error: (err) => {
11329
+ // Failed fetching existing, but we start the session anyway
11330
+ this.start();
11331
+ }, complete: () => {
11332
+ // Normal start
11333
+ this.start();
11334
+ } });
11166
11335
  }
11167
11336
  else {
11168
11337
  // No project def -> error
@@ -11184,6 +11353,12 @@ class AudioRecorder extends BasicRecorder {
11184
11353
  chCnt = ProjectUtil.audioChannelCount(project);
11185
11354
  console.info("Project requested recording channel count: " + chCnt);
11186
11355
  this.autoGainControlConfigs = project.autoGainControlConfigs;
11356
+ if (project.chunkedRecording === true) {
11357
+ this.uploadChunkSizeSeconds = BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS;
11358
+ }
11359
+ else {
11360
+ this.uploadChunkSizeSeconds = null;
11361
+ }
11187
11362
  }
11188
11363
  else {
11189
11364
  console.error("Empty project configuration!");
@@ -11283,12 +11458,7 @@ class AudioRecorder extends BasicRecorder {
11283
11458
  }
11284
11459
  }
11285
11460
  startItem() {
11286
- this.enableWakeLockCond();
11287
- this.transportActions.startAction.disabled = true;
11288
- this.transportActions.pauseAction.disabled = true;
11289
- if (this.readonly) {
11290
- return;
11291
- }
11461
+ super.startItem();
11292
11462
  this.transportActions.fwdAction.disabled = true;
11293
11463
  this.transportActions.fwdNextAction.disabled = true;
11294
11464
  this.transportActions.bwdAction.disabled = true;
@@ -11440,8 +11610,7 @@ class AudioRecorder extends BasicRecorder {
11440
11610
  }
11441
11611
  }
11442
11612
  started() {
11443
- this.startedDate = new Date();
11444
- this.transportActions.startAction.disabled = true;
11613
+ super.started();
11445
11614
  this.statusAlertType = 'info';
11446
11615
  this.statusMsg = 'Recording...';
11447
11616
  let maxRecordingTimeMs = MAX_RECORDING_TIME_MS;
@@ -11474,24 +11643,6 @@ class AudioRecorder extends BasicRecorder {
11474
11643
  this.ac.stop();
11475
11644
  }
11476
11645
  }
11477
- // addRecordingFileByDescriptor(rfd:RecordingFileDescriptorImpl){
11478
- // let prIdx=0;
11479
- // if(this.items) {
11480
- // let it = this.items[prIdx];
11481
- // if (it) {
11482
- // if (!it.recs) {
11483
- // it.recs = new Array<SprRecordingFile>();
11484
- // }
11485
- //
11486
- // } else {
11487
- // //console.debug("WARN: No recording item with code: \"" +rfd.recording.itemcode+ "\" found.");
11488
- // }
11489
- // }
11490
- // }
11491
- //
11492
- // addRecordingFileByPromptIndex(promptIndex:number, rf:SprRecordingFile){
11493
- //
11494
- // }
11495
11646
  stopped() {
11496
11647
  this.updateStartActionDisableState();
11497
11648
  this.transportActions.stopAction.disabled = true;
@@ -11506,41 +11657,39 @@ class AudioRecorder extends BasicRecorder {
11506
11657
  if (this._session) {
11507
11658
  sessId = this._session.sessionId;
11508
11659
  }
11509
- let rf = new RecordingFile(UUID.generate(), sessId, ad);
11660
+ if (!this.rfUuid) {
11661
+ this.rfUuid = UUID.generate();
11662
+ }
11663
+ let rf = new RecordingFile(this.rfUuid, sessId, ad);
11510
11664
  rf._startedAsDateObj = this.startedDate;
11511
11665
  if (rf._startedAsDateObj) {
11512
11666
  rf.startedDate = rf._startedAsDateObj.toString();
11513
11667
  }
11514
- let apiEndPoint = '';
11515
- if (this.config && this.config.apiEndPoint) {
11516
- apiEndPoint = this.config.apiEndPoint;
11517
- }
11518
- if (apiEndPoint !== '') {
11519
- apiEndPoint = apiEndPoint + '/';
11668
+ rf.frames = ad.length;
11669
+ this.displayRecFile = rf;
11670
+ this.recorderCombiPane.push(rf);
11671
+ // Upload if upload enabled and not in chunked upload mode
11672
+ if (this.enableUploadRecordings && !this.uploadChunkSizeSeconds) {
11673
+ let apiEndPoint = '';
11674
+ if (this.config && this.config.apiEndPoint) {
11675
+ apiEndPoint = this.config.apiEndPoint;
11676
+ }
11677
+ if (apiEndPoint !== '') {
11678
+ apiEndPoint = apiEndPoint + '/';
11679
+ }
11680
+ let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
11681
+ let recUrl = sessionsUrl + '/' + rf.session + '/' + RECFILE_API_CTX + '/' + rf.uuid;
11682
+ // convert asynchronously to 16-bit integer PCM
11683
+ // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
11684
+ // TODO duplicate conversion for manual download
11685
+ this.processingRecording = true;
11686
+ let ww = new WavWriter();
11687
+ ww.writeAsync(ad, (wavFile) => {
11688
+ this.postRecordingMultipart(wavFile, rf.uuid, rf.session, rf._startedAsDateObj, recUrl);
11689
+ this.processingRecording = false;
11690
+ this.changeDetectorRef.detectChanges();
11691
+ });
11520
11692
  }
11521
- let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
11522
- let recUrl = sessionsUrl + '/' + rf.session + '/' + RECFILE_API_CTX + '/' + rf.uuid;
11523
- //
11524
- //
11525
- //
11526
- // // convert asynchronously to 16-bit integer PCM
11527
- // // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
11528
- // // TODO duplicate conversion for manual download
11529
- // //console.log("Build wav writer...");
11530
- this.processingRecording = true;
11531
- let ww = new WavWriter();
11532
- ww.writeAsync(ad, (wavFile) => {
11533
- //this.postRecording(wavFile, recUrl);
11534
- //rf._dateAsDateObj=new Date();
11535
- rf.frames = ad.length;
11536
- this.displayRecFile = rf;
11537
- //this.recordingListComp.recordingList.push(rf);
11538
- this.recorderCombiPane.push(rf);
11539
- this.postRecordingMultipart(wavFile, rf.uuid, rf.session, rf._startedAsDateObj, recUrl);
11540
- this.processingRecording = false;
11541
- this.changeDetectorRef.detectChanges();
11542
- });
11543
- // }
11544
11693
  }
11545
11694
  // check complete session
11546
11695
  let complete = true;
@@ -11551,11 +11700,6 @@ class AudioRecorder extends BasicRecorder {
11551
11700
  this.updateNavigationActions();
11552
11701
  this.changeDetectorRef.detectChanges();
11553
11702
  }
11554
- postRecording(wavFile, recUrl) {
11555
- let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
11556
- let ul = new Upload(wavBlob, recUrl);
11557
- this.uploader.queueUpload(ul);
11558
- }
11559
11703
  postRecordingMultipart(wavFile, uuid, sessionId, startedDate, recUrl) {
11560
11704
  let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
11561
11705
  let fd = new FormData();
@@ -11572,6 +11716,24 @@ class AudioRecorder extends BasicRecorder {
11572
11716
  let ul = new Upload(fd, recUrl);
11573
11717
  this.uploader.queueUpload(ul);
11574
11718
  }
11719
+ postChunkAudioBuffer(audioBuffer, chunkIdx) {
11720
+ this.processingRecording = true;
11721
+ let ww = new WavWriter();
11722
+ //new REST API URL
11723
+ let apiEndPoint = '';
11724
+ if (this.config && this.config.apiEndPoint) {
11725
+ apiEndPoint = this.config.apiEndPoint;
11726
+ }
11727
+ if (apiEndPoint !== '') {
11728
+ apiEndPoint = apiEndPoint + '/';
11729
+ }
11730
+ let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
11731
+ let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.rfUuid + '/' + chunkIdx;
11732
+ ww.writeAsync(audioBuffer, (wavFile) => {
11733
+ this.postRecording(wavFile, recUrl);
11734
+ this.processingRecording = false;
11735
+ });
11736
+ }
11575
11737
  stop() {
11576
11738
  if (this.ac) {
11577
11739
  this.ac.close();
@@ -11599,8 +11761,8 @@ class AudioRecorder extends BasicRecorder {
11599
11761
  }
11600
11762
  }
11601
11763
  }
11602
- AudioRecorder.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.2", ngImport: i0, type: AudioRecorder, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }, { token: i1$1.ActivatedRoute }, { token: i1$2.MatDialog }, { token: ProjectService }, { token: SessionService }, { token: RecordingService }, { token: SpeechRecorderUploader }, { token: SPEECHRECORDER_CONFIG }], target: i0.ɵɵFactoryTarget.Component });
11603
- AudioRecorder.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.2", type: AudioRecorder, selector: "app-audiorecorder", inputs: { _project: "_project", projectName: "projectName", dataSaved: "dataSaved" }, host: { listeners: { "window:keypress": "onKeyPress($event)", "window:keydown": "onKeyDown($event)" } }, providers: [SessionService], viewQueries: [{ propertyName: "recorderCombiPane", first: true, predicate: RecorderCombiPane, descendants: true, static: true }, { propertyName: "liveLevelDisplay", first: true, predicate: LevelBar, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
11764
+ AudioRecorder.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.2", ngImport: i0, type: AudioRecorder, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }, { token: i1$1.ActivatedRoute }, { token: i1$2.MatDialog }, { token: SessionService }, { token: RecordingService }, { token: SpeechRecorderUploader }, { token: SPEECHRECORDER_CONFIG }], target: i0.ɵɵFactoryTarget.Component });
11765
+ AudioRecorder.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.2", type: AudioRecorder, selector: "app-audiorecorder", inputs: { projectName: "projectName", dataSaved: "dataSaved" }, host: { listeners: { "window:keypress": "onKeyPress($event)", "window:keydown": "onKeyDown($event)" } }, providers: [SessionService], viewQueries: [{ propertyName: "recorderCombiPane", first: true, predicate: RecorderCombiPane, descendants: true, static: true }, { propertyName: "liveLevelDisplay", first: true, predicate: LevelBar, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
11604
11766
  <app-warningbar [show]="isTestSession()" warningText="Test recording only!"></app-warningbar>
11605
11767
  <app-warningbar [show]="isDefaultAudioTestSession()"
11606
11768
  warningText="This test uses default audio device! Regular sessions may require a particular audio device (microphone)!"></app-warningbar>
@@ -11755,12 +11917,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11755
11917
  }`
11756
11918
  ]
11757
11919
  }]
11758
- }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }, { type: i1$1.ActivatedRoute }, { type: i1$2.MatDialog }, { type: ProjectService }, { type: SessionService }, { type: RecordingService }, { type: SpeechRecorderUploader }, { type: SpeechRecorderConfig, decorators: [{
11920
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }, { type: i1$1.ActivatedRoute }, { type: i1$2.MatDialog }, { type: SessionService }, { type: RecordingService }, { type: SpeechRecorderUploader }, { type: SpeechRecorderConfig, decorators: [{
11759
11921
  type: Inject,
11760
11922
  args: [SPEECHRECORDER_CONFIG]
11761
- }] }]; }, propDecorators: { _project: [{
11762
- type: Input
11763
- }], projectName: [{
11923
+ }] }]; }, propDecorators: { projectName: [{
11764
11924
  type: Input
11765
11925
  }], recorderCombiPane: [{
11766
11926
  type: ViewChild,
@@ -11778,16 +11938,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11778
11938
  args: ['window:keydown', ['$event']]
11779
11939
  }] } });
11780
11940
  class AudioRecorderComponent extends RecorderComponent {
11781
- constructor(injector, route, router, changeDetectorRef, sessionService, projectService, recFilesService) {
11782
- super();
11941
+ constructor(injector, route, router, changeDetectorRef, sessionService, projectService, uploader) {
11942
+ super(uploader);
11783
11943
  this.injector = injector;
11784
11944
  this.route = route;
11785
11945
  this.router = router;
11786
11946
  this.changeDetectorRef = changeDetectorRef;
11787
11947
  this.sessionService = sessionService;
11788
11948
  this.projectService = projectService;
11789
- this.recFilesService = recFilesService;
11790
- this.dataSaved = true;
11949
+ this.uploader = uploader;
11791
11950
  }
11792
11951
  ngOnInit() {
11793
11952
  //super.ngOnInit();
@@ -11822,6 +11981,9 @@ class AudioRecorderComponent extends RecorderComponent {
11822
11981
  });
11823
11982
  }
11824
11983
  ngAfterViewInit() {
11984
+ this.uploader.listener = (ue) => {
11985
+ this.uploadUpdate(ue);
11986
+ };
11825
11987
  this.route.queryParams.subscribe((params) => {
11826
11988
  if (params['sessionId']) {
11827
11989
  this.fetchSession(params['sessionId']);
@@ -11838,50 +12000,68 @@ class AudioRecorderComponent extends RecorderComponent {
11838
12000
  //super.ngOnDestroy();
11839
12001
  }
11840
12002
  fetchSession(sessionId) {
11841
- //Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'statusMsg: Player initialized.'. Current value: 'statusMsg: Fetching session info...'.
11842
- // params.subscribe seems not to be asynchronous
11843
- // this.sm.statusAlertType='info';
11844
- // this.sm.statusMsg = 'Fetching session info...';
11845
- // this.sm.statusWaiting=true;
11846
12003
  let sessObs = this.sessionService.sessionObserver(sessionId);
11847
12004
  if (sessObs) {
11848
- sessObs.subscribe(sess => {
11849
- this.ar.statusAlertType = 'info';
11850
- this.ar.statusMsg = 'Received session info.';
11851
- this.ar.statusWaiting = false;
11852
- this.session = sess;
11853
- this.ar.session = sess;
11854
- if (sess.project) {
11855
- //console.debug("Session associated project: "+sess.project)
11856
- this.projectService.projectObservable(sess.project).subscribe(project => {
11857
- this.ar.project = project;
11858
- this.ar.fetchRecordings(sess);
11859
- }, reason => {
11860
- this.ar.statusMsg = reason;
11861
- this.ar.statusAlertType = 'error';
11862
- this.ar.statusWaiting = false;
11863
- console.error("Error fetching project config: " + reason);
11864
- });
11865
- }
11866
- else {
11867
- console.info("Session has no associated project. Using default configuration.");
12005
+ sessObs.subscribe({
12006
+ next: (sess) => {
12007
+ this.ar.statusAlertType = 'info';
12008
+ this.ar.statusMsg = 'Received session info.';
12009
+ this.ar.statusWaiting = false;
12010
+ this.session = sess;
12011
+ this.ar.session = sess;
12012
+ if (sess.project) {
12013
+ //console.debug("Session associated project: "+sess.project)
12014
+ this.projectService.projectObservable(sess.project).subscribe({
12015
+ next: (project) => {
12016
+ this.ar.project = project;
12017
+ this.ar.fetchRecordings(sess);
12018
+ }, error: (reason) => {
12019
+ this.ar.statusMsg = reason;
12020
+ this.ar.statusAlertType = 'error';
12021
+ this.ar.statusWaiting = false;
12022
+ console.error("Error fetching project config: " + reason);
12023
+ }
12024
+ });
12025
+ }
12026
+ else {
12027
+ console.info("Session has no associated project. Using default configuration.");
12028
+ }
12029
+ },
12030
+ error: (reason) => {
12031
+ this.ar.statusMsg = reason;
12032
+ this.ar.statusAlertType = 'error';
12033
+ this.ar.statusWaiting = false;
12034
+ console.error("Error fetching session " + reason);
11868
12035
  }
11869
- }, (reason) => {
11870
- this.ar.statusMsg = reason;
11871
- this.ar.statusAlertType = 'error';
11872
- this.ar.statusWaiting = false;
11873
- console.error("Error fetching session " + reason);
11874
12036
  });
11875
12037
  }
11876
12038
  }
12039
+ uploadUpdate(ue) {
12040
+ let upStatus = ue.status;
12041
+ this.dataSaved = (UploaderStatus.DONE === upStatus);
12042
+ let percentUpl = ue.percentDone();
12043
+ if (UploaderStatus.ERR === upStatus) {
12044
+ this.ar.uploadStatus = 'warn';
12045
+ }
12046
+ else {
12047
+ if (percentUpl < 50) {
12048
+ this.ar.uploadStatus = 'accent';
12049
+ }
12050
+ else {
12051
+ this.ar.uploadStatus = 'success';
12052
+ }
12053
+ this.ar.uploadProgress = percentUpl;
12054
+ }
12055
+ this.changeDetectorRef.detectChanges();
12056
+ }
11877
12057
  ready() {
11878
12058
  return this.dataSaved && !this.ar.isActive();
11879
12059
  }
11880
12060
  }
11881
- AudioRecorderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.2", ngImport: i0, type: AudioRecorderComponent, deps: [{ token: i0.Injector }, { token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: i0.ChangeDetectorRef }, { token: SessionService }, { token: ProjectService }, { token: RecordingService }], target: i0.ɵɵFactoryTarget.Component });
12061
+ AudioRecorderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.2", ngImport: i0, type: AudioRecorderComponent, deps: [{ token: i0.Injector }, { token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: i0.ChangeDetectorRef }, { token: SessionService }, { token: ProjectService }, { token: SpeechRecorderUploader }], target: i0.ɵɵFactoryTarget.Component });
11882
12062
  AudioRecorderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.2", type: AudioRecorderComponent, selector: "app-audiorecorder-comp", providers: [SessionService], viewQueries: [{ propertyName: "ar", first: true, predicate: AudioRecorder, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
11883
12063
  <app-audiorecorder [projectName]="_project?.name" [dataSaved]="dataSaved"></app-audiorecorder>
11884
- `, isInline: true, styles: [":host{flex:2;display:flex;height:100%;flex-direction:column;min-height:0}\n"], components: [{ type: AudioRecorder, selector: "app-audiorecorder", inputs: ["_project", "projectName", "dataSaved"] }] });
12064
+ `, isInline: true, styles: [":host{flex:2;display:flex;height:100%;flex-direction:column;min-height:0}\n"], components: [{ type: AudioRecorder, selector: "app-audiorecorder", inputs: ["projectName", "dataSaved"] }] });
11885
12065
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImport: i0, type: AudioRecorderComponent, decorators: [{
11886
12066
  type: Component,
11887
12067
  args: [{
@@ -11899,7 +12079,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11899
12079
 
11900
12080
  }`]
11901
12081
  }]
11902
- }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: i0.ChangeDetectorRef }, { type: SessionService }, { type: ProjectService }, { type: RecordingService }]; }, propDecorators: { ar: [{
12082
+ }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: i0.ChangeDetectorRef }, { type: SessionService }, { type: ProjectService }, { type: SpeechRecorderUploader }]; }, propDecorators: { ar: [{
11903
12083
  type: ViewChild,
11904
12084
  args: [AudioRecorder, { static: true }]
11905
12085
  }] } });
@@ -11948,7 +12128,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11948
12128
  }]
11949
12129
  }] });
11950
12130
 
11951
- const VERSION = '2.24.0';
12131
+ const VERSION = '2.25.0';
11952
12132
 
11953
12133
  /*
11954
12134
  * Public API Surface of speechrecorderng