@chat21/chat21-web-widget 5.0.90-rc.1 → 5.0.90-rc.2

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 (24) hide show
  1. package/CHANGELOG.md +8 -3
  2. package/package.json +1 -1
  3. package/src/app/app.module.ts +5 -2
  4. package/src/app/component/conversation-detail/conversation/conversation.component.html +2 -1
  5. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +31 -0
  6. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.scss +108 -0
  7. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +23 -0
  8. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +96 -0
  9. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +16 -5
  10. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +38 -11
  11. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +125 -42
  12. package/src/app/component/message/audio-track/audio-track.component.html +32 -0
  13. package/src/app/component/message/audio-track/audio-track.component.scss +107 -0
  14. package/src/app/component/message/{audio/audio.component.spec.ts → audio-track/audio-track.component.spec.ts} +6 -6
  15. package/src/app/component/message/audio-track/audio-track.component.ts +147 -0
  16. package/src/app/component/message/bubble-message/bubble-message.component.html +30 -5
  17. package/src/app/component/message/bubble-message/bubble-message.component.scss +7 -0
  18. package/src/app/component/message/bubble-message/bubble-message.component.ts +1 -0
  19. package/src/app/providers/global-settings.service.ts +32 -32
  20. package/src/app/utils/globals.ts +12 -6
  21. package/src/assets/twp/index-dev.html +26 -9
  22. package/src/app/component/message/audio/audio.component.html +0 -20
  23. package/src/app/component/message/audio/audio.component.scss +0 -122
  24. package/src/app/component/message/audio/audio.component.ts +0 -122
@@ -27,7 +27,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
27
27
  @Input() channelType: string;
28
28
  @Input() userFullname: string;
29
29
  @Input() userEmail: string;
30
- @Input() showAttachmentButton: boolean;
30
+ @Input() showAttachmentFooterButton: boolean;
31
+ @Input() showEmojiFooterButton: boolean
31
32
  // @Input() showContinueConversationButton: boolean;
32
33
  @Input() isConversationArchived: boolean;
33
34
  @Input() hideTextAreaContent: boolean;
@@ -60,6 +61,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
60
61
  // ========= end:: send image ========= //
61
62
 
62
63
  // isNewConversation = true;
64
+ isStartRec: boolean = false;
65
+ isStopRec: boolean = false;
63
66
  textInputTextArea: string;
64
67
  conversationHandlerService: ConversationHandlerService
65
68
 
@@ -99,7 +102,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
99
102
  }
100
103
 
