@chat21/chat21-web-widget 5.0.90-rc.1 → 5.0.90-rc.3
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.
- package/CHANGELOG.md +10 -3
- package/package.json +1 -1
- package/src/app/app.module.ts +5 -2
- package/src/app/component/conversation-detail/conversation/conversation.component.html +2 -1
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +31 -0
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.scss +108 -0
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +23 -0
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +96 -0
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +16 -5
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +38 -11
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +125 -42
- package/src/app/component/message/audio-track/audio-track.component.html +32 -0
- package/src/app/component/message/audio-track/audio-track.component.scss +107 -0
- package/src/app/component/message/{audio/audio.component.spec.ts → audio-track/audio-track.component.spec.ts} +6 -6
- package/src/app/component/message/audio-track/audio-track.component.ts +147 -0
- package/src/app/component/message/bubble-message/bubble-message.component.html +30 -5
- package/src/app/component/message/bubble-message/bubble-message.component.scss +7 -0
- package/src/app/component/message/bubble-message/bubble-message.component.ts +1 -0
- package/src/app/providers/global-settings.service.ts +32 -32
- package/src/app/utils/globals.ts +12 -6
- package/src/assets/twp/index-dev.html +26 -9
- package/src/app/component/message/audio/audio.component.html +0 -20
- package/src/app/component/message/audio/audio.component.scss +0 -122
- package/src/app/component/message/audio/audio.component.ts +0 -122
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts
CHANGED
|
@@ -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()
|
|
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.
|
|
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.
|
|
113
|
-
|
|
115
|
+
this.logger.log('[CONV-FOOTER] detectFiles: ', event);
|
|
114
116
|
if (event) {
|
|
115
117
|
this.selectedFiles = event.target.files;
|
|
116
|
-
this.logger.
|
|
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.
|
|
124
|
-
this.logger.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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.
|
|
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.
|
|
265
|
-
that.logger.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
523
|
+
this.logger.log('[CONV-FOOTER] onSendPressed:event', event);
|
|
441
524
|
event.preventDefault();
|
|
442
|
-
this.logger.
|
|
525
|
+
this.logger.log('[CONV-FOOTER] AppComponent::onSendPressed::isFilePendingToUpload:', this.isFilePendingToUpload);
|
|
443
526
|
if (this.isFilePendingToUpload) {
|
|
444
|
-
this.logger.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
684
|
+
this.logger.log('[CONV-FOOTER] onPaste items ', items);
|
|
602
685
|
for (const item of items) {
|
|
603
|
-
this.logger.
|
|
604
|
-
this.logger.
|
|
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.
|
|
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.
|
|
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.
|
|
623
|
-
this.logger.
|
|
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.
|
|
715
|
+
this.logger.log('[CONV-FOOTER] onDrop items ', items);
|
|
633
716
|
for (const item of items) {
|
|
634
|
-
this.logger.
|
|
635
|
-
this.logger.
|
|
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.
|
|
640
|
-
this.logger.
|
|
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 {
|
|
3
|
+
import { AudioTrackComponent } from './audio-track.component';
|
|
4
4
|
|
|
5
|
-
describe('
|
|
6
|
-
let component:
|
|
7
|
-
let fixture: ComponentFixture<
|
|
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: [
|
|
11
|
+
declarations: [ AudioTrackComponent ]
|
|
12
12
|
})
|
|
13
13
|
.compileComponents();
|
|
14
14
|
|
|
15
|
-
fixture = TestBed.createComponent(
|
|
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
|
+
}
|