speechrecorderng 2.25.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/esm2020/lib/audio/array_audio_buffer.mjs +101 -0
  2. package/esm2020/lib/audio/array_audio_buffer_input_stream.mjs +86 -0
  3. package/esm2020/lib/audio/audio_data_holder.mjs +109 -0
  4. package/esm2020/lib/audio/audio_display.mjs +5 -5
  5. package/esm2020/lib/audio/audio_player.mjs +9 -7
  6. package/esm2020/lib/audio/capture/capture.mjs +6 -1
  7. package/esm2020/lib/audio/dsp/level_measure.mjs +97 -55
  8. package/esm2020/lib/audio/io/stream.mjs +3 -3
  9. package/esm2020/lib/audio/persistor.mjs +12 -6
  10. package/esm2020/lib/audio/playback/array_audio_buffer_source_node.mjs +265 -0
  11. package/esm2020/lib/audio/playback/player.mjs +184 -50
  12. package/esm2020/lib/audio/ui/audio_canvas_layer_comp.mjs +25 -13
  13. package/esm2020/lib/audio/ui/audio_display_scroll_pane.mjs +2 -2
  14. package/esm2020/lib/audio/ui/audiosignal.mjs +206 -77
  15. package/esm2020/lib/audio/ui/container.mjs +14 -14
  16. package/esm2020/lib/audio/ui/livelevel.mjs +17 -3
  17. package/esm2020/lib/audio/ui/sonagram.mjs +343 -160
  18. package/esm2020/lib/io/stream.mjs +77 -1
  19. package/esm2020/lib/net/uploader.mjs +15 -3
  20. package/esm2020/lib/speechrecorder/recording.mjs +52 -7
  21. package/esm2020/lib/speechrecorder/recordings/recordings.service.mjs +99 -7
  22. package/esm2020/lib/speechrecorder/session/audiorecorder.mjs +131 -117
  23. package/esm2020/lib/speechrecorder/session/basicrecorder.mjs +36 -12
  24. package/esm2020/lib/speechrecorder/session/progress.mjs +2 -2
  25. package/esm2020/lib/speechrecorder/session/prompting.mjs +2 -2
  26. package/esm2020/lib/speechrecorder/session/recorder_combi_pane.mjs +6 -3
  27. package/esm2020/lib/speechrecorder/session/recording_file_cache.mjs +182 -0
  28. package/esm2020/lib/speechrecorder/session/recording_list.mjs +23 -10
  29. package/esm2020/lib/speechrecorder/session/recordingfile/recording-file-meta.component.mjs +12 -6
  30. package/esm2020/lib/speechrecorder/session/recordingfile/recording-file-u-i.component.mjs +5 -5
  31. package/esm2020/lib/speechrecorder/session/recordingfile/recording-file-view.component.mjs +13 -9
  32. package/esm2020/lib/speechrecorder/session/recordingfile/recordingfile-service.mjs +8 -5
  33. package/esm2020/lib/speechrecorder/session/sessionmanager.mjs +140 -113
  34. package/esm2020/lib/speechrecorderng.component.mjs +3 -3
  35. package/esm2020/lib/speechrecorderng.module.mjs +5 -4
  36. package/esm2020/lib/spr.module.version.mjs +2 -2
  37. package/esm2020/lib/ui/recordingitem_display.mjs +2 -2
  38. package/esm2020/lib/utils/wake_lock.mjs +48 -3
  39. package/fesm2015/speechrecorderng.mjs +2415 -786
  40. package/fesm2015/speechrecorderng.mjs.map +1 -1
  41. package/fesm2020/speechrecorderng.mjs +2404 -786
  42. package/fesm2020/speechrecorderng.mjs.map +1 -1
  43. package/lib/audio/array_audio_buffer.d.ts +15 -0
  44. package/lib/audio/array_audio_buffer_input_stream.d.ts +14 -0
  45. package/lib/audio/audio_data_holder.d.ts +29 -0
  46. package/lib/audio/audio_display.d.ts +2 -1
  47. package/lib/audio/audio_player.d.ts +2 -1
  48. package/lib/audio/capture/capture.d.ts +2 -0
  49. package/lib/audio/dsp/level_measure.d.ts +2 -1
  50. package/lib/audio/persistor.d.ts +8 -3
  51. package/lib/audio/playback/array_audio_buffer_source_node.d.ts +24 -0
  52. package/lib/audio/playback/player.d.ts +6 -0
  53. package/lib/audio/ui/audio_canvas_layer_comp.d.ts +4 -1
  54. package/lib/audio/ui/audio_display_scroll_pane.d.ts +2 -1
  55. package/lib/audio/ui/audiosignal.d.ts +3 -2
  56. package/lib/audio/ui/container.d.ts +3 -2
  57. package/lib/audio/ui/livelevel.d.ts +4 -2
  58. package/lib/audio/ui/sonagram.d.ts +3 -2
  59. package/lib/io/stream.d.ts +17 -0
  60. package/lib/net/uploader.d.ts +6 -1
  61. package/lib/speechrecorder/recording.d.ts +15 -3
  62. package/lib/speechrecorder/recordings/recordings.service.d.ts +2 -0
  63. package/lib/speechrecorder/session/audiorecorder.d.ts +6 -10
  64. package/lib/speechrecorder/session/basicrecorder.d.ts +10 -5
  65. package/lib/speechrecorder/session/progress.d.ts +1 -1
  66. package/lib/speechrecorder/session/prompting.d.ts +1 -1
  67. package/lib/speechrecorder/session/recorder_combi_pane.d.ts +3 -1
  68. package/lib/speechrecorder/session/recording_file_cache.d.ts +30 -0
  69. package/lib/speechrecorder/session/recording_list.d.ts +3 -1
  70. package/lib/speechrecorder/session/recordingfile/recording-file-meta.component.d.ts +2 -1
  71. package/lib/speechrecorder/session/recordingfile/recording-file-view.component.d.ts +1 -0
  72. package/lib/speechrecorder/session/sessionmanager.d.ts +13 -18
  73. package/lib/speechrecorderng.module.d.ts +2 -1
  74. package/lib/spr.module.version.d.ts +1 -1
  75. package/lib/utils/wake_lock.d.ts +12 -0
  76. package/package.json +1 -1
@@ -36,6 +36,7 @@ import * as i2$2 from '@angular/material/table';
36
36
  import { MatTableDataSource, MatTableModule } from '@angular/material/table';
37
37
  import { MatSelectModule } from '@angular/material/select';
38
38
  import { MatInputModule } from '@angular/material/input';
39
+ import { MatMenuModule } from '@angular/material/menu';
39
40
 