101
104
  ngAfterViewInit() {
102
- this.logger.debug('[CONV-FOOTER] --------ngAfterViewInit: conversation-footer-------- ');
105
+ this.logger.log('[CONV-FOOTER] --------ngAfterViewInit: conversation-footer-------- ');
103
106
  // setTimeout(() => {
104
107
  this.showEmojiPicker = true
105
108
  // }, 500);
@@ -109,19 +112,18 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
109
112
  // START LOAD IMAGE //
110
113
  /** load the selected image locally and open the pop up preview */
111
114
  detectFiles(event) {
112
- this.logger.debug('[CONV-FOOTER] detectFiles: ', event);
113
-
115
+ this.logger.log('[CONV-FOOTER] detectFiles: ', event);
114
116
  if (event) {
115
117
  this.selectedFiles = event.target.files;
116
- this.logger.debug('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles', this.selectedFiles);
118
+ this.logger.log('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles', this.selectedFiles);
117
119
  // this.onAttachmentButtonClicked.emit(this.selectedFiles)
118
120
  if (this.selectedFiles == null) {
119
121
  this.isFilePendingToUpload = false;
120
122
  } else {
121
123
  this.isFilePendingToUpload = true;
122
124
  }
123
- this.logger.debug('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles::isFilePendingToUpload', this.isFilePendingToUpload);
124
- this.logger.debug('[CONV-FOOTER] fileChange: ', event.target.files);
125
+ this.logger.log('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles::isFilePendingToUpload', this.isFilePendingToUpload);
126
+ this.logger.log('[CONV-FOOTER] fileChange: ', event.target.files);
125
127
  if (event.target.files.length <= 0) {
126
128
  this.isFilePendingToUpload = false;
127
129
  } else {
@@ -184,16 +186,18 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
184
186
  }
185
187
  }
186
188
 
189
+
190
+
187
191
 
188
192
  loadFile() {
189
- this.logger.debug('[CONV-FOOTER] that.fileXLoad: ', this.arrayFilesLoad);
193
+ this.logger.log('[CONV-FOOTER] that.fileXLoad: ', this.arrayFilesLoad);
190
194
  // at the moment I only manage the upload of one image at a time
191
195
  if (this.arrayFilesLoad[0] && this.arrayFilesLoad[0].file) {
192
196
  const fileXLoad = this.arrayFilesLoad[0].file;
193
197
  const uid = this.arrayFilesLoad[0].uid;
194
198
  const type = this.arrayFilesLoad[0].type;
195
199
  const size = this.arrayFilesLoad[0].size
196
- this.logger.debug('[CONV-FOOTER] that.fileXLoad: ', type);
200
+ this.logger.log('[CONV-FOOTER] that.fileXLoad: ', type);
197
201
  let metadata;
198
202
  if (type.startsWith('image') && !type.includes('svg')) {
199
203
  metadata = {
@@ -214,7 +218,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
214
218
  'size': size
215
219
  };
216
220
  }
217
- this.logger.debug('[CONV-FOOTER] metadata -------> ', metadata);
221
+ this.logger.log('[CONV-FOOTER] metadata -------> ', metadata);
218
222
  // this.scrollToBottom();
219
223
  // 1 - aggiungo messaggio localmente
220
224
  // this.addLocalMessageImage(metadata);
@@ -231,17 +235,20 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
231
235
 
232
236
  uploadSingle(metadata, file, messageText?: string) {
233
237
  const that = this;
234
- const send_order_btn = <HTMLInputElement>document.getElementById('chat21-start-upload-doc');
235
- send_order_btn.disabled = true;
236
- that.logger.debug('[CONV-FOOTER] AppComponent::uploadSingle::', metadata, file);
238
+ try {
239
+ const send_order_btn = <HTMLInputElement>document.getElementById('chat21-start-upload-doc');
240
+ send_order_btn.disabled = true;
241
+ } catch (error) {
242
+ that.logger.log('[CONV-FOOTER] error::', error);
243
+ }
244
+ that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle::', metadata, file);
237
245
  // const file = this.selectedFiles.item(0);
238
246
  const currentUpload = new UploadModel(file);
239
-
240
247
  // const uploadTask = this.upSvc.pushUpload(currentUpload);
241
248
  // uploadTask.then(snapshot => {
242
249
  // return snapshot.ref.getDownloadURL(); // Will return a promise with the download link
243
250
  // }).then(downloadURL => {
244
- // that.logger.debug('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', downloadURL]);
251
+ // that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', downloadURL]);
245
252
  // that.g.wdLog([`Successfully uploaded file and got download link - ${downloadURL}`]);
246
253
 
247
254
  // metadata.src = downloadURL;
@@ -261,8 +268,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
261
268
  // this.resetLoadImage();
262
269
 
263
270
  this.uploadService.upload(this.senderId, currentUpload).then(data => {
264
- that.logger.debug('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', data);
265
- that.logger.debug(`[CONV-FOOTER] Successfully uploaded file and got download link - ${data}`);
271
+ that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', data);
272
+ that.logger.log(`[CONV-FOOTER] Successfully uploaded file and got download link - ${data}`);
266
273
 
267
274
  metadata.src = data.src;
268
275
  metadata.downloadURL = data.downloadURL;
@@ -291,7 +298,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
291
298
  that.logger.error(`[CONV-FOOTER] uploadSingle:: Failed to upload file and get link - ${error}`);
292
299
  that.isFilePendingToUpload = false;
293
300
  });
294
- that.logger.debug('[CONV-FOOTER] reader-result: ', file);
301
+ that.logger.log('[CONV-FOOTER] reader-result: ', file);
295
302
  }
296
303
 
297
304
  /**
@@ -304,7 +311,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
304
311
  sendMessage(msg: string, type: string, metadata?: any, additional_attributes?: any) { // sponziello
305
312
  (metadata) ? metadata = metadata : metadata = '';
306
313
  this.onEmojiiPickerShow.emit(false)
307
- this.logger.debug('[CONV-FOOTER] SEND MESSAGE: ', msg, type, metadata, additional_attributes);
314
+ this.logger.log('[CONV-FOOTER] SEND MESSAGE: ', msg, type, metadata, additional_attributes);
308
315
  if (msg && msg.trim() !== '' || type === TYPE_MSG_IMAGE || type === TYPE_MSG_FILE ) {
309
316
 
310
317
  // msg = htmlEntities(msg);
@@ -387,7 +394,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
387
394
  }
388
395
 
389
396
  private restoreTextArea() {
390
- // that.logger.debug('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea');
397
+ // that.logger.log('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea');
391
398
  this.resizeInputField();
392
399
  const textArea = (<HTMLInputElement>document.getElementById('chat21-main-message-context'));
393
400
  this.textInputTextArea = ''; // clear the textarea
@@ -397,7 +404,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
397
404
  if(textArea.style.height > this.HEIGHT_DEFAULT){
398
405
  document.getElementById('chat21-button-send').style.removeProperty('right')
399
406
  }
400
- this.logger.debug('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea::textArea:', 'restored');
407
+ this.logger.log('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea::textArea:', 'restored');
401
408
  }
402
409
  this.setFocusOnId('chat21-main-message-context');
403
410
  }
@@ -411,7 +418,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
411
418
  try {
412
419
  const target = document.getElementById('chat21-main-message-context') as HTMLInputElement;
413
420
  // tslint:disable-next-line:max-line-length
414
- // that.logger.debug('[CONV-FOOTER] H:: this.textInputTextArea', (document.getElementById('chat21-main-message-context') as HTMLInputElement).value , target.style.height, target.scrollHeight, target.offsetHeight, target.clientHeight);
421
+ // that.logger.log('[CONV-FOOTER] H:: this.textInputTextArea', (document.getElementById('chat21-main-message-context') as HTMLInputElement).value , target.style.height, target.scrollHeight, target.offsetHeight, target.clientHeight);
415
422
  target? target.style.height = '100%': null;
416
423
  if (target && target.value === '\n') {
417
424
  target.value = '';
@@ -431,28 +438,104 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
431
438
  }
432
439
  }
433
440
 
441
+
442
+
443
+ onStartRecording() {
444
+ this.isStartRec = true;
445
+ this.isStopRec = false;
446
+ }
447
+
448
+ onDeleteRecording(){
449
+ this.isStartRec = false;
450
+ this.isStopRec = false;
451
+ }
452
+
453
+ onEndRecording(audioBlob: Blob | null) {
454
+ this.isStartRec = false;
455
+ this.isStopRec = true;
456
+ }
457
+
458
+
459
+
460
+ onSendRecording(audioBlob: Blob | null) {
461
+ this.isStartRec = false;
462
+ if (audioBlob) {
463
+ this.convertBlobToBase64(audioBlob);
464
+ this.isStopRec = false;
465
+ } else {
466
+ this.isStopRec = false;
467
+ }
468
+ }
469
+
470
+
471
+
472
+ // Funzione per convertire Blob in Base64 usando FileReader
473
+ async convertBlobToBase64(audioBlob: Blob) {
474
+ let that = this;
475
+ if (audioBlob) {
476
+ this.isFilePendingToUpload = true;
477
+ } else {
478
+ this.isFilePendingToUpload = false;
479
+ }
480
+ const type = audioBlob.type;
481
+ const size = audioBlob.size;
482
+ const reader = new FileReader();
483
+ reader.readAsDataURL(audioBlob);
484
+ //reader.addEventListener('load', function () {
485
+ reader.onloadend = () => {
486
+ that.isFileSelected = true;
487
+ // const base64data = reader.result as string;
488
+ that.logger.log('[CONV-FOOTER] onload file');
489
+ const fileXLoad = {
490
+ src: reader.result.toString(),
491
+ title: 'audio-file'
492
+ };
493
+ const uid = (new Date().getTime()).toString(36);
494
+ that.arrayFilesLoad[0] = { uid: uid, file: fileXLoad, type: type, size: size };
495
+ that.logger.log('[CONV-FOOTER] OK: ', that.arrayFilesLoad[0]);
496
+ // SEND MESSAGE
497
+ let metadata = {
498
+ 'name': 'audio-file',
499
+ 'src': fileXLoad.src,
500
+ 'type': type,
501
+ 'uid': uid,
502
+ 'size': size
503
+ };
504
+
505
+ const file = new File([audioBlob], "audio-file.mp3", {
506
+ type: audioBlob.type,
507
+ lastModified: Date.now()
508
+ });
509
+
510
+ that.uploadSingle(metadata, file, '');
511
+ };
512
+ //}, false);
513
+ }
514
+
515
+
516
+
434
517
  onTextAreaChange(){
435
518
  this.resizeInputField()
436
519
  this.setWritingMessages(this.textInputTextArea)
437
520
  }
438
521
 
439
522
  onSendPressed(event) {
440
- this.logger.debug('[CONV-FOOTER] onSendPressed:event', event);
523
+ this.logger.log('[CONV-FOOTER] onSendPressed:event', event);
441
524
  event.preventDefault();
442
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
525
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
443
526
  if (this.isFilePendingToUpload) {
444
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed', 'is a file');
527
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed', 'is a file');
445
528
  // its a file
446
529
  this.loadFile();
447
530
  this.isFilePendingToUpload = false;
448
531
  // disabilito pulsanti
449
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
532
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
450
533
  } else {
451
534
  if ( this.textInputTextArea && this.textInputTextArea.length > 0 ) {
452
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed', 'is a message');
535
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed', 'is a message');
453
536
  // its a message
454
537
  if (this.textInputTextArea.trim() !== '') {
455
- // that.logger.debug('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
538
+ // that.logger.log('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
456
539
  // this.resizeInputField();
457
540
  // this.messagingService.sendMessage(msg, TYPE_MSG_TEXT);
458
541
  // this.setDepartment();
@@ -526,7 +609,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
526
609
  setTimeout(function () {
527
610
  const textarea = document.getElementById(id);
528
611
  if (textarea) {
529
- // that.logger.debug('[CONV-FOOTER] 1--------> FOCUSSSSSS : ', textarea);
612
+ // that.logger.log('[CONV-FOOTER] 1--------> FOCUSSSSSS : ', textarea);
530
613
  textarea.setAttribute('value', ' ');
531
614
  textarea.focus();
532
615
  }
@@ -566,7 +649,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
566
649
  this.textInputTextArea = ((document.getElementById('chat21-main-message-context') as HTMLInputElement).value);
567
650
  if (keyCode === 13) { // ENTER pressed
568
651
  if (this.textInputTextArea && this.textInputTextArea.trim() !== '') {
569
- // that.logger.debug('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
652
+ // that.logger.log('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
570
653
  // this.resizeInputField();
571
654
  // this.messagingService.sendMessage(msg, TYPE_MSG_TEXT);
572
655
  // this.setDepartment();
@@ -598,14 +681,14 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
598
681
  this.resizeInputField()
599
682
  const items = (event.clipboardData || event.originalEvent.clipboardData).items;
600
683
  let file = null;
601
- this.logger.debug('[CONV-FOOTER] onPaste items ', items);
684
+ this.logger.log('[CONV-FOOTER] onPaste items ', items);
602
685
  for (const item of items) {
603
- this.logger.debug('[CONV-FOOTER] onPaste item ', item);
604
- this.logger.debug('[CONV-FOOTER] onPaste item.type ', item.type);
686
+ this.logger.log('[CONV-FOOTER] onPaste item ', item);
687
+ this.logger.log('[CONV-FOOTER] onPaste item.type ', item.type);
605
688
  if (item.type.startsWith("image")) {
606
689
  // SEND TEXT MESSAGE IF EXIST
607
690
  // if(this.textInputTextArea){
608
- // this.logger.debug('[CONV-FOOTER] onPaste texttt ', this.textInputTextArea);
691
+ // this.logger.log('[CONV-FOOTER] onPaste texttt ', this.textInputTextArea);
609
692
  // this.sendMessage(this.textInputTextArea, TYPE_MSG_TEXT)
610
693
  // }
611
694
 
@@ -615,12 +698,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
615
698
  this.logger.error('[CONV-FOOTER] onPaste - error while restoring textArea:',e)
616
699
  }
617
700
 
618
- this.logger.debug('[CONV-FOOTER] onPaste item.type', item.type);
701
+ this.logger.log('[CONV-FOOTER] onPaste item.type', item.type);
619
702
  file = item.getAsFile();
620
703
  const data = {target: new ClipboardEvent('').clipboardData || new DataTransfer()};
621
704
  data.target.items.add(new File([file], file.name, { type: file.type }));
622
- this.logger.debug('[CONV-FOOTER] onPaste data', data);
623
- this.logger.debug('[CONV-FOOTER] onPaste file ', file);
705
+ this.logger.log('[CONV-FOOTER] onPaste data', data);
706
+ this.logger.log('[CONV-FOOTER] onPaste file ', file);
624
707
  this.detectFiles(data)
625
708
  }
626
709
  }
@@ -629,15 +712,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
629
712
  onDrop(event){
630
713
  const items = event.dataTransfer.files;
631
714
  let file = null;
632
- this.logger.debug('[CONV-FOOTER] onDrop items ', items);
715
+ this.logger.log('[CONV-FOOTER] onDrop items ', items);
633
716
  for (const item of items) {
634
- this.logger.debug('[CONV-FOOTER] onDrop item ', item);
635
- this.logger.debug('[CONV-FOOTER] onDrop item.type ', item.type);
717
+ this.logger.log('[CONV-FOOTER] onDrop item ', item);
718
+ this.logger.log('[CONV-FOOTER] onDrop item.type ', item.type);
636
719
 
637
720
  const data = {target: {files: new DataTransfer()}}
638
721
  data.target.files = items
639
- this.logger.debug('[CONV-FOOTER] onDrop data', data);
640
- this.logger.debug('[CONV-FOOTER] onDrop file ', file);
722
+ this.logger.log('[CONV-FOOTER] onDrop data', data);
723
+ this.logger.log('[CONV-FOOTER] onDrop file ', file);
641
724
  this.detectFiles(data)
642
725
  }
643
726
  }
@@ -0,0 +1,32 @@
1
+
2
+ <!-- <audio *ngIf="metadata" controls>
3
+ <source [src]="metadata.src" type="audio/mpeg">
4
+ </audio> *ngIf="!metadata"-->
5
+ <div class="audio-container">
6
+
7
+ <div class="audio-track">
8
+ <button *ngIf="!isPlaying" class="play-pause" (click)="playPauseAudio()">
9
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
10
+ <path d="M320-200v-560l440 280-440 280Z"/>
11
+ </svg>
12
+ <!-- <i class="material-icons">play_arrow</i> -->
13
+ </button>
14
+ <button *ngIf="isPlaying" class="play-pause" (click)="playPauseAudio()">
15
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
16
+ <path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/>
17
+ </svg>
18
+ <!-- <i class="material-icons">pause</i> -->
19
+ </button>
20
+ <div class="duration" [style.color]="color" [style.font-size]="fontSize" >
21
+ <span *ngIf="!isPlaying">{{ audioDuration ? formatTime(audioDuration) : '00:00' }}</span>
22
+ <span *ngIf="isPlaying">{{ formatTime(currentTime) }}</span>
23
+ </div>
24
+
25
+ </div>
26
+
27
+ <div class="audio-player-custom">
28
+ <audio #audioElement [src]="audioUrl"></audio>
29
+ <canvas #canvasElement class="waveformCanvas"></canvas>
30
+ </div>
31
+
32
+ </div>
@@ -0,0 +1,107 @@
1
+
2
+ :host {
3
+ --backgroundColor: #{var(--blue)};
4
+ --textColor: #{var(--bck-msg-sent)};
5
+ --hoverBackgroundColor: #{var(--bck-msg-sent)};
6
+ --hoverTextColor: #{var(--blue)};
7
+ --max-width: #{var(--button-in-msg-max-width)};
8
+ }
9
+
10
+ .audio-container{
11
+ position: relative;
12
+ display: inline-flex;
13
+ width: 100%;
14
+ padding: 0px 12px;
15
+ margin: 6px 0px;
16
+ }
17
+
18
+ audio {
19
+ width: 272px;
20
+ height: 30px;
21
+ margin: 0;
22
+ padding: 10px;
23
+ }
24
+
25
+ .audio-recorder {
26
+ text-align: center;
27
+ margin: 0px;
28
+ display: inline-flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ height: 100%;
32
+ width: 100%;
33
+ float: left;
34
+ }
35
+
36
+ button {
37
+ margin: 0px;
38
+ padding: 0px;
39
+ font-size: 16px;
40
+ border: none;
41
+ background-color: transparent;
42
+ color: var(--icon-fill-color);
43
+ fill: var(--icon-fill-color);
44
+ }
45
+
46
+ .waveformCanvas {
47
+ width: 100%;
48
+ height: 28px;
49
+ z-index: 1;
50
+ padding: 0px;
51
+ margin: 0%;
52
+ }
53
+
54
+ .audio-track {
55
+ // width: 247px;//272px;
56
+ // height: 30px;
57
+ position: relative;
58
+ display: flex;
59
+ align-items: center;
60
+ // margin: 0 13px;
61
+ margin: 0px;
62
+ .play-pause {
63
+ font-size: 20px;
64
+ width: 30px;
65
+ background-color: transparent;
66
+ border-radius: 50%;
67
+ height: 30px;
68
+ margin: 0px;
69
+ transition: background-color 0.5s ease;
70
+
71
+ }
72
+ .play-pause:hover {
73
+ // background-color: #ddd;
74
+ // background-color: rgb(82, 160, 252);
75
+ background-color: #fff;
76
+ svg{
77
+ //fill:#fff;
78
+ fill: rgb(82, 160, 252);
79
+ }
80
+ }
81
+ .duration {
82
+ padding: 0 3px;
83
+ }
84
+ }
85
+
86
+ .audio-player-custom {
87
+ // width: 200px;
88
+ // height: 32px;
89
+ // margin-left: 75px;
90
+ // position: absolute;
91
+ // overflow: hidden;
92
+ // z-index: 1;
93
+ // display: flex;
94
+ // align-items: center;
95
+
96
+ // width: calc(100% - 75px);
97
+ // height: 32px;
98
+ // margin-left: 65px;
99
+ // position: absolute;
100
+ overflow: hidden;
101
+ z-index: 1;
102
+ display: flex;
103
+ align-items: center;
104
+ width: 100%;
105
+ }
106
+
107
+
@@ -1,18 +1,18 @@
1
1
  import { ComponentFixture, TestBed } from '@angular/core/testing';
2
2
 
3
- import { AudioComponent } from './audio.component';
3
+ import { AudioTrackComponent } from './audio-track.component';
4
4
 
5
- describe('AudioComponent', () => {
6
- let component: AudioComponent;
7
- let fixture: ComponentFixture<AudioComponent>;
5
+ describe('AudioTrackComponent', () => {
6
+ let component: AudioTrackComponent;
7
+ let fixture: ComponentFixture<AudioTrackComponent>;
8
8
 
9
9
  beforeEach(async () => {
10
10
  await TestBed.configureTestingModule({
11
- declarations: [ AudioComponent ]
11
+ declarations: [ AudioTrackComponent ]
12
12
  })
13
13
  .compileComponents();
14
14
 
15
- fixture = TestBed.createComponent(AudioComponent);
15
+ fixture = TestBed.createComponent(AudioTrackComponent);
16
16
  component = fixture.componentInstance;
17
17
  fixture.detectChanges();
18
18
  });
@@ -0,0 +1,147 @@
1
+ import { Component, ElementRef, AfterViewInit, Input, ViewChild } from '@angular/core';
2
+ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
3
+ import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
4
+
5
+ @Component({
6
+ selector: 'chat-audio-track',
7
+ templateUrl: './audio-track.component.html',
8
+ styleUrls: ['./audio-track.component.scss']
9
+ })
10
+ export class AudioTrackComponent implements AfterViewInit {
11
+
12
+ @ViewChild('audioElement', { static: true }) audioElement!: ElementRef<HTMLAudioElement>;
13
+ @ViewChild('canvasElement', { static: true }) waveformCanvas!: ElementRef<HTMLCanvasElement>;
14
+
15
+ @Input() audioBlob: Blob | null = null;
16
+ @Input() metadata: any | null = null;
17
+ @Input() color: string;
18
+ @Input() fontSize: string;
19
+ @Input() stylesMap: Map<string, string>;
20
+
21
+ audioUrl: SafeUrl | null = null;
22
+ rawAudioUrl: string | null = null;
23
+ audioContext!: AudioContext;
24
+ audioBuffer!: AudioBuffer;
25
+ audioDuration: number | null = null;
26
+ currentTime: number = 0;
27
+ isPlaying: boolean = false;
28
+
29
+ constructor(private sanitizer: DomSanitizer) {}
30
+
31
+ ngAfterViewInit() {
32
+ console.log('stylesssss', this.stylesMap)
33
+ if (this.audioBlob) {
34
+ this.rawAudioUrl = URL.createObjectURL(this.audioBlob);
35
+ this.audioUrl = this.sanitizer.bypassSecurityTrustUrl(this.rawAudioUrl);
36
+ this.setupAudioContext();
37
+ } else {
38
+ this.rawAudioUrl = this.metadata.src;
39
+ this.audioUrl = this.sanitizer.bypassSecurityTrustUrl(this.rawAudioUrl);
40
+ this.setupAudioContext();
41
+ }
42
+ }
43
+
44
+ async setupAudioContext() {
45
+ this.audioContext = new AudioContext();
46
+ if (this.rawAudioUrl) {
47
+ const response = await fetch(this.rawAudioUrl);
48
+ const audioData = await response.arrayBuffer();
49
+ this.audioBuffer = await this.audioContext.decodeAudioData(audioData);
50
+ this.getAudioDuration();
51
+ this.drawWaveform(this.audioBuffer);
52
+ }
53
+ }
54
+
55
+ drawWaveform(audioBuffer: AudioBuffer) {
56
+ const canvas = this.waveformCanvas.nativeElement;
57
+ const canvasCtx = canvas.getContext('2d');
58
+ if (!canvasCtx) return;
59
+ const width = canvas.width;
60
+ const height = canvas.height;
61
+ const rawData = audioBuffer.getChannelData(0);
62
+
63
+ const samples = 60;
64
+ const blockSize = Math.floor(rawData.length / samples);
65
+ const waveform = new Float32Array(samples);
66
+
67
+ for (let i = 0; i < samples; i++) {
68
+ let sum = 0;
69
+ for (let j = 0; j < blockSize; j++) {
70
+ sum += Math.abs(rawData[i * blockSize + j]);
71
+ }
72
+ waveform[i] = sum / blockSize;
73
+ }
74
+
75
+ canvasCtx.clearRect(0, 0, width, height);
76
+ const padding = 1;
77
+ const barWidth = (width / samples) - padding * 2;
78
+ const audio = this.audioElement.nativeElement;
79
+ const playedPercent = audio.currentTime / this.audioDuration;
80
+ // console.log('playedPercent: ', audio.currentTime, this.audioDuration);
81
+
82
+ for (let i = 0; i < samples; i++) {
83
+ var barHeight = waveform[i] * height * 4;
84
+ if (barHeight < 4) barHeight = 4;
85
+ const x = i * (barWidth + padding * 2) + padding;
86
+
87
+ if (i / samples < playedPercent) {
88
+ canvasCtx.fillStyle = this.color;
89
+ } else {
90
+ canvasCtx.fillStyle = convertColorToRGBA(this.color, 50);;
91
+ }
92
+ canvasCtx.fillRect(x, height / 2 - barHeight, barWidth, barHeight);
93
+ canvasCtx.fillRect(x, height / 2, barWidth, barHeight);
94
+ }
95
+ }
96
+
97
+ playPauseAudio() {
98
+ const audio = this.audioElement.nativeElement;
99
+ if (audio.paused) {
100
+ this.isPlaying = true;
101
+ this.updateWaveform();
102
+ audio.play();
103
+ this.audioContext.resume();
104
+ } else {
105
+ audio.pause();
106
+ this.isPlaying = false;
107
+ }
108
+ audio.ontimeupdate = () => {
109
+ this.currentTime = audio.currentTime;
110
+ this.updateWaveform();
111
+ };
112
+ audio.onended = () => {
113
+ this.isPlaying = false;
114
+ };
115
+ }
116
+
117
+
118
+ updateWaveform() {
119
+ this.drawWaveform(this.audioBuffer);
120
+ if (this.isPlaying) {
121
+ requestAnimationFrame(() => this.updateWaveform());
122
+ }
123
+ }
124
+
125
+ formatTime(seconds: number): string {
126
+ const minutes = Math.floor(seconds / 60);
127
+ const sec = Math.floor(seconds % 60);
128
+ return `${minutes}:${sec < 10 ? '0' + sec : sec}`;
129
+ }
130
+
131
+ getAudioDuration() {
132
+ const audio = new Audio();
133
+ audio.src = this.rawAudioUrl!;
134
+ audio.addEventListener('loadedmetadata', () => {
135
+ if (audio.duration === Infinity) {
136
+ audio.currentTime = Number.MAX_SAFE_INTEGER;
137
+ audio.ontimeupdate = () => {
138
+ audio.ontimeupdate = null;
139
+ audio.currentTime = 0;
140
+ this.audioDuration = audio.duration;
141
+ };
142
+ } else {
143
+ this.audioDuration = audio.duration;
144
+ }
145
+ });
146
+ }
147
+ }