@chat21/chat21-web-widget 5.0.88 → 5.0.89-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 (23) hide show
  1. package/CHANGELOG.md +5 -5
  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.ts +2 -1
  5. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +29 -0
  6. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.scss +103 -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 +14 -3
  10. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +26 -10
  11. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +123 -41
  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/utils/globals.ts +1 -1
  20. package/src/assets/twp/index-dev.html +1 -0
  21. package/src/app/component/message/audio/audio.component.html +0 -20
  22. package/src/app/component/message/audio/audio.component.scss +0 -122
  23. package/src/app/component/message/audio/audio.component.ts +0 -122
@@ -60,6 +60,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
60
60
  // ========= end:: send image ========= //
61
61
 
62
62
  // isNewConversation = true;
63
+ isStartRec: boolean = false;
64
+ isStopRec: boolean = false;
63
65
  textInputTextArea: string;
64
66
  conversationHandlerService: ConversationHandlerService
65
67
 
@@ -99,7 +101,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
99
101
  }
100
102
 
101
103
  ngAfterViewInit() {
102
- this.logger.debug('[CONV-FOOTER] --------ngAfterViewInit: conversation-footer-------- ');
104
+ this.logger.log('[CONV-FOOTER] --------ngAfterViewInit: conversation-footer-------- ');
103
105
  // setTimeout(() => {
104
106
  this.showEmojiPicker = true
105
107
  // }, 500);
@@ -109,19 +111,18 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
109
111
  // START LOAD IMAGE //
110
112
  /** load the selected image locally and open the pop up preview */
111
113
  detectFiles(event) {
112
- this.logger.debug('[CONV-FOOTER] detectFiles: ', event);
113
-
114
+ this.logger.log('[CONV-FOOTER] detectFiles: ', event);
114
115
  if (event) {
115
116
  this.selectedFiles = event.target.files;
116
- this.logger.debug('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles', this.selectedFiles);
117
+ this.logger.log('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles', this.selectedFiles);
117
118
  // this.onAttachmentButtonClicked.emit(this.selectedFiles)
118
119
  if (this.selectedFiles == null) {
119
120
  this.isFilePendingToUpload = false;
120
121
  } else {
121
122
  this.isFilePendingToUpload = true;
122
123
  }
123
- this.logger.debug('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles::isFilePendingToUpload', this.isFilePendingToUpload);
124
- this.logger.debug('[CONV-FOOTER] fileChange: ', event.target.files);
124
+ this.logger.log('[CONV-FOOTER] AppComponent:detectFiles::selectedFiles::isFilePendingToUpload', this.isFilePendingToUpload);
125
+ this.logger.log('[CONV-FOOTER] fileChange: ', event.target.files);
125
126
  if (event.target.files.length <= 0) {
126
127
  this.isFilePendingToUpload = false;
127
128
  } else {
@@ -184,16 +185,18 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
184
185
  }
185
186
  }
186
187
 
188
+
189
+
187
190
 
188
191
  loadFile() {
189
- this.logger.debug('[CONV-FOOTER] that.fileXLoad: ', this.arrayFilesLoad);
192
+ this.logger.log('[CONV-FOOTER] that.fileXLoad: ', this.arrayFilesLoad);
190
193
  // at the moment I only manage the upload of one image at a time
191
194
  if (this.arrayFilesLoad[0] && this.arrayFilesLoad[0].file) {
192
195
  const fileXLoad = this.arrayFilesLoad[0].file;
193
196
  const uid = this.arrayFilesLoad[0].uid;
194
197
  const type = this.arrayFilesLoad[0].type;
195
198
  const size = this.arrayFilesLoad[0].size
196
- this.logger.debug('[CONV-FOOTER] that.fileXLoad: ', type);
199
+ this.logger.log('[CONV-FOOTER] that.fileXLoad: ', type);
197
200
  let metadata;
198
201
  if (type.startsWith('image') && !type.includes('svg')) {
199
202
  metadata = {
@@ -214,7 +217,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
214
217
  'size': size
215
218
  };
216
219
  }
217
- this.logger.debug('[CONV-FOOTER] metadata -------> ', metadata);
220
+ this.logger.log('[CONV-FOOTER] metadata -------> ', metadata);
218
221
  // this.scrollToBottom();
219
222
  // 1 - aggiungo messaggio localmente
220
223
  // this.addLocalMessageImage(metadata);
@@ -231,17 +234,20 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
231
234
 
232
235
  uploadSingle(metadata, file, messageText?: string) {
233
236
  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);
237
+ try {
238
+ const send_order_btn = <HTMLInputElement>document.getElementById('chat21-start-upload-doc');
239
+ send_order_btn.disabled = true;
240
+ } catch (error) {
241
+ that.logger.log('[CONV-FOOTER] error::', error);
242
+ }
243
+ that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle::', metadata, file);
237
244
  // const file = this.selectedFiles.item(0);
238
245
  const currentUpload = new UploadModel(file);
239
-
240
246
  // const uploadTask = this.upSvc.pushUpload(currentUpload);
241
247
  // uploadTask.then(snapshot => {
242
248
  // return snapshot.ref.getDownloadURL(); // Will return a promise with the download link
243
249
  // }).then(downloadURL => {
244
- // that.logger.debug('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', downloadURL]);
250
+ // that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', downloadURL]);
245
251
  // that.g.wdLog([`Successfully uploaded file and got download link - ${downloadURL}`]);
246
252
 
247
253
  // metadata.src = downloadURL;
@@ -261,8 +267,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
261
267
  // this.resetLoadImage();
262
268
 
263
269
  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}`);
270
+ that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', data);
271
+ that.logger.log(`[CONV-FOOTER] Successfully uploaded file and got download link - ${data}`);
266
272
 
267
273
  metadata.src = data.src;
268
274
  metadata.downloadURL = data.downloadURL;
@@ -291,7 +297,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
291
297
  that.logger.error(`[CONV-FOOTER] uploadSingle:: Failed to upload file and get link - ${error}`);
292
298
  that.isFilePendingToUpload = false;
293
299
  });
294
- that.logger.debug('[CONV-FOOTER] reader-result: ', file);
300
+ that.logger.log('[CONV-FOOTER] reader-result: ', file);
295
301
  }
296
302
 
297
303
  /**
@@ -304,7 +310,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
304
310
  sendMessage(msg: string, type: string, metadata?: any, additional_attributes?: any) { // sponziello
305
311
  (metadata) ? metadata = metadata : metadata = '';
306
312
  this.onEmojiiPickerShow.emit(false)
307
- this.logger.debug('[CONV-FOOTER] SEND MESSAGE: ', msg, type, metadata, additional_attributes);
313
+ this.logger.log('[CONV-FOOTER] SEND MESSAGE: ', msg, type, metadata, additional_attributes);
308
314
  if (msg && msg.trim() !== '' || type === TYPE_MSG_IMAGE || type === TYPE_MSG_FILE ) {
309
315
 
310
316
  // msg = htmlEntities(msg);
@@ -387,7 +393,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
387
393
  }
388
394
 
389
395
  private restoreTextArea() {
390
- // that.logger.debug('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea');
396
+ // that.logger.log('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea');
391
397
  this.resizeInputField();
392
398
  const textArea = (<HTMLInputElement>document.getElementById('chat21-main-message-context'));
393
399
  this.textInputTextArea = ''; // clear the textarea
@@ -397,7 +403,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
397
403
  if(textArea.style.height > this.HEIGHT_DEFAULT){
398
404
  document.getElementById('chat21-button-send').style.removeProperty('right')
399
405
  }
400
- this.logger.debug('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea::textArea:', 'restored');
406
+ this.logger.log('[CONV-FOOTER] AppComponent:restoreTextArea::restoreTextArea::textArea:', 'restored');
401
407
  }
402
408
  this.setFocusOnId('chat21-main-message-context');
403
409
  }
@@ -411,7 +417,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
411
417
  try {
412
418
  const target = document.getElementById('chat21-main-message-context') as HTMLInputElement;
413
419
  // 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);
420
+ // 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
421
  target? target.style.height = '100%': null;
416
422
  if (target && target.value === '\n') {
417
423
  target.value = '';
@@ -431,28 +437,104 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
431
437
  }
432
438
  }
433
439
 
440
+
441
+
442
+ onStartRecording() {
443
+ this.isStartRec = true;
444
+ this.isStopRec = false;
445
+ }
446
+
447
+ onDeleteRecording(){
448
+ this.isStartRec = false;
449
+ this.isStopRec = false;
450
+ }
451
+
452
+ onEndRecording(audioBlob: Blob | null) {
453
+ this.isStartRec = false;
454
+ this.isStopRec = true;
455
+ }
456
+
457
+
458
+
459
+ onSendRecording(audioBlob: Blob | null) {
460
+ this.isStartRec = false;
461
+ if (audioBlob) {
462
+ this.convertBlobToBase64(audioBlob);
463
+ this.isStopRec = false;
464
+ } else {
465
+ this.isStopRec = false;
466
+ }
467
+ }
468
+
469
+
470
+
471
+ // Funzione per convertire Blob in Base64 usando FileReader
472
+ async convertBlobToBase64(audioBlob: Blob) {
473
+ let that = this;
474
+ if (audioBlob) {
475
+ this.isFilePendingToUpload = true;
476
+ } else {
477
+ this.isFilePendingToUpload = false;
478
+ }
479
+ const type = audioBlob.type;
480
+ const size = audioBlob.size;
481
+ const reader = new FileReader();
482
+ reader.readAsDataURL(audioBlob);
483
+ //reader.addEventListener('load', function () {
484
+ reader.onloadend = () => {
485
+ that.isFileSelected = true;
486
+ // const base64data = reader.result as string;
487
+ that.logger.log('[CONV-FOOTER] onload file');
488
+ const fileXLoad = {
489
+ src: reader.result.toString(),
490
+ title: 'audio-file'
491
+ };
492
+ const uid = (new Date().getTime()).toString(36);
493
+ that.arrayFilesLoad[0] = { uid: uid, file: fileXLoad, type: type, size: size };
494
+ that.logger.log('[CONV-FOOTER] OK: ', that.arrayFilesLoad[0]);
495
+ // SEND MESSAGE
496
+ let metadata = {
497
+ 'name': 'audio-file',
498
+ 'src': fileXLoad.src,
499
+ 'type': type,
500
+ 'uid': uid,
501
+ 'size': size
502
+ };
503
+
504
+ const file = new File([audioBlob], "audio-file.mp3", {
505
+ type: audioBlob.type,
506
+ lastModified: Date.now()
507
+ });
508
+
509
+ that.uploadSingle(metadata, file, '');
510
+ };
511
+ //}, false);
512
+ }
513
+
514
+
515
+
434
516
  onTextAreaChange(){
435
517
  this.resizeInputField()
436
518
  this.setWritingMessages(this.textInputTextArea)
437
519
  }
438
520
 
439
521
  onSendPressed(event) {
440
- this.logger.debug('[CONV-FOOTER] onSendPressed:event', event);
522
+ this.logger.log('[CONV-FOOTER] onSendPressed:event', event);
441
523
  event.preventDefault();
442
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
524
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
443
525
  if (this.isFilePendingToUpload) {
444
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed', 'is a file');
526
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed', 'is a file');
445
527
  // its a file
446
528
  this.loadFile();
447
529
  this.isFilePendingToUpload = false;
448
530
  // disabilito pulsanti
449
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
531
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
450
532
  } else {
451
533
  if ( this.textInputTextArea && this.textInputTextArea.length > 0 ) {
452
- this.logger.debug('[CONV-FOOTER] AppComponent::onSendPressed', 'is a message');
534
+ this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed', 'is a message');
453
535
  // its a message
454
536
  if (this.textInputTextArea.trim() !== '') {
455
- // that.logger.debug('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
537
+ // that.logger.log('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
456
538
  // this.resizeInputField();
457
539
  // this.messagingService.sendMessage(msg, TYPE_MSG_TEXT);
458
540
  // this.setDepartment();
@@ -526,7 +608,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
526
608
  setTimeout(function () {
527
609
  const textarea = document.getElementById(id);
528
610
  if (textarea) {
529
- // that.logger.debug('[CONV-FOOTER] 1--------> FOCUSSSSSS : ', textarea);
611
+ // that.logger.log('[CONV-FOOTER] 1--------> FOCUSSSSSS : ', textarea);
530
612
  textarea.setAttribute('value', ' ');
531
613
  textarea.focus();
532
614
  }
@@ -566,7 +648,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
566
648
  this.textInputTextArea = ((document.getElementById('chat21-main-message-context') as HTMLInputElement).value);
567
649
  if (keyCode === 13) { // ENTER pressed
568
650
  if (this.textInputTextArea && this.textInputTextArea.trim() !== '') {
569
- // that.logger.debug('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
651
+ // that.logger.log('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
570
652
  // this.resizeInputField();
571
653
  // this.messagingService.sendMessage(msg, TYPE_MSG_TEXT);
572
654
  // this.setDepartment();
@@ -598,14 +680,14 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
598
680
  this.resizeInputField()
599
681
  const items = (event.clipboardData || event.originalEvent.clipboardData).items;
600
682
  let file = null;
601
- this.logger.debug('[CONV-FOOTER] onPaste items ', items);
683
+ this.logger.log('[CONV-FOOTER] onPaste items ', items);
602
684
  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);
685
+ this.logger.log('[CONV-FOOTER] onPaste item ', item);
686
+ this.logger.log('[CONV-FOOTER] onPaste item.type ', item.type);
605
687
  if (item.type.startsWith("image")) {
606
688
  // SEND TEXT MESSAGE IF EXIST
607
689
  // if(this.textInputTextArea){
608
- // this.logger.debug('[CONV-FOOTER] onPaste texttt ', this.textInputTextArea);
690
+ // this.logger.log('[CONV-FOOTER] onPaste texttt ', this.textInputTextArea);
609
691
  // this.sendMessage(this.textInputTextArea, TYPE_MSG_TEXT)
610
692
  // }
611
693
 
@@ -615,12 +697,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
615
697
  this.logger.error('[CONV-FOOTER] onPaste - error while restoring textArea:',e)
616
698
  }
617
699
 
618
- this.logger.debug('[CONV-FOOTER] onPaste item.type', item.type);
700
+ this.logger.log('[CONV-FOOTER] onPaste item.type', item.type);
619
701
  file = item.getAsFile();
620
702
  const data = {target: new ClipboardEvent('').clipboardData || new DataTransfer()};
621
703
  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);
704
+ this.logger.log('[CONV-FOOTER] onPaste data', data);
705
+ this.logger.log('[CONV-FOOTER] onPaste file ', file);
624
706
  this.detectFiles(data)
625
707
  }
626
708
  }
@@ -629,15 +711,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
629
711
  onDrop(event){
630
712
  const items = event.dataTransfer.files;
631
713
  let file = null;
632
- this.logger.debug('[CONV-FOOTER] onDrop items ', items);
714
+ this.logger.log('[CONV-FOOTER] onDrop items ', items);
633
715
  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);
716
+ this.logger.log('[CONV-FOOTER] onDrop item ', item);
717
+ this.logger.log('[CONV-FOOTER] onDrop item.type ', item.type);
636
718
 
637
719
  const data = {target: {files: new DataTransfer()}}
638
720
  data.target.files = items
639
- this.logger.debug('[CONV-FOOTER] onDrop data', data);
640
- this.logger.debug('[CONV-FOOTER] onDrop file ', file);
721
+ this.logger.log('[CONV-FOOTER] onDrop data', data);
722
+ this.logger.log('[CONV-FOOTER] onDrop file ', file);
641
723
  this.detectFiles(data)
642
724
  }
643
725
  }
@@ -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
+ }