40
41
  class ActionEvent {
41
42
  constructor(_value = null) {
@@ -109,6 +110,514 @@ class Action {
109
110
  }
110
111
  }
111
112
 
113
+ class ArrayAudioBufferInputStream {
114
+ constructor(arrayAudioBuffer) {
115
+ this.arrayAudioBuffer = arrayAudioBuffer;
116
+ this._framePos = 0;
117
+ this.chunkFramePos = 0;
118
+ this.chunkIdx = 0;
119
+ this.eod = false;
120
+ //console.debug("Array audio input stream array audio buffer frames: "+arrayAudioBuffer.frameLen);
121
+ }
122
+ close() {
123
+ }
124
+ skipFrames(n) {
125
+ let toSkip = n;
126
+ if (this.chunkIdx >= this.arrayAudioBuffer.chunkCount) {
127
+ this.eod = true;
128
+ if (toSkip > 0) {
129
+ throw Error('Skip out of bounds: Cannot skip ' + toSkip + ' frames.');
130
+ }
131
+ }
132
+ else {
133
+ let chunkBuf0 = this.arrayAudioBuffer.data[0][this.chunkIdx];
134
+ let chunkBufsLen = chunkBuf0.length;
135
+ let currBufSkippable = chunkBufsLen - this.chunkFramePos;
136
+ if (n >= currBufSkippable) {
137
+ this._framePos += currBufSkippable;
138
+ toSkip -= currBufSkippable;
139
+ this.chunkIdx++;
140
+ this.chunkFramePos = 0;
141
+ if (this.chunkIdx < this.arrayAudioBuffer.chunkCount) {
142
+ this.skipFrames(toSkip);
143
+ }
144
+ else {
145
+ if (toSkip > 0) {
146
+ throw Error('Skip out of bounds: Cannot skip ' + toSkip + ' frames.');
147
+ }
148
+ }
149
+ }
150
+ else {
151
+ toSkip -= n;
152
+ this.chunkFramePos += n;
153
+ }
154
+ }
155
+ }
156
+ read(buffers) {
157
+ let read = 0;
158
+ let toRead = buffers[0].length;
159
+ while (read < toRead && !this.eod) {
160
+ //console.debug("Chunk "+this.chunkIdx+" of "+this.arrayAudioBuffer.chunkCount+" chunks.")
161
+ if (this.chunkIdx >= this.arrayAudioBuffer.chunkCount) {
162
+ this.eod = true;
163
+ //console.debug("Array audio input stream end of data read frames: "+this.framePos);
164
+ }
165
+ else {
166
+ let chunkBuf0 = this.arrayAudioBuffer.data[0][this.chunkIdx];
167
+ let chunkBufsLen = chunkBuf0.length;
168
+ let chunkBufAvail = chunkBufsLen - this.chunkFramePos;
169
+ let r = chunkBufAvail;
170
+ if (r > toRead - read) {
171
+ r = toRead - read;
172
+ }
173
+ for (let ch = 0; ch < buffers.length; ch++) {
174
+ let chChunkBuf = this.arrayAudioBuffer.data[ch][this.chunkIdx];
175
+ for (let bi = 0; bi < r; bi++) {
176
+ buffers[ch][read + bi] = chChunkBuf[this.chunkFramePos + bi];
177
+ }
178
+ }
179
+ read += r;
180
+ this.chunkFramePos += r;
181
+ if (this.chunkFramePos >= chunkBufsLen) {
182
+ this.chunkIdx++;
183
+ this.chunkFramePos = 0;
184
+ }
185
+ }
186
+ }
187
+ this._framePos += read;
188
+ //console.debug("Read: "+read+", frame pos: "+this.framePos)
189
+ if (this._framePos > this.arrayAudioBuffer.frameLen) {
190
+ console.error("Array audio input stream frame pos: " + this._framePos + " greater then frame length: " + this.arrayAudioBuffer.frameLen);
191
+ }
192
+ return read;
193
+ }
194
+ get framePos() {
195
+ return this._framePos;
196
+ }
197
+ }
198
+
199
+ class EditFloat32ArrayInputStream {
200
+ constructor(_srcInputStream, offset = 0, length) {
201
+ this._srcInputStream = _srcInputStream;
202
+ this.offset = offset;
203
+ this.length = length;
204
+ this.framePos = 0;
205
+ this.readFrames = 0;
206
+ if (this.offset < 0) {
207
+ throw Error('Parameter offset must be undefined or greater or equal zero.');
208
+ }
209
+ if (this.length !== undefined && this.length < 0) {
210
+ throw Error('Parameter length must be undefined or greater or equal zero.');
211
+ }
212
+ }
213
+ skipToOffset() {
214
+ if (this.framePos == 0 && this.offset > 0) {
215
+ this._srcInputStream.skipFrames(this.offset);
216
+ this.framePos += this.offset;
217
+ }
218
+ }
219
+ read(buffers) {
220
+ this.skipToOffset();
221
+ let read = 0;
222
+ if (this.length === undefined) {
223
+ read = this._srcInputStream.read(buffers);
224
+ }
225
+ else {
226
+ if (buffers.length > 0) {
227
+ let bufsCh0 = buffers[0];
228
+ let bufsLen = bufsCh0.length;
229
+ let avail = this.length - this.readFrames;
230
+ if (avail > 0) {
231
+ if (avail > bufsLen) {
232
+ read = this._srcInputStream.read(buffers);
233
+ }
234
+ else {
235
+ // temporary buffers required
236
+ let tmpBufs = new Array(buffers.length);
237
+ for (let ch = 0; ch < buffers.length; ch++) {
238
+ tmpBufs[ch] = new Float32Array(avail);
239
+ }
240
+ read = this._srcInputStream.read(tmpBufs);
241
+ for (let ch = 0; ch < buffers.length; ch++) {
242
+ buffers[ch].set(tmpBufs[ch]);
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ this.readFrames += read;
249
+ this.framePos += read;
250
+ return read;
251
+ }
252
+ skipFrames(n) {
253
+ this.skipToOffset();
254
+ if (this.length === undefined) {
255
+ this._srcInputStream.skipFrames(n);
256
+ }
257
+ else {
258
+ let avail = this.length - this.readFrames;
259
+ if (avail > 0) {
260
+ if (avail >= n) {
261
+ this._srcInputStream.skipFrames(n);
262
+ this.readFrames += n;
263
+ this.framePos += n;
264
+ }
265
+ else {
266
+ throw Error('Tried to skip out of bounds.');
267
+ }
268
+ }
269
+ }
270
+ }
271
+ close() {
272
+ this._srcInputStream.close();
273
+ }
274
+ }
275
+ class Float32ArrayChunkerOutStream {
276
+ constructor(outStream) {
277
+ this.outStream = outStream;
278
+ this.bufs = new Array();
279
+ this._channels = 0;
280
+ this._chunkSize = 0;
281
+ this.filled = 0;
282
+ this.receivedFrames = 0;
283
+ this.sentFrames = 0;
284
+ }
285
+ createBuffers() {
286
+ this.bufs = new Array(this._channels);
287
+ for (let ch = 0; ch < this._channels; ch++) {
288
+ this.bufs[ch] = new Float32Array(this._chunkSize);
289
+ }
290
+ }
291
+ set chunkSize(chunkSize) {
292
+ this._chunkSize = chunkSize;
293
+ this.createBuffers();
294
+ }
295
+ get chunkSize() {
296
+ return this._chunkSize;
297
+ }
298
+ set channels(channels) {
299
+ this._channels = channels;
300
+ this.createBuffers();
301
+ }
302
+ get channels() {
303
+ return this._channels;
304
+ }
305
+ available() {
306
+ return this._chunkSize - this.filled;
307
+ }
308
+ write(buffers) {
309
+ let copied = 0;
310
+ if (buffers.length > 0) {
311
+ let buffersLen = buffers[0].length;
312
+ this.receivedFrames += buffersLen;
313
+ let avail = buffersLen;
314
+ // Fill out buffers until all values copied
315
+ while (avail > 0) {
316
+ let toFill = this._chunkSize - this.filled;
317
+ if (toFill > avail) {
318
+ toFill = avail;
319
+ }
320
+ let sliceEnd = copied + toFill;
321
+ // Firefox on Android sends only the first channel
322
+ for (let ch = 0; ch < buffersLen; ch++) {
323
+ if (buffers[ch]) {
324
+ let cpPrt = buffers[ch].slice(copied, sliceEnd);
325
+ let buf = this.bufs[ch];
326
+ buf.set(cpPrt, this.filled);
327
+ }
328
+ }
329
+ copied += toFill;
330
+ avail -= toFill;
331
+ this.filled += toFill;
332
+ if (this.filled == this._chunkSize) {
333
+ this.outStream.write(this.bufs);
334
+ this.sentFrames += this.filled;
335
+ this.filled = 0;
336
+ }
337
+ }
338
+ }
339
+ return copied;
340
+ }
341
+ flush() {
342
+ if (this.filled > 0) {
343
+ let restBufs = new Array(this._channels);
344
+ for (let ch = 0; ch < this._channels; ch++) {
345
+ restBufs[ch] = this.bufs[ch].slice(0, this.filled);
346
+ }
347
+ this.outStream.write(restBufs);
348
+ this.outStream.flush();
349
+ this.sentFrames += this.filled;
350
+ this.filled = 0;
351
+ }
352
+ }
353
+ close() {
354
+ this.outStream.close();
355
+ }
356
+ }
357
+
358
+ // Code from audio_source_worklet.js as string constant to be loaded as module
359
+ // Changes in audio_source_worklet.js must be copied and pasted to this string constant
360
+ const aswpStr = "\n" +
361
+ "// Important note: Changes in audio_source_worklet.js must be copied and pasted to the string constant aswpStr in array_audio_buffer_source_node.ts\n" +
362
+ "\n" +
363
+ "\n" +
364
+ "class AudioSourceProcessor extends AudioWorkletProcessor{\n" +
365
+ "\n" +
366
+ " filledFrames=0;\n" +
367
+ " audioBuffers=new Array();\n" +
368
+ " currentAudioBuffer=null;\n" +
369
+ " currentAudioBufferFramePos=0;\n" +
370
+ " currentAudioBufferAvail=0;\n" +
371
+ " running=false;\n" +
372
+ " ended=false;\n" +
373
+ "\n" +
374
+ " constructor() {\n" +
375
+ " super({numberOfInputs:0,numberOfOutputs:1});\n" +
376
+ " this.port.onmessage=(msgEv)=>{\n" +
377
+ " // received audio playback data from application\n" +
378
+ " //console.debug(\"Audio source worklet msg: Received.\");\n" +
379
+ "\n" +
380
+ " if(msgEv.data.cmd){\n" +
381
+ " if('data'===msgEv.data.cmd) {\n" +
382
+ " let chs = msgEv.data.chs;\n" +
383
+ "\n" +
384
+ " let audioData = new Array(chs);\n" +
385
+ " for (let ch = 0; ch < chs; ch++) {\n" +
386
+ " audioData[ch] = new Float32Array(msgEv.data.audioData[ch]);\n" +
387
+ " }\n" +
388
+ " let msgChBufLen=audioData[0].length;\n" +
389
+ " this.audioBuffers.push(audioData);\n" +
390
+ " this.filledFrames += msgChBufLen;\n" +
391
+ " //console.debug(\"Audio source worklet msg: Filled \" + this.filledFrames+ \" in \"+this.audioBuffers.length+\" buffers.\");\n" +
392
+ "\n" +
393
+ " }else if('start'===msgEv.data.cmd){\n" +
394
+ " this.running=true;\n" +
395
+ " }else if('stop'===msgEv.data.cmd){\n" +
396
+ " //console.debug(\"Stop...\");\n" +
397
+ " this.running=false;\n" +
398
+ " // clear buffers\n" +
399
+ " this.filledFrames=0;\n" +
400
+ " while(this.audioBuffers.length > 0) {\n" +
401
+ " this.audioBuffers.pop();\n" +
402
+ " }\n" +
403
+ " this.currentAudioBuffer=new Float32Array(0);\n" +
404
+ " }\n" +
405
+ " }\n" +
406
+ " }\n" +
407
+ " }\n" +
408
+ "\n" +
409
+ " process(\n" +
410
+ " inputs,\n" +
411
+ " outputs,\n" +
412
+ " parameters\n" +
413
+ " ){\n" +
414
+ " //console.debug(\"Audio source worklet: process \"+outputs.length+ \" output buffers.\");\n" +
415
+ " // copy ring buffer data to outputs\n" +
416
+ " if(!this.running){\n" +
417
+ " return !this.ended;\n" +
418
+ " }\n" +
419
+ "\n" +
420
+ " let output=outputs[0];\n" +
421
+ " let chs=output.length;\n" +
422
+ " //console.debug(\"Audio source worklet: Output channels: \"+chs);\n" +
423
+ " if(chs>0) {\n" +
424
+ "\n" +
425
+ " let outCh0 = output[0];\n" +
426
+ " let outChLen = outCh0.length;\n" +
427
+ "\n" +
428
+ " if(!this.currentAudioBuffer){\n" +
429
+ " // get first buffer\n" +
430
+ " let nxtBuff=this.audioBuffers.shift();\n" +
431
+ " if(nxtBuff) {\n" +
432
+ " this.currentAudioBuffer = nxtBuff;\n" +
433
+ " this.currentAudioBufferFramePos=0;\n" +
434
+ " this.currentAudioBufferAvail=this.currentAudioBuffer[0].length;\n" +
435
+ " }else{\n" +
436
+ " return true;\n" +
437
+ " }\n" +
438
+ " }\n" +
439
+ "\n" +
440
+ " let copied=0;\n" +
441
+ " do{\n" +
442
+ " if(this.currentAudioBufferAvail==0){\n" +
443
+ " let nxtBuff=this.audioBuffers.shift();\n" +
444
+ " if(nxtBuff){\n" +
445
+ " this.currentAudioBuffer=nxtBuff;\n" +
446
+ " this.currentAudioBufferFramePos=0;\n" +
447
+ " this.currentAudioBufferAvail=this.currentAudioBuffer[0].length;\n" +
448
+ " //console.debug(\"Next buffer with \"+this.currentAudioBufferAvail+ \" frames\");\n" +
449
+ " this.port.postMessage({eventType:'bufferNotification',filledFrames:this.filledFrames});\n" +
450
+ " }else{\n" +
451
+ " this.ended=true;\n" +
452
+ " this.port.postMessage({eventType:'ended'});\n" +
453
+ " //console.debug(\"Stream ended\");\n" +
454
+ " break;\n" +
455
+ " }\n" +
456
+ " }\n" +
457
+ " //console.debug(\"outChLen: \"+outChLen+\", copied: \"+copied+\", current avail: \"+this.currentAudioBufferAvail);\n" +
458
+ " let toCopy=outChLen-copied;\n" +
459
+ " if(toCopy>this.currentAudioBufferAvail){\n" +
460
+ " toCopy=this.currentAudioBufferAvail;\n" +
461
+ " }\n" +
462
+ " //console.debug(\"Copy \"+toCopy+\" frames...\");\n" +
463
+ " for(let ch=0;ch<chs;ch++) {\n" +
464
+ " let outCh=output[ch];\n" +
465
+ " for (let i = 0; i < toCopy; i++) {\n" +
466
+ " outCh[copied+i]=this.currentAudioBuffer[ch][this.currentAudioBufferFramePos+i];\n" +
467
+ " }\n" +
468
+ " }\n" +
469
+ " copied+=toCopy;\n" +
470
+ " this.currentAudioBufferFramePos+=toCopy;\n" +
471
+ " this.currentAudioBufferAvail-=toCopy;\n" +
472
+ "\n" +
473
+ " }while(copied<outChLen);\n" +
474
+ " this.filledFrames-=copied;\n" +
475
+ " //console.debug(\"Copied \"+copied+\" frames.\");\n" +
476
+ " }\n" +
477
+ " return !this.ended;\n" +
478
+ " }\n" +
479
+ "}\n" +
480
+ "\n" +
481
+ "registerProcessor('audio-source-worklet',AudioSourceProcessor);\n";
482
+ class ArrayAudioBufferSourceNode extends AudioWorkletNode {
483
+ constructor(context) {
484
+ super(context, 'audio-source-worklet');
485
+ this._bufferFillSeconds = ArrayAudioBufferSourceNode.DEFAULT_BUFFER_FILL_SECONDS;
486
+ this.bufferFillFrames = 0;
487
+ this._arrayAudioBuffer = null;
488
+ this._audioInputStream = null;
489
+ this._aisBufs = null;
490
+ this.filledFrames = 0;
491
+ this.onended = null;
492
+ this.channelCountMode = 'explicit';
493
+ this.port.onmessage = (msgEv) => {
494
+ if (msgEv.data) {
495
+ let evType = msgEv.data.eventType;
496
+ if (evType) {
497
+ if ('bufferNotification' === evType) {
498
+ this.filledFrames = msgEv.data.filledFrames;
499
+ //console.debug("Buffer notification: filled frames: " + this.filledFrames);
500
+ this.fillBuffer();
501
+ }
502
+ else if ('ended' === evType) {
503
+ let drainTime = 0;
504
+ if (this._arrayAudioBuffer?.sampleRate) {
505
+ drainTime = ArrayAudioBufferSourceNode.QUANTUM_FRAME_LEN / this._arrayAudioBuffer.sampleRate;
506
+ }
507
+ //let dstAny:any=this.context.destination;
508
+ //console.debug('Destination node tail-time: '+dstAny['tail-time']);
509
+ window.setTimeout(() => {
510
+ this.onended?.call(this);
511
+ }, drainTime * 1000);
512
+ }
513
+ }
514
+ }
515
+ };
516
+ }
517
+ fillBuffer(frameOffset) {
518
+ if (this._arrayAudioBuffer && this._audioInputStream && this._aisBufs) {
519
+ let filled = this.filledFrames;
520
+ let bufLen = 0;
521
+ while (filled < this.bufferFillFrames) {
522
+ let read = this._audioInputStream.read(this._aisBufs);
523
+ //console.log("ArrayAudioBufferSourceNode: read: "+read)
524
+ if (read) {
525
+ let trBuffers = new Array(this.channelCount);
526
+ for (let ch = 0; ch < this.channelCount; ch++) {
527
+ let adCh = this._aisBufs[ch];
528
+ let adChCopy = new Float32Array(adCh.length);
529
+ bufLen = adChCopy.length;
530
+ adChCopy.set(adCh);
531
+ trBuffers[ch] = adChCopy.buffer;
532
+ }
533
+ this.port.postMessage({
534
+ cmd: 'data',
535
+ chs: this.channelCount,
536
+ audioData: trBuffers
537
+ }, trBuffers);
538
+ filled += read;
539
+ }
540
+ else {
541
+ break;
542
+ }
543
+ }
544
+ }
545
+ }
546
+ get arrayAudioBuffer() {
547
+ return this._arrayAudioBuffer;
548
+ }
549
+ set arrayAudioBuffer(value) {
550
+ this._arrayAudioBuffer = value;
551
+ if (this._arrayAudioBuffer?.channelCount) {
552
+ this.channelCount = this._arrayAudioBuffer?.channelCount;
553
+ this.bufferFillFrames = this._arrayAudioBuffer.sampleRate * this._bufferFillSeconds;
554
+ }
555
+ }
556
+ get bufferFillSeconds() {
557
+ return this._bufferFillSeconds;
558
+ }
559
+ set bufferFillSeconds(value) {
560
+ this._bufferFillSeconds = value;
561
+ }
562
+ start(when, offset, duration) {
563
+ if (when) {
564
+ throw Error("when, offest,duration parameters currently not supported by ArrayAudioBufferSourceNode class");
565
+ }
566
+ if (this._arrayAudioBuffer) {
567
+ let arrAis = new ArrayAudioBufferInputStream(this._arrayAudioBuffer);
568
+ if (offset === undefined && duration === undefined) {
569
+ this._audioInputStream = arrAis;
570
+ }
571
+ else {
572
+ let offsetFrames = 0;
573
+ let durationFrames = undefined;
574
+ if (offset !== undefined) {
575
+ offsetFrames = Math.floor(offset * this._arrayAudioBuffer.sampleRate);
576
+ }
577
+ if (duration !== undefined) {
578
+ durationFrames = Math.floor(duration * this._arrayAudioBuffer.sampleRate);
579
+ }
580
+ this._audioInputStream = new EditFloat32ArrayInputStream(arrAis, offsetFrames, durationFrames);
581
+ }
582
+ let chs = this._arrayAudioBuffer.channelCount;
583
+ this._aisBufs = new Array(chs);
584
+ for (let ch = 0; ch < chs; ch++) {
585
+ this._aisBufs[ch] = new Float32Array(1024);
586
+ }
587
+ this.fillBuffer();
588
+ this.port.postMessage({ cmd: 'start' });
589
+ }
590
+ }
591
+ stop() {
592
+ this.port.postMessage({ cmd: 'stop' });
593
+ this.onended?.call(this);
594
+ }
595
+ }
596
+ ArrayAudioBufferSourceNode.QUANTUM_FRAME_LEN = 128;
597
+ ArrayAudioBufferSourceNode.DEFAULT_BUFFER_FILL_SECONDS = 10;
598
+ class AudioSourceWorkletModuleLoader {
599
+ static loadModule(context) {
600
+ return new Promise((resolve, reject) => {
601
+ if (AudioSourceWorkletModuleLoader.moduleLoaded) {
602
+ resolve.call(self);
603
+ }
604
+ else {
605
+ let audioWorkletModuleBlob = new Blob([aswpStr], { type: 'text/javascript' });
606
+ let audioWorkletModuleBlobUrl = window.URL.createObjectURL(audioWorkletModuleBlob);
607
+ context.resume();
608
+ context.audioWorklet.addModule(audioWorkletModuleBlobUrl).then(() => {
609
+ AudioSourceWorkletModuleLoader.moduleLoaded = true;
610
+ resolve.call(self);
611
+ }).catch((reason) => {
612
+ console.error(reason);
613
+ reject.call(reason);
614
+ });
615
+ }
616
+ });
617
+ }
618
+ }
619
+ AudioSourceWorkletModuleLoader.moduleLoaded = false;
620
+
112
621
  var EventType;
113
622
  (function (EventType) {
114
623
  EventType[EventType["CLOSED"] = 0] = "CLOSED";
@@ -135,10 +644,17 @@ class AudioPlayer {
135
644
  this.running = false;
136
645
  this._audioClip = null;
137
646
  this._audioBuffer = null;
647
+ this._arrayAudioBuffer = null;
138
648
  this.sourceBufferNode = null;
649
+ this.sourceAudioWorkletNode = null;
139
650
  this.playStartTime = null;
140
651
  this.timerVar = null;
141
652
  this.context = context;
653
+ // AudioSourceWorkletModuleLoader.loadModule(this.context).then(()=>{
654
+ // console.debug("Audio source worklet module loaded.");
655
+ // }).catch((error: any)=>{
656
+ // console.error('Could not add module '+error);
657
+ // });
142
658
  this.listener = listener;
143
659
  this.bufSize = AudioPlayer.DEFAULT_BUFSIZE;
144
660
  this.n = navigator;
@@ -169,17 +685,23 @@ class AudioPlayer {
169
685
  return this._stopAction;
170
686
  }
171
687
  set audioClip(audioClip) {
172
- var length = 0;
173
- var chs = 0;
174
- if (audioClip && audioClip.buffer) {
175
- chs = audioClip.buffer.numberOfChannels;
688
+ let length = 0;
689
+ let chs = 0;
690
+ if (audioClip && audioClip.audioDataHolder) {
691
+ let audioDataHolder = audioClip.audioDataHolder;
692
+ chs = audioDataHolder.numberOfChannels;
176
693
  if (chs > 0) {
177
- length = audioClip.buffer.length;
694
+ length = audioDataHolder.frameLen;
178
695
  if (chs > this.context.destination.maxChannelCount) {
179
696
  // TODO exception
180
697
  }
181
698
  }
182
- this.audioBuffer = audioClip.buffer;
699
+ if (audioDataHolder.buffer) {
700
+ this.audioBuffer = audioDataHolder.buffer;
701
+ }
702
+ if (audioDataHolder.arrayBuffer) {
703
+ this.arrayAudioBuffer = audioDataHolder.arrayBuffer;
704
+ }
183
705
  audioClip.addSelectionObserver((ac) => {
184
706
  this._startSelectionAction.disabled = this.startSelectionDisabled();
185
707
  if (!this.startSelectionAction.disabled && this._autoPlayOnSelectToggleAction.value) {
@@ -189,12 +711,14 @@ class AudioPlayer {
189
711
  }
190
712
  else {
191
713
  this.audioBuffer = null;
714
+ //this.arrayAudioBuffer=null;
192
715
  }
193
716
  this._audioClip = audioClip;
194
717
  }
195
718
  set audioBuffer(audioBuffer) {
196
719
  this.stop();
197
720
  this._audioBuffer = audioBuffer;
721
+ this._arrayAudioBuffer = null;
198
722
  if (audioBuffer && this.context) {
199
723
  this._startAction.disabled = false;
200
724
  this._startSelectionAction.disabled = this.startSelectionDisabled();
@@ -213,23 +737,86 @@ class AudioPlayer {
213
737
  get audioBuffer() {
214
738
  return this._audioBuffer;
215
739
  }
216
- start() {
217
- if (!this._startAction.disabled && !this.running) {
218
- this.context.resume();
219
- this.sourceBufferNode = this.context.createBufferSource();
220
- this.sourceBufferNode.buffer = this._audioBuffer;
221
- this.sourceBufferNode.connect(this.context.destination);
222
- this.sourceBufferNode.onended = () => this.onended();
223
- this.playStartTime = this.context.currentTime;
224
- this.running = true;
225
- this.sourceBufferNode.start();
226
- this.playStartTime = this.context.currentTime;
740
+ set arrayAudioBuffer(arrayAudioBuffer) {
741
+ this.stop();
742
+ this._audioBuffer = null;
743
+ this._arrayAudioBuffer = arrayAudioBuffer;
744
+ if (arrayAudioBuffer && this.context) {
745
+ AudioSourceWorkletModuleLoader.loadModule(this.context).then(() => {
746
+ this._startAction.disabled = false;
747
+ this._startSelectionAction.disabled = this.startSelectionDisabled();
748
+ if (this.listener) {
749
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.READY));
750
+ }
751
+ }).catch((error) => {
752
+ this._startAction.disabled = true;
753
+ this._startSelectionAction.disabled = true;
754
+ if (this.listener) {
755
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
756
+ }
757
+ console.error('Could not add module ' + error);
758
+ });
759
+ }
760
+ else {
227
761
  this._startAction.disabled = true;
228
762
  this._startSelectionAction.disabled = true;
229
- this._stopAction.disabled = false;
230
- //this.timerVar = window.setInterval((e)=>this.updatePlayPosition(), 200);
231
763
  if (this.listener) {
232
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
764
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.CLOSED));
765
+ }
766
+ }
767
+ }
768
+ get arrayAudioBuffer() {
769
+ return this._arrayAudioBuffer;
770
+ }
771
+ start() {
772
+ if (!this._startAction.disabled && !this.running) {
773
+ this.context.resume();
774
+ if (this._audioBuffer) {
775
+ this.sourceBufferNode = this.context.createBufferSource();
776
+ this.sourceBufferNode.buffer = this._audioBuffer;
777
+ this.sourceBufferNode.connect(this.context.destination);
778
+ this.sourceBufferNode.onended = () => this.onended();
779
+ this.playStartTime = this.context.currentTime;
780
+ this.running = true;
781
+ this.sourceBufferNode.start();
782
+ this.playStartTime = this.context.currentTime;
783
+ this._startAction.disabled = true;
784
+ this._startSelectionAction.disabled = true;
785
+ this._stopAction.disabled = false;
786
+ //this.timerVar = window.setInterval((e)=>this.updatePlayPosition(), 200);
787
+ if (this.listener) {
788
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
789
+ }
790
+ }
791
+ else if (this._arrayAudioBuffer) {
792
+ if (this._arrayAudioBuffer) {
793
+ this.sourceAudioWorkletNode = new ArrayAudioBufferSourceNode(this.context);
794
+ this.sourceAudioWorkletNode.onprocessorerror = (ev) => {
795
+ let msg = 'Unknwon error';
796
+ if (ev instanceof ErrorEvent) {
797
+ msg = ev.message;
798
+ }
799
+ console.error("Audio source worklet error: " + msg);
800
+ if (this.listener) {
801
+ // TODO
802
+ // this.listener.error(msg);
803
+ // this.listener.audioPlayerUpdate(new AudioPlayerEvent());
804
+ }
805
+ };
806
+ this.sourceAudioWorkletNode.arrayAudioBuffer = this._arrayAudioBuffer;
807
+ this.sourceAudioWorkletNode.connect(this.context.destination); // this already starts playing
808
+ this.sourceAudioWorkletNode.onended = () => this.onended();
809
+ this.playStartTime = this.context.currentTime;
810
+ this.running = true;
811
+ this.sourceAudioWorkletNode.start();
812
+ this.playStartTime = this.context.currentTime;
813
+ this._startAction.disabled = true;
814
+ this._startSelectionAction.disabled = true;
815
+ this._stopAction.disabled = false;
816
+ if (this.listener) {
817
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
818
+ }
819
+ }
233
820
  }
234
821
  }
235
822
  }
@@ -239,34 +826,79 @@ class AudioPlayer {
239
826
  startSelected() {
240
827
  if (!this._startAction.disabled && !this.running) {
241
828
  this.context.resume();
242
- this.sourceBufferNode = this.context.createBufferSource();
243
- this.sourceBufferNode.buffer = this._audioBuffer;
244
- this.sourceBufferNode.connect(this.context.destination);
245
- this.sourceBufferNode.onended = () => this.onended();
246
- this.playStartTime = this.context.currentTime;
247
- this.running = true;
248
- // unfortunately Web Audio API uses time values not frames
249
- let ac = this._audioClip;
250
- let offset = 0;
251
- if (ac && ac.selection) {
252
- let s = ac.selection;
253
- let sr = ac.buffer.sampleRate;
254
- offset = s.leftFrame / sr;
255
- let stopPosInsecs = s.rightFrame / sr;
256
- let dur = stopPosInsecs - offset;
257
- // TODO check valid values
258
- this.sourceBufferNode.start(0, offset, dur);
259
- }
260
- else {
261
- this.sourceBufferNode.start();
262
- }
263
- this.playStartTime = this.context.currentTime - offset;
264
- this._startAction.disabled = true;
265
- this._startSelectionAction.disabled = true;
266
- this._stopAction.disabled = false;
267
- //this.timerVar = window.setInterval((e)=>this.updatePlayPosition(), 200);
268
- if (this.listener) {
269
- this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
829
+ if (this._audioBuffer) {
830
+ this.sourceBufferNode = this.context.createBufferSource();
831
+ this.sourceBufferNode.buffer = this._audioBuffer;
832
+ this.sourceBufferNode.connect(this.context.destination);
833
+ this.sourceBufferNode.onended = () => this.onended();
834
+ this.playStartTime = this.context.currentTime;
835
+ this.running = true;
836
+ // unfortunately Web Audio API uses time values not frames
837
+ let ac = this._audioClip;
838
+ let offset = 0;
839
+ if (ac && ac.selection) {
840
+ let s = ac.selection;
841
+ let sr = ac.audioDataHolder.sampleRate;
842
+ offset = s.leftFrame / sr;
843
+ let stopPosInsecs = s.rightFrame / sr;
844
+ let dur = stopPosInsecs - offset;
845
+ // TODO check valid values
846
+ this.sourceBufferNode.start(0, offset, dur);
847
+ }
848
+ else {
849
+ this.sourceBufferNode.start();
850
+ }
851
+ this.playStartTime = this.context.currentTime - offset;
852
+ this._startAction.disabled = true;
853
+ this._startSelectionAction.disabled = true;
854
+ this._stopAction.disabled = false;
855
+ //this.timerVar = window.setInterval((e)=>this.updatePlayPosition(), 200);
856
+ if (this.listener) {
857
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
858
+ }
859
+ }
860
+ else if (this._arrayAudioBuffer) {
861
+ if (this._arrayAudioBuffer) {
862
+ let aabsn = new ArrayAudioBufferSourceNode(this.context);
863
+ this.sourceAudioWorkletNode = aabsn;
864
+ aabsn.onprocessorerror = (ev) => {
865
+ let msg = 'Unknwon error';
866
+ if (ev instanceof ErrorEvent) {
867
+ msg = ev.message;
868
+ }
869
+ console.error("Audio source worklet error: " + msg);
870
+ if (this.listener) {
871
+ // TODO
872
+ // this.listener.error(msg);
873
+ // this.listener.audioPlayerUpdate(new AudioPlayerEvent());
874
+ }
875
+ };
876
+ aabsn.arrayAudioBuffer = this._arrayAudioBuffer;
877
+ aabsn.connect(this.context.destination); // this already starts playing
878
+ aabsn.onended = () => this.onended();
879
+ this.playStartTime = this.context.currentTime;
880
+ this.running = true;
881
+ let ac = this._audioClip;
882
+ let offset = 0;
883
+ if (ac && ac.selection) {
884
+ let s = ac.selection;
885
+ let sr = ac.audioDataHolder.sampleRate;
886
+ offset = s.leftFrame / sr;
887
+ let stopPosInsecs = s.rightFrame / sr;
888
+ let dur = stopPosInsecs - offset;
889
+ aabsn.start(0, offset, dur);
890
+ }
891
+ else {
892
+ aabsn.start();
893
+ }
894
+ this.playStartTime = this.context.currentTime - offset;
895
+ this._startAction.disabled = true;
896
+ this._startSelectionAction.disabled = true;
897
+ this._stopAction.disabled = false;
898
+ if (this.listener) {
899
+ this.listener.audioPlayerUpdate(new AudioPlayerEvent(EventType.STARTED));
900
+ }
901
+ }
270
902
  }
271
903
  }
272
904
  }
@@ -275,6 +907,9 @@ class AudioPlayer {
275
907
  if (this.sourceBufferNode) {
276
908
  this.sourceBufferNode.stop();
277
909
  }
910
+ if (this.sourceAudioWorkletNode) {
911
+ this.sourceAudioWorkletNode.stop();
912
+ }
278
913
  if (this.timerVar !== null) {
279
914
  window.clearInterval(this.timerVar);
280
915
  }
@@ -288,7 +923,7 @@ class AudioPlayer {
288
923
  if (this.timerVar != null) {
289
924
  window.clearInterval(this.timerVar);
290
925
  }
291
- this._startAction.disabled = !(this.audioBuffer);
926
+ this._startAction.disabled = !(this.audioBuffer || this.arrayAudioBuffer);
292
927
  this._startSelectionAction.disabled = this.startSelectionDisabled();
293
928
  this._stopAction.disabled = true;
294
929
  this.running = false;
@@ -305,10 +940,17 @@ class AudioPlayer {
305
940
  }
306
941
  get playPositionFrames() {
307
942
  let ppFrs = null;
943
+ let sr = null;
308
944
  if (this._audioBuffer) {
945
+ sr = this._audioBuffer.sampleRate;
946
+ }
947
+ else if (this._arrayAudioBuffer) {
948
+ sr = this._arrayAudioBuffer.sampleRate;
949
+ }
950
+ if (sr) {
309
951
  let ppTime = this.playPositionTime;
310
952
  if (ppTime !== null) {
311
- ppFrs = this._audioBuffer.sampleRate * ppTime;
953
+ ppFrs = sr * ppTime;
312
954
  }
313
955
  }
314
956
  return ppFrs;
@@ -522,6 +1164,107 @@ class ProjectUtil {
522
1164
  }
523
1165
  ProjectUtil.DEFAULT_AUDIO_CHANNEL_COUNT = 2;
524
1166
 
1167
+ class ArrayAudioBuffer {
1168
+ constructor(_channelCount, _sampleRate, _data) {
1169
+ this._channelCount = _channelCount;
1170
+ this._sampleRate = _sampleRate;
1171
+ this._data = _data;
1172
+ this._chunkCount = 0;
1173
+ this._frameLen = 0;
1174
+ if (this._data.length > 0) {
1175
+ let ch0Data = this.data[0];
1176
+ for (let ch0Chk of ch0Data) {
1177
+ this._chunkCount++;
1178
+ this._frameLen += ch0Chk.length;
1179
+ }
1180
+ }
1181
+ }
1182
+ static fromAudioBuffer(audioBuffer, chunkFrameSize = 8192) {
1183
+ let aab;
1184
+ let chs = audioBuffer.numberOfChannels;
1185
+ let frameLength = audioBuffer.length;
1186
+ //let chunksSize=Math.ceil(frameLength/chunkFrameSize);
1187
+ let framePos = 0;
1188
+ let data = new Array(chs);
1189
+ for (let ch = 0; ch < chs; ch++) {
1190
+ data[ch] = new Array();
1191
+ }
1192
+ let toCopy = frameLength - framePos;
1193
+ while (toCopy > 0) {
1194
+ let toCopyChunk = chunkFrameSize;
1195
+ if (toCopyChunk > toCopy) {
1196
+ // last chunk, the rest
1197
+ toCopyChunk = toCopy;
1198
+ }
1199
+ for (let ch = 0; ch < chs; ch++) {
1200
+ data[ch].push(audioBuffer.getChannelData(ch).slice(framePos, framePos + toCopyChunk));
1201
+ }
1202
+ framePos += toCopyChunk;
1203
+ toCopy -= toCopyChunk;
1204
+ }
1205
+ aab = new ArrayAudioBuffer(chs, audioBuffer.sampleRate, data);
1206
+ return aab;
1207
+ }
1208
+ get channelCount() {
1209
+ return this._channelCount;
1210
+ }
1211
+ frames(framePos, frameLen, bufs) {
1212
+ let ccFramePos = 0;
1213
+ let trgFramePos = framePos;
1214
+ let ch0Data = this.data[0];
1215
+ let cPos = 0;
1216
+ let filled = 0;
1217
+ let ci = 0;
1218
+ while (filled < frameLen && ci < this._chunkCount) {
1219
+ // Current chunk
1220
+ let cc0 = ch0Data[ci];
1221
+ let ccLen = cc0.length;
1222
+ let ccFrameEndPos = ccFramePos + ccLen;
1223
+ if (trgFramePos >= ccFramePos && trgFramePos < ccFrameEndPos) {
1224
+ let toCp = frameLen - filled;
1225
+ cPos = trgFramePos - ccFramePos;
1226
+ if (cPos + toCp > ccLen) {
1227
+ toCp = ccLen - cPos;
1228
+ }
1229
+ for (let ch = 0; ch < bufs.length; ch++) {
1230
+ let cc = this.data[ch][ci];
1231
+ for (let i = 0; i < toCp; i++) {
1232
+ bufs[ch][filled + i] = cc[cPos + i];
1233
+ }
1234
+ }
1235
+ filled += toCp;
1236
+ trgFramePos += toCp;
1237
+ cPos += toCp;
1238
+ ccFramePos += toCp;
1239
+ if (cPos >= ccLen) {
1240
+ ccFramePos = ccFrameEndPos;
1241
+ cPos = 0;
1242
+ ci++;
1243
+ }
1244
+ }
1245
+ else {
1246
+ // next chunk
1247
+ ccFramePos = ccFrameEndPos;
1248
+ cPos = 0;
1249
+ ci++;
1250
+ }
1251
+ }
1252
+ return filled;
1253
+ }
1254
+ get sampleRate() {
1255
+ return this._sampleRate;
1256
+ }
1257
+ get frameLen() {
1258
+ return this._frameLen;
1259
+ }
1260
+ get chunkCount() {
1261
+ return this._chunkCount;
1262
+ }
1263
+ get data() {
1264
+ return this._data;
1265
+ }
1266
+ }
1267
+
525
1268
  const CHROME_ACTIVATE_ECHO_CANCELLATION_WITH_AGC = false;
526
1269
  const DEBUG_TRACE_LEVEL = 0;
527
1270
  const ENABLE_AUDIO_WORKLET = true;
@@ -1092,6 +1835,10 @@ class AudioCapture {
1092
1835
  }
1093
1836
  return ab;
1094
1837
  }
1838
+ audioBufferArray() {
1839
+ let aba = new ArrayAudioBuffer(this.channelCount, this.currentSampleRate, this.data);
1840
+ return aba;
1841
+ }
1095
1842
  }
1096
1843
  AudioCapture.BUFFER_SIZE = 8192;
1097
1844
 
@@ -1471,21 +2218,28 @@ class RecordingFileDescriptorImpl {
1471
2218
  constructor() { }
1472
2219
  }
1473
2220
  class RecordingFile {
1474
- constructor(uuid, sessionId, audioBuffer) {
2221
+ constructor(uuid, sessionId, audioDataHolder) {
1475
2222
  this.recordingFileId = null;
1476
2223
  this.uuid = null;
2224
+ this.serverPersisted = false;
2225
+ this.keepAudioDataCache = false;
1477
2226
  this.date = null;
1478
2227
  this._dateAsDateObj = null;
1479
2228
  this.startedDate = null;
1480
2229
  this._startedAsDateObj = null;
1481
- this.audioBuffer = null;
2230
+ this.audioDataHolder = null;
1482
2231
  this.session = null;
1483
2232
  this.frames = null;
2233
+ this.timeLength = null;
1484
2234
  this.editSampleRate = null;
1485
2235
  this.editStartFrame = null;
1486
2236
  this.editEndFrame = null;
1487
2237
  this.session = sessionId;
1488
- this.audioBuffer = audioBuffer;
2238
+ this.audioDataHolder = audioDataHolder;
2239
+ if (audioDataHolder) {
2240
+ this.frames = audioDataHolder.frameLen;
2241
+ this.timeLength = audioDataHolder.duration;
2242
+ }
1489
2243
  this.uuid = uuid;
1490
2244
  }
1491
2245
  filenameString() {
@@ -1497,14 +2251,27 @@ class RecordingFile {
1497
2251
  fns += this.uuid;
1498
2252
  return fns;
1499
2253
  }
2254
+ equals(otherRecordingFile) {
2255
+ if (otherRecordingFile !== null) {
2256
+ if (otherRecordingFile === this) {
2257
+ return true;
2258
+ }
2259
+ if (otherRecordingFile.uuid === this.uuid) {
2260
+ return true;
2261
+ }
2262
+ }
2263
+ return false;
2264
+ }
2265
+ toString() {
2266
+ return 'Recording file: UUID: ' + this.uuid + ', session: ' + this.session;
2267
+ }
1500
2268
  }
1501
2269
  class SprRecordingFile extends RecordingFile {
1502
- constructor(sessionId, itemcode, version, audioBuffer) {
1503
- super(UUID.generate(), sessionId, audioBuffer);
2270
+ constructor(sessionId, itemcode, version, audioDataHolder) {
2271
+ super(UUID.generate(), sessionId, audioDataHolder);
1504
2272
  this.session = sessionId;
1505
2273
  this.itemCode = itemcode;
1506
2274
  this.version = version;
1507
- this.audioBuffer = audioBuffer;
1508
2275
  }
1509
2276
  filenameString() {
1510
2277
  let fns = '';
@@ -1519,6 +2286,31 @@ class SprRecordingFile extends RecordingFile {
1519
2286
  fns += this.uuid;
1520
2287
  return fns;
1521
2288
  }
2289
+ toString() {
2290
+ return 'Recording file: UUID: ' + this.uuid + ', session: ' + this.session + ', itemcode: ' + this.itemCode + ', version: ' + this.version + ', UUID: ' + this.uuid;
2291
+ }
2292
+ }
2293
+ class RecordingFileUtils {
2294
+ static setAudioData(rf, audioDataHolder) {
2295
+ rf.audioDataHolder = audioDataHolder;
2296
+ if (audioDataHolder) {
2297
+ rf.frames = audioDataHolder.frameLen;
2298
+ rf.timeLength = audioDataHolder.duration;
2299
+ }
2300
+ }
2301
+ static sampleCount(rf) {
2302
+ if (rf.audioDataHolder) {
2303
+ return rf.audioDataHolder.sampleCounts();
2304
+ }
2305
+ else {
2306
+ return 0;
2307
+ }
2308
+ }
2309
+ static expireAudioData(rf) {
2310
+ let rv = rf.audioDataHolder;
2311
+ rf.audioDataHolder = null;
2312
+ return rv;
2313
+ }
1522
2314
  }
1523
2315
 
1524
2316
  // state of an upload
@@ -1569,7 +2361,8 @@ class UploaderStatusChangeEvent {
1569
2361
  }
1570
2362
  }
1571
2363
  class Upload {
1572
- constructor(blob, url) {
2364
+ constructor(blob, url, serverPersistable = null) {
2365
+ this.serverPersistable = serverPersistable;
1573
2366
  this.toString = () => {
1574
2367
  let s = `Upload: Status: ${this.status}, URL: ${this._url}`;
1575
2368
  if (this._data instanceof Blob) {
@@ -1590,6 +2383,17 @@ class Upload {
1590
2383
  get data() {
1591
2384
  return this._data;
1592
2385
  }
2386
+ done() {
2387
+ this.status = UploadStatus$1.DONE;
2388
+ //console.debug("Single upload done.");
2389
+ if (this.serverPersistable) {
2390
+ this.serverPersistable.serverPersisted = true;
2391
+ //console.debug("Single upload set server persisted: "+this.serverPersistable);
2392
+ }
2393
+ else {
2394
+ //console.debug("Server persistable not set.");
2395
+ }
2396
+ }
1593
2397
  }
1594
2398
  class Uploader {
1595
2399
  constructor(http, withCredentials = false) {
@@ -1628,7 +2432,7 @@ class Uploader {
1628
2432
  return si;
1629
2433
  }
1630
2434
  uploadDone(ul) {
1631
- ul.status = UploadStatus$1.DONE;
2435
+ ul.done();
1632
2436
  // remove upload from queue
1633
2437
  for (let i = 0; i < this.que.length; i++) {
1634
2438
  if (this.que[i] === ul) {
@@ -2059,15 +2863,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
2059
2863
  }], ctorParameters: function () { return []; } });
2060
2864
 
2061
2865
  class AudioClip {
2062
- constructor(buffer) {
2866
+ constructor(_audioDataHolder) {
2867
+ this._audioDataHolder = _audioDataHolder;
2063
2868
  this._selection = null;
2869
+ this._levelInfos = null;
2064
2870
  this.selectionObservers = new Array();
2065
- this._buffer = buffer;
2066
2871
  }
2067
- get buffer() {
2068
- return this._buffer;
2872
+ get audioDataHolder() {
2873
+ return this._audioDataHolder;
2069
2874
  }
2070
- ;
2071
2875
  get selection() {
2072
2876
  return this._selection;
2073
2877
  }
@@ -2082,6 +2886,12 @@ class AudioClip {
2082
2886
  selObs(this);
2083
2887
  }
2084
2888
  }
2889
+ get levelInfos() {
2890
+ return this._levelInfos;
2891
+ }
2892
+ set levelInfos(value) {
2893
+ this._levelInfos = value;
2894
+ }
2085
2895
  addSelectionObserver(selectionObserver, init = false) {
2086
2896
  let obsAlreadyInList = this.selectionObservers.find((obs) => (obs === selectionObserver));
2087
2897
  if (!obsAlreadyInList) {
@@ -2164,19 +2974,31 @@ class ViewSelection {
2164
2974
  class BasicAudioCanvasLayerComponent extends CanvasLayerComponent {
2165
2975
  constructor() {
2166
2976
  super(...arguments);
2167
- this._audioData = null;
2977
+ //protected _audioData: AudioBuffer|null=null;
2978
+ //protected _arrayAudioData: ArrayAudioBuffer|null=null;
2979
+ this._audioDataHolder = null;
2168
2980
  this._bgColor = 'white';
2169
2981
  this._selectColor = 'rgba(0%,0%,100%,25%)';
2170
2982
  }
2983
+ frameLength() {
2984
+ let frameLength = null;
2985
+ // if (this._audioData && this._audioData.numberOfChannels > 0) {
2986
+ // let ch0 = this._audioData.getChannelData(0);
2987
+ // frameLength = ch0.length;
2988
+ //
2989
+ // }else if(this._arrayAudioData){
2990
+ // frameLength=this._arrayAudioData.frameLen;
2991
+ // }
2992
+ return frameLength;
2993
+ }
2171
2994
  /**
2172
2995
  * Returns pixel position depending on current zoom setting.
2173
2996
  * @param framePos audio frame (sample) position
2174
2997
  */
2175
2998
  frameToXPixelPosition(framePos) {
2176
2999
  let pixelPos = null;
2177
- if (this._audioData && this._audioData.numberOfChannels > 0) {
2178
- let ch0 = this._audioData.getChannelData(0);
2179
- let frameLength = ch0.length;
3000
+ let frameLength = this._audioDataHolder?.frameLen;
3001
+ if (frameLength !== undefined) {
2180
3002
  let vw;
2181
3003
  if (this.bounds) {
2182
3004
  vw = this.bounds.dimension.width;
@@ -2206,9 +3028,8 @@ class BasicAudioCanvasLayerComponent extends CanvasLayerComponent {
2206
3028
  }
2207
3029
  viewPortXPixelToFramePosition(xViewPortPixelPos) {
2208
3030
  let vpXramePos = null;
2209
- if (this._audioData && this._audioData.numberOfChannels > 0) {
2210
- let ch0 = this._audioData.getChannelData(0);
2211
- let frameLength = ch0.length;
3031
+ let frameLength = this._audioDataHolder?.frameLen;
3032
+ if (frameLength !== undefined) {
2212
3033
  let vw;
2213
3034
  if (this.bounds) {
2214
3035
  vw = this.bounds.dimension.width;
@@ -2385,8 +3206,8 @@ class AudioCanvasLayerComponent extends BasicAudioCanvasLayerComponent {
2385
3206
  if (viewSel) {
2386
3207
  let frameStart = this.viewPortXPixelToFramePosition(viewSel.startX);
2387
3208
  let frameEnd = this.viewPortXPixelToFramePosition(viewSel.endX);
2388
- if (this._audioData && frameStart != null && frameEnd != null) {
2389
- ns = new Selection(this._audioData.sampleRate, frameStart, frameEnd);
3209
+ if (this._audioDataHolder && frameStart != null && frameEnd != null) {
3210
+ ns = new Selection(this._audioDataHolder.sampleRate, frameStart, frameEnd);
2390
3211
  }
2391
3212
  }
2392
3213
  this.selectingEventEmitter.emit(ns);
@@ -2396,8 +3217,8 @@ class AudioCanvasLayerComponent extends BasicAudioCanvasLayerComponent {
2396
3217
  if (viewSel) {
2397
3218
  let frameStart = this.viewPortXPixelToFramePosition(viewSel.startX);
2398
3219
  let frameEnd = this.viewPortXPixelToFramePosition(viewSel.endX);
2399
- if (this._audioData && frameStart != null && frameEnd != null) {
2400
- ns = new Selection(this._audioData.sampleRate, frameStart, frameEnd);
3220
+ if (this._audioDataHolder && frameStart != null && frameEnd != null) {
3221
+ ns = new Selection(this._audioDataHolder.sampleRate, frameStart, frameEnd);
2401
3222
  }
2402
3223
  }
2403
3224
  this.selectedEventEmitter.emit(ns);
@@ -2478,7 +3299,7 @@ class AudioCanvasLayerComponent extends BasicAudioCanvasLayerComponent {
2478
3299
  g.lineTo(xViewPortPixelpos, h);
2479
3300
  g.closePath();
2480
3301
  g.stroke();
2481
- if (this._audioData) {
3302
+ if (this._audioDataHolder) {
2482
3303
  g.font = '14px sans-serif';
2483
3304
  g.fillStyle = 'yellow';
2484
3305
  g.fillText(framePos.toString(), xViewPortPixelpos + 2, 50);
@@ -2490,6 +3311,7 @@ class AudioCanvasLayerComponent extends BasicAudioCanvasLayerComponent {
2490
3311
  }
2491
3312
  }
2492
3313
  }
3314
+ AudioCanvasLayerComponent.ENABLE_STREAMING_NUMBER_OF_SAMPLES_THRESHOLD = 10 * 60 * 48000; // Use streaming/chunking if audio clip has more than this number of samples
2493
3315
  AudioCanvasLayerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: AudioCanvasLayerComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
2494
3316
  AudioCanvasLayerComponent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.11", type: AudioCanvasLayerComponent, inputs: { pointerPosition: "pointerPosition", selecting: "selecting", selection: "selection" }, outputs: { pointerPositionEventEmitter: "pointerPositionEventEmitter", selectingEventEmitter: "selectingEventEmitter", selectedEventEmitter: "selectedEventEmitter" }, host: { listeners: { "document:mouseup": "onMouseup($event)" } }, viewQueries: [{ propertyName: "bgCanvasRef", first: true, predicate: ["bg"], descendants: true, static: true }, { propertyName: "cursorCanvasRef", first: true, predicate: ["cursor"], descendants: true, static: true }], usesInheritance: true, ngImport: i0 });
2495
3317
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: AudioCanvasLayerComponent, decorators: [{
@@ -2524,7 +3346,7 @@ class AudioSignal extends AudioCanvasLayerComponent {
2524
3346
  this._playFramePosition = null;
2525
3347
  this.worker = null;
2526
3348
  this.workerURL = WorkerHelper.buildWorkerBlobURL(this.workerFunction);
2527
- this._audioData = null;
3349
+ this._audioDataHolder = null;
2528
3350
  this._bgColor = 'black';
2529
3351
  this._selectColor = 'rgba(255,255,0,0.8)';
2530
3352
  }
@@ -2577,25 +3399,26 @@ class AudioSignal extends AudioCanvasLayerComponent {
2577
3399
  */
2578
3400
  workerFunction() {
2579
3401
  addEventListener('message', ({ data }) => {
2580
- let audioData = data.audioData;
2581
- let l = data.l;
2582
- let w = data.w;
2583
- let h = data.h;
2584
- let vw = data.vw;
2585
- let chs = data.chs;
2586
- let frameLength = data.frameLength;
3402
+ let audioData = data.audioData; // audio data part required to render view port
3403
+ let auOffset = data.audioDataOffset;
3404
+ let l = data.l; // left pixel position of view port
3405
+ let w = data.w; // width of viewport
3406
+ let vw = data.vw; // total width of (virtual) audio view (not viewport width)
3407
+ let chs = data.chs; // number of channels
3408
+ let dataFrameLength = data.audioDataFrameLength; // frame length of audio data part required for view port
3409
+ let frameLength = data.frameLength; // total frame length (of audio clip)
3410
+ //console.debug("W: left: "+l+", w:"+w+", vw: "+vw+", chs: "+chs+", frameLength: "+frameLength);
2587
3411
  let psMinMax = new Float32Array(0);
2588
- if (audioData && w >= 0 && vw > 0) {
3412
+ if (audioData && audioData.length > 0 && w >= 0 && vw > 0) {
2589
3413
  let framesPerPixel = frameLength / vw;
2590
- let y = 0;
2591
3414
  let pointsLen = w * chs;
2592
3415
  // one for min one for max
2593
3416
  let arrLen = pointsLen * 2;
2594
3417
  psMinMax = new Float32Array(arrLen);
2595
3418
  let chFramePos = 0;
3419
+ let chFrameLength = audioData.length / chs;
2596
3420
  for (let ch = 0; ch < chs; ch++) {
2597
- let x = 0;
2598
- chFramePos = ch * frameLength;
3421
+ chFramePos = ch * chFrameLength;
2599
3422
  for (let pii = 0; pii < w; pii++) {
2600
3423
  let virtPii = l + pii;
2601
3424
  let pMin = Infinity;
@@ -2605,24 +3428,28 @@ class AudioSignal extends AudioCanvasLayerComponent {
2605
3428
  for (let ai = 0; ai < framesPerPixel; ai++) {
2606
3429
  let framePos = pixelFramePos + ai;
2607
3430
  let a = 0;
2608
- if (framePos >= 0 && framePos < audioData.length) {
2609
- a = audioData[framePos];
2610
- }
2611
- if (a < pMin) {
2612
- pMin = a;
2613
- }
2614
- if (a > pMax) {
2615
- pMax = a;
3431
+ let bufPos = framePos - auOffset;
3432
+ //let bufPos=framePos;
3433
+ if (bufPos >= 0 && bufPos < audioData.length) {
3434
+ a = audioData[bufPos];
3435
+ //console.debug("W: ch: "+ch+", pixelFramePos: "+pixelFramePos+", framePos: "+framePos+", auOffset: "+auOffset+", bufPos: "+bufPos+", audioData.length: "+audioData.length+", a: "+a);
3436
+ if (a < pMin) {
3437
+ pMin = a;
3438
+ }
3439
+ if (a > pMax) {
3440
+ pMax = a;
3441
+ }
2616
3442
  }
2617
3443
  }
2618
3444
  let psMinPos = ch * w + pii;
2619
3445
  psMinMax[psMinPos] = pMin;
2620
3446
  let psMaxPos = pointsLen + psMinPos;
2621
3447
  psMinMax[psMaxPos] = pMax;
3448
+ //console.debug("psMinMax["+psMinPos+"]="+pMin+",psMinMax["+psMaxPos+"]="+pMax);
2622
3449
  }
2623
3450
  }
2624
3451
  }
2625
- postMessage({ psMinMax: psMinMax, l: data.l, t: data.t, w: data.w, h: data.h, chs: data.chs }, [psMinMax.buffer]);
3452
+ postMessage({ psMinMax: psMinMax, l: data.l, w: data.w, chs: data.chs, eod: data.eod }, [psMinMax.buffer]);
2626
3453
  });
2627
3454
  }
2628
3455
  startDraw(clear = true) {
@@ -2650,41 +3477,143 @@ class AudioSignal extends AudioCanvasLayerComponent {
2650
3477
  if (this.bounds && this.bounds.dimension) {
2651
3478
  let w = Math.round(this.bounds.dimension.width);
2652
3479
  let h = Math.round(this.bounds.dimension.height);
2653
- if (this._audioData && w > 0 && h > 0) {
3480
+ if (this._audioDataHolder && w > 0 && h > 0) {
2654
3481
  //this.wo = new Worker('./audiosignal.worker.js',{type: 'module'});
2655
3482
  this.worker = new Worker(this.workerURL);
2656
3483
  //this.wo = new Worker('worker/audiosignal.worker.ts');
2657
3484
  //let Worker = require('worker!../../../workers/uploader/main');
2658
- let chs = this._audioData.numberOfChannels;
2659
- let frameLength = this._audioData.getChannelData(0).length;
2660
- // if(frameLength != this.audioData.getChannelData(1).length){
2661
- // alert("Ungleiche Länge");
2662
- // }
2663
- let ad = new Float32Array(chs * frameLength);
2664
- for (let ch = 0; ch < chs; ch++) {
2665
- ad.set(this._audioData.getChannelData(ch), ch * frameLength);
2666
- }
2667
- //let start = Date.now();
3485
+ let chs = this._audioDataHolder.numberOfChannels;
3486
+ let leftPos = Math.round(this.bounds.position.left);
3487
+ let renderPos = leftPos;
3488
+ let vw = Math.round(this.virtualDimension.width);
3489
+ let frameLength = this._audioDataHolder.frameLen;
3490
+ let framesPerPixel = Math.ceil(frameLength / vw);
3491
+ let audioBuffer = this._audioDataHolder.buffer;
3492
+ //let arrayAudioBuffer=this._audioDataHolder.arrayBuffer;
3493
+ let arrAbBuf;
3494
+ //let ais:ArrayAudioBufferInputStream|null=null;
3495
+ //let aisBuf:Float32Array[]|null=null;
3496
+ let psMinMax = null;
2668
3497
  if (this.worker) {
2669
3498
  this.worker.onmessage = (me) => {
2670
- //console.log("As rendertime: ", Date.now() - start);
2671
- this.drawRendered(me);
2672
- if (this.worker) {
2673
- this.worker.terminate();
3499
+ if (me.data.eod === true) {
3500
+ let psMinMaxTmp;
3501
+ if (psMinMax) {
3502
+ psMinMaxTmp = psMinMax;
3503
+ }
3504
+ else {
3505
+ psMinMaxTmp = me.data.psMinMax;
3506
+ }
3507
+ this.drawRendered(leftPos, w, h, chs, psMinMaxTmp);
3508
+ if (this.worker) {
3509
+ this.worker.terminate();
3510
+ }
3511
+ this.worker = null;
3512
+ }
3513
+ else if (this._audioDataHolder && arrAbBuf) {
3514
+ let rw = me.data.w;
3515
+ let rPointsLen = chs * rw;
3516
+ let pointsLen = chs * w;
3517
+ for (let ch = 0; ch < chs; ch++) {
3518
+ if (psMinMax) {
3519
+ let rBasePos = ch * rw;
3520
+ let basePos = ch * w;
3521
+ let rPosMin = rBasePos;
3522
+ let rPosMax = rPointsLen + rPosMin;
3523
+ let posMin = basePos + (renderPos - leftPos);
3524
+ let posMax = pointsLen + posMin;
3525
+ psMinMax[posMin] = me.data.psMinMax[rPosMin];
3526
+ //console.debug('Min: ('+pos+'): '+me.data.psMinMax[0]);
3527
+ psMinMax[posMax] = me.data.psMinMax[rPosMax];
3528
+ // console.debug('Max: ('+(pointsLen+pos)+'): '+me.data.psMinMax[1]);
3529
+ //console.debug("psMinMax["+posMin+"]="+me.data.psMinMax[rPosMin]+" (rPosMin="+rPosMin+"),psMinMax["+posMax+"]="+me.data.psMinMax[rPosMax]);
3530
+ }
3531
+ }
3532
+ let eod = false;
3533
+ renderPos++;
3534
+ let ad;
3535
+ let leftFramePos = Math.floor(frameLength * renderPos / vw);
3536
+ if (renderPos < leftPos + w) {
3537
+ let read = this._audioDataHolder.frames(leftFramePos, framesPerPixel, arrAbBuf);
3538
+ //console.debug("First read frame: "+arrAbBuf[0][0]);
3539
+ ad = new Float32Array(chs * framesPerPixel);
3540
+ for (let ch = 0; ch < chs; ch++) {
3541
+ ad.set(arrAbBuf[ch], ch * framesPerPixel);
3542
+ }
3543
+ eod = (read <= 0);
3544
+ }
3545
+ else {
3546
+ ad = new Float32Array();
3547
+ eod = true;
3548
+ }
3549
+ let adBuf = ad.buffer;
3550
+ if (this.worker) {
3551
+ this.worker.postMessage({
3552
+ l: renderPos,
3553
+ w: me.data.w,
3554
+ h: h,
3555
+ vw: vw,
3556
+ chs: chs,
3557
+ frameLength: frameLength,
3558
+ audioData: ad,
3559
+ audioDataOffset: leftFramePos,
3560
+ eod: eod
3561
+ }, [adBuf]);
3562
+ }
2674
3563
  }
2675
- this.worker = null;
2676
3564
  };
2677
3565
  }
2678
- this.worker.postMessage({
2679
- l: Math.round(this.bounds.position.left),
2680
- t: Math.round(this.bounds.position.top),
2681
- w: w,
2682
- h: h,
2683
- vw: Math.round(this.virtualDimension.width),
2684
- chs: chs,
2685
- frameLength: frameLength,
2686
- audioData: ad
2687
- }, [ad.buffer]);
3566
+ if (audioBuffer && audioBuffer.length * audioBuffer.numberOfChannels < AudioCanvasLayerComponent.ENABLE_STREAMING_NUMBER_OF_SAMPLES_THRESHOLD) {
3567
+ // Render whole clip at once
3568
+ arrAbBuf = null;
3569
+ psMinMax = null;
3570
+ let ad = new Float32Array(chs * frameLength);
3571
+ for (let ch = 0; ch < chs; ch++) {
3572
+ ad.set(audioBuffer.getChannelData(ch), ch * frameLength);
3573
+ }
3574
+ this.worker.postMessage({
3575
+ l: leftPos,
3576
+ w: w,
3577
+ vw: vw,
3578
+ chs: chs,
3579
+ frameLength: frameLength,
3580
+ audioData: ad,
3581
+ audioDataOffset: 0,
3582
+ eod: true
3583
+ }, [ad.buffer]);
3584
+ }
3585
+ else {
3586
+ // Render pixel by pixel
3587
+ if (w > 0) {
3588
+ if (framesPerPixel > 0) {
3589
+ let rw = 1;
3590
+ //ais = new ArrayAudioBufferInputStream(arrayAudioBuffer);
3591
+ arrAbBuf = new Array(chs);
3592
+ psMinMax = new Float32Array(chs * w * 2);
3593
+ for (let ch = 0; ch < chs; ch++) {
3594
+ arrAbBuf[ch] = new Float32Array(framesPerPixel);
3595
+ }
3596
+ let leftFramePos = Math.floor(frameLength * renderPos / vw);
3597
+ let auOffset = leftFramePos; // should always be 0
3598
+ //let read=arrayAudioBuffer.frames(leftFramePos,framesPerPixel,arrAbBuf);
3599
+ let read = this._audioDataHolder.frames(leftFramePos, framesPerPixel, arrAbBuf);
3600
+ let ad = new Float32Array(chs * framesPerPixel);
3601
+ for (let ch = 0; ch < chs; ch++) {
3602
+ ad.set(arrAbBuf[ch], ch * framesPerPixel);
3603
+ }
3604
+ this.worker.postMessage({
3605
+ l: renderPos,
3606
+ w: rw,
3607
+ vw: vw,
3608
+ chs: chs,
3609
+ frameLength: frameLength,
3610
+ audioData: ad,
3611
+ audioDataOffset: auOffset,
3612
+ eod: (read <= 0)
3613
+ }, [ad.buffer]);
3614
+ }
3615
+ }
3616
+ }
2688
3617
  }
2689
3618
  else {
2690
3619
  let g = this.signalCanvas.getContext("2d");
@@ -2694,42 +3623,42 @@ class AudioSignal extends AudioCanvasLayerComponent {
2694
3623
  }
2695
3624
  }
2696
3625
  }
2697
- drawRendered(me) {
3626
+ drawRendered(left, w, h, chs, psMinMax) {
2698
3627
  this.drawBg();
2699
- this.signalCanvas.style.left = me.data.l.toString() + 'px';
2700
- this.signalCanvas.width = me.data.w;
2701
- this.signalCanvas.height = me.data.h;
3628
+ this.signalCanvas.style.left = left.toString() + 'px';
3629
+ this.signalCanvas.width = w;
3630
+ this.signalCanvas.height = h;
2702
3631
  let g = this.signalCanvas.getContext("2d");
2703
3632
  if (g) {
2704
- g.clearRect(0, 0, me.data.w, me.data.h);
3633
+ g.clearRect(0, 0, w, h);
2705
3634
  //g.fillStyle = "black";
2706
3635
  //g.fillRect(0, 0, me.data.w, me.data.h);
2707
- let pointsLen = me.data.w * me.data.chs;
3636
+ let pointsLen = w * chs;
2708
3637
  // one for min one for max
2709
3638
  let arrLen = pointsLen * 2;
2710
- if (this._audioData) {
3639
+ if (this._audioDataHolder) {
2711
3640
  let std = Date.now();
2712
- let chH = me.data.h / me.data.chs;
3641
+ let chH = h / chs;
2713
3642
  let y = 0;
2714
- for (let ch = 0; ch < me.data.chs; ch++) {
3643
+ for (let ch = 0; ch < chs; ch++) {
2715
3644
  let x = 0;
2716
- let psMinPos = ch * me.data.w;
3645
+ let psMinPos = ch * w;
2717
3646
  let psMaxPos = pointsLen + psMinPos;
2718
3647
  g.fillStyle = 'green';
2719
3648
  g.strokeStyle = 'green';
2720
3649
  // draw audio signal as single polygon
2721
3650
  g.beginPath();
2722
- g.moveTo(0, y + (chH / 2) + me.data.psMinMax[psMaxPos] * chH / 2);
2723
- for (let pii = 0; pii < me.data.w; pii++) {
2724
- let psMax = me.data.psMinMax[psMaxPos + pii];
3651
+ g.moveTo(0, y + (chH / 2) + psMinMax[psMaxPos] * chH / 2);
3652
+ for (let pii = 0; pii < w; pii++) {
3653
+ let psMax = psMinMax[psMaxPos + pii];
2725
3654
  let pv = psMax * chH / 2;
2726
3655
  let yd = y + (chH / 2) - pv;
2727
3656
  //console.log("LineTo: : "+pii+" "+yd)
2728
3657
  g.lineTo(pii, yd);
2729
3658
  }
2730
- let revPixelStart = me.data.w - 1;
3659
+ let revPixelStart = w - 1;
2731
3660
  for (let pii = revPixelStart; pii >= 0; pii--) {
2732
- let psMin = me.data.psMinMax[psMinPos + pii];
3661
+ let psMin = psMinMax[psMinPos + pii];
2733
3662
  let pv = psMin * chH / 2;
2734
3663
  let yd = y + (chH / 2) - pv;
2735
3664
  //console.log("LineTo: : "+pii+" "+yd)
@@ -2753,13 +3682,23 @@ class AudioSignal extends AudioCanvasLayerComponent {
2753
3682
  g.clearRect(0, 0, w, h);
2754
3683
  //g.fillStyle = "black";
2755
3684
  //g.fillRect(0, 0, w, h);
2756
- if (this._audioData) {
3685
+ if (this._audioDataHolder) {
2757
3686
  let std = Date.now();
2758
- let chs = this._audioData.numberOfChannels;
3687
+ let chs = this._audioDataHolder.numberOfChannels;
2759
3688
  let chH = h / chs;
2760
- let frameLength = this._audioData.getChannelData(0).length;
3689
+ let frameLength = this._audioDataHolder.frameLen;
2761
3690
  let framesPerPixel = frameLength / w;
2762
3691
  let y = 0;
3692
+ let ais = null;
3693
+ let audioBuffer = this._audioDataHolder.buffer;
3694
+ let aisBuffer = null;
3695
+ if (!audioBuffer) {
3696
+ ais = this._audioDataHolder.audioInputStream();
3697
+ aisBuffer = new Array(chs);
3698
+ for (let ch = 0; ch < chs; ch++) {
3699
+ aisBuffer[ch] = new Float32Array(framesPerPixel);
3700
+ }
3701
+ }
2763
3702
  for (let ch = 0; ch < chs; ch++) {
2764
3703
  let x = 0;
2765
3704
  let psMin = new Float32Array(w);
@@ -2768,20 +3707,33 @@ class AudioSignal extends AudioCanvasLayerComponent {
2768
3707
  for (let pii = 0; pii < w; pii++) {
2769
3708
  let pMin = 0;
2770
3709
  let pMax = 0;
2771
- // calculate pixel min/max amplitude
2772
- for (let ai = 0; ai < framesPerPixel; ai++) {
2773
- //let framePos=(pii*framesPerPixel)+ai;
2774
- let a = this._audioData.getChannelData(ch)[framePos++];
2775
- if (a < pMin) {
2776
- pMin = a;
3710
+ if (audioBuffer) {
3711
+ // calculate pixel min/max amplitude
3712
+ for (let ai = 0; ai < framesPerPixel; ai++) {
3713
+ //let framePos=(pii*framesPerPixel)+ai;
3714
+ let a = audioBuffer.getChannelData(ch)[framePos++];
3715
+ if (a < pMin) {
3716
+ pMin = a;
3717
+ }
3718
+ if (a > pMax) {
3719
+ pMax = a;
3720
+ }
2777
3721
  }
2778
- if (a > pMax) {
2779
- pMax = a;
3722
+ }
3723
+ else if (ais && aisBuffer) {
3724
+ let r = ais.read(aisBuffer);
3725
+ for (let ai = 0; ai < r; ai++) {
3726
+ let a = aisBuffer[ch][ai];
3727
+ if (a < pMin) {
3728
+ pMin = a;
3729
+ }
3730
+ if (a > pMax) {
3731
+ pMax = a;
3732
+ }
2780
3733
  }
2781
3734
  }
2782
3735
  psMin[pii] = pMin;
2783
3736
  psMax[pii] = pMax;
2784
- //console.log("Min: ", pMin, " max: ", pMax);
2785
3737
  }
2786
3738
  g.fillStyle = 'green';
2787
3739
  g.strokeStyle = 'green';
@@ -2806,12 +3758,11 @@ class AudioSignal extends AudioCanvasLayerComponent {
2806
3758
  g.stroke();
2807
3759
  y += chH;
2808
3760
  }
2809
- //this.drawPlayPosition();
2810
3761
  }
2811
3762
  }
2812
3763
  }
2813
3764
  setData(audioData) {
2814
- this._audioData = audioData;
3765
+ this._audioDataHolder = audioData;
2815
3766
  this.playFramePosition = 0;
2816
3767
  }
2817
3768
  }
@@ -3180,7 +4131,7 @@ class Sonagram extends AudioCanvasLayerComponent {
3180
4131
  this._playFramePosition = null;
3181
4132
  this.dftSize = DEFAULT_DFT_SIZE;
3182
4133
  this.worker = null;
3183
- this._audioData = null;
4134
+ this._audioDataHolder = null;
3184
4135
  this.markers = new Array();
3185
4136
  this.dft = new DFTFloat32(this.dftSize);
3186
4137
  this.workerURL = WorkerHelper.buildWorkerBlobURL(this.workerFunction);
@@ -3233,7 +4184,7 @@ class Sonagram extends AudioCanvasLayerComponent {
3233
4184
  g.lineTo(xViewPortPixelpos, h);
3234
4185
  g.closePath();
3235
4186
  g.stroke();
3236
- if (this._audioData) {
4187
+ if (this._audioDataHolder) {
3237
4188
  let framePosRound = this.viewPortXPixelToFramePosition(xViewPortPixelpos);
3238
4189
  if (framePosRound != null) {
3239
4190
  g.font = '14px sans-serif';
@@ -3497,11 +4448,21 @@ class Sonagram extends AudioCanvasLayerComponent {
3497
4448
  }
3498
4449
  GaussianWindow.DEFAULT_SIGMA = 0.3;
3499
4450
  self.onmessage = function (msg) {
4451
+ //console.debug("Sonagram render thread");
3500
4452
  let l = msg.data.l;
3501
4453
  let w = msg.data.w;
3502
4454
  let h = msg.data.h;
3503
4455
  let vw = msg.data.vw;
3504
4456
  let chs = msg.data.chs;
4457
+ let audioDataOffset = 0;
4458
+ let adOffset = msg.data.audioDataOffset;
4459
+ if (adOffset) {
4460
+ audioDataOffset = adOffset;
4461
+ }
4462
+ let maxPsd = null;
4463
+ if (msg.data.maxPsd !== undefined) {
4464
+ maxPsd = msg.data.maxPsd;
4465
+ }
3505
4466
  let audioData = new Array(chs);
3506
4467
  for (let ch = 0; ch < chs; ch++) {
3507
4468
  audioData[ch] = new Float32Array(msg.data['audioData'][ch]);
@@ -3517,16 +4478,17 @@ class Sonagram extends AudioCanvasLayerComponent {
3517
4478
  }
3518
4479
  let imgData = new Uint8ClampedArray(arrSize);
3519
4480
  //console.log("Render method:");
3520
- if (audioData && arrSize > 0) {
4481
+ //console.debug("Created imgData arrSize: "+arrSize+" ", w, "x", h);
4482
+ let calcMaxPsd = -Infinity;
4483
+ if (arrSize > 0) {
3521
4484
  let chH = Math.round(h / chs);
3522
4485
  let framesPerPixel = frameLength / vw;
3523
- //console.log("Render: ", w, "x", h);
4486
+ //console.debug("Render: ", w, "x", h);
3524
4487
  let b = new Float32Array(dftSize);
3525
4488
  let sona = new Array(chs);
3526
- let maxPsd = -Infinity;
3527
- let p = 0;
4489
+ //let p = 0;
3528
4490
  for (let ch = 0; ch < chs; ch++) {
3529
- p = ch * frameLength;
4491
+ //p = ch * frameLength;
3530
4492
  let chDataLen = audioData[ch].length;
3531
4493
  let x = 0;
3532
4494
  // initialize DFT array buffer
@@ -3543,8 +4505,13 @@ class Sonagram extends AudioCanvasLayerComponent {
3543
4505
  // initialize for negative sample positions and out of bounds positions
3544
4506
  let chDat = 0;
3545
4507
  // Set audio sample if available
3546
- if (samplePos >= 0 && samplePos < chDataLen) {
3547
- chDat = audioData[ch][samplePos];
4508
+ let adp = samplePos - audioDataOffset;
4509
+ if (adp >= 0 && adp < chDataLen) {
4510
+ chDat = audioData[ch][adp];
4511
+ //console.debug("Audio data: "+chDat);
4512
+ }
4513
+ else {
4514
+ //console.debug("Sample buf pos oob: adp: "+adp+", chDataLen: "+chDataLen+", samplePos: "+samplePos+", i: "+i);
3548
4515
  }
3549
4516
  // apply Window
3550
4517
  b[i] = chDat * wf.getScale(i);
@@ -3554,63 +4521,78 @@ class Sonagram extends AudioCanvasLayerComponent {
3554
4521
  // Get maximum value of spectral energy
3555
4522
  for (let s = 0; s < dftBands; s++) {
3556
4523
  let psd = (2 * Math.pow(spectr[s], 2)) / dftBands;
3557
- if (psd > maxPsd) {
3558
- maxPsd = psd;
4524
+ if (psd > calcMaxPsd) {
4525
+ calcMaxPsd = psd;
3559
4526
  }
3560
4527
  }
3561
4528
  // Set render model data for this pixel
3562
4529
  sona[ch][pii] = spectr;
3563
4530
  }
3564
4531
  }
3565
- //maxPsd = (2 * Math.pow(max, 2)) / dftBands;
3566
- for (let ch = 0; ch < chs; ch++) {
3567
- for (let pii = 0; pii < w; pii++) {
3568
- let allBlack = true;
3569
- for (let y = 0; y < chH; y++) {
3570
- let freqIdx = Math.round(y * dftBands / chH);
3571
- // calculate the one sided power spectral density PSD (f, t) in Pa2/Hz
3572
- // PSD(f) proportional to 2|X(f)|2 / (t2 - t1)
3573
- let val = sona[ch][pii][freqIdx];
3574
- let psd = (2 * Math.pow(val, 2)) / dftBands;
3575
- // Calculate logarithmic value
3576
- //let psdLog = ips.dsp.DSPUtils.toLevelInDB(psd / maxPsd);
3577
- let linearLevel = psd / maxPsd;
3578
- let psdLog = 10 * Math.log(linearLevel) / Math.log(10);
3579
- // Fixed dynamic Range value of 70dB for now
3580
- let dynRangeInDb = 70;
3581
- let scaledVal = (psdLog + dynRangeInDb) / dynRangeInDb;
3582
- // are the following checks necessary for clamped array ?
3583
- if (scaledVal > 1.0)
3584
- scaledVal = 1;
3585
- if (scaledVal < 0.0) {
3586
- scaledVal = 0;
3587
- }
3588
- let rgbVal = Math.round(255 * scaledVal);
3589
- if (rgbVal < 0) {
3590
- // System.out.println("Neg RGB val: "+rgbVal);
3591
- rgbVal = 0;
3592
- }
3593
- if (rgbVal > 255) {
3594
- rgbVal = 255;
3595
- }
3596
- rgbVal = 255 - rgbVal;
3597
- if (rgbVal > 0) {
3598
- allBlack = false;
4532
+ if (!msg.data.norender) {
4533
+ if (!maxPsd) {
4534
+ maxPsd = calcMaxPsd;
4535
+ }
4536
+ for (let ch = 0; ch < chs; ch++) {
4537
+ for (let pii = 0; pii < w; pii++) {
4538
+ let allBlack = true;
4539
+ for (let y = 0; y < chH; y++) {
4540
+ let freqIdx = Math.round(y * dftBands / chH);
4541
+ // calculate the one sided power spectral density PSD (f, t) in Pa2/Hz
4542
+ // PSD(f) proportional to 2|X(f)|2 / (t2 - t1)
4543
+ let val = sona[ch][pii][freqIdx];
4544
+ let psd = (2 * Math.pow(val, 2)) / dftBands;
4545
+ // Calculate logarithmic value
4546
+ //let psdLog = ips.dsp.DSPUtils.toLevelInDB(psd / maxPsd);
4547
+ let linearLevel = psd / maxPsd;
4548
+ let psdLog = 10 * Math.log(linearLevel) / Math.log(10);
4549
+ // Fixed dynamic Range value of 70dB for now
4550
+ let dynRangeInDb = 70;
4551
+ let scaledVal = (psdLog + dynRangeInDb) / dynRangeInDb;
4552
+ // are the following checks necessary for clamped array ?
4553
+ if (scaledVal > 1.0)
4554
+ scaledVal = 1;
4555
+ if (scaledVal < 0.0) {
4556
+ scaledVal = 0;
4557
+ }
4558
+ let rgbVal = Math.round(255 * scaledVal);
4559
+ if (rgbVal < 0) {
4560
+ // System.out.println("Neg RGB val: "+rgbVal);
4561
+ rgbVal = 0;
4562
+ }
4563
+ if (rgbVal > 255) {
4564
+ rgbVal = 255;
4565
+ }
4566
+ rgbVal = 255 - rgbVal;
4567
+ if (rgbVal > 0) {
4568
+ allBlack = false;
4569
+ }
4570
+ let py = chH - y;
4571
+ let dataPos = ((((ch * chH) + py) * w) + pii) * 4;
4572
+ imgData[dataPos + 0] = rgbVal; //R
4573
+ imgData[dataPos + 1] = rgbVal; //G
4574
+ imgData[dataPos + 2] = rgbVal; //B
4575
+ imgData[dataPos + 3] = 255; //A (alpha: fully opaque)
4576
+ //console.debug("Rendered: py: "+py+", rgbval: "+rgbVal);
4577
+ // example 1x1, 2chs
4578
+ // ch0x0y0R,ch0x0y0G,ch0x0y0B,ch0x0y0A,
4579
+ // ch0x1y0R,ch0x1y0G,ch0x1y0B,ch0x1y0A,
4580
+ // ch0x0y0R,ch0x0y0G,ch0x0y0B,ch0x0y0A,
4581
+ // ch0x1y1R,ch0x1y1G,ch0x1y1B,ch0x1y1A,
4582
+ // ch1x0y0R,ch1x0y0G,ch1x0y0B,ch1x0y0A,
4583
+ // ch1x1y0R,ch1x1y0G,ch1x1y0B,ch1x1y0A,
4584
+ // ch1x0y0R,ch1x0y0G,ch1x0y0B,ch1x0y0A,
4585
+ // ch1x1y1R,ch1x1y1G,ch1x1y1B,ch1x1y1A
3599
4586
  }
3600
- let py = chH - y;
3601
- let dataPos = ((((ch * chH) + py) * w) + pii) * 4;
3602
- imgData[dataPos + 0] = rgbVal; //R
3603
- imgData[dataPos + 1] = rgbVal; //G
3604
- imgData[dataPos + 2] = rgbVal; //B
3605
- imgData[dataPos + 3] = 255; //A (alpha: fully opaque)
4587
+ // if (allBlack) {
4588
+ // console.log("Black: ", pii, " ", ch);
4589
+ // }
3606
4590
  }
3607
- // if (allBlack) {
3608
- // console.log("Black: ", pii, " ", ch);
3609
- // }
3610
4591
  }
3611
4592
  }
3612
4593
  }
3613
- postMessage({ imgData: imgData, l: l, w: msg.data.w, h: msg.data.h, vw: vw }, [imgData.buffer]);
4594
+ //console.debug("Render thread post message imgData: "+imgData.length)
4595
+ postMessage({ imgData: imgData, l: l, w: msg.data.w, h: msg.data.h, vw: vw, maxPsd: calcMaxPsd, terminate: msg.data.terminate }, [imgData.buffer]);
3614
4596
  };
3615
4597
  }
3616
4598
  startDraw(clear = true) {
@@ -3640,42 +4622,190 @@ class Sonagram extends AudioCanvasLayerComponent {
3640
4622
  if (this.bounds) {
3641
4623
  let w = Math.round(this.bounds.dimension.width);
3642
4624
  let h = Math.round(this.bounds.dimension.height);
3643
- if (this._audioData && w > 0 && h > 0) {
4625
+ if (this._audioDataHolder && w > 0 && h > 0) {
3644
4626
  this.worker = new Worker(this.workerURL);
3645
4627
  //this.wo = new Worker('./worker/sonagram.worker', { type: `module` });
3646
- let chs = this._audioData.numberOfChannels;
3647
- let frameLength = this._audioData.getChannelData(0).length;
3648
- let ada = new Array(chs);
3649
- for (let ch = 0; ch < chs; ch++) {
3650
- // Need a copy here for the worker, otherwise this.audioData is not accessible after posting to the worker
3651
- ada[ch] = this._audioData.getChannelData(ch).buffer.slice(0);
3652
- }
3653
- let start = Date.now();
4628
+ let chs = this._audioDataHolder.numberOfChannels;
4629
+ let vw = Math.round(this.virtualDimension.width);
4630
+ let frameLength = this._audioDataHolder.frameLen;
4631
+ let framesPerPixel = Math.ceil(frameLength / vw);
4632
+ let leftPos = Math.round(this.bounds.position.left);
4633
+ let renderPos = leftPos;
4634
+ let audioBuffer = this._audioDataHolder.buffer;
4635
+ //let arrayAudioBuffer=this._audioDataHolder.arrayBuffer;
4636
+ let arrAbBuf;
4637
+ let imgData;
4638
+ let maxPsd = -Infinity;
4639
+ let norender = true;
3654
4640
  if (this.worker) {
3655
4641
  this.worker.onmessage = (me) => {
3656
- this.drawRendered(me);
3657
- if (this.worker) {
3658
- this.worker.terminate();
4642
+ if (true === me.data.terminate) {
4643
+ let drawImgData;
4644
+ if (imgData) {
4645
+ drawImgData = imgData;
4646
+ }
4647
+ else {
4648
+ drawImgData = me.data.imgData;
4649
+ }
4650
+ this.drawRendered(w, h, drawImgData);
4651
+ if (this.worker) {
4652
+ this.worker.terminate();
4653
+ }
4654
+ this.worker = null;
4655
+ }
4656
+ else {
4657
+ // set rendered vertical values of one pixel of timescale
4658
+ //let dataPos = renderPos * h * 4;
4659
+ if (norender) {
4660
+ if (me.data.maxPsd > maxPsd) {
4661
+ maxPsd = me.data.maxPsd;
4662
+ //console.debug("new maxPsd: "+maxPsd);
4663
+ }
4664
+ }
4665
+ else {
4666
+ let chH = Math.round(h / chs);
4667
+ let idp = me.data.l - leftPos;
4668
+ for (let ch = 0; ch < chs; ch++) {
4669
+ for (let y = 0; y < chH; y++) {
4670
+ let py = chH - y;
4671
+ let dataPos = ((((ch * chH) + py) * w) + idp) * 4;
4672
+ let mePos = ((ch * chH) + py) * 4;
4673
+ //let lastPos = dataPos + me.data.imgData.length;
4674
+ //if (lastPos < imgData.length) {
4675
+ // set one pixel
4676
+ imgData[dataPos] = me.data.imgData[mePos];
4677
+ imgData[dataPos + 1] = me.data.imgData[mePos + 1];
4678
+ imgData[dataPos + 2] = me.data.imgData[mePos + 2];
4679
+ imgData[dataPos + 3] = me.data.imgData[mePos + 3];
4680
+ //} else {
4681
+ //console.error("Out of range: " + dataPos + "+" + me.data.imgData.length + ">=" + imgData.length);
4682
+ // }
4683
+ }
4684
+ }
4685
+ }
4686
+ if (this._audioDataHolder && arrAbBuf && this.worker) {
4687
+ // proceed with next pixel
4688
+ renderPos++;
4689
+ //console.debug("Render pos: "+renderPos);
4690
+ let terminate = false;
4691
+ let windowEnd = renderPos >= leftPos + w;
4692
+ if (windowEnd) {
4693
+ if (norender) {
4694
+ // phase two: rendering
4695
+ norender = false;
4696
+ // start from beginning
4697
+ renderPos = leftPos;
4698
+ //console.debug("now rendering: maxPsd: "+maxPsd);
4699
+ }
4700
+ else {
4701
+ // terminate render phase
4702
+ terminate = true;
4703
+ }
4704
+ }
4705
+ let leftFramePos = Math.floor(frameLength * renderPos / vw) - this.dftSize / 2;
4706
+ if (leftFramePos < 0) {
4707
+ leftFramePos = 0;
4708
+ }
4709
+ let ada = new Array(chs);
4710
+ //console.debug("Render pos: "+renderPos+" leftFramePos: "+leftFramePos);
4711
+ if (!terminate) {
4712
+ if (this._audioDataHolder) {
4713
+ let read = this._audioDataHolder.frames(leftFramePos, this.dftSize, arrAbBuf);
4714
+ for (let ch = 0; ch < chs; ch++) {
4715
+ // Need a copy here for the worker, otherwise this.audioData is not accessible after posting to the worker
4716
+ ada[ch] = arrAbBuf[ch].buffer.slice(0);
4717
+ }
4718
+ }
4719
+ }
4720
+ else {
4721
+ for (let ch = 0; ch < chs; ch++) {
4722
+ ada[ch] = new ArrayBuffer(0);
4723
+ }
4724
+ }
4725
+ this.worker.postMessage({
4726
+ audioData: ada,
4727
+ audioDataOffset: leftFramePos,
4728
+ l: renderPos,
4729
+ w: me.data.w,
4730
+ h: h,
4731
+ vw: vw,
4732
+ chs: chs,
4733
+ frameLength: frameLength,
4734
+ dftSize: this.dftSize,
4735
+ maxPsd: maxPsd,
4736
+ norender: norender,
4737
+ terminate: terminate
4738
+ }, ada);
4739
+ }
3659
4740
  }
3660
- this.worker = null;
3661
4741
  };
3662
4742
  }
3663
- if (this.markerCanvas) {
3664
- let g = this.markerCanvas.getContext("2d");
3665
- if (g) {
3666
- g.fillText("Rendering...", 10, 20);
4743
+ if (audioBuffer && audioBuffer.length * audioBuffer.numberOfChannels < AudioCanvasLayerComponent.ENABLE_STREAMING_NUMBER_OF_SAMPLES_THRESHOLD) {
4744
+ let ada = new Array(chs);
4745
+ for (let ch = 0; ch < chs; ch++) {
4746
+ // Need a copy here for the worker, otherwise this.audioData is not accessible after posting to the worker
4747
+ ada[ch] = audioBuffer.getChannelData(ch).buffer.slice(0);
4748
+ }
4749
+ let start = Date.now();
4750
+ if (this.markerCanvas) {
4751
+ let g = this.markerCanvas.getContext("2d");
4752
+ if (g) {
4753
+ g.fillText("Rendering...", 10, 20);
4754
+ }
4755
+ }
4756
+ this.worker.postMessage({
4757
+ audioData: ada,
4758
+ l: leftPos,
4759
+ w: w,
4760
+ h: h,
4761
+ vw: Math.round(this.virtualDimension.width),
4762
+ chs: chs,
4763
+ frameLength: frameLength,
4764
+ dftSize: this.dftSize,
4765
+ terminate: true
4766
+ }, ada);
4767
+ }
4768
+ else {
4769
+ if (w > 0) {
4770
+ if (framesPerPixel > 0) {
4771
+ let arrSize = w * h * 4;
4772
+ if (arrSize < 0) {
4773
+ arrSize = 0;
4774
+ }
4775
+ imgData = new Uint8ClampedArray(arrSize);
4776
+ let rw = 1;
4777
+ //ais = new ArrayAudioBufferInputStream(arrayAudioBuffer);
4778
+ arrAbBuf = new Array(chs);
4779
+ for (let ch = 0; ch < chs; ch++) {
4780
+ arrAbBuf[ch] = new Float32Array(this.dftSize);
4781
+ }
4782
+ let leftFramePos = Math.floor(frameLength * renderPos / vw) - this.dftSize / 2;
4783
+ let framesToRead = this.dftSize;
4784
+ if (leftFramePos < 0) {
4785
+ //framesToRead=this.dftSize+leftFramePos;
4786
+ leftFramePos = 0;
4787
+ }
4788
+ let read = this._audioDataHolder.frames(leftFramePos, framesToRead, arrAbBuf);
4789
+ let ad = new Float32Array(chs * framesToRead);
4790
+ for (let ch = 0; ch < chs; ch++) {
4791
+ ad.set(arrAbBuf[ch], ch * framesToRead);
4792
+ }
4793
+ this.worker.postMessage({
4794
+ l: renderPos,
4795
+ w: rw,
4796
+ h: h,
4797
+ vw: vw,
4798
+ chs: chs,
4799
+ frameLength: frameLength,
4800
+ audioData: ad,
4801
+ audioDataOffset: leftFramePos,
4802
+ dftSize: this.dftSize,
4803
+ norender: norender,
4804
+ terminate: false
4805
+ }, [ad.buffer]);
4806
+ }
3667
4807
  }
3668
4808
  }
3669
- this.worker.postMessage({
3670
- audioData: ada,
3671
- l: Math.round(this.bounds.position.left),
3672
- w: w,
3673
- h: h,
3674
- vw: Math.round(this.virtualDimension.width),
3675
- chs: chs,
3676
- frameLength: frameLength,
3677
- dftSize: this.dftSize
3678
- }, ada);
3679
4809
  }
3680
4810
  else {
3681
4811
  let g = this.sonagramCanvas.getContext("2d");
@@ -3685,17 +4815,17 @@ class Sonagram extends AudioCanvasLayerComponent {
3685
4815
  }
3686
4816
  }
3687
4817
  }
3688
- drawRendered(me) {
4818
+ drawRendered(w, h, imgData) {
3689
4819
  if (this.sonagramCanvas) {
3690
- this.sonagramCanvas.width = me.data.w;
3691
- this.sonagramCanvas.height = me.data.h;
4820
+ this.sonagramCanvas.width = w;
4821
+ this.sonagramCanvas.height = h;
3692
4822
  let g = this.sonagramCanvas.getContext("2d");
3693
4823
  if (g) {
3694
- let imgDataArr = me.data.imgData;
3695
- if (me.data.w > 0 && me.data.h > 0) {
3696
- let imgData = g.createImageData(me.data.w, me.data.h);
3697
- imgData.data.set(imgDataArr);
3698
- g.putImageData(imgData, 0, 0);
4824
+ let imgDataArr = imgData;
4825
+ if (w > 0 && h > 0) {
4826
+ let gImgData = g.createImageData(w, h);
4827
+ gImgData.data.set(imgDataArr);
4828
+ g.putImageData(gImgData, 0, 0);
3699
4829
  }
3700
4830
  }
3701
4831
  }
@@ -3711,91 +4841,95 @@ class Sonagram extends AudioCanvasLayerComponent {
3711
4841
  g.clearRect(0, 0, w, h);
3712
4842
  g.fillStyle = "white";
3713
4843
  g.fillRect(0, 0, w, h);
3714
- if (this._audioData) {
4844
+ if (this._audioDataHolder) {
3715
4845
  let spectSize = Math.floor(this.dftSize / 2);
3716
- let chs = this._audioData.numberOfChannels;
4846
+ let chs = this._audioDataHolder.numberOfChannels;
3717
4847
  let chH = h / chs;
3718
- let frameLength = this._audioData.getChannelData(0).length;
4848
+ let frameLength = this._audioDataHolder.frameLen;
3719
4849
  let framesPerPixel = frameLength / w;
3720
4850
  let y = 0;
3721
- // TODO
3722
- let b = new Float32Array(this.dftSize);
3723
- let sona = new Array(chs);
3724
- let max = 0;
3725
- let maxPsd = -Infinity;
3726
- for (let ch = 0; ch < chs; ch++) {
3727
- let x = 0;
3728
- sona[ch] = new Array(w);
3729
- let chData = this._audioData.getChannelData(ch);
3730
- // TODO center buffer
3731
- let framePos = 0;
3732
- for (let pii = 0; pii < w; pii++) {
3733
- framePos = Math.round(pii * framesPerPixel);
3734
- // calculate DFT at pixel position
3735
- for (let i = 0; i < this.dftSize; i++) {
3736
- let chDat = chData[framePos + i];
3737
- b[i] = chDat;
3738
- }
3739
- let spectr = this.dft.processRealMagnitude(b);
3740
- sona[ch][pii] = spectr;
3741
- // @ts-ignore
3742
- let pMax = Math.max.apply(null, spectr);
3743
- if (pMax > max) {
3744
- max = pMax;
3745
- }
3746
- for (let s = 0; s < spectSize; s++) {
3747
- let psd = (2 * Math.pow(spectr[s], 2)) / spectSize;
3748
- if (psd > maxPsd) {
3749
- maxPsd = psd;
4851
+ let audioBuffer = this._audioDataHolder.buffer;
4852
+ let arrayAudioBuffer = this._audioDataHolder.arrayBuffer;
4853
+ if (audioBuffer) {
4854
+ let b = new Float32Array(this.dftSize);
4855
+ let sona = new Array(chs);
4856
+ let max = 0;
4857
+ let maxPsd = -Infinity;
4858
+ for (let ch = 0; ch < chs; ch++) {
4859
+ let x = 0;
4860
+ sona[ch] = new Array(w);
4861
+ let chData = audioBuffer.getChannelData(ch);
4862
+ // TODO center buffer
4863
+ let framePos = 0;
4864
+ for (let pii = 0; pii < w; pii++) {
4865
+ framePos = Math.round(pii * framesPerPixel);
4866
+ // calculate DFT at pixel position
4867
+ for (let i = 0; i < this.dftSize; i++) {
4868
+ let chDat = chData[framePos + i];
4869
+ b[i] = chDat;
3750
4870
  }
3751
- }
3752
- }
3753
- }
3754
- //console.log("max: ", max);
3755
- maxPsd = (2 * Math.pow(max, 2)) / spectSize;
3756
- for (let ch = 0; ch < chs; ch++) {
3757
- let framePos = 0;
3758
- for (let pii = 0; pii < w; pii++) {
3759
- framePos = pii * framesPerPixel;
3760
- for (let y = 0; y < h; y++) {
3761
- let freqIdx = Math.round(y * spectSize / h);
3762
- // calculate the one sided power spectral density PSD (f, t) in Pa2/Hz
3763
- // PSD(f) proportional to 2|X(f)|2 / (t2 - t1)
3764
- let val = sona[ch][pii][freqIdx];
3765
- let psd = (2 * Math.pow(val, 2)) / spectSize;
3766
- // Calculate logarithmic
3767
- let psdLog = DSPUtils.toLevelInDB(psd / maxPsd);
3768
- let dynRangeInDb = 70;
3769
- let scaledVal = (psdLog + dynRangeInDb) / dynRangeInDb;
3770
- if (scaledVal > 1)
3771
- scaledVal = 1;
3772
- if (scaledVal < 0) {
3773
- scaledVal = 0;
4871
+ let spectr = this.dft.processRealMagnitude(b);
4872
+ sona[ch][pii] = spectr;
4873
+ // @ts-ignore
4874
+ let pMax = Math.max.apply(null, spectr);
4875
+ if (pMax > max) {
4876
+ max = pMax;
3774
4877
  }
3775
- let rgbVal = (255 * scaledVal);
3776
- if (rgbVal < 0) {
3777
- // System.out.println("Neg RGB val: "+rgbVal);
3778
- rgbVal = 0;
4878
+ for (let s = 0; s < spectSize; s++) {
4879
+ let psd = (2 * Math.pow(spectr[s], 2)) / spectSize;
4880
+ if (psd > maxPsd) {
4881
+ maxPsd = psd;
4882
+ }
3779
4883
  }
3780
- if (rgbVal > 255) {
3781
- rgbVal = 255;
4884
+ }
4885
+ }
4886
+ //console.log("max: ", max);
4887
+ maxPsd = (2 * Math.pow(max, 2)) / spectSize;
4888
+ for (let ch = 0; ch < chs; ch++) {
4889
+ let framePos = 0;
4890
+ for (let pii = 0; pii < w; pii++) {
4891
+ framePos = pii * framesPerPixel;
4892
+ for (let y = 0; y < h; y++) {
4893
+ let freqIdx = Math.round(y * spectSize / h);
4894
+ // calculate the one sided power spectral density PSD (f, t) in Pa2/Hz
4895
+ // PSD(f) proportional to 2|X(f)|2 / (t2 - t1)
4896
+ let val = sona[ch][pii][freqIdx];
4897
+ let psd = (2 * Math.pow(val, 2)) / spectSize;
4898
+ // Calculate logarithmic
4899
+ let psdLog = DSPUtils.toLevelInDB(psd / maxPsd);
4900
+ let dynRangeInDb = 70;
4901
+ let scaledVal = (psdLog + dynRangeInDb) / dynRangeInDb;
4902
+ if (scaledVal > 1)
4903
+ scaledVal = 1;
4904
+ if (scaledVal < 0) {
4905
+ scaledVal = 0;
4906
+ }
4907
+ let rgbVal = (255 * scaledVal);
4908
+ if (rgbVal < 0) {
4909
+ // System.out.println("Neg RGB val: "+rgbVal);
4910
+ rgbVal = 0;
4911
+ }
4912
+ if (rgbVal > 255) {
4913
+ rgbVal = 255;
4914
+ }
4915
+ rgbVal = 255 - rgbVal;
4916
+ let colorStr = CSSUtils.toColorString(rgbVal, rgbVal, rgbVal);
4917
+ g.fillStyle = colorStr;
4918
+ g.fillRect(pii, chH - y, 1, 1);
3782
4919
  }
3783
- rgbVal = 255 - rgbVal;
3784
- let colorStr = CSSUtils.toColorString(rgbVal, rgbVal, rgbVal);
3785
- g.fillStyle = colorStr;
3786
- g.fillRect(pii, chH - y, 1, 1);
3787
4920
  }
3788
4921
  }
4922
+ this.drawPlayPosition();
4923
+ }
4924
+ else if (arrayAudioBuffer) {
4925
+ throw Error("Redraw with array audio buffer not supported.");
3789
4926
  }
3790
- this.drawPlayPosition();
3791
4927
  }
3792
4928
  }
3793
4929
  }
3794
4930
  setData(audioData) {
3795
- this._audioData = audioData;
4931
+ this._audioDataHolder = audioData;
3796
4932
  this.playFramePosition = 0;
3797
- //this.redraw();
3798
- //this.startRender();
3799
4933
  }
3800
4934
  }
3801
4935
  Sonagram.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: Sonagram, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
@@ -4020,13 +5154,13 @@ class AudioClipUIContainer extends BasicAudioCanvasLayerComponent {
4020
5154
  }
4021
5155
  currentXZoom() {
4022
5156
  let xz = this._xZoom;
4023
- if (xz == null && this._audioData) {
5157
+ if (xz == null && this._audioDataHolder) {
4024
5158
  let ow = this.ce.offsetWidth;
4025
5159
  if (ow < 1) {
4026
5160
  // at least one pixel width to avoid x-zoom zero values
4027
5161
  ow = 1;
4028
5162
  }
4029
- xz = ow / this._audioData.duration;
5163
+ xz = ow / this._audioDataHolder.duration;
4030
5164
  }
4031
5165
  return xz;
4032
5166
  }
@@ -4107,7 +5241,7 @@ class AudioClipUIContainer extends BasicAudioCanvasLayerComponent {
4107
5241
  layout(clear = true) {
4108
5242
  if (this.ce && this.dc) {
4109
5243
  const clientW = this.ce.clientWidth;
4110
- if (this._audioData) {
5244
+ if (this._audioDataHolder) {
4111
5245
  if (this._fixFitToPanel) {
4112
5246
  // Set the virtual canvas width to the visible width
4113
5247
  if (this.ce.style.width != '100%') {
@@ -4118,7 +5252,7 @@ class AudioClipUIContainer extends BasicAudioCanvasLayerComponent {
4118
5252
  else {
4119
5253
  if (this._xZoom) {
4120
5254
  // Set the virtual canvas width according to the value of the user selected xZoom value
4121
- const newClW = Math.round(this._xZoom * this._audioData.duration);
5255
+ const newClW = Math.round(this._xZoom * this._audioDataHolder?.duration);
4122
5256
  this.ce.style.width = newClW + 'px';
4123
5257
  }
4124
5258
  else {
@@ -4130,22 +5264,22 @@ class AudioClipUIContainer extends BasicAudioCanvasLayerComponent {
4130
5264
  this._layout(clear, true);
4131
5265
  }
4132
5266
  }
4133
- set audioData(audioData) {
5267
+ set audioData(audioDataHolder) {
4134
5268
  this._audioClip = null;
4135
- this._audioData = audioData;
4136
- this.as.setData(audioData);
4137
- this.so.setData(audioData);
5269
+ this._audioDataHolder = audioDataHolder;
5270
+ this.as.setData(audioDataHolder);
5271
+ this.so.setData(audioDataHolder);
4138
5272
  this.layout();
4139
5273
  }
4140
5274
  get audioData() {
4141
- return this._audioData;
5275
+ return this._audioDataHolder;
4142
5276
  }
4143
5277
  set audioClip(audioClip) {
4144
5278
  this._audioClip = audioClip;
4145
5279
  let audioData = null;
4146
5280
  let sel = null;
4147
5281
  if (audioClip) {
4148
- audioData = audioClip.buffer;
5282
+ audioData = audioClip.audioDataHolder;
4149
5283
  if (this._audioClip) {
4150
5284
  this._audioClip.addSelectionObserver((clip) => {
4151
5285
  this.selection = clip.selection;
@@ -4153,9 +5287,9 @@ class AudioClipUIContainer extends BasicAudioCanvasLayerComponent {
4153
5287
  }
4154
5288
  sel = audioClip.selection;
4155
5289
  }
4156
- this._audioData = audioData;
4157
- this.as.setData(this._audioData);
4158
- this.so.setData(this._audioData);
5290
+ this._audioDataHolder = audioData;
5291
+ this.as.setData(this._audioDataHolder);
5292
+ this.so.setData(this._audioDataHolder);
4159
5293
  this.selecting = null;
4160
5294
  this.selection = sel;
4161
5295
  this.layout();
@@ -4347,7 +5481,7 @@ class AudioDisplayScrollPane {
4347
5481
  let audioData = null;
4348
5482
  let sel = null;
4349
5483
  if (audioClip) {
4350
- audioData = audioClip.buffer;
5484
+ audioData = audioClip.audioDataHolder;
4351
5485
  sel = audioClip.selection;
4352
5486
  audioClip.addSelectionObserver((clip) => {
4353
5487
  this.zoomSelectedAction.disabled = (clip.selection == null);
@@ -4618,17 +5752,17 @@ class AudioDisplay {
4618
5752
  console.log("Play started");
4619
5753
  this.status = 'Playing...';
4620
5754
  }
4621
- set audioData(audioBuffer) {
4622
- this.audioDisplayScrollPane.audioData = audioBuffer;
5755
+ set audioData(audioData) {
5756
+ this.audioDisplayScrollPane.audioData = audioData;
4623
5757
  if (this.playStartAction) {
4624
- this.playStartAction.disabled = (audioBuffer == null);
5758
+ this.playStartAction.disabled = (audioData == null);
4625
5759
  }
4626
5760
  }
4627
5761
  set audioClip(audioClip) {
4628
5762
  let audioData = null;
4629
5763
  let sel = null;
4630
5764
  if (audioClip) {
4631
- audioData = audioClip.buffer;
5765
+ audioData = audioClip.audioDataHolder.buffer;
4632
5766
  sel = audioClip.selection;
4633
5767
  }
4634
5768
  this._audioClip = audioClip;
@@ -4741,7 +5875,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
4741
5875
 
4742
5876
  class Progress {
4743
5877
  constructor() {
4744
- this.items = null;
5878
+ this.items = undefined;
4745
5879
  this.selectedItemIdx = 0;
4746
5880
  this.enableDownload = false;
4747
5881
  this.onRowSelect = new EventEmitter();
@@ -5481,7 +6615,7 @@ class Prompting {
5481
6615
  constructor() {
5482
6616
  this.promptItem = null;
5483
6617
  this.showPrompt = false;
5484
- this.items = null;
6618
+ this.items = undefined;
5485
6619
  this.enableDownload = false;
5486
6620
  this.audioSignalCollapsed = true;
5487
6621
  this.displayAudioClip = null;
@@ -5792,6 +6926,7 @@ class LevelBar {
5792
6926
  this._streamingMode = false;
5793
6927
  this._staticLevelInfos = null;
5794
6928
  this._playFramePosition = null;
6929
+ this._stateLoading = false;
5795
6930
  this.warnDBLevel = DEFAULT_WARN_DB_LEVEL$1;
5796
6931
  this.dbValues = new Array();
5797
6932
  }
@@ -5825,17 +6960,22 @@ class LevelBar {
5825
6960
  }
5826
6961
  }
5827
6962
  set displayLevelInfos(levelInfos) {
5828
- this._staticLevelInfos = levelInfos;
5829
6963
  if (levelInfos) {
6964
+ this._staticLevelInfos = levelInfos;
5830
6965
  this.dbValues = levelInfos.bufferLevelInfos.map((li) => {
5831
6966
  return li.powerLevelsDB();
5832
6967
  });
5833
6968
  }
5834
6969
  else {
6970
+ this._staticLevelInfos = null;
5835
6971
  this.dbValues = new Array();
5836
6972
  }
5837
6973
  this.layoutStatic();
5838
6974
  }
6975
+ set stateLoading(loading) {
6976
+ this._stateLoading = loading;
6977
+ this.layoutStatic();
6978
+ }
5839
6979
  set channelCount(channelCount) {
5840
6980
  this.reset();
5841
6981
  }
@@ -6016,6 +7156,12 @@ class LevelBar {
6016
7156
  }
6017
7157
  }
6018
7158
  }
7159
+ else if (this._stateLoading) {
7160
+ g.strokeStyle = 'white';
7161
+ g.fillStyle = 'white';
7162
+ g.font = '20px sans-serif';
7163
+ g.fillText("Loading...", 10, 25);
7164
+ }
6019
7165
  }
6020
7166
  }
6021
7167
  }
@@ -6058,7 +7204,7 @@ class LevelBar {
6058
7204
  }
6059
7205
  }
6060
7206
  LevelBar.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: LevelBar, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
6061
- LevelBar.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: LevelBar, selector: "audio-levelbar", inputs: { streamingMode: "streamingMode", displayLevelInfos: "displayLevelInfos" }, host: { listeners: { "scroll": "onScroll($event)", "window:resize": "onResize($event)" } }, viewQueries: [{ propertyName: "virtualCanvasRef", first: true, predicate: ["virtualCanvas"], descendants: true, static: true }, { propertyName: "liveLevelCanvasRef", first: true, predicate: ["levelbar"], descendants: true, static: true }, { propertyName: "markerCanvasRef", first: true, predicate: ["markerCanvas"], descendants: true, static: true }], ngImport: i0, template: `
7207
+ LevelBar.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: LevelBar, selector: "audio-levelbar", inputs: { streamingMode: "streamingMode", displayLevelInfos: "displayLevelInfos", stateLoading: "stateLoading" }, host: { listeners: { "scroll": "onScroll($event)", "window:resize": "onResize($event)" } }, viewQueries: [{ propertyName: "virtualCanvasRef", first: true, predicate: ["virtualCanvas"], descendants: true, static: true }, { propertyName: "liveLevelCanvasRef", first: true, predicate: ["levelbar"], descendants: true, static: true }, { propertyName: "markerCanvasRef", first: true, predicate: ["markerCanvas"], descendants: true, static: true }], ngImport: i0, template: `
6062
7208
  <div #virtualCanvas>
6063
7209
  <canvas #levelbar></canvas>
6064
7210
  <canvas #markerCanvas></canvas>
@@ -6115,6 +7261,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
6115
7261
  type: Input
6116
7262
  }], displayLevelInfos: [{
6117
7263
  type: Input
7264
+ }], stateLoading: [{
7265
+ type: Input
6118
7266
  }], onResize: [{
6119
7267
  type: HostListener,
6120
7268
  args: ['window:resize', ['$event']]
@@ -6705,47 +7853,66 @@ class PeakLevelInterceptor {
6705
7853
  }
6706
7854
  }
6707
7855
  class LevelMeasure {
7856
+ //private bufferLevelInfos=new Array<LevelInfo>();
7857
+ //private peakLevelInfo!:LevelInfo;
6708
7858
  constructor() {
6709
7859
  this.worker = null;
6710
7860
  this.workerURL = WorkerHelper.buildWorkerBlobURL(this.workerFunction);
6711
7861
  }
6712
- calcBufferLevelInfos(audioBuffer, bufferTimeLength) {
7862
+ calcBufferLevelInfos(audioDataHolder, bufferTimeLength) {
6713
7863
  return new Promise((resolve) => {
6714
- let chs = audioBuffer.numberOfChannels;
6715
- let bufferFrameLength = Math.round(audioBuffer.sampleRate * bufferTimeLength);
6716
- let buffers = new Array(chs);
7864
+ let chs = audioDataHolder.numberOfChannels;
7865
+ let bufferFrameLength = Math.round(audioDataHolder.sampleRate * bufferTimeLength);
7866
+ let ais = audioDataHolder.audioInputStream();
7867
+ let audioBuffers = new Array(chs);
7868
+ let trBuffers = new Array(chs);
6717
7869
  for (let ch = 0; ch < chs; ch++) {
6718
- let adCh = audioBuffer.getChannelData(ch);
6719
- let adChCopy = new Float32Array(adCh.length);
6720
- adChCopy.set(adCh);
6721
- buffers[ch] = adChCopy.buffer;
7870
+ audioBuffers[ch] = new Float32Array(bufferFrameLength);
6722
7871
  }
7872
+ let bufferLevelInfos = new Array();
7873
+ let peakLevelInfo = new LevelInfo(chs);
6723
7874
  this.worker = new Worker(this.workerURL);
6724
7875
  this.worker.onmessage = (me) => {
6725
- let linLevelArrs = new Array(chs);
6726
- for (let ch = 0; ch < chs; ch++) {
6727
- linLevelArrs[ch] = new Float32Array(me.data.linLevelBuffers[ch]);
6728
- }
6729
- let bufferCount = Math.ceil(me.data.frameLength / me.data.bufferFrameLength);
6730
- let framePos = 0;
6731
- let bufferLevelInfos = new Array();
6732
- let peakLevelInfo = new LevelInfo(chs);
6733
- for (let bi = 0; bi < bufferCount; bi++) {
6734
- let minLevels = new Array(chs);
6735
- let maxLevels = new Array(chs);
7876
+ if (me.data.linLevelBuffers) {
7877
+ let linLevelArrs = new Array(chs);
7878
+ for (let ch = 0; ch < chs; ch++) {
7879
+ linLevelArrs[ch] = new Float32Array(me.data.linLevelBuffers[ch]);
7880
+ }
7881
+ let bufferCount = Math.ceil(me.data.frameLength / me.data.bufferFrameLength);
7882
+ let framePos = 0;
7883
+ for (let bi = 0; bi < bufferCount; bi++) {
7884
+ let minLevels = new Array(chs);
7885
+ let maxLevels = new Array(chs);
7886
+ for (let ch = 0; ch < chs; ch++) {
7887
+ let linLvlArrPos = bi * 2;
7888
+ minLevels[ch] = linLevelArrs[ch][linLvlArrPos];
7889
+ maxLevels[ch] = linLevelArrs[ch][linLvlArrPos + 1];
7890
+ }
7891
+ let bli = new LevelInfo(chs, framePos, me.data.bufferFrameLength, minLevels, maxLevels);
7892
+ bufferLevelInfos.push(bli);
7893
+ peakLevelInfo.merge(bli);
7894
+ }
7895
+ }
7896
+ if (me.data.eod === true) {
7897
+ // end of data, terminate worker and return result
7898
+ this.terminateWorker();
7899
+ resolve(new LevelInfos(bufferLevelInfos, peakLevelInfo));
7900
+ }
7901
+ else {
7902
+ let read = ais.read(audioBuffers);
6736
7903
  for (let ch = 0; ch < chs; ch++) {
6737
- let linLvlArrPos = bi * 2;
6738
- minLevels[ch] = linLevelArrs[ch][linLvlArrPos];
6739
- maxLevels[ch] = linLevelArrs[ch][linLvlArrPos + 1];
7904
+ let copy = new Float32Array(audioBuffers[ch]);
7905
+ trBuffers[ch] = copy.buffer;
6740
7906
  }
6741
- let bli = new LevelInfo(chs, framePos, me.data.bufferFrameLength, minLevels, maxLevels);
6742
- bufferLevelInfos.push(bli);
6743
- peakLevelInfo.merge(bli);
7907
+ this.worker?.postMessage({ bufferFrameLength: bufferFrameLength, audioData: trBuffers, chs: chs, len: read }, trBuffers);
6744
7908
  }
6745
- this.terminateWorker();
6746
- resolve(new LevelInfos(bufferLevelInfos, peakLevelInfo));
6747
7909
  };
6748
- this.worker.postMessage({ bufferFrameLength: bufferFrameLength, audioData: buffers, chs: chs }, buffers);
7910
+ let read = ais.read(audioBuffers);
7911
+ for (let ch = 0; ch < chs; ch++) {
7912
+ let copy = new Float32Array(audioBuffers[ch]);
7913
+ trBuffers[ch] = copy.buffer;
7914
+ }
7915
+ this.worker?.postMessage({ bufferFrameLength: bufferFrameLength, audioData: trBuffers, chs: chs, len: read }, trBuffers);
6749
7916
  });
6750
7917
  }
6751
7918
  terminateWorker() {
@@ -6756,40 +7923,63 @@ class LevelMeasure {
6756
7923
  */
6757
7924
  workerFunction() {
6758
7925
  self.onmessage = function (msg) {
6759
- let chs = msg.data.chs;
7926
+ let len = msg.data.len;
6760
7927
  let bufferFrameLength = msg.data.bufferFrameLength;
6761
- let audioData = new Array(chs);
6762
- let linLevels = new Array(chs);
6763
- for (let ch = 0; ch < chs; ch++) {
6764
- audioData[ch] = new Float32Array(msg.data.audioData[ch]);
7928
+ if (len == -1) {
7929
+ // start
7930
+ postMessage({
7931
+ eod: false,
7932
+ bufferFrameLength: bufferFrameLength,
7933
+ frameLength: len
7934
+ });
6765
7935
  }
6766
- let frameLength = audioData[0].length;
6767
- let bufferCount = Math.ceil(frameLength / bufferFrameLength);
6768
- for (let ch = 0; ch < chs; ch++) {
6769
- linLevels[ch] = new Float32Array(bufferCount * 2);
7936
+ if (len == 0) {
7937
+ postMessage({
7938
+ eod: true,
7939
+ bufferFrameLength: bufferFrameLength,
7940
+ frameLength: len
7941
+ });
6770
7942
  }
6771
- if (audioData && chs > 0) {
7943
+ else {
7944
+ let chs = msg.data.chs;
7945
+ let audioData = new Array(chs);
7946
+ let linLevels = new Array(chs);
6772
7947
  for (let ch = 0; ch < chs; ch++) {
6773
- let chData = audioData[ch];
6774
- for (let s = 0; s < frameLength; s++) {
6775
- let bi = Math.floor(s / bufferFrameLength);
6776
- let lvlArrPos = bi * 2;
6777
- //let bs = s % bufferFrameLength;
6778
- let chSample = chData[s];
6779
- if (chSample < linLevels[ch][lvlArrPos]) {
6780
- linLevels[ch][lvlArrPos] = chSample;
6781
- }
6782
- lvlArrPos++;
6783
- if (chSample > linLevels[ch][lvlArrPos]) {
6784
- linLevels[ch][lvlArrPos] = chSample;
6785
- }
6786
- }
7948
+ audioData[ch] = new Float32Array(msg.data.audioData[ch]);
6787
7949
  }
6788
- const linLevelBufs = new Array(chs);
7950
+ //let frameLength = audioData[0].length;
7951
+ let bufferCount = Math.ceil(len / bufferFrameLength);
6789
7952
  for (let ch = 0; ch < chs; ch++) {
6790
- linLevelBufs[ch] = linLevels[ch].buffer;
7953
+ linLevels[ch] = new Float32Array(bufferCount * 2);
7954
+ }
7955
+ if (audioData && chs > 0) {
7956
+ for (let ch = 0; ch < chs; ch++) {
7957
+ let chData = audioData[ch];
7958
+ for (let s = 0; s < len; s++) {
7959
+ let bi = Math.floor(s / bufferFrameLength);
7960
+ let lvlArrPos = bi * 2;
7961
+ //let bs = s % bufferFrameLength;
7962
+ let chSample = chData[s];
7963
+ if (chSample < linLevels[ch][lvlArrPos]) {
7964
+ linLevels[ch][lvlArrPos] = chSample;
7965
+ }
7966
+ lvlArrPos++;
7967
+ if (chSample > linLevels[ch][lvlArrPos]) {
7968
+ linLevels[ch][lvlArrPos] = chSample;
7969
+ }
7970
+ }
7971
+ }
7972
+ const linLevelBufs = new Array(chs);
7973
+ for (let ch = 0; ch < chs; ch++) {
7974
+ linLevelBufs[ch] = linLevels[ch].buffer;
7975
+ }
7976
+ postMessage({
7977
+ eod: false,
7978
+ bufferFrameLength: bufferFrameLength,
7979
+ frameLength: len,
7980
+ linLevelBuffers: linLevelBufs
7981
+ }, linLevelBufs);
6791
7982
  }
6792
- postMessage({ bufferFrameLength: bufferFrameLength, frameLength: frameLength, linLevelBuffers: linLevelBufs }, linLevelBufs);
6793
7983
  }
6794
7984
  };
6795
7985
  }
@@ -7161,7 +8351,7 @@ RecordingItemDisplay.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", v
7161
8351
  <audio-levelbar fxFlex="1 0 1" [streamingMode]="streamingMode" [displayLevelInfos]="_displayLevelInfos"></audio-levelbar>
7162
8352
  <spr-recordingitemcontrols fxFlex="0 0 0" [audioLoaded]="displayAudioBuffer!==null" [playStartAction]="playStartAction" [playStopAction]="playStopAction" [peakDbLvl]="peakDbLvl" [agc]="_agc" (onShowRecordingDetails)="onShowRecordingDetails.emit()"></spr-recordingitemcontrols>
7163
8353
  </div>
7164
- `, isInline: true, styles: ["div{width:100%;background:darkgray;padding:4px;box-sizing:border-box;flex-wrap:nowrap}\n", "audio-levelbar{box-sizing:border-box}\n"], components: [{ type: LevelBar, selector: "audio-levelbar", inputs: ["streamingMode", "displayLevelInfos"] }, { type: RecordingItemControls, selector: "spr-recordingitemcontrols", inputs: ["audioSignalCollapsed", "enableDownload", "peakDbLvl", "agc", "audioLoaded", "playStartAction", "playStopAction", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }], directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i3$1.DefaultStyleDirective, selector: " [ngStyle], [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]", inputs: ["ngStyle", "ngStyle.xs", "ngStyle.sm", "ngStyle.md", "ngStyle.lg", "ngStyle.xl", "ngStyle.lt-sm", "ngStyle.lt-md", "ngStyle.lt-lg", "ngStyle.lt-xl", "ngStyle.gt-xs", "ngStyle.gt-sm", "ngStyle.gt-md", "ngStyle.gt-lg"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i8.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }] });
8354
+ `, isInline: true, styles: ["div{width:100%;background:darkgray;padding:4px;box-sizing:border-box;flex-wrap:nowrap}\n", "audio-levelbar{box-sizing:border-box}\n"], components: [{ type: LevelBar, selector: "audio-levelbar", inputs: ["streamingMode", "displayLevelInfos", "stateLoading"] }, { type: RecordingItemControls, selector: "spr-recordingitemcontrols", inputs: ["audioSignalCollapsed", "enableDownload", "peakDbLvl", "agc", "audioLoaded", "playStartAction", "playStopAction", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }], directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i3$1.DefaultStyleDirective, selector: " [ngStyle], [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]", inputs: ["ngStyle", "ngStyle.xs", "ngStyle.sm", "ngStyle.md", "ngStyle.lg", "ngStyle.xl", "ngStyle.lt-sm", "ngStyle.lt-md", "ngStyle.lt-lg", "ngStyle.lt-xl", "ngStyle.gt-xs", "ngStyle.gt-sm", "ngStyle.gt-md", "ngStyle.gt-lg"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i8.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }] });
7165
8355
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: RecordingItemDisplay, decorators: [{
7166
8356
  type: Component,
7167
8357
  args: [{
@@ -7207,89 +8397,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
7207
8397
  type: Input
7208
8398
  }] } });
7209
8399
 
7210
- class Float32ArrayChunkerOutStream {
7211
- constructor(outStream) {
7212
- this.outStream = outStream;
7213
- this.bufs = new Array();
7214
- this._channels = 0;
7215
- this._chunkSize = 0;
7216
- this.filled = 0;
7217
- this.receivedFrames = 0;
7218
- this.sentFrames = 0;
7219
- }
7220
- createBuffers() {
7221
- this.bufs = new Array(this._channels);
7222
- for (let ch = 0; ch < this._channels; ch++) {
7223
- this.bufs[ch] = new Float32Array(this._chunkSize);
7224
- }
7225
- }
7226
- set chunkSize(chunkSize) {
7227
- this._chunkSize = chunkSize;
7228
- this.createBuffers();
7229
- }
7230
- get chunkSize() {
7231
- return this._chunkSize;
7232
- }
7233
- set channels(channels) {
7234
- this._channels = channels;
7235
- this.createBuffers();
7236
- }
7237
- get channels() {
7238
- return this._channels;
7239
- }
7240
- available() {
7241
- return this._chunkSize - this.filled;
7242
- }
7243
- write(buffers) {
7244
- let copied = 0;
7245
- if (buffers.length > 0) {
7246
- let buffersLen = buffers[0].length;
7247
- this.receivedFrames += buffersLen;
7248
- let avail = buffersLen;
7249
- // Fill out buffers until all values copied
7250
- while (avail > 0) {
7251
- let toFill = this._chunkSize - this.filled;
7252
- if (toFill > avail) {
7253
- toFill = avail;
7254
- }
7255
- let sliceEnd = copied + toFill;
7256
- // Firefox on Android sends only the first channel
7257
- for (let ch = 0; ch < buffersLen; ch++) {
7258
- if (buffers[ch]) {
7259
- let cpPrt = buffers[ch].slice(copied, sliceEnd);
7260
- let buf = this.bufs[ch];
7261
- buf.set(cpPrt, this.filled);
7262
- }
7263
- }
7264
- copied += toFill;
7265
- avail -= toFill;
7266
- this.filled += toFill;
7267
- if (this.filled == this._chunkSize) {
7268
- this.outStream.write(this.bufs);
7269
- this.sentFrames += this.filled;
7270
- this.filled = 0;
7271
- }
7272
- }
7273
- }
7274
- return copied;
7275
- }
7276
- flush() {
7277
- if (this.filled > 0) {
7278
- let restBufs = new Array(this._channels);
7279
- for (let ch = 0; ch < this._channels; ch++) {
7280
- restBufs[ch] = this.bufs[ch].slice(0, this.filled);
7281
- }
7282
- this.outStream.write(restBufs);
7283
- this.outStream.flush();
7284
- this.sentFrames += this.filled;
7285
- this.filled = 0;
7286
- }
7287
- }
7288
- close() {
7289
- this.outStream.close();
7290
- }
7291
- }
7292
-
7293
8400
  class SequenceAudioFloat32ChunkerOutStream extends Float32ArrayChunkerOutStream {
7294
8401
  constructor(outStream, chunkDurationSeconds) {
7295
8402
  super(outStream);
@@ -7298,11 +8405,11 @@ class SequenceAudioFloat32ChunkerOutStream extends Float32ArrayChunkerOutStream
7298
8405
  this.sequenceAudioFloat32OutStream = outStream;
7299
8406
  }
7300
8407
  setFormat(channels, sampleRate) {
7301
- console.debug("SequenceAudioFloat32ChunkerOutStream:setFormat(channels: " + channels + ",sampleRate: " + sampleRate + ")");
8408
+ //console.debug("SequenceAudioFloat32ChunkerOutStream:setFormat(channels: "+channels+",sampleRate: "+sampleRate+")")
7302
8409
  this.channels = channels;
7303
8410
  this.sampleRate = sampleRate;
7304
8411
  this.chunkSize = Math.round(sampleRate * this.chunkDurationSeconds);
7305
- console.debug("SequenceAudioFloat32ChunkerOutStream: chunkSize: " + this.chunkSize);
8412
+ //console.debug("SequenceAudioFloat32ChunkerOutStream: chunkSize: "+this.chunkSize);
7306
8413
  this.sequenceAudioFloat32OutStream.setFormat(channels, sampleRate);
7307
8414
  }
7308
8415
  nextStream() {
@@ -7350,12 +8457,24 @@ class SequenceAudioFloat32OutStreamMultiplier {
7350
8457
 
7351
8458
  const WAKE_LOCK_VIDEO_MP4_URI = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAG31tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1NSByMjkxNyAwYTg0ZDk4IC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxOCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTIgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAJ2WIhAA7//73Tr8Cm1TCKgOSVwr2yqQmr/tBF6luwQMZUeHtiyA0IQAAAAtBmiRsQ7/+qZYe8N4CAExhdmM1OC41NC4xMDAAQiAIwRg4AAAACEGeQniF/wMzIRAEYIwcIRAEYIwcAAAACAGeYXRCvwQkIRAEYIwcIRAEYIwcAAAACAGeY2pCvwQlIRAEYIwcIRAEYIwcAAAAEUGaaEmo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AAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAEAAAAAChzdHNjAAAAAAAAAAIAAAABAAAAAgAAAAEAAAACAAAAAQAAAAEAAAP8c3RzegAAAAAAAAAAAAAA+gAAAt0AAAAPAAAADAAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABUAAAAOAAAADAAAAAwAAAAVAAAADgAAAAwAAAAMAAAAFQAAAA4AAAAMAAAADAAAABQAAAP0c3RjbwAAAAAAAAD5AAAAMAAAAzMAAANLAAADYwAAA3sAAAOWAAADsAAAA8gAAAPgAAAD+wAABBUAAAQtAAAEPwAABGAAAAR6AAAEkgAABKQAAATFAAAE3wAABPcAAAUJAAAFKgAABUQAAAVWAAAFbgAABY8AAAWpAAAFuwAABdMAAAX0AAAGCAAABiAAAAY4AAAGWQAABm0AAAaFAAAGnQAABr4AAAbSAAAG6gAABwIAAAcdAAAHNwAAB08AAAdnAAAHggAAB5wAAAe0AAAHxgAAB+cAAAgBAAAIGQAACCsAAAhMAAAIZgAACH4AAAiQAAAIsQAACMsAAAjdAAAI9QAACRYAAAkwAAAJQgAACVoAAAl7AAAJjwAACacAAAm/AAAJ4AAACfQAAAoMAAAKJAAACkUAAApZAAAKcQAACokAAAqkAAAKvgAACtYAAAruAAALCQAACyMAAAs7AAALTQAAC24AAAuIAAALoAAAC7IAAAvTAAAL7QAADAUAAAwXAAAMOAAADFIAAAxkAAAMfAAADJ0AAAy3AAAMyQAADOEAAA0CAAANFgAADS4AAA1GAAANZwAADXsAAA2TAAANqwAADcwAAA3gAAAN+AAADhAAAA4rAAAORQAADl0AAA51AAAOkAAADqoAAA7CAAAO1AAADvUAAA8PAAAPJwAADzkAAA9aAAAPdAAAD4wAAA+eAAAPvwAAD9kAAA/rAAAQAwAAECQAABA+AAAQUAAAEGgAABCJAAAQowAAELUAABDNAAAQ7gAAEQIAABEaAAARMgAAEVMAABFnAAARfwAAEZcAABGyAAAR3QAAEfUAABINAAASKAAAEkIAABJaAAAScgAAEo0AABKnAAASvwAAEtEAABLyAAATDAAAEyQAABM2AAATVwAAE3EAABODAAATmwAAE7wAABPWAAAT6AAAFAAAABQhAAAUOwAAFE0AABRlAAAUhgAAFJoAABSyAAAUygAAFOsAABT/AAAVFwAAFS8AABVKAAAVZAAAFXwAABWUAAAVrwAAFckAABXhAAAV+QAAFhQAABYuAAAWRgAAFlgAABZ5AAAWkwAAFqsAABa9AAAW3gAAFvgAABcKAAAXIgAAF0MAABddAAAXbwAAF4cAABeoAAAXwgAAF9QAABfsAAAYDQAAGCEAABg5AAAYUQAAGHIAABiGAAAYngAAGLYAABjRAAAY6wAAGQMAABkbAAAZNgAAGVAAABloAAAZgAAAGZsAABm1AAAZzQAAGd8AABoAAAAaGgAAGjIAABpEAAAaZQAAGn8AABqRAAAaqQAAGsoAABrkAAAa9gAAGw4AABsvAAAbSQAAG1sAABtzAAATPXRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAnKAAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAJxAAAAQAAAEAAAAAErVtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAa+qFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAABJgbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAABIkc3RibAAAAGpzdHNkAAAAAAAAAAEAAABabXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAA2ZXNkcwAAAAADgICAJQACAASAgIAXQBUAAAAAAfQAAAAILwWAgIAFEhBW5QAGgICAAQIAAAAgc3R0cwAAAAAAAAACAAABrwAABAAAAAABAAACqAAABpRzdHNjAAAAAAAAAIsAAAABAAAAAQAAAAEAAAACAAAAAgAAAAEAAAAFAAAAAQAAAAEAAAAGAAAAAgAAAAEAAAAJAAAAAQAAAAEAAAAKAAAAAgAAAAEAAAAMAAAAAQAAAAEAAAANAAAAAgAAAAEAAAAQAAAAAQAAAAEAAAARAAAAAgAAAAEAAAAUAAAAAQAAAAEAAAAVAAAAAgAAAAEAAAAXAAAAAQAAAAEAAAAYAAAAAgAAAAEAAAAbAAAAAQAAAAEAAAAcAAAAAgAAAAEAAAAeAAAAAQAAAAEAAAAfAAAAAgAAAAEAAAAiAAAAAQAAAAEAAAAjAAAAAgAAAAEAAAAmAAAAAQAAAAEAAAAnAAAAAgAAAAEAAAApAAAAAQAAAAEAAAAqAAAAAgAAAAEAAAAtAAAAAQAAAAEAAAAuAAAAAgAAAAEAAAAwAAAAAQAAAAEAAAAxAAAAAgAAAAEAAAA0AAAAAQAAAAEAAAA1AAAAAgAAAAEAAAA4AAAAAQAAAAEAAAA5AAAAAgAAAAEAAAA7AAAAAQAAAAEAAAA8AAAAAgAAAAEAAAA/AAAAAQAAAAEAAABAAAAAAgAAAAEAAABCAAAAAQAAAAEAAABDAAAAAgAAAAEAAABGAAAAAQAAAAEAAABHAAAAAgAAAAEAAABKAAAAAQAAAAEAAABLAAAAAgAAAAEAAABNAAAAAQAAAAEAAABOAAAAAgAAAAEAAABRAAAAAQAAAAEAAABSAAAAAgAAAAEAAABUAAAAAQAAAAEAAABVAAAAAgAAAAEAAABYAAAAAQAAAAEAAABZAAAAAgAAAAEAAABcAAAAAQAAAAEAAABdAAAAAgAAAAEAAABfAAAAAQAAAAEAAABgAAAAAgAAAAEAAABjAAAAAQAAAAEAAABkAAAAAgAAAAEAAABmAAAAAQAAAAEAAABnAAAAAgAAAAEAAABqAAAAAQAAAAEAAABrAAAAAgAAAAEAAABuAAAAAQAAAAEAAABvAAAAAgAAAAEAAABxAAAAAQAAAAEAAAByAAAAAgAAAAEAAAB1AAAAAQAAAAEAAAB2AAAAAgAAAAEAAAB4AAAAAQAAAAEAAAB5AAAAAgAAAAEAAAB8AAAAAQAAAAEAAAB9AAAAAgAAAAEAAACAAAAAAQAAAAEAAACBAAAAAgAAAAEAAACDAAAAAQAAAAEAAACEAAAAAgAAAAEAAACHAAAAAQAAAAEAAACIAAAAAgAAAAEAAACLAAAAAQAAAAEAAACMAAAAAgAAAAEAAACOAAAAAQAAAAEAAACPAAAAAgAAAAEAAACSAAAAAQAAAAEAAACTAAAAAgAAAAEAAACVAAAAAQAAAAEAAACWAAAAAgAAAAEAAACZAAAAAQAAAAEAAACaAAAAAgAAAAEAAACdAAAAAQAAAAEAAACeAAAAAgAAAAEAAACgAAAAAQAAAAEAAAChAAAAAgAAAAEAAACkAAAAAQAAAAEAAAClAAAAAgAAAAEAAACnAAAAAQAAAAEAAACoAAAAAgAAAAEAAACrAAAAAQAAAAEAAACsAAAAAgAAAAEAAACvAAAAAQAAAAEAAACwAAAAAgAAAAEAAACyAAAAAQAAAAEAAACzAAAAAgAAAAEAAAC2AAAAAQAAAAEAAAC3AAAAAgAAAAEAAAC5AAAAAQAAAAEAAAC6AAAAAgAAAAEAAAC9AAAAAQAAAAEAAAC+AAAAAgAAAAEAAADBAAAAAQAAAAEAAADCAAAAAgAAAAEAAADEAAAAAQAAAAEAAADFAAAAAgAAAAEAAADIAAAAAQAAAAEAAADJAAAAAgAAAAEAAADLAAAAAQAAAAEAAADMAAAAAgAAAAEAAADPAAAAAQAAAAEAAADQAAAAAgAAAAEAAADTAAAAAQAAAAEAAADUAAAAAgAAAAEAAADWAAAAAQAAAAEAAADXAAAAAgAAAAEAAADaAAAAAQAAAAEAAADbAAAAAgAAAAEAAADdAAAAAQAAAAEAAADeAAAAAgAAAAEAAADhAAAAAQAAAAEAAADiAAAAAgAAAAEAAADlAAAAAQAAAAEAAADmAAAAAgAAAAEAAADoAAAAAQAAAAEAAADpAAAAAgAAAAEAAADsAAAAAQAAAAEAAADtAAAAAgAAAAEAAADvAAAAAQAAAAEAAADwAAAAAgAAAAEAAADzAAAAAQAAAAEAAAD0AAAAAgAAAAEAAAD3AAAAAQAAAAEAAAD4AAAAAgAAAAEAAAD5AAAABQAAAAEAAAbUc3RzegAAAAAAAAAAAAABsAAAABcAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAXAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAA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';
7352
8459
 
8460
+ /**
8461
+ * Utility to prevent devices from screen lock.
8462
+ * If supported uses the HTML5 wake lock API, if not, plays an invisible video.
8463
+ *
8464
+ * Note: I've used the GitHub project richtr/nosleep.js for that. The worked in most cases, but failed sometimes on an
8465
+ * Android smartphone.
8466
+ */
7353
8467
  class WakeLockManager {
7354
8468
  constructor() {
7355
8469
  this.wakeLockApiSupported = false;
8470
+ this.wakeLockRetryCount = 0;
8471
+ this.randomSeekRequired = false;
7356
8472
  this._behaviorSubject = new BehaviorSubject(false);
8473
+ this.VIDEO_LENGTH = 10;
7357
8474
  this.wakeLockApiSupported = ('wakeLock' in navigator);
7358
8475
  console.debug("Wake lock API supported: " + this.wakeLockApiSupported);
8476
+ this.userAgent = UserAgentBuilder.userAgent();
8477
+ this.randomSeekRequired = (this.userAgent?.detectedBrowser === Browser$1.Safari);
7359
8478
  }
7360
8479
  get behaviorSubject() {
7361
8480
  return this._behaviorSubject;
@@ -7365,6 +8484,7 @@ class WakeLockManager {
7365
8484
  //@ts-ignore
7366
8485
  navigator.wakeLock.request('screen').then((wls) => {
7367
8486
  this.wakeLockSentinel = wls;
8487
+ this.wakeLockRetryCount = 0;
7368
8488
  console.debug('Wake lock screen request successful.');
7369
8489
  this._behaviorSubject.next(true);
7370
8490
  }).catch((reason) => {
@@ -7375,7 +8495,7 @@ class WakeLockManager {
7375
8495
  else {
7376
8496
  if (!this.mp4VideoElement) {
7377
8497
  this.mp4VideoElement = document.createElement('video');
7378
- this.mp4VideoElement.loop = true;
8498
+ this.mp4VideoElement.loop = !this.randomSeekRequired;
7379
8499
  this.mp4VideoElement.playsInline = true;
7380
8500
  this.mp4VideoElement.src = WAKE_LOCK_VIDEO_MP4_URI;
7381
8501
  this.mp4VideoElement.addEventListener('play', (ev) => {
@@ -7390,13 +8510,44 @@ class WakeLockManager {
7390
8510
  console.debug('Wake lock video pause.');
7391
8511
  this._behaviorSubject.next(false);
7392
8512
  });
8513
+ if (this.randomSeekRequired) {
8514
+ this.mp4VideoElement.addEventListener('timeupdate', (ev) => {
8515
+ if (this.randomSeekRequired && this.mp4VideoElement) {
8516
+ let thirdVideoLen = this.VIDEO_LENGTH / 3;
8517
+ if (this.mp4VideoElement.currentTime > thirdVideoLen * 2) {
8518
+ let newPos = (thirdVideoLen * Math.random());
8519
+ //console.debug('Wake lock random seek to '+newPos+'s');
8520
+ this.mp4VideoElement.currentTime = newPos;
8521
+ }
8522
+ }
8523
+ });
8524
+ }
7393
8525
  this.mp4VideoElement.addEventListener('error', (ev) => {
7394
8526
  console.debug('Wake lock video error: ' + ev.message);
7395
8527
  this._behaviorSubject.error(ev.error);
7396
8528
  });
7397
8529
  console.debug('Added listeners to wake lock video.');
7398
8530
  }
7399
- this.mp4VideoElement.play();
8531
+ this.startWakeLockVideo();
8532
+ }
8533
+ }
8534
+ startWakeLockVideo() {
8535
+ if (this.mp4VideoElement) {
8536
+ this.mp4VideoElement.play().then(() => {
8537
+ this.wakeLockRetryCount = 0;
8538
+ }).catch((err) => {
8539
+ console.debug('Failed starting wake lock video!');
8540
+ if (this.wakeLockRetryCount < 1) {
8541
+ window.setTimeout(() => {
8542
+ this.wakeLockRetryCount++;
8543
+ console.debug('Retry wake lock video #' + this.wakeLockRetryCount);
8544
+ this.startWakeLockVideo();
8545
+ }, 4000);
8546
+ }
8547
+ else {
8548
+ console.debug('Giving up to try to start wake lock video.');
8549
+ }
8550
+ });
7400
8551
  }
7401
8552
  }
7402
8553
  disableWakeLock() {
@@ -7464,7 +8615,8 @@ class ChunkManager {
7464
8615
  }
7465
8616
  }
7466
8617
  let BasicRecorder = class BasicRecorder {
7467
- constructor(dialog, sessionService, uploader, config) {
8618
+ constructor(changeDetectorRef, dialog, sessionService, uploader, config) {
8619
+ this.changeDetectorRef = changeDetectorRef;
7468
8620
  this.dialog = dialog;
7469
8621
  this.sessionService = sessionService;
7470
8622
  this.uploader = uploader;
@@ -7479,14 +8631,15 @@ let BasicRecorder = class BasicRecorder {
7479
8631
  this._selectedDeviceId = undefined;
7480
8632
  this._channelCount = 2;
7481
8633
  this._session = null;
8634
+ this._recordingFile = null;
7482
8635
  this.startedDate = null;
7483
8636
  this.uploadProgress = 100;
7484
8637
  this.uploadStatus = 'ok';
7485
8638
  this.audioSignalCollapsed = true;
7486
8639
  this.peakLevelInDb = MIN_DB_LEVEL;
7487
- this.displayLevelInfos = null;
7488
8640
  this.displayAudioClip = null;
7489
8641
  this.audioFetchSubscription = null;
8642
+ this.audioFetching = false;
7490
8643
  this.destroyed = false;
7491
8644
  this.navigationDisabled = true;
7492
8645
  // Default: Disabled chunked upload
@@ -7580,6 +8733,28 @@ let BasicRecorder = class BasicRecorder {
7580
8733
  this.ac.audioOutStream = outStream;
7581
8734
  }
7582
8735
  }
8736
+ showRecording() {
8737
+ this._controlAudioPlayer.stop();
8738
+ if (this.displayAudioClip) {
8739
+ let dap = this.displayAudioClip;
8740
+ let adh = dap.audioDataHolder;
8741
+ if (adh) {
8742
+ this.levelMeasure.calcBufferLevelInfos(adh, LEVEL_BAR_INTERVALL_SECONDS).then((levelInfos) => {
8743
+ dap.levelInfos = levelInfos;
8744
+ this.changeDetectorRef.detectChanges();
8745
+ });
8746
+ }
8747
+ this.playStartAction.disabled = false;
8748
+ }
8749
+ else {
8750
+ this.playStartAction.disabled = true;
8751
+ // Collapse audio signal display if open
8752
+ if (!this.audioSignalCollapsed) {
8753
+ this.audioSignalCollapsed = true;
8754
+ }
8755
+ }
8756
+ this.changeDetectorRef.detectChanges();
8757
+ }
7583
8758
  start() {
7584
8759
  this.statusAlertType = 'info';
7585
8760
  this.statusMsg = 'Starting session...';
@@ -7788,24 +8963,21 @@ let BasicRecorder = class BasicRecorder {
7788
8963
  }
7789
8964
  startItem() {
7790
8965
  this.startedDate = null;
7791
- //this.enableWakeLockCond();
8966
+ this.enableWakeLockCond();
7792
8967
  this.rfUuid = UUID.generate();
7793
8968
  this.transportActions.startAction.disabled = true;
7794
8969
  this.transportActions.pauseAction.disabled = true;
7795
- if (this.readonly) {
7796
- return;
7797
- }
7798
8970
  }
7799
8971
  started() {
7800
- this.enableWakeLockCond();
8972
+ //this.enableWakeLockCond();
7801
8973
  if (!this.startedDate) {
7802
8974
  this.startedDate = new Date();
7803
8975
  }
7804
8976
  this.transportActions.startAction.disabled = true;
7805
8977
  }
7806
- postRecording(wavFile, recUrl) {
8978
+ postRecording(wavFile, recUrl, rf) {
7807
8979
  let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
7808
- let ul = new Upload(wavBlob, recUrl);
8980
+ let ul = new Upload(wavBlob, recUrl, rf);
7809
8981
  this.uploader.queueUpload(ul);
7810
8982
  }
7811
8983
  postAudioStreamStart() {
@@ -7839,42 +9011,334 @@ let BasicRecorder = class BasicRecorder {
7839
9011
  if (this.config && this.config.apiEndPoint) {
7840
9012
  apiEndPoint = this.config.apiEndPoint;
7841
9013
  }
7842
- if (apiEndPoint !== '') {
7843
- apiEndPoint = apiEndPoint + '/';
9014
+ if (apiEndPoint !== '') {
9015
+ apiEndPoint = apiEndPoint + '/';
9016
+ }
9017
+ let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
9018
+ let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.rfUuid + '/concatChunksRequest';
9019
+ let fd = new FormData();
9020
+ fd.set('uuid', this.rfUuid);
9021
+ fd.set('chunkCount', chunkCount.toString());
9022
+ let ul = new Upload(fd, recUrl, this._recordingFile);
9023
+ this.uploader.queueUpload(ul);
9024
+ console.debug("Queued for upload: " + this._recordingFile);
9025
+ }
9026
+ else {
9027
+ console.error("Recording file UUID not set!");
9028
+ }
9029
+ }
9030
+ closed() {
9031
+ this.statusAlertType = 'info';
9032
+ this.statusMsg = 'Session closed.';
9033
+ }
9034
+ error(msg = 'An unknown error occured during recording.', advice = 'Please retry.') {
9035
+ this.statusMsg = 'ERROR: Recording.';
9036
+ this.statusAlertType = 'error';
9037
+ this.dialog.open(MessageDialog, {
9038
+ data: {
9039
+ type: 'error',
9040
+ title: 'Recording error',
9041
+ msg: msg,
9042
+ advice: advice
9043
+ }
9044
+ });
9045
+ }
9046
+ };
9047
+ // Enable only for developemnt/debug purposes of array audio buffers !!
9048
+ BasicRecorder.FORCE_ARRRAY_AUDIO_BUFFER = false;
9049
+ BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS = 30;
9050
+ BasicRecorder = __decorate([
9051
+ __param(4, Inject(SPEECHRECORDER_CONFIG))
9052
+ ], BasicRecorder);
9053
+
9054
+ class AudioDataHolder {
9055
+ constructor(_buffer, _arrayBuffer = null) {
9056
+ this._buffer = _buffer;
9057
+ this._arrayBuffer = _arrayBuffer;
9058
+ this._numberOfChannels = 0;
9059
+ this._sampleRate = 0;
9060
+ this._frameLen = 0;
9061
+ this._duration = 0;
9062
+ if (this._buffer && this._arrayBuffer) {
9063
+ throw Error('Only one of either audio buffer or array audio buffer must be set!');
9064
+ }
9065
+ if (this._buffer || this._arrayBuffer) {
9066
+ if (this._buffer) {
9067
+ this._numberOfChannels = this._buffer.numberOfChannels;
9068
+ this._sampleRate = this._buffer.sampleRate;
9069
+ this._frameLen = this._buffer.length;
9070
+ this._duration = this._frameLen / this._sampleRate;
9071
+ }
9072
+ else if (this._arrayBuffer) {
9073
+ this._numberOfChannels = this._arrayBuffer.channelCount;
9074
+ this._sampleRate = this._arrayBuffer.sampleRate;
9075
+ this._frameLen = this._arrayBuffer.frameLen;
9076
+ this._duration = this._frameLen / this._sampleRate;
9077
+ }
9078
+ }
9079
+ else {
9080
+ throw Error(AudioDataHolder.ONE_OF_MUST_BE_SET_ERR_MSG);
9081
+ }
9082
+ }
9083
+ get duration() {
9084
+ return this._duration;
9085
+ }
9086
+ get sampleRate() {
9087
+ return this._sampleRate;
9088
+ }
9089
+ get numberOfChannels() {
9090
+ return this._numberOfChannels;
9091
+ }
9092
+ get frameLen() {
9093
+ return this._frameLen;
9094
+ }
9095
+ sampleCounts() {
9096
+ return this._numberOfChannels * this._frameLen;
9097
+ }
9098
+ frames(framePos, frameLen, bufs) {
9099
+ let read = 0;
9100
+ if (this._buffer) {
9101
+ let toRead = frameLen;
9102
+ if (this._buffer.length < framePos + frameLen) {
9103
+ toRead = this._buffer.length - framePos;
9104
+ }
9105
+ for (let ch = 0; ch < bufs.length; ch++) {
9106
+ let chData = this._buffer.getChannelData(ch);
9107
+ for (let i = 0; i < toRead; i++) {
9108
+ bufs[ch][i] = chData[framePos + i];
9109
+ }
9110
+ }
9111
+ read = toRead;
9112
+ }
9113
+ else if (this._arrayBuffer) {
9114
+ read = this._arrayBuffer.frames(framePos, frameLen, bufs);
9115
+ }
9116
+ return read;
9117
+ }
9118
+ audioInputStream() {
9119
+ if (this._buffer) {
9120
+ return new AudioBufferInputStream(this._buffer);
9121
+ }
9122
+ if (this._arrayBuffer) {
9123
+ return new ArrayAudioBufferInputStream(this._arrayBuffer);
9124
+ }
9125
+ throw Error(AudioDataHolder.ONE_OF_MUST_BE_SET_ERR_MSG);
9126
+ }
9127
+ get buffer() {
9128
+ return this._buffer;
9129
+ }
9130
+ get arrayBuffer() {
9131
+ return this._arrayBuffer;
9132
+ }
9133
+ }
9134
+ AudioDataHolder.ONE_OF_MUST_BE_SET_ERR_MSG = 'One of either audio buffer or array audio buffer must be set!';
9135
+ class AudioBufferInputStream {
9136
+ constructor(audioBuffer) {
9137
+ this.audioBuffer = audioBuffer;
9138
+ this.framePos = 0;
9139
+ }
9140
+ close() {
9141
+ }
9142
+ skipFrames(n) {
9143
+ this.framePos += n;
9144
+ }
9145
+ read(buffers) {
9146
+ let read = 0;
9147
+ let toRead = buffers[0].length;
9148
+ if (this.framePos + toRead > this.audioBuffer.length) {
9149
+ toRead = this.audioBuffer.length - this.framePos;
9150
+ }
9151
+ for (let ch = 0; ch < buffers.length; ch++) {
9152
+ for (let i = 0; i < toRead; i++) {
9153
+ buffers[ch][i] = this.audioBuffer.getChannelData(ch)[this.framePos + i];
9154
+ }
9155
+ }
9156
+ read = toRead;
9157
+ this.framePos += toRead;
9158
+ return read;
9159
+ }
9160
+ }
9161
+
9162
+ class BasicRecFilesCache {
9163
+ constructor() {
9164
+ //public static readonly DEFAULT_MAX_SAMPLES=30*48000; // TEST 30 seconds
9165
+ this._sampleCount = 0;
9166
+ this.maxSampleCount = SprItemsCache.DEFAULT_MAX_SAMPLES;
9167
+ this.currentRecordingFile = null;
9168
+ }
9169
+ }
9170
+ BasicRecFilesCache.DEBUG = false;
9171
+ BasicRecFilesCache.DEFAULT_MAX_SAMPLES = 10 * 60 * 48000; // 20 Minutes mono 48kHz
9172
+ class SprItemsCache extends BasicRecFilesCache {
9173
+ constructor() {
9174
+ super(...arguments);
9175
+ this._items = new Array();
9176
+ }
9177
+ get items() {
9178
+ return this._items;
9179
+ }
9180
+ addItem(item) {
9181
+ this._items.push(item);
9182
+ }
9183
+ getItem(index) {
9184
+ return this._items[index];
9185
+ }
9186
+ length() {
9187
+ return this._items.length;
9188
+ }
9189
+ tryExpire(toBeExpiredRf) {
9190
+ if (BasicRecFilesCache.DEBUG)
9191
+ console.debug("Rec. files cache: " + toBeExpiredRf.toString() + " try expire:");
9192
+ if (!toBeExpiredRf.equals(this.currentRecordingFile)) {
9193
+ if (BasicRecFilesCache.DEBUG)
9194
+ console.debug("Rec. files cache: " + toBeExpiredRf.toString() + " not current file...");
9195
+ if (toBeExpiredRf.serverPersisted) {
9196
+ if (BasicRecFilesCache.DEBUG)
9197
+ console.debug("Rec. files cache: " + toBeExpiredRf.toString() + " is server persisted...");
9198
+ // expire recording files first stored to the cache
9199
+ let expiredAudio = RecordingFileUtils.expireAudioData(toBeExpiredRf);
9200
+ if (expiredAudio) {
9201
+ this._sampleCount -= expiredAudio.sampleCounts();
9202
+ if (BasicRecFilesCache.DEBUG)
9203
+ console.debug("Rec. files cache: Expired: " + toBeExpiredRf.toString() + ". Cache samples now: " + this._sampleCount);
9204
+ }
9205
+ }
9206
+ else {
9207
+ if (BasicRecFilesCache.DEBUG)
9208
+ console.debug("Rec. files cache: #" + toBeExpiredRf.toString() + " not yet persisted on server.");
9209
+ }
9210
+ }
9211
+ else {
9212
+ if (BasicRecFilesCache.DEBUG)
9213
+ console.debug("Rec. files cache: " + toBeExpiredRf.toString() + " is current file.");
9214
+ }
9215
+ }
9216
+ expire() {
9217
+ // expire corrected versions first
9218
+ if (BasicRecFilesCache.DEBUG)
9219
+ console.debug("Rec. files cache: Expire? current: " + this._sampleCount + ", max: " + this.maxSampleCount);
9220
+ if (this._sampleCount > this.maxSampleCount) {
9221
+ // expire older versions of an item first
9222
+ for (let ii = 0; ii < this._items.length; ii++) {
9223
+ if (this._sampleCount <= this.maxSampleCount) {
9224
+ break;
9225
+ }
9226
+ let it = this._items[ii];
9227
+ let itRfs = it.recs;
9228
+ if (itRfs && itRfs.length > 1) {
9229
+ for (let rfi = 0; rfi < itRfs.length - 1; rfi++) {
9230
+ if (this._sampleCount <= this.maxSampleCount) {
9231
+ break;
9232
+ }
9233
+ let toBeExpiredRf = itRfs[rfi];
9234
+ this.tryExpire(toBeExpiredRf);
9235
+ }
9236
+ }
9237
+ }
9238
+ // if that's not sufficient expire audio data of latest versions as well
9239
+ for (let ii = 0; ii < this._items.length; ii++) {
9240
+ if (this._sampleCount <= this.maxSampleCount) {
9241
+ break;
9242
+ }
9243
+ let it = this._items[ii];
9244
+ let itRfs = it.recs;
9245
+ if (itRfs && itRfs.length > 0) {
9246
+ for (let rfi = 0; rfi < itRfs.length; rfi++) {
9247
+ if (this._sampleCount <= this.maxSampleCount) {
9248
+ break;
9249
+ }
9250
+ let toBeExpiredRf = itRfs[rfi];
9251
+ this.tryExpire(toBeExpiredRf);
9252
+ }
9253
+ }
7844
9254
  }
7845
- let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
7846
- let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.rfUuid + '/concatChunksRequest';
7847
- let fd = new FormData();
7848
- fd.set('uuid', this.rfUuid);
7849
- fd.set('chunkCount', chunkCount.toString());
7850
- let ul = new Upload(fd, recUrl);
7851
- this.uploader.queueUpload(ul);
7852
9255
  }
7853
- else {
7854
- console.error("Recording file UUID not set!");
9256
+ }
9257
+ addSprRecFile(item, sprRecFile) {
9258
+ this.expire();
9259
+ if (!item.recs) {
9260
+ item.recs = new Array();
7855
9261
  }
9262
+ item.recs.push(sprRecFile);
9263
+ this._sampleCount += RecordingFileUtils.sampleCount(sprRecFile);
9264
+ if (BasicRecFilesCache.DEBUG)
9265
+ console.debug("Rec. files cache: Added. Cache samples: " + this._sampleCount);
7856
9266
  }
7857
- closed() {
7858
- this.statusAlertType = 'info';
7859
- this.statusMsg = 'Session closed.';
9267
+ setSprRecFileAudioData(sprRecFile, adh) {
9268
+ this.expire();
9269
+ if (BasicRecFilesCache.DEBUG)
9270
+ console.debug("Rec. files cache: Set audio data after expire. Cache samples: " + this._sampleCount);
9271
+ let currSampleCnt = RecordingFileUtils.sampleCount(sprRecFile);
9272
+ this._sampleCount -= currSampleCnt;
9273
+ if (BasicRecFilesCache.DEBUG)
9274
+ console.debug("Rec. files cache: Set audio data subtracted curr sample count: " + currSampleCnt + ". Cache samples: " + this._sampleCount);
9275
+ RecordingFileUtils.setAudioData(sprRecFile, adh);
9276
+ let newSampleCnt = RecordingFileUtils.sampleCount(sprRecFile);
9277
+ this._sampleCount += newSampleCnt;
9278
+ if (BasicRecFilesCache.DEBUG)
9279
+ console.debug("Rec. files cache: Set audio data added new sample count: " + newSampleCnt + ". Cache samples: " + this._sampleCount);
9280
+ let fl = adh?.frameLen;
9281
+ if (fl) {
9282
+ sprRecFile.frames = fl;
9283
+ }
9284
+ if (BasicRecFilesCache.DEBUG)
9285
+ console.debug("Rec. files cache: Set audio data. Cache samples: " + this._sampleCount);
7860
9286
  }
7861
- error(msg = 'An unknown error occured during recording.', advice = 'Please retry.') {
7862
- this.statusMsg = 'ERROR: Recording.';
7863
- this.statusAlertType = 'error';
7864
- this.dialog.open(MessageDialog, {
7865
- data: {
7866
- type: 'error',
7867
- title: 'Recording error',
7868
- msg: msg,
7869
- advice: advice
9287
+ }
9288
+ class RecFilesCache extends BasicRecFilesCache {
9289
+ constructor() {
9290
+ super(...arguments);
9291
+ this._recFiles = new Array();
9292
+ }
9293
+ get recFiles() {
9294
+ return this._recFiles;
9295
+ }
9296
+ getRecFile(index) {
9297
+ return this._recFiles[index];
9298
+ }
9299
+ length() {
9300
+ return this._recFiles.length;
9301
+ }
9302
+ expire() {
9303
+ if (this._sampleCount > this.maxSampleCount) {
9304
+ // audio recorder list is sorted: lower index is newer
9305
+ for (let rfI = this._recFiles.length - 1; rfI >= 0; rfI--) {
9306
+ if (this._sampleCount <= this.maxSampleCount) {
9307
+ break;
9308
+ }
9309
+ let toBeExpiredRf = this._recFiles[rfI];
9310
+ if (toBeExpiredRf.serverPersisted) {
9311
+ // expire recording files first stored to the cache
9312
+ let expiredAudio = RecordingFileUtils.expireAudioData(toBeExpiredRf);
9313
+ if (expiredAudio) {
9314
+ this._sampleCount -= expiredAudio.sampleCounts();
9315
+ if (BasicRecFilesCache.DEBUG)
9316
+ console.debug("Rec. files cache: Expired #" + rfI + ". Cache samples: " + this._sampleCount);
9317
+ }
9318
+ }
9319
+ else {
9320
+ if (BasicRecFilesCache.DEBUG)
9321
+ console.debug("Rec. files cache: #" + rfI + " not yet server persisted.");
9322
+ }
7870
9323
  }
7871
- });
9324
+ }
7872
9325
  }
7873
- };
7874
- BasicRecorder.DEFAULT_CHUNK_SIZE_SECONDS = 30;
7875
- BasicRecorder = __decorate([
7876
- __param(3, Inject(SPEECHRECORDER_CONFIG))
7877
- ], BasicRecorder);
9326
+ addRecFile(recFile) {
9327
+ this.expire();
9328
+ this._recFiles.push(recFile);
9329
+ this._sampleCount += RecordingFileUtils.sampleCount(recFile);
9330
+ if (BasicRecFilesCache.DEBUG)
9331
+ console.debug("Rec. files cache: Added. Cache samples: " + this._sampleCount);
9332
+ }
9333
+ setRecFileAudioData(recFile, adh) {
9334
+ this.expire();
9335
+ this._sampleCount -= RecordingFileUtils.sampleCount(recFile);
9336
+ RecordingFileUtils.setAudioData(recFile, adh);
9337
+ this._sampleCount += RecordingFileUtils.sampleCount(recFile);
9338
+ if (BasicRecFilesCache.DEBUG)
9339
+ console.debug("Rec. files cache: Set audio data. Cache samples: " + this._sampleCount);
9340
+ }
9341
+ }
7878
9342
 
7879
9343
  /**
7880
9344
  * Created by klausj on 17.06.2017.
@@ -7967,16 +9431,65 @@ class RecordingService {
7967
9431
  let recUrl = recFilesUrl + '/' + encItemcode + '/' + version;
7968
9432
  return this.audioRequest(recUrl);
7969
9433
  }
9434
+ fetchRecordingFileAudioBuffer(aCtx, projectName, recordingFile) {
9435
+ let wobs = new Observable(observer => {
9436
+ let recFileId = recordingFile.recordingFileId;
9437
+ if (!recFileId) {
9438
+ recFileId = recordingFile.uuid;
9439
+ }
9440
+ if (recordingFile.session && recFileId) {
9441
+ let obs = this.fetchAudiofile(projectName, recordingFile.session, recFileId);
9442
+ obs.subscribe({
9443
+ next: resp => {
9444
+ // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9445
+ if (resp.body) {
9446
+ aCtx.decodeAudioData(resp.body, ab => {
9447
+ observer.next(ab);
9448
+ observer.complete();
9449
+ }, error => {
9450
+ observer.error(error);
9451
+ observer.complete();
9452
+ });
9453
+ }
9454
+ else {
9455
+ observer.error('Fetching audio file: response has no body');
9456
+ }
9457
+ },
9458
+ error: (error) => {
9459
+ if (error.status == 404) {
9460
+ // Interpret not as an error, the file ist not recorded yet
9461
+ observer.next(null);
9462
+ observer.complete();
9463
+ }
9464
+ else {
9465
+ // all other states are errors
9466
+ observer.error(error);
9467
+ observer.complete();
9468
+ }
9469
+ }
9470
+ });
9471
+ }
9472
+ else {
9473
+ observer.error();
9474
+ }
9475
+ });
9476
+ return wobs;
9477
+ }
7970
9478
  fetchAndApplyRecordingFile(aCtx, projectName, recordingFile) {
7971
9479
  let wobs = new Observable(observer => {
7972
- if (recordingFile.session && recordingFile.recordingFileId) {
7973
- let obs = this.fetchAudiofile(projectName, recordingFile.session, recordingFile.recordingFileId);
9480
+ let recFileId = recordingFile.recordingFileId;
9481
+ if (!recFileId) {
9482
+ recFileId = recordingFile.uuid;
9483
+ }
9484
+ if (recordingFile.session && recFileId) {
9485
+ let obs = this.fetchAudiofile(projectName, recordingFile.session, recFileId);
7974
9486
  obs.subscribe(resp => {
7975
9487
  //console.log("Fetched audio file. HTTP response status: "+resp.status+", type: "+resp.type+", byte length: "+ resp.body.byteLength);
7976
9488
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
7977
9489
  if (resp.body) {
7978
9490
  aCtx.decodeAudioData(resp.body, ab => {
7979
- recordingFile.audioBuffer = ab;
9491
+ let adh = new AudioDataHolder(ab, null);
9492
+ RecordingFileUtils.setAudioData(recordingFile, adh);
7980
9493
  if (this.debugDelay > 0) {
7981
9494
  window.setTimeout(() => {
7982
9495
  observer.next(recordingFile);
@@ -8014,6 +9527,47 @@ class RecordingService {
8014
9527
  });
8015
9528
  return wobs;
8016
9529
  }
9530
+ fetchSprRecordingFileAudioBuffer(aCtx, projectName, recordingFile) {
9531
+ let wobs = new Observable(observer => {
9532
+ if (recordingFile.session) {
9533
+ let obs = this.fetchSprAudiofile(projectName, recordingFile.session, recordingFile.itemCode, recordingFile.version);
9534
+ obs.subscribe({
9535
+ next: resp => {
9536
+ // Do not use Promise version, which does not work with Safari 13 (13.0.5)
9537
+ if (resp.body) {
9538
+ aCtx.decodeAudioData(resp.body, ab => {
9539
+ //RecordingFileUtils.setAudioData(recordingFile,new AudioDataHolder(ab,null));
9540
+ observer.next(ab);
9541
+ observer.complete();
9542
+ }, error => {
9543
+ observer.error(error);
9544
+ observer.complete();
9545
+ });
9546
+ }
9547
+ else {
9548
+ observer.error('Fetching audio file: response has no body');
9549
+ }
9550
+ },
9551
+ error: (error) => {
9552
+ if (error.status == 404) {
9553
+ // Interpret not as an error, the file ist not recorded yet
9554
+ observer.next(null);
9555
+ observer.complete();
9556
+ }
9557
+ else {
9558
+ // all other states are errors
9559
+ observer.error(error);
9560
+ observer.complete();
9561
+ }
9562
+ }
9563
+ });
9564
+ }
9565
+ else {
9566
+ observer.error();
9567
+ }
9568
+ });
9569
+ return wobs;
9570
+ }
8017
9571
  fetchAndApplySprRecordingFile(aCtx, projectName, recordingFile) {
8018
9572
  let wobs = new Observable(observer => {
8019
9573
  if (recordingFile.session) {
@@ -8023,7 +9577,7 @@ class RecordingService {
8023
9577
  // Do not use Promise version, which does not work with Safari 13 (13.0.5)
8024
9578
  if (resp.body) {
8025
9579
  aCtx.decodeAudioData(resp.body, ab => {
8026
- recordingFile.audioBuffer = ab;
9580
+ RecordingFileUtils.setAudioData(recordingFile, new AudioDataHolder(ab, null));
8027
9581
  if (this.debugDelay > 0) {
8028
9582
  window.setTimeout(() => {
8029
9583
  observer.next(recordingFile);
@@ -8068,7 +9622,8 @@ class RecordingService {
8068
9622
  // Do not use Promise version, which does not work with Safari 13
8069
9623
  if (resp.body) {
8070
9624
  aCtx.decodeAudioData(resp.body, ab => {
8071
- let rf = new SprRecordingFile(sessId, itemcode, version, ab);
9625
+ let adh = new AudioDataHolder(ab, null);
9626
+ let rf = new SprRecordingFile(sessId, itemcode, version, adh);
8072
9627
  if (this.debugDelay > 0) {
8073
9628
  window.setTimeout(() => {
8074
9629
  observer.next(rf);
@@ -8184,14 +9739,9 @@ const DEFAULT_PRE_REC_DELAY = 1000;
8184
9739
  const DEFAULT_POST_REC_DELAY = 500;
8185
9740
  class SessionManager extends BasicRecorder {
8186
9741
  constructor(changeDetectorRef, renderer, dialog, sessionService, recFileService, uploader, config) {
8187
- super(dialog, sessionService, uploader, config);
8188
- this.changeDetectorRef = changeDetectorRef;
9742
+ super(changeDetectorRef, dialog, sessionService, uploader, config);
8189
9743
  this.renderer = renderer;
8190
- this.dialog = dialog;
8191
- this.sessionService = sessionService;
8192
9744
  this.recFileService = recFileService;
8193
- this.uploader = uploader;
8194
- this.config = config;
8195
9745
  this.enableUploadRecordings = true;
8196
9746
  this.enableDownloadRecordings = false;
8197
9747
  this.status = 0 /* BLOCKED */;
@@ -8225,7 +9775,7 @@ class SessionManager extends BasicRecorder {
8225
9775
  };
8226
9776
  }
8227
9777
  ngOnDestroy() {
8228
- console.debug("Com destroy, disable wake lock.");
9778
+ //console.debug("Com destroy, disable wake lock.")
8229
9779
  this.disableWakeLockCond();
8230
9780
  this.destroyed = true;
8231
9781
  // TODO stop capture /playback
@@ -8245,7 +9795,7 @@ class SessionManager extends BasicRecorder {
8245
9795
  context = AudioContextProvider.audioContextInstance();
8246
9796
  }
8247
9797
  catch (err) {
8248
- this.status = 8 /* ERROR */;
9798
+ this.status = 9 /* ERROR */;
8249
9799
  let errMsg = 'Unknown error';
8250
9800
  if (err instanceof Error) {
8251
9801
  errMsg = err.message;
@@ -8269,7 +9819,7 @@ class SessionManager extends BasicRecorder {
8269
9819
  console.info("No audio context available!");
8270
9820
  }
8271
9821
  if (!context || !navigator.mediaDevices) {
8272
- this.status = 8 /* ERROR */;
9822
+ this.status = 9 /* ERROR */;
8273
9823
  let errMsg = 'Browser does not support Media streams!';
8274
9824
  this.statusMsg = 'ERROR: ' + errMsg;
8275
9825
  this.statusAlertType = 'error';
@@ -8356,7 +9906,7 @@ class SessionManager extends BasicRecorder {
8356
9906
  progressPercentValue() {
8357
9907
  let v = 100;
8358
9908
  if (this.items) {
8359
- v = this.promptIndex * 100 / (this.items.length - 1);
9909
+ v = this.promptIndex * 100 / (this.items.length() - 1);
8360
9910
  }
8361
9911
  return v;
8362
9912
  }
@@ -8370,9 +9920,6 @@ class SessionManager extends BasicRecorder {
8370
9920
  return ((this._session != null) && (this._session.type === 'TEST_DEF_A') && (this.audioDevices != null) && this.audioDevices.length > 0);
8371
9921
  }
8372
9922
  set controlAudioPlayer(controlAudioPlayer) {
8373
- if (this._controlAudioPlayer) {
8374
- //this._controlAudioPlayer.listener=null;
8375
- }
8376
9923
  this._controlAudioPlayer = controlAudioPlayer;
8377
9924
  if (this._controlAudioPlayer) {
8378
9925
  this._controlAudioPlayer.listener = this;
@@ -8440,7 +9987,12 @@ class SessionManager extends BasicRecorder {
8440
9987
  }
8441
9988
  }
8442
9989
  startItem() {
9990
+ this.status = 2 /* STARTING */;
8443
9991
  super.startItem();
9992
+ if (this.readonly) {
9993
+ this.status = 1 /* IDLE */;
9994
+ return;
9995
+ }
8444
9996
  this.transportActions.fwdAction.disabled = true;
8445
9997
  this.transportActions.fwdNextAction.disabled = true;
8446
9998
  this.transportActions.bwdAction.disabled = true;
@@ -8452,6 +10004,8 @@ class SessionManager extends BasicRecorder {
8452
10004
  this.autorecording = true;
8453
10005
  }
8454
10006
  if (this.ac != null) {
10007
+ // Hide loading hint on livelevel display
10008
+ this.audioFetching = false;
8455
10009
  if (!this.ac.opened) {
8456
10010
  if (this._selectedDeviceId) {
8457
10011
  console.log("Open session with audio device Id: \'" + this._selectedDeviceId + "\' for " + this._channelCount + " channels");
@@ -8468,7 +10022,7 @@ class SessionManager extends BasicRecorder {
8468
10022
  }
8469
10023
  loadScript() {
8470
10024
  this.promptItemCount = 0;
8471
- this.items = new Array();
10025
+ this.items = new SprItemsCache();
8472
10026
  let ln = 0;
8473
10027
  //TODO randomize not supported
8474
10028
  for (let si = 0; si < this._script.sections.length; si++) {
@@ -8482,7 +10036,7 @@ class SessionManager extends BasicRecorder {
8482
10036
  let pi = pis[piSectIdx];
8483
10037
  let promptAsStr = PromptitemUtil.toPlainTextString(pi);
8484
10038
  let it = new Item$1(promptAsStr, section.training, (!pi.type || pi.type === 'recording'));
8485
- this.items.push(it);
10039
+ this.items.addItem(it);
8486
10040
  ln++;
8487
10041
  }
8488
10042
  }
@@ -8523,10 +10077,10 @@ class SessionManager extends BasicRecorder {
8523
10077
  }
8524
10078
  downloadRecording() {
8525
10079
  if (this.displayRecFile) {
8526
- let ab = this.displayRecFile.audioBuffer;
10080
+ let ab = this.displayRecFile.audioDataHolder;
8527
10081
  let ww = new WavWriter();
8528
- if (ab) {
8529
- let wavFile = ww.writeAsync(ab, (wavFile) => {
10082
+ if (ab?.buffer) {
10083
+ let wavFile = ww.writeAsync(ab.buffer, (wavFile) => {
8530
10084
  let blob = new Blob([wavFile], { type: 'audio/wav' });
8531
10085
  let rfUrl = URL.createObjectURL(blob);
8532
10086
  let dataDnlLnk = this.renderer.createElement('a');
@@ -8549,7 +10103,10 @@ class SessionManager extends BasicRecorder {
8549
10103
  set displayRecFile(displayRecFile) {
8550
10104
  this._displayRecFile = displayRecFile;
8551
10105
  if (this._displayRecFile) {
8552
- let ab = this._displayRecFile.audioBuffer;
10106
+ if (this.items) {
10107
+ this.items.currentRecordingFile = this._displayRecFile;
10108
+ }
10109
+ let ab = this._displayRecFile.audioDataHolder;
8553
10110
  if (ab) {
8554
10111
  this.displayAudioClip = new AudioClip(ab);
8555
10112
  this.controlAudioPlayer.audioClip = this.displayAudioClip;
@@ -8557,27 +10114,46 @@ class SessionManager extends BasicRecorder {
8557
10114
  else {
8558
10115
  // clear for now ...
8559
10116
  this.displayAudioClip = null;
8560
- this.controlAudioPlayer.audioClip = null;
10117
+ if (this.controlAudioPlayer) {
10118
+ this.controlAudioPlayer.audioClip = null;
10119
+ }
8561
10120
  if (this._controlAudioPlayer && this._session) {
8562
10121
  //... and try to fetch from server
8563
- this.audioFetchSubscription = this.recFileService.fetchAndApplySprRecordingFile(this._controlAudioPlayer.context, this._session.project, this._displayRecFile).subscribe((rf) => {
8564
- let fab = null;
8565
- if (rf && this._displayRecFile) {
8566
- fab = this._displayRecFile.audioBuffer;
8567
- }
8568
- else {
8569
- this.statusMsg = 'Recording file could not be loaded.';
10122
+ this.audioFetching = true;
10123
+ let rf = this._displayRecFile;
10124
+ this.audioFetchSubscription = this.recFileService.fetchSprRecordingFileAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
10125
+ next: (ab) => {
10126
+ this.audioFetching = false;
10127
+ let fabDh = null;
10128
+ if (ab) {
10129
+ if (rf && this.items) {
10130
+ if (SessionManager.FORCE_ARRRAY_AUDIO_BUFFER) {
10131
+ let aab = ArrayAudioBuffer.fromAudioBuffer(ab);
10132
+ fabDh = new AudioDataHolder(null, aab);
10133
+ }
10134
+ else {
10135
+ fabDh = new AudioDataHolder(ab);
10136
+ }
10137
+ this.items.setSprRecFileAudioData(rf, fabDh);
10138
+ }
10139
+ }
10140
+ else {
10141
+ // Should actually be handled by the error resolver
10142
+ this.statusMsg = 'Recording file could not be loaded.';
10143
+ this.statusAlertType = 'error';
10144
+ }
10145
+ if (fabDh) {
10146
+ // this.displayAudioClip could have been changed meanwhile, but the recorder unsubcribes before changing the item. Therefore there should be no risk to set to wrong item
10147
+ this.displayAudioClip = new AudioClip(fabDh);
10148
+ }
10149
+ this.controlAudioPlayer.audioClip = this.displayAudioClip;
10150
+ this.showRecording();
10151
+ }, error: err => {
10152
+ console.error("Could not load recording file from server: " + err);
10153
+ this.audioFetching = false;
10154
+ this.statusMsg = 'Recording file could not be loaded: ' + err;
8570
10155
  this.statusAlertType = 'error';
8571
10156
  }
8572
- if (fab) {
8573
- this.displayAudioClip = new AudioClip(fab);
8574
- }
8575
- this.controlAudioPlayer.audioClip = this.displayAudioClip;
8576
- this.showRecording();
8577
- }, err => {
8578
- console.error("Could not load recording file from server: " + err);
8579
- this.statusMsg = 'Recording file could not be loaded: ' + err;
8580
- this.statusAlertType = 'error';
8581
10157
  });
8582
10158
  }
8583
10159
  else {
@@ -8588,34 +10164,14 @@ class SessionManager extends BasicRecorder {
8588
10164
  }
8589
10165
  else {
8590
10166
  this.displayAudioClip = null;
8591
- this.controlAudioPlayer.audioClip = null;
10167
+ if (this.controlAudioPlayer) {
10168
+ this.controlAudioPlayer.audioClip = null;
10169
+ }
8592
10170
  }
8593
10171
  }
8594
10172
  get displayRecFile() {
8595
10173
  return this._displayRecFile;
8596
10174
  }
8597
- showRecording() {
8598
- this.controlAudioPlayer.stop();
8599
- if (this.displayAudioClip) {
8600
- this.levelMeasure.calcBufferLevelInfos(this.displayAudioClip.buffer, LEVEL_BAR_INTERVALL_SECONDS).then((levelInfos) => {
8601
- this.displayLevelInfos = levelInfos;
8602
- this.changeDetectorRef.detectChanges();
8603
- });
8604
- this.playStartAction.disabled = false;
8605
- }
8606
- else {
8607
- // TODO
8608
- // Setting to null does not trigger a change if it was null before (happens after nextitem() in AUTOPROGRESS mode)
8609
- // The level bar display does not clear, it shows the last captured stream
8610
- this.displayLevelInfos = null;
8611
- this.playStartAction.disabled = true;
8612
- // Collapse audio signal display if open
8613
- if (!this.audioSignalCollapsed) {
8614
- this.audioSignalCollapsed = true;
8615
- }
8616
- }
8617
- this.changeDetectorRef.detectChanges();
8618
- }
8619
10175
  isRecordingItem() {
8620
10176
  return (this.promptItem != null && this.promptItem.type !== 'nonrecording');
8621
10177
  }
@@ -8630,17 +10186,20 @@ class SessionManager extends BasicRecorder {
8630
10186
  if (this.audioFetchSubscription) {
8631
10187
  this.audioFetchSubscription.unsubscribe();
8632
10188
  }
10189
+ this.audioFetching = false;
8633
10190
  this.clearPrompt();
8634
10191
  let isNonrecording = (this.promptItem.type === 'nonrecording');
8635
10192
  if (isNonrecording || !this.section.promptphase || this.section.promptphase === 'IDLE') {
8636
10193
  this.applyPrompt();
8637
10194
  }
8638
10195
  if (isNonrecording) {
10196
+ this.displayRecFile = null;
10197
+ this.displayRecFileVersion = 0;
8639
10198
  this.startStopSignalState = 4 /* OFF */;
8640
10199
  }
8641
10200
  else {
8642
10201
  if (this.items) {
8643
- let it = this.items[this.promptIndex];
10202
+ let it = this.items.getItem(this.promptIndex);
8644
10203
  if (!it.recs) {
8645
10204
  it.recs = new Array();
8646
10205
  }
@@ -8657,13 +10216,13 @@ class SessionManager extends BasicRecorder {
8657
10216
  this.displayRecFileVersion = 0;
8658
10217
  }
8659
10218
  }
8660
- if (!temporary) {
8661
- this.showRecording();
8662
- }
8663
10219
  if (!this.readonly) {
8664
10220
  this.startStopSignalState = 0 /* IDLE */;
8665
10221
  }
8666
10222
  }
10223
+ if (!temporary) {
10224
+ this.showRecording();
10225
+ }
8667
10226
  this.updateStartActionDisableState();
8668
10227
  this.updateNavigationActions();
8669
10228
  }
@@ -8675,10 +10234,10 @@ class SessionManager extends BasicRecorder {
8675
10234
  this.enableNavigation();
8676
10235
  }
8677
10236
  isRecording() {
8678
- return (this.status === 2 /* PRE_RECORDING */ || this.status === 3 /* RECORDING */);
10237
+ return (this.status === 3 /* PRE_RECORDING */ || this.status === 4 /* RECORDING */);
8679
10238
  }
8680
10239
  isActive() {
8681
- return (!(this.status === 0 /* BLOCKED */ || this.status === 1 /* IDLE */ || this.status === 8 /* ERROR */) || this.processingRecording || this.sessionService.uploadCount > 0);
10240
+ return (!(this.status === 0 /* BLOCKED */ || this.status === 1 /* IDLE */ || this.status === 9 /* ERROR */) || this.processingRecording || this.sessionService.uploadCount > 0);
8682
10241
  }
8683
10242
  prevItem() {
8684
10243
  let newPrIdx = this._promptIndex;
@@ -8701,7 +10260,7 @@ class SessionManager extends BasicRecorder {
8701
10260
  updateNavigationActions() {
8702
10261
  let fwdNxtActionDisabled = true;
8703
10262
  if (this.items) {
8704
- let currRecs = this.items[this._promptIndex].recs;
10263
+ let currRecs = this.items.getItem(this._promptIndex).recs;
8705
10264
  fwdNxtActionDisabled = !(currRecs != null && currRecs.length > 0);
8706
10265
  }
8707
10266
  this.transportActions.fwdNextAction.disabled = this.navigationDisabled || fwdNxtActionDisabled;
@@ -8715,10 +10274,10 @@ class SessionManager extends BasicRecorder {
8715
10274
  newPrIdx = 0;
8716
10275
  }
8717
10276
  if (this.items != null) {
8718
- let itRecs = this.items[newPrIdx].recs;
10277
+ let itRecs = this.items.getItem(newPrIdx).recs;
8719
10278
  while (itRecs != null && (itRecs.length > 0) && newPrIdx < this.promptItemCount) {
8720
10279
  newPrIdx++;
8721
- itRecs = this.items[newPrIdx].recs;
10280
+ itRecs = this.items.getItem(newPrIdx).recs;
8722
10281
  }
8723
10282
  if (!itRecs || itRecs.length == 0) {
8724
10283
  this.promptIndex = newPrIdx;
@@ -8739,7 +10298,24 @@ class SessionManager extends BasicRecorder {
8739
10298
  }
8740
10299
  }
8741
10300
  started() {
8742
- this.status = 2 /* PRE_RECORDING */;
10301
+ let ic = this.promptItem.itemcode;
10302
+ let rf = null;
10303
+ if (this._session && ic) {
10304
+ let sessId = this._session.sessionId;
10305
+ let cpIdx = this.promptIndex;
10306
+ if (this.items) {
10307
+ let it = this.items.getItem(cpIdx);
10308
+ if (!it.recs) {
10309
+ it.recs = new Array();
10310
+ }
10311
+ rf = new SprRecordingFile(sessId, ic, it.recs.length, null);
10312
+ //it.recs.push(rf);
10313
+ this.items.addSprRecFile(it, rf);
10314
+ this.items.currentRecordingFile = rf;
10315
+ }
10316
+ }
10317
+ this._recordingFile = rf;
10318
+ this.status = 3 /* PRE_RECORDING */;
8743
10319
  super.started();
8744
10320
  this.startStopSignalState = 1 /* PRERECORDING */;
8745
10321
  if (this._session) {
@@ -8793,7 +10369,7 @@ class SessionManager extends BasicRecorder {
8793
10369
  this.maxRecTimerRunning = true;
8794
10370
  this.preRecTimerId = window.setTimeout(() => {
8795
10371
  this.preRecTimerRunning = false;
8796
- this.status = 3 /* RECORDING */;
10372
+ this.status = 4 /* RECORDING */;
8797
10373
  this.startStopSignalState = 3 /* RECORDING */;
8798
10374
  if (this.section.mode === 'AUTORECORDING') {
8799
10375
  this.transportActions.nextAction.disabled = false;
@@ -8812,19 +10388,19 @@ class SessionManager extends BasicRecorder {
8812
10388
  this.preRecTimerRunning = true;
8813
10389
  }
8814
10390
  stopItem() {
8815
- this.status = 4 /* POST_REC_STOP */;
10391
+ this.status = 5 /* POST_REC_STOP */;
8816
10392
  this.startStopSignalState = 2 /* POSTRECORDING */;
8817
10393
  this.transportActions.stopAction.disabled = true;
8818
10394
  this.transportActions.nextAction.disabled = true;
8819
10395
  this.postRecTimerId = window.setTimeout(() => {
8820
10396
  this.postRecTimerRunning = false;
8821
- this.status = 6 /* STOPPING_STOP */;
10397
+ this.status = 7 /* STOPPING_STOP */;
8822
10398
  this.stopRecording();
8823
10399
  }, this.postDelay);
8824
10400
  this.postRecTimerRunning = true;
8825
10401
  }
8826
10402
  pauseItem() {
8827
- this.status = 5 /* POST_REC_PAUSE */;
10403
+ this.status = 6 /* POST_REC_PAUSE */;
8828
10404
  this.transportActions.pauseAction.disabled = true;
8829
10405
  this.startStopSignalState = 2 /* POSTRECORDING */;
8830
10406
  this.transportActions.stopAction.disabled = true;
@@ -8832,7 +10408,7 @@ class SessionManager extends BasicRecorder {
8832
10408
  this.transportActions.pauseAction.disabled = true;
8833
10409
  this.postRecTimerId = window.setTimeout(() => {
8834
10410
  this.postRecTimerRunning = false;
8835
- this.status = 7 /* STOPPING_PAUSE */;
10411
+ this.status = 8 /* STOPPING_PAUSE */;
8836
10412
  this.stopRecording();
8837
10413
  }, this.postDelay);
8838
10414
  this.postRecTimerRunning = true;
@@ -8856,7 +10432,7 @@ class SessionManager extends BasicRecorder {
8856
10432
  this.postRecTimerRunning = false;
8857
10433
  }
8858
10434
  this.maxRecTimerRunning = false;
8859
- this.status = 6 /* STOPPING_STOP */;
10435
+ this.status = 7 /* STOPPING_STOP */;
8860
10436
  if (this.ac) {
8861
10437
  this.ac.stop();
8862
10438
  }
@@ -8865,13 +10441,14 @@ class SessionManager extends BasicRecorder {
8865
10441
  if (rfd.recording && rfd.recording.itemcode) {
8866
10442
  let prIdx = this.promptIndexByItemcode(rfd.recording.itemcode);
8867
10443
  if (this.items != null && prIdx !== null) {
8868
- let it = this.items[prIdx];
10444
+ let it = this.items.getItem(prIdx);
8869
10445
  if (it && this._session) {
8870
- if (!it.recs) {
8871
- it.recs = new Array();
8872
- }
10446
+ // if (!it.recs) {
10447
+ // it.recs = new Array<SprRecordingFile>();
10448
+ // }
8873
10449
  let rf = new SprRecordingFile(this._session.sessionId, rfd.recording.itemcode, rfd.version, null);
8874
- it.recs[rfd.version] = rf;
10450
+ rf.serverPersisted = true;
10451
+ this.items.addSprRecFile(it, rf);
8875
10452
  }
8876
10453
  else {
8877
10454
  //console.debug("WARN: No recording item with code: \"" +rfd.recording.itemcode+ "\" found.");
@@ -8893,22 +10470,24 @@ class SessionManager extends BasicRecorder {
8893
10470
  this.statusMsg = 'Recorded.';
8894
10471
  this.startStopSignalState = 0 /* IDLE */;
8895
10472
  let ad = null;
10473
+ let ada = null;
10474
+ let adh = null;
10475
+ let frameLen = 0;
8896
10476
  if (this.ac != null) {
8897
- ad = this.ac.audioBuffer();
8898
- }
8899
- let ic = this.promptItem.itemcode;
8900
- let rf = null;
8901
- if (this._session && ic) {
8902
- let sessId = this._session.sessionId;
8903
- let cpIdx = this.promptIndex;
8904
- if (this.items) {
8905
- let it = this.items[cpIdx];
8906
- if (!it.recs) {
8907
- it.recs = new Array();
8908
- }
8909
- rf = new SprRecordingFile(sessId, ic, it.recs.length, ad);
8910
- it.recs.push(rf);
10477
+ if (this.uploadChunkSizeSeconds || SessionManager.FORCE_ARRRAY_AUDIO_BUFFER) {
10478
+ ada = this.ac.audioBufferArray();
10479
+ frameLen = ada.frameLen;
10480
+ }
10481
+ else {
10482
+ ad = this.ac.audioBuffer();
10483
+ frameLen = ad.length;
8911
10484
  }
10485
+ adh = new AudioDataHolder(ad, ada);
10486
+ }
10487
+ // Use an own reference since the writing of the wave file is asynchronous and this._recordingFile might already contain the next recording
10488
+ let rf = this._recordingFile;
10489
+ if (rf && rf instanceof SprRecordingFile) {
10490
+ this.items?.setSprRecFileAudioData(rf, adh);
8912
10491
  if (this.enableUploadRecordings && !this.uploadChunkSizeSeconds) {
8913
10492
  // TODO use SpeechRecorderconfig resp. RecfileService
8914
10493
  // convert asynchronously to 16-bit integer PCM
@@ -8916,7 +10495,7 @@ class SessionManager extends BasicRecorder {
8916
10495
  // TODO duplicate conversion for manual download
8917
10496
  //console.log("Build wav writer...");
8918
10497
  this.processingRecording = true;
8919
- if (ad && rf) {
10498
+ if (ad) {
8920
10499
  let ww = new WavWriter();
8921
10500
  //new REST API URL
8922
10501
  let apiEndPoint = '';
@@ -8929,7 +10508,7 @@ class SessionManager extends BasicRecorder {
8929
10508
  let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
8930
10509
  let recUrl = sessionsUrl + '/' + rf.session + '/' + RECFILE_API_CTX + '/' + rf.itemCode;
8931
10510
  ww.writeAsync(ad, (wavFile) => {
8932
- this.postRecording(wavFile, recUrl);
10511
+ this.postRecording(wavFile, recUrl, rf);
8933
10512
  this.processingRecording = false;
8934
10513
  this.updateWakeLock();
8935
10514
  });
@@ -8940,15 +10519,15 @@ class SessionManager extends BasicRecorder {
8940
10519
  let complete = true;
8941
10520
  if (this.items) {
8942
10521
  // search backwards, to gain faster detection of incomplete state
8943
- for (let ri = this.items.length - 1; ri >= 0; ri--) {
8944
- let it = this.items[ri];
10522
+ for (let ri = this.items.length() - 1; ri >= 0; ri--) {
10523
+ let it = this.items.getItem(ri);
8945
10524
  if (it.recording && !it.training && (!it.recs || it.recs.length == 0)) {
8946
10525
  complete = false;
8947
10526
  break;
8948
10527
  }
8949
10528
  }
8950
10529
  }
8951
- let autoStart = (this.status === 6 /* STOPPING_STOP */);
10530
+ let autoStart = (this.status === 7 /* STOPPING_STOP */);
8952
10531
  this.status = 1 /* IDLE */;
8953
10532
  let startNext = false;
8954
10533
  if (complete) {
@@ -9005,13 +10584,13 @@ class SessionManager extends BasicRecorder {
9005
10584
  let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
9006
10585
  let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.promptItem.itemcode + '/' + this.rfUuid + '/' + chunkIdx;
9007
10586
  ww.writeAsync(audioBuffer, (wavFile) => {
9008
- this.postRecording(wavFile, recUrl);
10587
+ this.postRecording(wavFile, recUrl, null);
9009
10588
  this.processingRecording = false;
9010
10589
  });
9011
10590
  }
9012
- postRecording(wavFile, recUrl) {
10591
+ postRecording(wavFile, recUrl, rf) {
9013
10592
  let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
9014
- let ul = new Upload(wavBlob, recUrl);
10593
+ let ul = new Upload(wavBlob, recUrl, rf);
9015
10594
  this.uploader.queueUpload(ul);
9016
10595
  }
9017
10596
  stop() {
@@ -9047,7 +10626,7 @@ SessionManager.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version
9047
10626
  <app-warningbar [show]="isDefaultAudioTestSession()" warningText="This test uses default audio device! Regular sessions may require a particular audio device (microphone)!"></app-warningbar>
9048
10627
  <app-sprprompting [projectName]="projectName"
9049
10628
  [startStopSignalState]="startStopSignalState" [promptItem]="promptItem" [showPrompt]="showPrompt"
9050
- [items]="items"
10629
+ [items]="items?.items"
9051
10630
  [transportActions]="transportActions"
9052
10631
  [selectedItemIdx]="promptIndex" (onItemSelect)="itemSelect($event)" (onNextItem)="nextItem()" (onPrevItem)="prevItem()"
9053
10632
  [audioSignalCollapsed]="audioSignalCollapsed" [displayAudioClip]="displayAudioClip"
@@ -9061,10 +10640,10 @@ SessionManager.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version
9061
10640
 
9062
10641
 
9063
10642
  <div fxLayout="row" fxLayout.xs="column" [ngStyle]="{'height.px':100,'min-height.px': 100}" [ngStyle.xs]="{'height.px':125,'min-height.px': 125}">
9064
- <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()" [displayLevelInfos]="displayLevelInfos"></audio-levelbar>
10643
+ <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()" [displayLevelInfos]="displayAudioClip?.levelInfos" [stateLoading]="audioFetching"></audio-levelbar>
9065
10644
  <div fxLayout="row">
9066
10645
  <spr-recordingitemcontrols fxFlex="10 0 1"
9067
- [audioLoaded]="displayAudioClip?.buffer!==null"
10646
+ [audioLoaded]="displayAudioClip?.audioDataHolder!==null"
9068
10647
  [playStartAction]="controlAudioPlayer?.startAction"
9069
10648
  [playStopAction]="controlAudioPlayer?.stopAction"
9070
10649
  [peakDbLvl]="peakLevelInDb"
@@ -9082,7 +10661,7 @@ SessionManager.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version
9082
10661
  <div fxFlex="1 1 30%" fxLayoutAlign="start center">
9083
10662
  <app-sprstatusdisplay fxHide.xs [statusMsg]="statusMsg" [statusAlertType]="statusAlertType" [statusWaiting]="statusWaiting"></app-sprstatusdisplay>
9084
10663
  </div>
9085
- <app-sprtransport fxFlex="10 0 30%" fxLayoutAlign="center center" [readonly]="readonly" [actions]="transportActions" [navigationEnabled]="items==null || items.length>1"></app-sprtransport>
10664
+ <app-sprtransport fxFlex="10 0 30%" fxLayoutAlign="center center" [readonly]="readonly" [actions]="transportActions" [navigationEnabled]="!items || items.length()>1"></app-sprtransport>
9086
10665
  <div fxFlex="1 1 30%" fxLayoutAlign="end center" fxLayout="row">
9087
10666
  <app-uploadstatus class="ricontrols" fxHide.xs fxLayoutAlign="end center" *ngIf="enableUploadRecordings" [value]="uploadProgress"
9088
10667
  [status]="uploadStatus" [awaitNewUpload]="processingRecording"></app-uploadstatus>
@@ -9090,7 +10669,7 @@ SessionManager.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version
9090
10669
  <app-readystateindicator class="ricontrols" fxLayoutAlign="end center" fxHide.xs [ready]="dataSaved && !isActive()"></app-readystateindicator>
9091
10670
  </div>
9092
10671
  </div>
9093
- `, isInline: true, styles: [":host{flex:2;background:lightgrey;display:flex;flex-direction:column;margin:0;padding:0;min-height:0px;overflow:hidden}\n", ".ricontrols{padding:4px;box-sizing:border-box;height:100%}\n", ".dark{background:darkgray}\n", ".controlpanel{align-content:center;align-items:center;margin:0;padding:20px;min-height:min-content}\n"], components: [{ type: WarningBar, selector: "app-warningbar", inputs: ["warningText", "show"] }, { type: Prompting, selector: "app-sprprompting", inputs: ["projectName", "startStopSignalState", "promptItem", "showPrompt", "items", "selectedItemIdx", "transportActions", "enableDownload", "audioSignalCollapsed", "displayAudioClip", "playStartAction", "playSelectionAction", "autoPlayOnSelectToggleAction", "playStopAction"], outputs: ["onItemSelect", "onNextItem", "onPrevItem"] }, { type: i7.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { type: LevelBar, selector: "audio-levelbar", inputs: ["streamingMode", "displayLevelInfos"] }, { type: RecordingItemControls, selector: "spr-recordingitemcontrols", inputs: ["audioSignalCollapsed", "enableDownload", "peakDbLvl", "agc", "audioLoaded", "playStartAction", "playStopAction", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }, { type: UploadStatus, selector: "app-uploadstatus", inputs: ["value", "awaitNewUpload", "status"] }, { type: WakeLockIndicator, selector: "app-wakelockindicator", inputs: ["screenLocked"] }, { type: ReadyStateIndicator, selector: "app-readystateindicator", inputs: ["ready"] }, { type: StatusDisplay, selector: "app-sprstatusdisplay", inputs: ["statusAlertType", "statusMsg", "statusWaiting"] }, { type: TransportPanel, selector: "app-sprtransport", inputs: ["readonly", "actions", "navigationEnabled", "pausingEnabled"] }], directives: [{ type: i3$1.DefaultShowHideDirective, selector: " [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]", inputs: ["fxShow", "fxShow.print", "fxShow.xs", "fxShow.sm", "fxShow.md", "fxShow.lg", "fxShow.xl", "fxShow.lt-sm", "fxShow.lt-md", "fxShow.lt-lg", "fxShow.lt-xl", "fxShow.gt-xs", "fxShow.gt-sm", "fxShow.gt-md", "fxShow.gt-lg", "fxHide", "fxHide.print", "fxHide.xs", "fxHide.sm", "fxHide.md", "fxHide.lg", "fxHide.xl", "fxHide.lt-sm", "fxHide.lt-md", "fxHide.lt-lg", "fxHide.lt-xl", "fxHide.gt-xs", "fxHide.gt-sm", "fxHide.gt-md", "fxHide.gt-lg"] }, { type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i3$1.DefaultStyleDirective, selector: " [ngStyle], [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]", inputs: ["ngStyle", "ngStyle.xs", "ngStyle.sm", "ngStyle.md", "ngStyle.lg", "ngStyle.xl", "ngStyle.lt-sm", "ngStyle.lt-md", "ngStyle.lt-lg", "ngStyle.lt-xl", "ngStyle.gt-xs", "ngStyle.gt-sm", "ngStyle.gt-md", "ngStyle.gt-lg"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i8.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i8.DefaultLayoutAlignDirective, selector: " [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]", inputs: ["fxLayoutAlign", "fxLayoutAlign.xs", "fxLayoutAlign.sm", "fxLayoutAlign.md", "fxLayoutAlign.lg", "fxLayoutAlign.xl", "fxLayoutAlign.lt-sm", "fxLayoutAlign.lt-md", "fxLayoutAlign.lt-lg", "fxLayoutAlign.lt-xl", "fxLayoutAlign.gt-xs", "fxLayoutAlign.gt-sm", "fxLayoutAlign.gt-md", "fxLayoutAlign.gt-lg"] }] });
10672
+ `, isInline: true, styles: [":host{flex:2;background:lightgrey;display:flex;flex-direction:column;margin:0;padding:0;min-height:0px;overflow:hidden}\n", ".ricontrols{padding:4px;box-sizing:border-box;height:100%}\n", ".dark{background:darkgray}\n", ".controlpanel{align-content:center;align-items:center;margin:0;padding:20px;min-height:min-content}\n"], components: [{ type: WarningBar, selector: "app-warningbar", inputs: ["warningText", "show"] }, { type: Prompting, selector: "app-sprprompting", inputs: ["projectName", "startStopSignalState", "promptItem", "showPrompt", "items", "selectedItemIdx", "transportActions", "enableDownload", "audioSignalCollapsed", "displayAudioClip", "playStartAction", "playSelectionAction", "autoPlayOnSelectToggleAction", "playStopAction"], outputs: ["onItemSelect", "onNextItem", "onPrevItem"] }, { type: i7.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { type: LevelBar, selector: "audio-levelbar", inputs: ["streamingMode", "displayLevelInfos", "stateLoading"] }, { type: RecordingItemControls, selector: "spr-recordingitemcontrols", inputs: ["audioSignalCollapsed", "enableDownload", "peakDbLvl", "agc", "audioLoaded", "playStartAction", "playStopAction", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }, { type: UploadStatus, selector: "app-uploadstatus", inputs: ["value", "awaitNewUpload", "status"] }, { type: WakeLockIndicator, selector: "app-wakelockindicator", inputs: ["screenLocked"] }, { type: ReadyStateIndicator, selector: "app-readystateindicator", inputs: ["ready"] }, { type: StatusDisplay, selector: "app-sprstatusdisplay", inputs: ["statusAlertType", "statusMsg", "statusWaiting"] }, { type: TransportPanel, selector: "app-sprtransport", inputs: ["readonly", "actions", "navigationEnabled", "pausingEnabled"] }], directives: [{ type: i3$1.DefaultShowHideDirective, selector: " [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]", inputs: ["fxShow", "fxShow.print", "fxShow.xs", "fxShow.sm", "fxShow.md", "fxShow.lg", "fxShow.xl", "fxShow.lt-sm", "fxShow.lt-md", "fxShow.lt-lg", "fxShow.lt-xl", "fxShow.gt-xs", "fxShow.gt-sm", "fxShow.gt-md", "fxShow.gt-lg", "fxHide", "fxHide.print", "fxHide.xs", "fxHide.sm", "fxHide.md", "fxHide.lg", "fxHide.xl", "fxHide.lt-sm", "fxHide.lt-md", "fxHide.lt-lg", "fxHide.lt-xl", "fxHide.gt-xs", "fxHide.gt-sm", "fxHide.gt-md", "fxHide.gt-lg"] }, { type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i3$1.DefaultStyleDirective, selector: " [ngStyle], [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]", inputs: ["ngStyle", "ngStyle.xs", "ngStyle.sm", "ngStyle.md", "ngStyle.lg", "ngStyle.xl", "ngStyle.lt-sm", "ngStyle.lt-md", "ngStyle.lt-lg", "ngStyle.lt-xl", "ngStyle.gt-xs", "ngStyle.gt-sm", "ngStyle.gt-md", "ngStyle.gt-lg"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i8.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i8.DefaultLayoutAlignDirective, selector: " [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]", inputs: ["fxLayoutAlign", "fxLayoutAlign.xs", "fxLayoutAlign.sm", "fxLayoutAlign.md", "fxLayoutAlign.lg", "fxLayoutAlign.xl", "fxLayoutAlign.lt-sm", "fxLayoutAlign.lt-md", "fxLayoutAlign.lt-lg", "fxLayoutAlign.lt-xl", "fxLayoutAlign.gt-xs", "fxLayoutAlign.gt-sm", "fxLayoutAlign.gt-md", "fxLayoutAlign.gt-lg"] }] });
9094
10673
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: SessionManager, decorators: [{
9095
10674
  type: Component,
9096
10675
  args: [{
@@ -9101,7 +10680,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
9101
10680
  <app-warningbar [show]="isDefaultAudioTestSession()" warningText="This test uses default audio device! Regular sessions may require a particular audio device (microphone)!"></app-warningbar>
9102
10681
  <app-sprprompting [projectName]="projectName"
9103
10682
  [startStopSignalState]="startStopSignalState" [promptItem]="promptItem" [showPrompt]="showPrompt"
9104
- [items]="items"
10683
+ [items]="items?.items"
9105
10684
  [transportActions]="transportActions"
9106
10685
  [selectedItemIdx]="promptIndex" (onItemSelect)="itemSelect($event)" (onNextItem)="nextItem()" (onPrevItem)="prevItem()"
9107
10686
  [audioSignalCollapsed]="audioSignalCollapsed" [displayAudioClip]="displayAudioClip"
@@ -9115,10 +10694,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
9115
10694
 
9116
10695
 
9117
10696
  <div fxLayout="row" fxLayout.xs="column" [ngStyle]="{'height.px':100,'min-height.px': 100}" [ngStyle.xs]="{'height.px':125,'min-height.px': 125}">
9118
- <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()" [displayLevelInfos]="displayLevelInfos"></audio-levelbar>
10697
+ <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()" [displayLevelInfos]="displayAudioClip?.levelInfos" [stateLoading]="audioFetching"></audio-levelbar>
9119
10698
  <div fxLayout="row">
9120
10699
  <spr-recordingitemcontrols fxFlex="10 0 1"
9121
- [audioLoaded]="displayAudioClip?.buffer!==null"
10700
+ [audioLoaded]="displayAudioClip?.audioDataHolder!==null"
9122
10701
  [playStartAction]="controlAudioPlayer?.startAction"
9123
10702
  [playStopAction]="controlAudioPlayer?.stopAction"
9124
10703
  [peakDbLvl]="peakLevelInDb"
@@ -9136,7 +10715,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
9136
10715
  <div fxFlex="1 1 30%" fxLayoutAlign="start center">
9137
10716
  <app-sprstatusdisplay fxHide.xs [statusMsg]="statusMsg" [statusAlertType]="statusAlertType" [statusWaiting]="statusWaiting"></app-sprstatusdisplay>
9138
10717
  </div>
9139
- <app-sprtransport fxFlex="10 0 30%" fxLayoutAlign="center center" [readonly]="readonly" [actions]="transportActions" [navigationEnabled]="items==null || items.length>1"></app-sprtransport>
10718
+ <app-sprtransport fxFlex="10 0 30%" fxLayoutAlign="center center" [readonly]="readonly" [actions]="transportActions" [navigationEnabled]="!items || items.length()>1"></app-sprtransport>
9140
10719
  <div fxFlex="1 1 30%" fxLayoutAlign="end center" fxLayout="row">
9141
10720
  <app-uploadstatus class="ricontrols" fxHide.xs fxLayoutAlign="end center" *ngIf="enableUploadRecordings" [value]="uploadProgress"
9142
10721
  [status]="uploadStatus" [awaitNewUpload]="processingRecording"></app-uploadstatus>
@@ -9280,7 +10859,7 @@ class SpeechrecorderngComponent extends RecorderComponent {
9280
10859
  ngAfterViewInit() {
9281
10860
  // let wakeLockSupp=('wakeLock' in navigator);
9282
10861
  // alert('Wake lock API supported: '+wakeLockSupp);
9283
- if (this.sm.status !== 8 /* ERROR */) {
10862
+ if (this.sm.status !== 9 /* ERROR */) {
9284
10863
  let initSuccess = this.init();
9285
10864
  if (initSuccess) {
9286
10865
  this.route.queryParams.subscribe((params) => {
@@ -9425,7 +11004,7 @@ class SpeechrecorderngComponent extends RecorderComponent {
9425
11004
  this.uploadUpdate(ue);
9426
11005
  };
9427
11006
  window.addEventListener('beforeunload', (e) => {
9428
- console.debug("Before page unload event");
11007
+ //console.debug("Before page unload event");
9429
11008
  if (this.ready()) {
9430
11009
  return;
9431
11010
  }
@@ -9744,14 +11323,15 @@ class AudioDisplayPlayer {
9744
11323
  if (this.aCtx) {
9745
11324
  this.aCtx.decodeAudioData(data, (audioBuffer) => {
9746
11325
  //console.debug("Audio Buffer Samplerate: ", audioBuffer.sampleRate)
9747
- this.audioClip = new AudioClip(audioBuffer);
11326
+ let adh = new AudioDataHolder(audioBuffer, null);
11327
+ this.audioClip = new AudioClip(adh);
9748
11328
  });
9749
11329
  }
9750
11330
  }
9751
- set audioData(audioBuffer) {
9752
- this.audioDisplayScrollPane.audioData = audioBuffer;
9753
- if (audioBuffer) {
9754
- let clip = new AudioClip(audioBuffer);
11331
+ set audioData(audioData) {
11332
+ this.audioDisplayScrollPane.audioData = audioData;
11333
+ if (audioData) {
11334
+ let clip = new AudioClip(audioData);
9755
11335
  if (this.ap) {
9756
11336
  this.ap.audioClip = clip;
9757
11337
  this.playStartAction.disabled = false;
@@ -9773,7 +11353,7 @@ class AudioDisplayPlayer {
9773
11353
  let audioData = null;
9774
11354
  let sel = null;
9775
11355
  if (audioClip) {
9776
- audioData = audioClip.buffer;
11356
+ audioData = audioClip.audioDataHolder;
9777
11357
  sel = audioClip.selection;
9778
11358
  if (this._audioClip) {
9779
11359
  this._audioClip.addSelectionObserver((ac) => {
@@ -10044,7 +11624,7 @@ class RecordingFileService {
10044
11624
  // Do not use Promise version, which does not work with Safari 13
10045
11625
  if (resp.body) {
10046
11626
  aCtx.decodeAudioData(resp.body, ab => {
10047
- recordingFile.audioBuffer = ab;
11627
+ RecordingFileUtils.setAudioData(recordingFile, new AudioDataHolder(ab));
10048
11628
  if (this.debugDelay > 0) {
10049
11629
  window.setTimeout(() => {
10050
11630
  observer.next(recordingFile);
@@ -10094,7 +11674,7 @@ class RecordingFileService {
10094
11674
  if (resp.body) {
10095
11675
  aCtx.decodeAudioData(resp.body, ab => {
10096
11676
  if (rf) {
10097
- rf.audioBuffer = ab;
11677
+ RecordingFileUtils.setAudioData(rf, new AudioDataHolder(ab));
10098
11678
  }
10099
11679
  else {
10100
11680
  observer.error('Recording file object null');
@@ -10145,7 +11725,8 @@ class RecordingFileService {
10145
11725
  if (resp.body) {
10146
11726
  aCtx.decodeAudioData(resp.body, ab => {
10147
11727
  if (rf) {
10148
- rf.audioBuffer = ab;
11728
+ let adh = new AudioDataHolder(ab);
11729
+ RecordingFileUtils.setAudioData(rf, adh);
10149
11730
  }
10150
11731
  else {
10151
11732
  observer.error('Recording file object null');
@@ -10188,7 +11769,7 @@ class RecordingFileService {
10188
11769
  // append UUID to make request URL unique to avoid localhost server caching
10189
11770
  recUrl = recUrl + '.json?requestUUID=' + UUID.generate();
10190
11771
  }
10191
- console.log("Path request URL: " + recUrl);
11772
+ //console.log("Path request URL: "+recUrl)
10192
11773
  return this.http.patch(recUrl, { editSampleRate: editSampleRate, editStartFrame: editStartFrame, editEndFrame: editEndFrame }, { withCredentials: this.withCredentials });
10193
11774
  }
10194
11775
  }
@@ -10206,6 +11787,7 @@ class RecordingFileMetaComponent {
10206
11787
  constructor() {
10207
11788
  this.sessionId = null;
10208
11789
  this._recordingFile = null;
11790
+ this.stateLoading = false;
10209
11791
  this.itemCode = null;
10210
11792
  this.uuid = null;
10211
11793
  }
@@ -10221,12 +11803,12 @@ class RecordingFileMetaComponent {
10221
11803
  }
10222
11804
  if (this.itemCode) {
10223
11805
  this.uuid = null;
10224
- console.debug("SprRecordingFile: " + this.itemCode + " UUID: " + this.uuid);
11806
+ //console.debug("SprRecordingFile: "+this.itemCode+ " UUID: "+this.uuid)
10225
11807
  }
10226
11808
  else {
10227
11809
  this.itemCode = null;
10228
11810
  this.uuid = this._recordingFile?.uuid;
10229
- console.debug("RecordingFile: " + this.itemCode + " UUID: " + this.uuid);
11811
+ //console.debug("RecordingFile: "+this.itemCode+ " UUID: "+this.uuid)
10230
11812
  }
10231
11813
  }
10232
11814
  else {
@@ -10243,10 +11825,11 @@ class RecordingFileMetaComponent {
10243
11825
  }
10244
11826
  }
10245
11827
  RecordingFileMetaComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: RecordingFileMetaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10246
- RecordingFileMetaComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: RecordingFileMetaComponent, selector: "app-recording-file-meta", inputs: { sessionId: "sessionId", recordingFile: "recordingFile" }, ngImport: i0, template: `
11828
+ RecordingFileMetaComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: RecordingFileMetaComponent, selector: "app-recording-file-meta", inputs: { sessionId: "sessionId", stateLoading: "stateLoading", recordingFile: "recordingFile" }, ngImport: i0, template: `
10247
11829
  <mat-card>
10248
11830
  <mat-card-title>Recording file ID: {{recordingFile?.recordingFileId}}</mat-card-title>
10249
11831
  <mat-card-content>
11832
+ <mat-progress-spinner *ngIf="stateLoading" mode="indeterminate" [diameter]="20"></mat-progress-spinner>
10250
11833
  <table>
10251
11834
  <tr *ngIf="itemCode">
10252
11835
  <td>Itemcode:</td>
@@ -10276,7 +11859,7 @@ RecordingFileMetaComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0
10276
11859
  </table>
10277
11860
  </mat-card-content>
10278
11861
  </mat-card>
10279
- `, isInline: true, components: [{ type: i1$4.MatCard, selector: "mat-card", exportAs: ["matCard"] }], directives: [{ type: i1$4.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { type: i1$4.MatCardContent, selector: "mat-card-content, [mat-card-content], [matCardContent]" }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
11862
+ `, isInline: true, components: [{ type: i1$4.MatCard, selector: "mat-card", exportAs: ["matCard"] }, { type: i1$3.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "diameter", "strokeWidth", "mode", "value"], exportAs: ["matProgressSpinner"] }], directives: [{ type: i1$4.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { type: i1$4.MatCardContent, selector: "mat-card-content, [mat-card-content], [matCardContent]" }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
10280
11863
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: RecordingFileMetaComponent, decorators: [{
10281
11864
  type: Component,
10282
11865
  args: [{
@@ -10285,6 +11868,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
10285
11868
  <mat-card>
10286
11869
  <mat-card-title>Recording file ID: {{recordingFile?.recordingFileId}}</mat-card-title>
10287
11870
  <mat-card-content>
11871
+ <mat-progress-spinner *ngIf="stateLoading" mode="indeterminate" [diameter]="20"></mat-progress-spinner>
10288
11872
  <table>
10289
11873
  <tr *ngIf="itemCode">
10290
11874
  <td>Itemcode:</td>
@@ -10319,6 +11903,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
10319
11903
  }]
10320
11904
  }], propDecorators: { sessionId: [{
10321
11905
  type: Input
11906
+ }], stateLoading: [{
11907
+ type: Input
10322
11908
  }], recordingFile: [{
10323
11909
  type: Input
10324
11910
  }] } });
@@ -10461,6 +12047,7 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
10461
12047
  this.recordingFileVersion = null;
10462
12048
  this.routedByQueryParam = false;
10463
12049
  this.posInList = null;
12050
+ this.audioFetching = false;
10464
12051
  this.naviInfoLoading = false;
10465
12052
  this.parentE = this.eRef.nativeElement;
10466
12053
  this.firstAction = new Action('First');
@@ -10630,18 +12217,20 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
10630
12217
  this.updateActions();
10631
12218
  let audioContext = AudioContextProvider.audioContextInstance();
10632
12219
  if (audioContext) {
12220
+ this.audioFetching = true;
10633
12221
  this.recordingFileService.fetchSprRecordingFile(audioContext, rfId).subscribe(value => {
12222
+ this.audioFetching = false;
10634
12223
  this.status = 'Audio file loaded.';
10635
12224
  let clip = null;
10636
12225
  this.recordingFile = value;
10637
12226
  if (this.recordingFile) {
10638
- let ab = this.recordingFile.audioBuffer;
12227
+ let ab = this.recordingFile.audioDataHolder;
10639
12228
  if (ab) {
10640
12229
  clip = new AudioClip(ab);
10641
12230
  let esffsr = null;
10642
12231
  let eeffsr = null;
10643
12232
  let esr = null;
10644
- if (clip.buffer != null) {
12233
+ if (clip.audioDataHolder != null) {
10645
12234
  esr = ab.sampleRate;
10646
12235
  if (esr != null) {
10647
12236
  esffsr = RecordingFileUtil.editStartFrameForSampleRate(this.recordingFile, esr);
@@ -10653,8 +12242,8 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
10653
12242
  sel = new Selection(ab.sampleRate, esffsr, eeffsr);
10654
12243
  }
10655
12244
  else {
10656
- let ch0 = ab.getChannelData(0);
10657
- let frameLength = ch0.length;
12245
+ //let ch0 = ab.getChannelData(0);
12246
+ let frameLength = ab.frameLen;
10658
12247
  sel = new Selection(esr, esffsr, frameLength);
10659
12248
  }
10660
12249
  }
@@ -10668,6 +12257,7 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
10668
12257
  this.audioClip = clip;
10669
12258
  this.loadedRecfile();
10670
12259
  }, error1 => {
12260
+ this.audioFetching = false;
10671
12261
  this.status = 'Error loading audio file!';
10672
12262
  });
10673
12263
  }
@@ -10777,7 +12367,7 @@ class RecordingFileViewComponent extends AudioDisplayPlayer {
10777
12367
  for (let avRfV of avRf) {
10778
12368
  os += avRfV.version + '/';
10779
12369
  }
10780
- console.debug(os);
12370
+ //console.debug(os);
10781
12371
  }
10782
12372
  }
10783
12373
  this.updatePos();
@@ -10793,7 +12383,7 @@ RecordingFileViewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0
10793
12383
 
10794
12384
  <audio-display-scroll-pane #audioDisplayScrollPane></audio-display-scroll-pane>
10795
12385
  <div class="ctrlview">
10796
- <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile"></app-recording-file-meta>
12386
+ <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile" [stateLoading]="audioFetching"></app-recording-file-meta>
10797
12387
 
10798
12388
  <audio-display-control [audioClip]="audioClip"
10799
12389
  [playStartAction]="playStartAction"
@@ -10806,7 +12396,7 @@ RecordingFileViewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0
10806
12396
  [zoomFitToPanelAction]="zoomFitToPanelAction"></audio-display-control>
10807
12397
  <app-recording-file-navi [items]="availRecFiles?.length" [itemPos]="posInList" [version]="recordingFileVersion" [versions]="versions" [firstAction]="firstAction" [prevAction]="prevAction" [nextAction]="nextAction" [lastAction]="lastAction" [selectVersion]="toVersionAction" [naviInfoLoading]="naviInfoLoading"></app-recording-file-navi>
10808
12398
  </div>
10809
- `, isInline: true, styles: [":host{flex:2;display:flex;flex-direction:column;min-height:0;overflow:hidden;padding:20px;z-index:5;box-sizing:border-box;background-color:#fff}\n", ".ctrlview{display:flex;flex-direction:row}\n", "audio-display-control{flex:3}\n"], components: [{ type: AudioDisplayScrollPane, selector: "audio-display-scroll-pane", inputs: ["audioClip"], outputs: ["zoomInAction", "zoomOutAction", "zoomSelectedAction", "zoomFitToPanelAction"] }, { type: RecordingFileMetaComponent, selector: "app-recording-file-meta", inputs: ["sessionId", "recordingFile"] }, { type: AudioDisplayControl, selector: "audio-display-control", inputs: ["audioClip", "playStartAction", "playSelectionAction", "playStopAction", "zoomInAction", "zoomOutAction", "zoomFitToPanelAction", "zoomSelectedAction", "autoPlayOnSelectToggleAction"] }, { type: RecordingFileNaviComponent, selector: "app-recording-file-navi", inputs: ["firstAction", "prevAction", "nextAction", "lastAction", "items", "itemPos", "selectVersion", "versions", "version", "naviInfoLoading"] }] });
12399
+ `, isInline: true, styles: [":host{flex:2;display:flex;flex-direction:column;min-height:0;overflow:hidden;padding:20px;z-index:5;box-sizing:border-box;background-color:#fff}\n", ".ctrlview{display:flex;flex-direction:row}\n", "audio-display-control{flex:3}\n"], components: [{ type: AudioDisplayScrollPane, selector: "audio-display-scroll-pane", inputs: ["audioClip"], outputs: ["zoomInAction", "zoomOutAction", "zoomSelectedAction", "zoomFitToPanelAction"] }, { type: RecordingFileMetaComponent, selector: "app-recording-file-meta", inputs: ["sessionId", "stateLoading", "recordingFile"] }, { type: AudioDisplayControl, selector: "audio-display-control", inputs: ["audioClip", "playStartAction", "playSelectionAction", "playStopAction", "zoomInAction", "zoomOutAction", "zoomFitToPanelAction", "zoomSelectedAction", "autoPlayOnSelectToggleAction"] }, { type: RecordingFileNaviComponent, selector: "app-recording-file-navi", inputs: ["firstAction", "prevAction", "nextAction", "lastAction", "items", "itemPos", "selectVersion", "versions", "version", "naviInfoLoading"] }] });
10810
12400
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: RecordingFileViewComponent, decorators: [{
10811
12401
  type: Component,
10812
12402
  args: [{
@@ -10815,7 +12405,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
10815
12405
 
10816
12406
  <audio-display-scroll-pane #audioDisplayScrollPane></audio-display-scroll-pane>
10817
12407
  <div class="ctrlview">
10818
- <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile"></app-recording-file-meta>
12408
+ <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile" [stateLoading]="audioFetching"></app-recording-file-meta>
10819
12409
 
10820
12410
  <audio-display-control [audioClip]="audioClip"
10821
12411
  [playStartAction]="playStartAction"
@@ -10908,7 +12498,7 @@ class RecordingFileUI extends RecordingFileViewComponent {
10908
12498
  }
10909
12499
  applySelection() {
10910
12500
  if (this.audioClip) {
10911
- let ab = this.audioClip.buffer;
12501
+ let ab = this.audioClip.audioDataHolder;
10912
12502
  let s = this.audioClip.selection;
10913
12503
  if (ab && this.recordingFile?.recordingFileId) {
10914
12504
  let sr = null;
@@ -10946,7 +12536,7 @@ RecordingFileUI.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", versio
10946
12536
 
10947
12537
  <audio-display-scroll-pane #audioDisplayScrollPane></audio-display-scroll-pane>
10948
12538
  <div class="ctrlview">
10949
- <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile"></app-recording-file-meta>
12539
+ <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile" [stateLoading]="audioFetching"></app-recording-file-meta>
10950
12540
  <audio-display-control [audioClip]="audioClip"
10951
12541
  [playStartAction]="playStartAction"
10952
12542
  [playSelectionAction]="playSelectionAction"
@@ -10960,7 +12550,7 @@ RecordingFileUI.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", versio
10960
12550
  </div>
10961
12551
 
10962
12552
  <button mat-raised-button color="accent" (click)="applySelection()" [disabled]="editSaved">{{this.applyButtonText()}}</button>
10963
- `, isInline: true, styles: [":host{flex:2;display:flex;flex-direction:column;min-height:0;overflow:hidden;padding:20px;z-index:5;box-sizing:border-box;background-color:#fff}\n", ".ctrlview{display:flex;flex-direction:row}\n", "audio-display-control{flex:3}\n"], components: [{ type: AudioDisplayScrollPane, selector: "audio-display-scroll-pane", inputs: ["audioClip"], outputs: ["zoomInAction", "zoomOutAction", "zoomSelectedAction", "zoomFitToPanelAction"] }, { type: RecordingFileMetaComponent, selector: "app-recording-file-meta", inputs: ["sessionId", "recordingFile"] }, { type: AudioDisplayControl, selector: "audio-display-control", inputs: ["audioClip", "playStartAction", "playSelectionAction", "playStopAction", "zoomInAction", "zoomOutAction", "zoomFitToPanelAction", "zoomSelectedAction", "autoPlayOnSelectToggleAction"] }, { type: RecordingFileNaviComponent, selector: "app-recording-file-navi", inputs: ["firstAction", "prevAction", "nextAction", "lastAction", "items", "itemPos", "selectVersion", "versions", "version", "naviInfoLoading"] }, { type: i3$2.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }] });
12553
+ `, isInline: true, styles: [":host{flex:2;display:flex;flex-direction:column;min-height:0;overflow:hidden;padding:20px;z-index:5;box-sizing:border-box;background-color:#fff}\n", ".ctrlview{display:flex;flex-direction:row}\n", "audio-display-control{flex:3}\n"], components: [{ type: AudioDisplayScrollPane, selector: "audio-display-scroll-pane", inputs: ["audioClip"], outputs: ["zoomInAction", "zoomOutAction", "zoomSelectedAction", "zoomFitToPanelAction"] }, { type: RecordingFileMetaComponent, selector: "app-recording-file-meta", inputs: ["sessionId", "stateLoading", "recordingFile"] }, { type: AudioDisplayControl, selector: "audio-display-control", inputs: ["audioClip", "playStartAction", "playSelectionAction", "playStopAction", "zoomInAction", "zoomOutAction", "zoomFitToPanelAction", "zoomSelectedAction", "autoPlayOnSelectToggleAction"] }, { type: RecordingFileNaviComponent, selector: "app-recording-file-navi", inputs: ["firstAction", "prevAction", "nextAction", "lastAction", "items", "itemPos", "selectVersion", "versions", "version", "naviInfoLoading"] }, { type: i3$2.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }] });
10964
12554
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: RecordingFileUI, decorators: [{
10965
12555
  type: Component,
10966
12556
  args: [{
@@ -10971,7 +12561,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
10971
12561
 
10972
12562
  <audio-display-scroll-pane #audioDisplayScrollPane></audio-display-scroll-pane>
10973
12563
  <div class="ctrlview">
10974
- <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile"></app-recording-file-meta>
12564
+ <app-recording-file-meta [sessionId]="sessionId" [recordingFile]="recordingFile" [stateLoading]="audioFetching"></app-recording-file-meta>
10975
12565
  <audio-display-control [audioClip]="audioClip"
10976
12566
  [playStartAction]="playStartAction"
10977
12567
  [playSelectionAction]="playSelectionAction"
@@ -11028,7 +12618,8 @@ class MediaUtils {
11028
12618
 
11029
12619
  class RecordingList {
11030
12620
  constructor() {
11031
- this.recordingList = new Array();
12621
+ //private recordingList:Array<RecordingFile>=new Array<RecordingFile>();
12622
+ this.recordingList = new RecFilesCache();
11032
12623
  //cols=['index','length','samples','samplerate','action'];
11033
12624
  this.cols = ['index', 'startedDate', 'length', 'action'];
11034
12625
  this.selectDisabled = false;
@@ -11040,7 +12631,7 @@ class RecordingList {
11040
12631
  this.buildDataSource();
11041
12632
  }
11042
12633
  buildDataSource() {
11043
- this.recordingList.sort((a, b) => {
12634
+ this.recordingList.recFiles.sort((a, b) => {
11044
12635
  let cmp = 0;
11045
12636
  let aD = null;
11046
12637
  let bD = null;
@@ -11061,24 +12652,35 @@ class RecordingList {
11061
12652
  }
11062
12653
  return cmp;
11063
12654
  });
11064
- this.recordingListDataSource.data = this.recordingList;
12655
+ this.recordingListDataSource.data = this.recordingList.recFiles;
11065
12656
  }
11066
- push(rf) {
11067
- this.recordingList.push(rf);
12657
+ addRecFile(rf) {
12658
+ this.recordingList.addRecFile(rf);
11068
12659
  this.buildDataSource();
11069
12660
  }
12661
+ setRecFileAudioData(recFile, adh) {
12662
+ this.recordingList.setRecFileAudioData(recFile, adh);
12663
+ }
11070
12664
  selectRecordingFile(rf) {
12665
+ this.recordingList.currentRecordingFile = rf;
11071
12666
  this.selectedRecordingFileChanged.emit(rf);
11072
12667
  }
11073
12668
  selectTop() {
11074
- if (this.recordingList.length > 0) {
11075
- this.selectRecordingFile(this.recordingList[0]);
12669
+ if (this.recordingList.recFiles.length > 0) {
12670
+ this.selectRecordingFile(this.recordingList.recFiles[0]);
11076
12671
  }
11077
12672
  }
11078
12673
  lengthTimeFormatted(rf) {
11079
12674
  let str = '--:--:--';
11080
- if (rf.frames && rf.audioBuffer) {
11081
- str = MediaUtils.toMediaTime(rf.frames / rf.audioBuffer?.sampleRate);
12675
+ let tl = null;
12676
+ if (rf.timeLength) {
12677
+ tl = rf.timeLength;
12678
+ }
12679
+ else if (rf.frames && rf.audioDataHolder) {
12680
+ tl = rf.frames / rf.audioDataHolder?.sampleRate;
12681
+ }
12682
+ if (tl) {
12683
+ str = MediaUtils.toMediaTime(tl);
11082
12684
  }
11083
12685
  return str;
11084
12686
  }
@@ -11164,8 +12766,11 @@ class RecorderCombiPane {
11164
12766
  }
11165
12767
  ngAfterViewInit() {
11166
12768
  }
11167
- push(rf) {
11168
- this.recordingListComp.push(rf);
12769
+ addRecFile(rf) {
12770
+ this.recordingListComp.addRecFile(rf);
12771
+ }
12772
+ setRecFileAudioData(recFile, adh) {
12773
+ this.recordingListComp.setRecFileAudioData(recFile, adh);
11169
12774
  }
11170
12775
  selectRecordingFile(rf) {
11171
12776
  this.selectedRecordingFileChanged.emit(rf);
@@ -11238,15 +12843,11 @@ class Item {
11238
12843
  }
11239
12844
  class AudioRecorder extends BasicRecorder {
11240
12845
  constructor(changeDetectorRef, renderer, route, dialog, sessionService, recFileService, uploader, config) {
11241
- super(dialog, sessionService, uploader, config);
11242
- this.changeDetectorRef = changeDetectorRef;
12846
+ super(changeDetectorRef, dialog, sessionService, uploader, config);
11243
12847
  this.renderer = renderer;
11244
12848
  this.route = route;
11245
- this.dialog = dialog;
11246
- this.sessionService = sessionService;
11247
12849
  this.recFileService = recFileService;
11248
12850
  this.uploader = uploader;
11249
- this.config = config;
11250
12851
  this._project = null;
11251
12852
  this.projectName = null;
11252
12853
  this.enableUploadRecordings = true;
@@ -11299,7 +12900,7 @@ class AudioRecorder extends BasicRecorder {
11299
12900
  context = AudioContextProvider.audioContextInstance();
11300
12901
  }
11301
12902
  catch (err) {
11302
- this.status = 4 /* ERROR */;
12903
+ this.status = 5 /* ERROR */;
11303
12904
  let errMsg = 'Unknown error';
11304
12905
  if (err instanceof Error) {
11305
12906
  errMsg = err.message;
@@ -11323,7 +12924,7 @@ class AudioRecorder extends BasicRecorder {
11323
12924
  console.info("No audio context available!");
11324
12925
  }
11325
12926
  if (!context || !navigator.mediaDevices) {
11326
- this.status = 4 /* ERROR */;
12927
+ this.status = 5 /* ERROR */;
11327
12928
  let errMsg = 'Browser does not support Media streams!';
11328
12929
  this.statusMsg = 'ERROR: ' + errMsg;
11329
12930
  this.statusAlertType = 'error';
@@ -11429,13 +13030,15 @@ class AudioRecorder extends BasicRecorder {
11429
13030
  if (rfs) {
11430
13031
  if (rfs instanceof Array) {
11431
13032
  rfs.forEach((rf) => {
13033
+ // the list comes from the server, asssuem all recording files as server persisted
13034
+ rf.serverPersisted = true;
11432
13035
  if (rf.startedDate) {
11433
13036
  rf._startedAsDateObj = new Date(rf.startedDate);
11434
13037
  }
11435
13038
  if (rf.date) {
11436
13039
  rf._dateAsDateObj = new Date(rf.date);
11437
13040
  }
11438
- this.recorderCombiPane.push(rf);
13041
+ this.recorderCombiPane.addRecFile(rf);
11439
13042
  });
11440
13043
  }
11441
13044
  else {
@@ -11489,6 +13092,7 @@ class AudioRecorder extends BasicRecorder {
11489
13092
  return this._project;
11490
13093
  }
11491
13094
  selectRecordingFile(rf) {
13095
+ this.audioFetching = false;
11492
13096
  this.displayRecFile = rf;
11493
13097
  }
11494
13098
  uploadUpdate(ue) {
@@ -11510,9 +13114,6 @@ class AudioRecorder extends BasicRecorder {
11510
13114
  this.changeDetectorRef.detectChanges();
11511
13115
  }
11512
13116
  set controlAudioPlayer(controlAudioPlayer) {
11513
- if (this._controlAudioPlayer) {
11514
- //this._controlAudioPlayer.listener=null;
11515
- }
11516
13117
  this._controlAudioPlayer = controlAudioPlayer;
11517
13118
  if (this._controlAudioPlayer) {
11518
13119
  this._controlAudioPlayer.listener = this;
@@ -11578,7 +13179,12 @@ class AudioRecorder extends BasicRecorder {
11578
13179
  }
11579
13180
  }
11580
13181
  startItem() {
13182
+ this.status = 2 /* STARTING */;
11581
13183
  super.startItem();
13184
+ if (this.readonly) {
13185
+ this.status = 1 /* IDLE */;
13186
+ return;
13187
+ }
11582
13188
  this.transportActions.fwdAction.disabled = true;
11583
13189
  this.transportActions.fwdNextAction.disabled = true;
11584
13190
  this.transportActions.bwdAction.disabled = true;
@@ -11587,6 +13193,7 @@ class AudioRecorder extends BasicRecorder {
11587
13193
  this.displayAudioClip = null;
11588
13194
  this.showRecording();
11589
13195
  if (this.ac) {
13196
+ this.audioFetching = false;
11590
13197
  if (!this.ac.opened) {
11591
13198
  if (this._selectedDeviceId) {
11592
13199
  console.log("Open session with audio device Id: \'" + this._selectedDeviceId + "\' for " + this._channelCount + " channels");
@@ -11603,10 +13210,10 @@ class AudioRecorder extends BasicRecorder {
11603
13210
  }
11604
13211
  downloadRecording() {
11605
13212
  if (this.displayRecFile) {
11606
- let ab = this.displayRecFile.audioBuffer;
13213
+ let ab = this.displayRecFile.audioDataHolder;
11607
13214
  let ww = new WavWriter();
11608
- if (ab) {
11609
- let wavFile = ww.writeAsync(ab, (wavFile) => {
13215
+ if (ab?.buffer) {
13216
+ let wavFile = ww.writeAsync(ab.buffer, (wavFile) => {
11610
13217
  let blob = new Blob([wavFile], { type: 'audio/wav' });
11611
13218
  let rfUrl = URL.createObjectURL(blob);
11612
13219
  let dataDnlLnk = this.renderer.createElement('a');
@@ -11629,35 +13236,50 @@ class AudioRecorder extends BasicRecorder {
11629
13236
  set displayRecFile(displayRecFile) {
11630
13237
  this._displayRecFile = displayRecFile;
11631
13238
  if (this._displayRecFile) {
11632
- let ab = this._displayRecFile.audioBuffer;
11633
- if (ab) {
11634
- this.displayAudioClip = new AudioClip(ab);
13239
+ let adh = this._displayRecFile.audioDataHolder;
13240
+ if (adh) {
13241
+ this.displayAudioClip = new AudioClip(adh);
13242
+ console.debug(" set recording file: display audio clip set");
11635
13243
  this.controlAudioPlayer.audioClip = this.displayAudioClip;
11636
13244
  }
11637
13245
  else {
11638
13246
  // clear for now ...
11639
13247
  this.displayAudioClip = null;
13248
+ console.debug("set recording file: display audio clip null");
11640
13249
  this.controlAudioPlayer.audioClip = null;
11641
13250
  if (this._controlAudioPlayer && this._session) {
11642
13251
  //... and try to fetch from server
11643
- this.audioFetchSubscription = this.recFileService.fetchAndApplyRecordingFile(this._controlAudioPlayer.context, this._session.project, this._displayRecFile).subscribe((rf) => {
11644
- let fab = null;
11645
- if (rf && this._displayRecFile) {
11646
- fab = this._displayRecFile.audioBuffer;
11647
- }
11648
- else {
11649
- this.statusMsg = 'Recording file could not be loaded.';
13252
+ this.audioFetching = true;
13253
+ let rf = this._displayRecFile;
13254
+ let clip = this.displayAudioClip;
13255
+ this.audioFetchSubscription = this.recFileService.fetchRecordingFileAudioBuffer(this._controlAudioPlayer.context, this._session.project, rf).subscribe({
13256
+ next: ab => {
13257
+ this.audioFetching = false;
13258
+ let fabDh = null;
13259
+ if (ab) {
13260
+ if (rf) {
13261
+ fabDh = new AudioDataHolder(ab);
13262
+ this.recorderCombiPane.setRecFileAudioData(rf, fabDh);
13263
+ }
13264
+ }
13265
+ else {
13266
+ console.error('Recording file could not be loaded.');
13267
+ this.statusMsg = 'Recording file could not be loaded.';
13268
+ this.statusAlertType = 'error';
13269
+ }
13270
+ if (fabDh) {
13271
+ // this.displayAudioClip could have been changed meanwhile, but the recorder unsubcribes before changing the item. Therefore there should be no risk to set to wrong item
13272
+ this.displayAudioClip = new AudioClip(fabDh);
13273
+ //console.debug("set recording file: display audio clip from fetched audio buffer");
13274
+ }
13275
+ this.controlAudioPlayer.audioClip = this.displayAudioClip;
13276
+ this.showRecording();
13277
+ }, error: err => {
13278
+ console.error("Could not load recording file from server: " + err);
13279
+ this.audioFetching = false;
13280
+ this.statusMsg = 'Recording file could not be loaded: ' + err;
11650
13281
  this.statusAlertType = 'error';
11651
13282
  }
11652
- if (fab) {
11653
- this.displayAudioClip = new AudioClip(fab);
11654
- }
11655
- this.controlAudioPlayer.audioClip = this.displayAudioClip;
11656
- this.showRecording();
11657
- }, err => {
11658
- console.error("Could not load recording file from server: " + err);
11659
- this.statusMsg = 'Recording file could not be loaded: ' + err;
11660
- this.statusAlertType = 'error';
11661
13283
  });
11662
13284
  }
11663
13285
  else {
@@ -11667,6 +13289,7 @@ class AudioRecorder extends BasicRecorder {
11667
13289
  }
11668
13290
  }
11669
13291
  else {
13292
+ console.debug("recording file null");
11670
13293
  this.displayAudioClip = null;
11671
13294
  this.controlAudioPlayer.audioClip = null;
11672
13295
  }
@@ -11675,28 +13298,6 @@ class AudioRecorder extends BasicRecorder {
11675
13298
  get displayRecFile() {
11676
13299
  return this._displayRecFile;
11677
13300
  }
11678
- showRecording() {
11679
- this.controlAudioPlayer.stop();
11680
- if (this.displayAudioClip) {
11681
- this.levelMeasure.calcBufferLevelInfos(this.displayAudioClip.buffer, LEVEL_BAR_INTERVALL_SECONDS).then((levelInfos) => {
11682
- this.displayLevelInfos = levelInfos;
11683
- this.changeDetectorRef.detectChanges();
11684
- });
11685
- this.playStartAction.disabled = false;
11686
- }
11687
- else {
11688
- // TODO
11689
- // Setting to null does not trigger a change if it was null before (happens after nextitem() in AUTOPROGRESS mode)
11690
- // The level bar display does not clear, it shows the last captured stream
11691
- this.displayLevelInfos = null;
11692
- this.playStartAction.disabled = true;
11693
- // Collapse audio signal display if open
11694
- if (!this.audioSignalCollapsed) {
11695
- this.audioSignalCollapsed = true;
11696
- }
11697
- }
11698
- this.changeDetectorRef.detectChanges();
11699
- }
11700
13301
  updateStartActionDisableState() {
11701
13302
  this.transportActions.startAction.disabled = !(this.ac);
11702
13303
  }
@@ -11707,10 +13308,10 @@ class AudioRecorder extends BasicRecorder {
11707
13308
  this.updateStartActionDisableState();
11708
13309
  }
11709
13310
  isRecording() {
11710
- return (this.status === 2 /* RECORDING */);
13311
+ return (this.status === 3 /* RECORDING */);
11711
13312
  }
11712
13313
  isActive() {
11713
- return (!(this.status === 0 /* BLOCKED */ || this.status === 1 /* IDLE */ || this.status === 4 /* ERROR */) || this.processingRecording || this.sessionService.uploadCount > 0);
13314
+ return (!(this.status === 0 /* BLOCKED */ || this.status === 1 /* IDLE */ || this.status === 5 /* ERROR */) || this.processingRecording || this.sessionService.uploadCount > 0);
11714
13315
  }
11715
13316
  updateWakeLock(dataSaved = this.dataSaved) {
11716
13317
  //console.debug("Update wake lock: dataSaved: "+dataSaved+", not active: "+! this.isActive())
@@ -11736,10 +13337,23 @@ class AudioRecorder extends BasicRecorder {
11736
13337
  }
11737
13338
  }
11738
13339
  started() {
11739
- this.status = 2 /* RECORDING */;
13340
+ this.status = 3 /* RECORDING */;
11740
13341
  super.started();
11741
13342
  this.statusAlertType = 'info';
11742
13343
  this.statusMsg = 'Recording...';
13344
+ if (!this.rfUuid) {
13345
+ this.rfUuid = UUID.generate();
13346
+ }
13347
+ let sessId = 0;
13348
+ if (this._session) {
13349
+ sessId = this._session.sessionId;
13350
+ }
13351
+ let rf = new RecordingFile(this.rfUuid, sessId, null);
13352
+ rf._startedAsDateObj = this.startedDate;
13353
+ if (rf._startedAsDateObj) {
13354
+ rf.startedDate = rf._startedAsDateObj.toString();
13355
+ }
13356
+ this._recordingFile = rf;
11743
13357
  let maxRecordingTimeMs = MAX_RECORDING_TIME_MS;
11744
13358
  this.maxRecTimerId = window.setTimeout(() => {
11745
13359
  this.stopRecordingMaxRec();
@@ -11750,7 +13364,7 @@ class AudioRecorder extends BasicRecorder {
11750
13364
  stopItem() {
11751
13365
  this.transportActions.stopAction.disabled = true;
11752
13366
  this.transportActions.nextAction.disabled = true;
11753
- this.status = 3 /* STOPPING_STOP */;
13367
+ this.status = 4 /* STOPPING_STOP */;
11754
13368
  this.stopRecording();
11755
13369
  }
11756
13370
  stopRecording() {
@@ -11764,7 +13378,7 @@ class AudioRecorder extends BasicRecorder {
11764
13378
  }
11765
13379
  stopRecordingMaxRec() {
11766
13380
  this.maxRecTimerRunning = false;
11767
- this.status = 3 /* STOPPING_STOP */;
13381
+ this.status = 4 /* STOPPING_STOP */;
11768
13382
  if (this.ac) {
11769
13383
  this.ac.stop();
11770
13384
  }
@@ -11776,72 +13390,75 @@ class AudioRecorder extends BasicRecorder {
11776
13390
  this.transportActions.pauseAction.disabled = true;
11777
13391
  this.statusAlertType = 'info';
11778
13392
  this.statusMsg = 'Recorded.';
11779
- let ad;
13393
+ let ad = null;
13394
+ let ada = null;
13395
+ let adh = null;
13396
+ let frameLen = 0;
11780
13397
  if (this.ac) {
11781
- ad = this.ac.audioBuffer();
13398
+ if (this.uploadChunkSizeSeconds || AudioRecorder.FORCE_ARRRAY_AUDIO_BUFFER) {
13399
+ ada = this.ac.audioBufferArray();
13400
+ frameLen = ada.frameLen;
13401
+ }
13402
+ else {
13403
+ ad = this.ac.audioBuffer();
13404
+ frameLen = ad.length;
13405
+ }
13406
+ adh = new AudioDataHolder(ad, ada);
11782
13407
  let sessId = 0;
11783
13408
  if (this._session) {
11784
13409
  sessId = this._session.sessionId;
11785
13410
  }
11786
- if (!this.rfUuid) {
11787
- this.rfUuid = UUID.generate();
11788
- }
11789
- let rf = new RecordingFile(this.rfUuid, sessId, ad);
11790
- rf._startedAsDateObj = this.startedDate;
11791
- if (rf._startedAsDateObj) {
11792
- rf.startedDate = rf._startedAsDateObj.toString();
11793
- }
11794
- rf.frames = ad.length;
11795
- this.displayRecFile = rf;
11796
- this.recorderCombiPane.push(rf);
11797
- // Upload if upload enabled and not in chunked upload mode
11798
- if (this.enableUploadRecordings && !this.uploadChunkSizeSeconds) {
11799
- let apiEndPoint = '';
11800
- if (this.config && this.config.apiEndPoint) {
11801
- apiEndPoint = this.config.apiEndPoint;
11802
- }
11803
- if (apiEndPoint !== '') {
11804
- apiEndPoint = apiEndPoint + '/';
13411
+ if (this._recordingFile) {
13412
+ // Use an own reference since the writing of the wave file is asynchronous and this._recordingFile might already contain the next recording
13413
+ let rf = this._recordingFile;
13414
+ RecordingFileUtils.setAudioData(rf, adh);
13415
+ this.recorderCombiPane.addRecFile(rf);
13416
+ // Upload if upload enabled and not in chunked upload mode
13417
+ if (this.enableUploadRecordings && !this.uploadChunkSizeSeconds && rf != null && ad != null) {
13418
+ let apiEndPoint = '';
13419
+ if (this.config && this.config.apiEndPoint) {
13420
+ apiEndPoint = this.config.apiEndPoint;
13421
+ }
13422
+ if (apiEndPoint !== '') {
13423
+ apiEndPoint = apiEndPoint + '/';
13424
+ }
13425
+ let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
13426
+ let recUrl = sessionsUrl + '/' + rf.session + '/' + RECFILE_API_CTX + '/' + rf.uuid;
13427
+ // convert asynchronously to 16-bit integer PCM
13428
+ // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
13429
+ // TODO duplicate conversion for manual download
13430
+ this.processingRecording = true;
13431
+ let ww = new WavWriter();
13432
+ ww.writeAsync(ad, (wavFile) => {
13433
+ this.postRecordingMultipart(wavFile, recUrl, rf);
13434
+ this.processingRecording = false;
13435
+ this.updateWakeLock();
13436
+ this.changeDetectorRef.detectChanges();
13437
+ });
11805
13438
  }
11806
- let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
11807
- let recUrl = sessionsUrl + '/' + rf.session + '/' + RECFILE_API_CTX + '/' + rf.uuid;
11808
- // convert asynchronously to 16-bit integer PCM
11809
- // TODO could we avoid conversion to save CPU resources and transfer float PCM directly?
11810
- // TODO duplicate conversion for manual download
11811
- this.processingRecording = true;
11812
- let ww = new WavWriter();
11813
- ww.writeAsync(ad, (wavFile) => {
11814
- this.postRecordingMultipart(wavFile, rf.uuid, rf.session, rf._startedAsDateObj, recUrl);
11815
- this.processingRecording = false;
11816
- this.updateWakeLock();
11817
- this.changeDetectorRef.detectChanges();
11818
- });
11819
13439
  }
11820
13440
  }
11821
- // check complete session
11822
- let complete = true;
11823
- let autoStart = (this.status === 3 /* STOPPING_STOP */);
13441
+ this.displayRecFile = this._recordingFile;
11824
13442
  this.status = 1 /* IDLE */;
11825
- let startNext = false;
11826
13443
  this.navigationDisabled = false;
11827
13444
  this.updateNavigationActions();
11828
13445
  this.updateWakeLock();
11829
13446
  this.changeDetectorRef.detectChanges();
11830
13447
  }
11831
- postRecordingMultipart(wavFile, uuid, sessionId, startedDate, recUrl) {
13448
+ postRecordingMultipart(wavFile, recUrl, rf) {
11832
13449
  let wavBlob = new Blob([wavFile], { type: 'audio/wav' });
11833
13450
  let fd = new FormData();
11834
- if (uuid) {
11835
- fd.set('uuid', uuid);
13451
+ if (rf.uuid) {
13452
+ fd.set('uuid', rf.uuid);
11836
13453
  }
11837
- if (sessionId !== null) {
11838
- fd.set('sessionId', sessionId.toString());
13454
+ if (rf.session !== null) {
13455
+ fd.set('sessionId', rf.session.toString());
11839
13456
  }
11840
- if (startedDate) {
11841
- fd.set('startedDate', startedDate.toJSON());
13457
+ if (rf._startedAsDateObj) {
13458
+ fd.set('startedDate', rf._startedAsDateObj.toJSON());
11842
13459
  }
11843
13460
  fd.set('audio', wavBlob);
11844
- let ul = new Upload(fd, recUrl);
13461
+ let ul = new Upload(fd, recUrl, rf);
11845
13462
  this.uploader.queueUpload(ul);
11846
13463
  }
11847
13464
  postChunkAudioBuffer(audioBuffer, chunkIdx) {
@@ -11857,8 +13474,9 @@ class AudioRecorder extends BasicRecorder {
11857
13474
  }
11858
13475
  let sessionsUrl = apiEndPoint + SessionService.SESSION_API_CTX;
11859
13476
  let recUrl = sessionsUrl + '/' + this.session?.sessionId + '/' + RECFILE_API_CTX + '/' + this.rfUuid + '/' + chunkIdx;
13477
+ let rf = this._recordingFile;
11860
13478
  ww.writeAsync(audioBuffer, (wavFile) => {
11861
- this.postRecording(wavFile, recUrl);
13479
+ this.postRecording(wavFile, recUrl, rf);
11862
13480
  this.processingRecording = false;
11863
13481
  });
11864
13482
  }
@@ -11907,11 +13525,11 @@ AudioRecorder.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version:
11907
13525
 
11908
13526
  <div fxLayout="row" fxLayout.xs="column" [ngStyle]="{'height.px':100,'min-height.px': 100}"
11909
13527
  [ngStyle.xs]="{'height.px':125,'min-height.px': 125}">
11910
- <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()"
11911
- [displayLevelInfos]="displayLevelInfos"></audio-levelbar>
13528
+ <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()" [stateLoading]="audioFetching"
13529
+ [displayLevelInfos]="displayAudioClip?.levelInfos"></audio-levelbar>
11912
13530
  <div fxLayout="row">
11913
13531
  <spr-recordingitemcontrols fxFlex="10 0 1"
11914
- [audioLoaded]="displayAudioClip?.buffer!==null"
13532
+ [audioLoaded]="displayAudioClip?.audioDataHolder!==null"
11915
13533
  [playStartAction]="controlAudioPlayer?.startAction"
11916
13534
  [playStopAction]="controlAudioPlayer?.stopAction"
11917
13535
  [peakDbLvl]="peakLevelInDb"
@@ -11950,7 +13568,7 @@ AudioRecorder.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version:
11950
13568
  [ready]="dataSaved && !isActive()"></app-readystateindicator>
11951
13569
  </div>
11952
13570
  </div>
11953
- `, isInline: true, styles: [":host{flex:2;background:lightgrey;display:flex;flex-direction:column;margin:0;padding:0;height:100%;min-height:0px;overflow:hidden}\n", ".ricontrols{padding:4px;box-sizing:border-box;height:100%}\n", ".dark{background:darkgray}\n", ".controlpanel{align-content:center;align-items:center;margin:0;padding:20px;min-height:min-content}\n", ".startstop{width:100%;text-align:center;align-content:center;align-items:center}\n", ".bigbutton{min-width:70px;min-height:50px;font-size:50px;border-radius:20px}\n"], components: [{ type: WarningBar, selector: "app-warningbar", inputs: ["warningText", "show"] }, { type: RecorderCombiPane, selector: "app-recordercombipane", inputs: ["selectDisabled", "selectedRecordingFile", "audioSignalCollapsed", "displayAudioClip", "playStartAction", "playSelectionAction", "autoPlayOnSelectToggleAction", "playStopAction"], outputs: ["selectedRecordingFileChanged"] }, { type: LevelBar, selector: "audio-levelbar", inputs: ["streamingMode", "displayLevelInfos"] }, { type: RecordingItemControls, selector: "spr-recordingitemcontrols", inputs: ["audioSignalCollapsed", "enableDownload", "peakDbLvl", "agc", "audioLoaded", "playStartAction", "playStopAction", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }, { type: UploadStatus, selector: "app-uploadstatus", inputs: ["value", "awaitNewUpload", "status"] }, { type: WakeLockIndicator, selector: "app-wakelockindicator", inputs: ["screenLocked"] }, { type: ReadyStateIndicator, selector: "app-readystateindicator", inputs: ["ready"] }, { type: StatusDisplay, selector: "app-sprstatusdisplay", inputs: ["statusAlertType", "statusMsg", "statusWaiting"] }, { type: i3$2.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i3$1.DefaultStyleDirective, selector: " [ngStyle], [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]", inputs: ["ngStyle", "ngStyle.xs", "ngStyle.sm", "ngStyle.md", "ngStyle.lg", "ngStyle.xl", "ngStyle.lt-sm", "ngStyle.lt-md", "ngStyle.lt-lg", "ngStyle.lt-xl", "ngStyle.gt-xs", "ngStyle.gt-sm", "ngStyle.gt-md", "ngStyle.gt-lg"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i8.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3$1.DefaultShowHideDirective, selector: " [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]", inputs: ["fxShow", "fxShow.print", "fxShow.xs", "fxShow.sm", "fxShow.md", "fxShow.lg", "fxShow.xl", "fxShow.lt-sm", "fxShow.lt-md", "fxShow.lt-lg", "fxShow.lt-xl", "fxShow.gt-xs", "fxShow.gt-sm", "fxShow.gt-md", "fxShow.gt-lg", "fxHide", "fxHide.print", "fxHide.xs", "fxHide.sm", "fxHide.md", "fxHide.lg", "fxHide.xl", "fxHide.lt-sm", "fxHide.lt-md", "fxHide.lt-lg", "fxHide.lt-xl", "fxHide.gt-xs", "fxHide.gt-sm", "fxHide.gt-md", "fxHide.gt-lg"] }] });
13571
+ `, isInline: true, styles: [":host{flex:2;background:lightgrey;display:flex;flex-direction:column;margin:0;padding:0;height:100%;min-height:0px;overflow:hidden}\n", ".ricontrols{padding:4px;box-sizing:border-box;height:100%}\n", ".dark{background:darkgray}\n", ".controlpanel{align-content:center;align-items:center;margin:0;padding:20px;min-height:min-content}\n", ".startstop{width:100%;text-align:center;align-content:center;align-items:center}\n", ".bigbutton{min-width:70px;min-height:50px;font-size:50px;border-radius:20px}\n"], components: [{ type: WarningBar, selector: "app-warningbar", inputs: ["warningText", "show"] }, { type: RecorderCombiPane, selector: "app-recordercombipane", inputs: ["selectDisabled", "selectedRecordingFile", "audioSignalCollapsed", "displayAudioClip", "playStartAction", "playSelectionAction", "autoPlayOnSelectToggleAction", "playStopAction"], outputs: ["selectedRecordingFileChanged"] }, { type: LevelBar, selector: "audio-levelbar", inputs: ["streamingMode", "displayLevelInfos", "stateLoading"] }, { type: RecordingItemControls, selector: "spr-recordingitemcontrols", inputs: ["audioSignalCollapsed", "enableDownload", "peakDbLvl", "agc", "audioLoaded", "playStartAction", "playStopAction", "displayLevelInfos"], outputs: ["onShowRecordingDetails", "onDownloadRecording"] }, { type: UploadStatus, selector: "app-uploadstatus", inputs: ["value", "awaitNewUpload", "status"] }, { type: WakeLockIndicator, selector: "app-wakelockindicator", inputs: ["screenLocked"] }, { type: ReadyStateIndicator, selector: "app-readystateindicator", inputs: ["ready"] }, { type: StatusDisplay, selector: "app-sprstatusdisplay", inputs: ["statusAlertType", "statusMsg", "statusWaiting"] }, { type: i3$2.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i3$1.DefaultStyleDirective, selector: " [ngStyle], [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]", inputs: ["ngStyle", "ngStyle.xs", "ngStyle.sm", "ngStyle.md", "ngStyle.lg", "ngStyle.xl", "ngStyle.lt-sm", "ngStyle.lt-md", "ngStyle.lt-lg", "ngStyle.lt-xl", "ngStyle.gt-xs", "ngStyle.gt-sm", "ngStyle.gt-md", "ngStyle.gt-lg"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i8.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3$1.DefaultShowHideDirective, selector: " [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]", inputs: ["fxShow", "fxShow.print", "fxShow.xs", "fxShow.sm", "fxShow.md", "fxShow.lg", "fxShow.xl", "fxShow.lt-sm", "fxShow.lt-md", "fxShow.lt-lg", "fxShow.lt-xl", "fxShow.gt-xs", "fxShow.gt-sm", "fxShow.gt-md", "fxShow.gt-lg", "fxHide", "fxHide.print", "fxHide.xs", "fxHide.sm", "fxHide.md", "fxHide.lg", "fxHide.xl", "fxHide.lt-sm", "fxHide.lt-md", "fxHide.lt-lg", "fxHide.lt-xl", "fxHide.gt-xs", "fxHide.gt-sm", "fxHide.gt-md", "fxHide.gt-lg"] }] });
11954
13572
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: AudioRecorder, decorators: [{
11955
13573
  type: Component,
11956
13574
  args: [{
@@ -11973,11 +13591,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
11973
13591
 
11974
13592
  <div fxLayout="row" fxLayout.xs="column" [ngStyle]="{'height.px':100,'min-height.px': 100}"
11975
13593
  [ngStyle.xs]="{'height.px':125,'min-height.px': 125}">
11976
- <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()"
11977
- [displayLevelInfos]="displayLevelInfos"></audio-levelbar>
13594
+ <audio-levelbar fxFlex="1 0 1" [streamingMode]="isRecording()" [stateLoading]="audioFetching"
13595
+ [displayLevelInfos]="displayAudioClip?.levelInfos"></audio-levelbar>
11978
13596
  <div fxLayout="row">
11979
13597
  <spr-recordingitemcontrols fxFlex="10 0 1"
11980
- [audioLoaded]="displayAudioClip?.buffer!==null"
13598
+ [audioLoaded]="displayAudioClip?.audioDataHolder!==null"
11981
13599
  [playStartAction]="controlAudioPlayer?.startAction"
11982
13600
  [playStopAction]="controlAudioPlayer?.stopAction"
11983
13601
  [peakDbLvl]="peakLevelInDb"
@@ -12255,20 +13873,20 @@ class SpeechrecorderngModule {
12255
13873
  }
12256
13874
  SpeechrecorderngModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: SpeechrecorderngModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
12257
13875
  SpeechrecorderngModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: SpeechrecorderngModule, declarations: [AudioSignal, Sonagram, ScrollPaneHorizontal, AudioClipUIContainer, AudioDisplayScrollPane, AudioDisplay, AudioDisplayPlayer, AudioDisplayControl, LevelBar, Progress, SimpleTrafficLight, Recinstructions, Prompter, PromptContainer, PromptingContainer, Prompting, StatusDisplay,
12258
- ProgressDisplay, RecordingItemDisplay, RecordingItemControls, UploadStatus, TransportPanel, WakeLockIndicator, ReadyStateIndicator, ControlPanel, WarningBar, AudioRecorder, SessionManager, MessageDialog, SessionFinishedDialog, SpeechrecorderngComponent, AudioRecorderComponent, RecordingFileViewComponent, RecordingFileUI, ScrollIntoViewDirective, RecordingFileNaviComponent, RecordingFileMetaComponent, RecordingList, RecorderCombiPane, AudioRecorder], imports: [i1$1.RouterModule, FlexLayoutModule, CommonModule, MatIconModule, MatButtonModule, MatDialogModule, MatProgressBarModule, MatProgressSpinnerModule, MatTooltipModule, HttpClientModule, MatCheckboxModule, MatCardModule, MatDividerModule, MatGridListModule, MatTableModule, MatInputModule, MatSelectModule, MatSnackBarModule], exports: [MessageDialog, SpeechrecorderngComponent, ScrollPaneHorizontal, AudioClipUIContainer, AudioDisplayScrollPane, AudioDisplay, AudioDisplayPlayer, AudioDisplayControl, LevelBar, AudioRecorder] });
12259
- SpeechrecorderngModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: SpeechrecorderngModule, providers: [SessionService, ProjectService, ScriptService, RecordingService, RecordingFileService, SpeechRecorderUploader], imports: [[RouterModule.forChild(SPR_ROUTES), FlexLayoutModule, CommonModule, MatIconModule, MatButtonModule, MatDialogModule, MatProgressBarModule, MatProgressSpinnerModule, MatTooltipModule, HttpClientModule, MatCheckboxModule, MatCardModule, MatDividerModule, MatGridListModule, MatTableModule, MatInputModule, MatSelectModule, MatSnackBarModule]] });
13876
+ ProgressDisplay, RecordingItemDisplay, RecordingItemControls, UploadStatus, TransportPanel, WakeLockIndicator, ReadyStateIndicator, ControlPanel, WarningBar, AudioRecorder, SessionManager, MessageDialog, SessionFinishedDialog, SpeechrecorderngComponent, AudioRecorderComponent, RecordingFileViewComponent, RecordingFileUI, ScrollIntoViewDirective, RecordingFileNaviComponent, RecordingFileMetaComponent, RecordingList, RecorderCombiPane, AudioRecorder], imports: [i1$1.RouterModule, FlexLayoutModule, CommonModule, MatIconModule, MatButtonModule, MatDialogModule, MatProgressBarModule, MatProgressSpinnerModule, MatTooltipModule, HttpClientModule, MatCheckboxModule, MatCardModule, MatDividerModule, MatGridListModule, MatTableModule, MatInputModule, MatSelectModule, MatSnackBarModule, MatMenuModule], exports: [MessageDialog, SpeechrecorderngComponent, ScrollPaneHorizontal, AudioClipUIContainer, AudioDisplayScrollPane, AudioDisplay, AudioDisplayPlayer, AudioDisplayControl, LevelBar, AudioRecorder] });
13877
+ SpeechrecorderngModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: SpeechrecorderngModule, providers: [SessionService, ProjectService, ScriptService, RecordingService, RecordingFileService, SpeechRecorderUploader], imports: [[RouterModule.forChild(SPR_ROUTES), FlexLayoutModule, CommonModule, MatIconModule, MatButtonModule, MatDialogModule, MatProgressBarModule, MatProgressSpinnerModule, MatTooltipModule, HttpClientModule, MatCheckboxModule, MatCardModule, MatDividerModule, MatGridListModule, MatTableModule, MatInputModule, MatSelectModule, MatSnackBarModule, MatMenuModule]] });
12260
13878
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: SpeechrecorderngModule, decorators: [{
12261
13879
  type: NgModule,
12262
13880
  args: [{
12263
13881
  declarations: [AudioSignal, Sonagram, ScrollPaneHorizontal, AudioClipUIContainer, AudioDisplayScrollPane, AudioDisplay, AudioDisplayPlayer, AudioDisplayControl, LevelBar, Progress, SimpleTrafficLight, Recinstructions, Prompter, PromptContainer, PromptingContainer, Prompting, StatusDisplay,
12264
13882
  ProgressDisplay, RecordingItemDisplay, RecordingItemControls, UploadStatus, TransportPanel, WakeLockIndicator, ReadyStateIndicator, ControlPanel, WarningBar, AudioRecorder, SessionManager, MessageDialog, SessionFinishedDialog, SpeechrecorderngComponent, AudioRecorderComponent, RecordingFileViewComponent, RecordingFileUI, ScrollIntoViewDirective, RecordingFileNaviComponent, RecordingFileMetaComponent, RecordingList, RecorderCombiPane, AudioRecorder],
12265
13883
  exports: [MessageDialog, SpeechrecorderngComponent, ScrollPaneHorizontal, AudioClipUIContainer, AudioDisplayScrollPane, AudioDisplay, AudioDisplayPlayer, AudioDisplayControl, LevelBar, AudioRecorder],
12266
- imports: [RouterModule.forChild(SPR_ROUTES), FlexLayoutModule, CommonModule, MatIconModule, MatButtonModule, MatDialogModule, MatProgressBarModule, MatProgressSpinnerModule, MatTooltipModule, HttpClientModule, MatCheckboxModule, MatCardModule, MatDividerModule, MatGridListModule, MatTableModule, MatInputModule, MatSelectModule, MatSnackBarModule],
13884
+ imports: [RouterModule.forChild(SPR_ROUTES), FlexLayoutModule, CommonModule, MatIconModule, MatButtonModule, MatDialogModule, MatProgressBarModule, MatProgressSpinnerModule, MatTooltipModule, HttpClientModule, MatCheckboxModule, MatCardModule, MatDividerModule, MatGridListModule, MatTableModule, MatInputModule, MatSelectModule, MatSnackBarModule, MatMenuModule],
12267
13885
  providers: [SessionService, ProjectService, ScriptService, RecordingService, RecordingFileService, SpeechRecorderUploader]
12268
13886
  }]
12269
13887
  }] });
12270
13888
 
12271
- const VERSION = '2.25.1';
13889
+ const VERSION = '3.0.0';
12272
13890
 
12273
13891
  /*
12274
13892
  * Public API Surface of speechrecorderng