speechrecorderng 2.24.2 → 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';
@@ -5657,98 +5658,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
5657
5658
  type: Output
5658
5659
  }] } });
5659
5660
 
5660
- class Float32ArrayChunkerOutStream {
5661
- constructor(outStream) {
5662
- this.outStream = outStream;
5663
- this.bufs = new Array();
5664
- this._channels = 0;
5665
- this._chunkSize = 0;
5666
- this.filled = 0;
5667
- this.receivedFrames = 0;
5668
- this.sentFrames = 0;
5669
- }
5670
- createBuffers() {
5671
- this.bufs = new Array(this._channels);
5672
- for (let ch = 0; ch < this._channels; ch++) {
5673
- this.bufs[ch] = new Float32Array(this._chunkSize);
5674
- }
5675
- }
5676
- set chunkSize(chunkSize) {
5677
- this._chunkSize = chunkSize;
5678
- this.createBuffers();
5679
- }
5680
- set channels(channels) {
5681
- this._channels = channels;
5682
- this.createBuffers();
5683
- }
5684
- write(buffers) {
5685
- let copied = 0;
5686
- if (buffers.length > 0) {
5687
- let buffersLen = buffers[0].length;
5688
- this.receivedFrames += buffersLen;
5689
- let avail = buffersLen;
5690
- // Fill out buffers until all values copied
5691
- while (avail > 0) {
5692
- let toFill = this._chunkSize - this.filled;
5693
- if (toFill > avail) {
5694
- toFill = avail;
5695
- }
5696
- let sliceEnd = copied + toFill;
5697
- // Firefox on Android sends only the first channel
5698
- for (let ch = 0; ch < buffersLen; ch++) {
5699
- if (buffers[ch]) {
5700
- let cpPrt = buffers[ch].slice(copied, sliceEnd);
5701
- let buf = this.bufs[ch];
5702
- buf.set(cpPrt, this.filled);
5703
- }
5704
- }
5705
- copied += toFill;
5706
- avail -= toFill;
5707
- this.filled += toFill;
5708
- if (this.filled == this._chunkSize) {
5709
- this.outStream.write(this.bufs);
5710
- this.sentFrames += this.filled;
5711
- this.filled = 0;
5712
- }
5713
- }
5714
- }
5715
- return copied;
5716
- }
5717
- flush() {
5718
- if (this.filled > 0) {
5719
- let restBufs = new Array(this._channels);
5720
- for (let ch = 0; ch < this._channels; ch++) {
5721
- restBufs[ch] = this.bufs[ch].slice(0, this.filled);
5722
- }
5723
- this.outStream.write(restBufs);
5724
- this.outStream.flush();
5725
- this.sentFrames += this.filled;
5726
- this.filled = 0;
5727
- }
5728
- }
5729
- close() {
5730
- this.outStream.close();
5731
- }
5732
- }
5733
-
5734
- class SequenceAudioFloat32ChunkerOutStream extends Float32ArrayChunkerOutStream {
5735
- constructor(outStream, chunkDurationSeconds) {
5736
- super(outStream);
5737
- this.chunkDurationSeconds = chunkDurationSeconds;
5738
- this.sampleRate = null;
5739
- this.sequenceAudioFloat32OutStream = outStream;
5740
- }
5741
- setFormat(channels, sampleRate) {
5742
- this.channels = channels;
5743
- this.sampleRate = sampleRate;
5744
- this.chunkSize = Math.round(sampleRate * this.chunkDurationSeconds);
5745
- this.sequenceAudioFloat32OutStream.setFormat(channels, sampleRate);
5746
- }
5747
- nextStream() {
5748
- this.sequenceAudioFloat32OutStream.nextStream();
5749
- }
5750
- }
5751
-
5752
5661
  class SessionFinishedDialog {
5753
5662
  constructor(dialogRef, data) {
5754
5663
  this.dialogRef = dialogRef;
@@ -7275,23 +7184,209 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
7275
7184
  type: Input
7276
7185
  }] } });
7277
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
+
7278
7328
  const FORCE_REQUEST_AUDIO_PERMISSIONS = false;
7279
7329
  const RECFILE_API_CTX = 'recfile';
7280
7330
  const MAX_RECORDING_TIME_MS = 1000 * 60 * 60 * 60; // 1 hour
7281
7331
  const LEVEL_BAR_INTERVALL_SECONDS = 0.1; // 100ms
7282
- class BasicRecorder {
7283
- 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) {
7284
7375
  this.dialog = dialog;
7285
7376
  this.sessionService = sessionService;
7377
+ this.uploader = uploader;
7378
+ this.config = config;
7286
7379
  this.statusMsg = '';
7287
7380
  this.statusWaiting = false;
7288
7381
  this.readonly = false;
7382
+ this.rfUuid = null;
7289
7383
  this.processingRecording = false;
7290
7384
  this.ac = null;
7291
7385
  this._wakeLock = false;
7292
7386
  this._selectedDeviceId = undefined;
7293
7387
  this._channelCount = 2;
7294
7388
  this._session = null;
7389
+ this.startedDate = null;
7295
7390
  this.uploadProgress = 100;
7296
7391
  this.uploadStatus = 'ok';
7297
7392
  this.audioSignalCollapsed = true;
@@ -7301,7 +7396,10 @@ class BasicRecorder {
7301
7396
  this.audioFetchSubscription = null;
7302
7397
  this.destroyed = false;
7303
7398
  this.navigationDisabled = true;
7399
+ // Default: Do not try to keep the device awake
7304
7400
  this.noSleep = null;
7401
+ // Default: Disabled chunked upload
7402
+ this._uploadChunkSizeSeconds = null;
7305
7403
  this.userAgent = UserAgentBuilder.userAgent();
7306
7404
  console.debug("Detected platform: " + this.userAgent.detectedPlatform);
7307
7405
  console.debug("Detected browser: " + this.userAgent.detectedBrowser);
@@ -7311,6 +7409,16 @@ class BasicRecorder {
7311
7409
  this.streamLevelMeasure = new StreamLevelMeasure();
7312
7410
  this.selCaptureDeviceId = null;
7313
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
+ }
7314
7422
  get wakeLock() {
7315
7423
  return this._wakeLock;
7316
7424
  }
@@ -7324,12 +7432,14 @@ class BasicRecorder {
7324
7432
  }
7325
7433
  if (!this.noSleep.isEnabled) {
7326
7434
  this.noSleep.enable();
7435
+ console.debug("Enabled wake lock.");
7327
7436
  }
7328
7437
  }
7329
7438
  }
7330
7439
  disableWakeLockCond() {
7331
7440
  if (this.noSleep && this.noSleep.isEnabled) {
7332
7441
  this.noSleep.disable();
7442
+ console.debug("Disabled wake lock.");
7333
7443
  }
7334
7444
  }
7335
7445
  set audioDevices(audioDevices) {
@@ -7351,6 +7461,27 @@ class BasicRecorder {
7351
7461
  this.statusAlertType = 'info';
7352
7462
  this.statusMsg = 'Ready.';
7353
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
+ }
7354
7485
  start() {
7355
7486
  this.statusAlertType = 'info';
7356
7487
  this.statusMsg = 'Starting session...';
@@ -7557,6 +7688,73 @@ class BasicRecorder {
7557
7688
  });
7558
7689
  }
7559
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
+ }
7560
7758
  closed() {
7561
7759
  this.statusAlertType = 'info';
7562
7760
  this.statusMsg = 'Session closed.';
@@ -7573,7 +7771,11 @@ class BasicRecorder {
7573
7771
  }
7574
7772
  });
7575
7773
  }
7576
- }
7774
+ };
7775
+ BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS = 30;
7776
+ BasicRecorder = __decorate([
7777
+ __param(3, Inject(SPEECHRECORDER_CONFIG))
7778
+ ], BasicRecorder);
7577
7779
 
7578
7780
  /**
7579
7781
  * Created by klausj on 17.06.2017.
@@ -7883,7 +8085,7 @@ const DEFAULT_PRE_REC_DELAY = 1000;
7883
8085
  const DEFAULT_POST_REC_DELAY = 500;
7884
8086
  class SessionManager extends BasicRecorder {
7885
8087
  constructor(changeDetectorRef, renderer, dialog, sessionService, recFileService, uploader, config) {
7886
- super(dialog, sessionService);
8088
+ super(dialog, sessionService, uploader, config);
7887
8089
  this.changeDetectorRef = changeDetectorRef;
7888
8090
  this.renderer = renderer;
7889
8091
  this.dialog = dialog;
@@ -7986,7 +8188,7 @@ class SessionManager extends BasicRecorder {
7986
8188
  if (this.ac) {
7987
8189
  this.transportActions.startAction.onAction = () => this.startItem();
7988
8190
  this.ac.listener = this;
7989
- this.ac.audioOutStream = new SequenceAudioFloat32ChunkerOutStream(this.streamLevelMeasure, LEVEL_BAR_INTERVALL_SECONDS);
8191
+ this.configureStreamCaptureStream();
7990
8192
  // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
7991
8193
  //this.ac.listDevices();
7992
8194
  }
@@ -8132,12 +8334,7 @@ class SessionManager extends BasicRecorder {
8132
8334
  }
8133
8335
  }
8134
8336
  startItem() {
8135
- this.enableWakeLockCond();
8136
- this.transportActions.startAction.disabled = true;
8137
- this.transportActions.pauseAction.disabled = true;
8138
- if (this.readonly) {
8139
- return;
8140
- }
8337
+ super.startItem();
8141
8338
  this.transportActions.fwdAction.disabled = true;
8142
8339
  this.transportActions.fwdNextAction.disabled = true;
8143
8340
  this.transportActions.bwdAction.disabled = true;
@@ -8436,8 +8633,8 @@ class SessionManager extends BasicRecorder {
8436
8633
  }
8437
8634
  }
8438
8635
  started() {
8636
+ super.started();
8439
8637
  this.status = 2 /* PRE_RECORDING */;
8440
- this.transportActions.startAction.disabled = true;
8441
8638
  this.startStopSignalState = 1 /* PRERECORDING */;
8442
8639
  if (this._session) {
8443
8640
  if (this._session.status === "LOADED") {
@@ -8606,7 +8803,7 @@ class SessionManager extends BasicRecorder {
8606
8803
  rf = new SprRecordingFile(sessId, ic, it.recs.length, ad);
8607
8804
  it.recs.push(rf);
8608
8805
  }
8609
- if (this.enableUploadRecordings) {
8806
+ if (this.enableUploadRecordings && !this.uploadChunkSizeSeconds) {
8610
8807
  // TODO use SpeechRecorderconfig resp. RecfileService
8611
8808
  // convert asynchronously to 16-bit integer PCM
8612
8809
  // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
@@ -8685,6 +8882,24 @@ class SessionManager extends BasicRecorder {
8685
8882
  }
8686
8883
  this.changeDetectorRef.detectChanges();
8687
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
+ }
8688
8903
  postRecording(wavFile, recUrl) {
8689
8904
  let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
8690
8905
  let ul = new Upload(wavBlob, recUrl);
@@ -9179,6 +9394,13 @@ class SpeechrecorderngComponent extends RecorderComponent {
9179
9394
  chCnt = ProjectUtil.audioChannelCount(project);
9180
9395
  console.info("Project requested recording channel count: " + chCnt);
9181
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
+ }
9182
9404
  }
9183
9405
  else {
9184
9406
  console.error("Empty project configuration!");
@@ -10895,13 +11117,12 @@ class Item {
10895
11117
  }
10896
11118
  }
10897
11119
  class AudioRecorder extends BasicRecorder {
10898
- constructor(changeDetectorRef, renderer, route, dialog, projectService, sessionService, recFileService, uploader, config) {
10899
- super(dialog, sessionService);
11120
+ constructor(changeDetectorRef, renderer, route, dialog, sessionService, recFileService, uploader, config) {
11121
+ super(dialog, sessionService, uploader, config);
10900
11122
  this.changeDetectorRef = changeDetectorRef;
10901
11123
  this.renderer = renderer;
10902
11124
  this.route = route;
10903
11125
  this.dialog = dialog;
10904
- this.projectService = projectService;
10905
11126
  this.sessionService = sessionService;
10906
11127
  this.recFileService = recFileService;
10907
11128
  this.uploader = uploader;
@@ -10912,7 +11133,6 @@ class AudioRecorder extends BasicRecorder {
10912
11133
  this.enableDownloadRecordings = false;
10913
11134
  this.status = 0 /* BLOCKED */;
10914
11135
  this.dataSaved = true;
10915
- this.startedDate = null;
10916
11136
  this.maxRecTimerId = null;
10917
11137
  this.maxRecTimerRunning = false;
10918
11138
  this._promptIndex = null;
@@ -11003,7 +11223,7 @@ class AudioRecorder extends BasicRecorder {
11003
11223
  if (this.ac) {
11004
11224
  this.transportActions.startAction.onAction = () => this.startItem();
11005
11225
  this.ac.listener = this;
11006
- this.ac.audioOutStream = new SequenceAudioFloat32ChunkerOutStream(this.streamLevelMeasure, LEVEL_BAR_INTERVALL_SECONDS);
11226
+ this.configureStreamCaptureStream();
11007
11227
  // Don't list the devices here. If we do not have audio permissions we only get anonymized devices without labels.
11008
11228
  //this.ac.listDevices();
11009
11229
  }
@@ -11030,23 +11250,6 @@ class AudioRecorder extends BasicRecorder {
11030
11250
  this.uploader.listener = (ue) => {
11031
11251
  this.uploadUpdate(ue);
11032
11252
  };
11033
- let wakeLockSupp = ('wakeLock' in navigator);
11034
- // if(wakeLockSupp) {
11035
- // let wakeLock = null;
11036
- // try {
11037
- // //@ts-ignore
11038
- // wakeLock = navigator.wakeLock.request('screen');
11039
- //
11040
- // //statusElem.textContent = 'Wake Lock is active!';
11041
- // } catch (err) {
11042
- // // The Wake Lock request has failed - usually system related, such as battery.
11043
- // console.error('Wakelock failed'+err)
11044
- // }
11045
- // }else{
11046
- // let noSleep=new NoSleep();
11047
- // noSleep.enable();
11048
- //
11049
- // }
11050
11253
  }
11051
11254
  onKeyPress(ke) {
11052
11255
  if (ke.key == ' ') {
@@ -11150,6 +11353,12 @@ class AudioRecorder extends BasicRecorder {
11150
11353
  chCnt = ProjectUtil.audioChannelCount(project);
11151
11354
  console.info("Project requested recording channel count: " + chCnt);
11152
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
+ }
11153
11362
  }
11154
11363
  else {
11155
11364
  console.error("Empty project configuration!");
@@ -11249,12 +11458,7 @@ class AudioRecorder extends BasicRecorder {
11249
11458
  }
11250
11459
  }
11251
11460
  startItem() {
11252
- this.enableWakeLockCond();
11253
- this.transportActions.startAction.disabled = true;
11254
- this.transportActions.pauseAction.disabled = true;
11255
- if (this.readonly) {
11256
- return;
11257
- }
11461
+ super.startItem();
11258
11462
  this.transportActions.fwdAction.disabled = true;
11259
11463
  this.transportActions.fwdNextAction.disabled = true;
11260
11464
  this.transportActions.bwdAction.disabled = true;
@@ -11406,8 +11610,7 @@ class AudioRecorder extends BasicRecorder {
11406
11610
  }
11407
11611
  }
11408
11612
  started() {
11409
- this.startedDate = new Date();
11410
- this.transportActions.startAction.disabled = true;
11613
+ super.started();
11411
11614
  this.statusAlertType = 'info';
11412
11615
  this.statusMsg = 'Recording...';
11413
11616
  let maxRecordingTimeMs = MAX_RECORDING_TIME_MS;
@@ -11454,38 +11657,39 @@ class AudioRecorder extends BasicRecorder {
11454
11657
  if (this._session) {
11455
11658
  sessId = this._session.sessionId;
11456
11659
  }
11457
- 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);
11458
11664
  rf._startedAsDateObj = this.startedDate;
11459
11665
  if (rf._startedAsDateObj) {
11460
11666
  rf.startedDate = rf._startedAsDateObj.toString();
11461
11667
  }
11462
- let apiEndPoint = '';
11463
- if (this.config && this.config.apiEndPoint) {
11464
- apiEndPoint = this.config.apiEndPoint;
11465
- }
11466
- if (apiEndPoint !== '') {
11467
- 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
+ });
11468
11692
  }
11469
- let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
11470
- let recUrl = sessionsUrl + '/' + rf.session + '/' + RECFILE_API_CTX + '/' + rf.uuid;
11471
- //
11472
- //
11473
- //
11474
- // // convert asynchronously to 16-bit integer PCM
11475
- // // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
11476
- // // TODO duplicate conversion for manual download
11477
- // //console.log("Build wav writer...");
11478
- this.processingRecording = true;
11479
- let ww = new WavWriter();
11480
- ww.writeAsync(ad, (wavFile) => {
11481
- rf.frames = ad.length;
11482
- this.displayRecFile = rf;
11483
- this.recorderCombiPane.push(rf);
11484
- this.postRecordingMultipart(wavFile, rf.uuid, rf.session, rf._startedAsDateObj, recUrl);
11485
- this.processingRecording = false;
11486
- this.changeDetectorRef.detectChanges();
11487
- });
11488
- // }
11489
11693
  }
11490
11694
  // check complete session
11491
11695
  let complete = true;
@@ -11496,11 +11700,6 @@ class AudioRecorder extends BasicRecorder {
11496
11700
  this.updateNavigationActions();
11497
11701
  this.changeDetectorRef.detectChanges();
11498
11702
  }
11499
- postRecording(wavFile, recUrl) {
11500
- let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
11501
- let ul = new Upload(wavBlob, recUrl);
11502
- this.uploader.queueUpload(ul);
11503
- }
11504
11703
  postRecordingMultipart(wavFile, uuid, sessionId, startedDate, recUrl) {
11505
11704
  let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
11506
11705
  let fd = new FormData();
@@ -11517,6 +11716,24 @@ class AudioRecorder extends BasicRecorder {
11517
11716
  let ul = new Upload(fd, recUrl);
11518
11717
  this.uploader.queueUpload(ul);
11519
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
+ }
11520
11737
  stop() {
11521
11738
  if (this.ac) {
11522
11739
  this.ac.close();
@@ -11544,8 +11761,8 @@ class AudioRecorder extends BasicRecorder {
11544
11761
  }
11545
11762
  }
11546
11763
  }
11547
- 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 });
11548
- 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: `
11549
11766
  <app-warningbar [show]="isTestSession()" warningText="Test recording only!"></app-warningbar>
11550
11767
  <app-warningbar [show]="isDefaultAudioTestSession()"
11551
11768
  warningText="This test uses default audio device! Regular sessions may require a particular audio device (microphone)!"></app-warningbar>
@@ -11700,12 +11917,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11700
11917
  }`
11701
11918
  ]
11702
11919
  }]
11703
- }], 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: [{
11704
11921
  type: Inject,
11705
11922
  args: [SPEECHRECORDER_CONFIG]
11706
- }] }]; }, propDecorators: { _project: [{
11707
- type: Input
11708
- }], projectName: [{
11923
+ }] }]; }, propDecorators: { projectName: [{
11709
11924
  type: Input
11710
11925
  }], recorderCombiPane: [{
11711
11926
  type: ViewChild,
@@ -11723,7 +11938,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11723
11938
  args: ['window:keydown', ['$event']]
11724
11939
  }] } });
11725
11940
  class AudioRecorderComponent extends RecorderComponent {
11726
- constructor(injector, route, router, changeDetectorRef, sessionService, projectService, recFilesService, uploader) {
11941
+ constructor(injector, route, router, changeDetectorRef, sessionService, projectService, uploader) {
11727
11942
  super(uploader);
11728
11943
  this.injector = injector;
11729
11944
  this.route = route;
@@ -11731,7 +11946,6 @@ class AudioRecorderComponent extends RecorderComponent {
11731
11946
  this.changeDetectorRef = changeDetectorRef;
11732
11947
  this.sessionService = sessionService;
11733
11948
  this.projectService = projectService;
11734
- this.recFilesService = recFilesService;
11735
11949
  this.uploader = uploader;
11736
11950
  }
11737
11951
  ngOnInit() {
@@ -11844,10 +12058,10 @@ class AudioRecorderComponent extends RecorderComponent {
11844
12058
  return this.dataSaved && !this.ar.isActive();
11845
12059
  }
11846
12060
  }
11847
- 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 }, { token: SpeechRecorderUploader }], 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 });
11848
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: `
11849
12063
  <app-audiorecorder [projectName]="_project?.name" [dataSaved]="dataSaved"></app-audiorecorder>
11850
- `, 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"] }] });
11851
12065
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImport: i0, type: AudioRecorderComponent, decorators: [{
11852
12066
  type: Component,
11853
12067
  args: [{
@@ -11865,7 +12079,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11865
12079
 
11866
12080
  }`]
11867
12081
  }]
11868
- }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: i0.ChangeDetectorRef }, { type: SessionService }, { type: ProjectService }, { type: RecordingService }, { type: SpeechRecorderUploader }]; }, 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: [{
11869
12083
  type: ViewChild,
11870
12084
  args: [AudioRecorder, { static: true }]
11871
12085
  }] } });
@@ -11914,7 +12128,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.2", ngImpor
11914
12128
  }]
11915
12129
  }] });
11916
12130
 
11917
- const VERSION = '2.24.2';
12131
+ const VERSION = '2.25.0';
11918
12132
 
11919
12133
  /*
11920
12134
  * Public API Surface of speechrecorderng