valtech-components 4.0.240 → 4.0.242

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.
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
56
56
  * Current version of valtech-components.
57
57
  * This is automatically updated during the publish process.
58
58
  */
59
- const VERSION = '4.0.240';
59
+ const VERSION = '4.0.242';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -72621,6 +72621,19 @@ class MessageBubbleComponent {
72621
72621
  this.actionsOpen.set(false);
72622
72622
  this.action.emit({ type, msgId: this.msg().msgId, token });
72623
72623
  }
72624
+ /** Diagnóstico: el navegador no pudo reproducir el audio recibido. */
72625
+ onAudioError(att, event) {
72626
+ const el = event.target;
72627
+ const codes = { 1: 'ABORTED', 2: 'NETWORK', 3: 'DECODE', 4: 'SRC_NOT_SUPPORTED' };
72628
+ const canPlay = att.mimeType ? document.createElement('audio').canPlayType(att.mimeType) : '';
72629
+ console.error('[MessageBubble] audio play error', {
72630
+ mimeType: att.mimeType,
72631
+ url: att.url.slice(0, 80),
72632
+ code: el.error?.code,
72633
+ reason: el.error ? codes[el.error.code] : 'unknown',
72634
+ canPlayThisType: canPlay,
72635
+ });
72636
+ }
72624
72637
  t(key) {
72625
72638
  return this.i18n.t(key, 'MessageBubble');
72626
72639
  }
@@ -72669,7 +72682,13 @@ class MessageBubbleComponent {
72669
72682
  @if (audioAttachments().length > 0) {
72670
72683
  <div class="audios">
72671
72684
  @for (att of audioAttachments(); track att.url) {
72672
- <audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
72685
+ <audio
72686
+ class="att-audio"
72687
+ controls
72688
+ preload="metadata"
72689
+ [src]="att.url"
72690
+ (error)="onAudioError(att, $event)"
72691
+ ></audio>
72673
72692
  }
72674
72693
  </div>
72675
72694
  }
@@ -72784,7 +72803,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72784
72803
  @if (audioAttachments().length > 0) {
72785
72804
  <div class="audios">
72786
72805
  @for (att of audioAttachments(); track att.url) {
72787
- <audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
72806
+ <audio
72807
+ class="att-audio"
72808
+ controls
72809
+ preload="metadata"
72810
+ [src]="att.url"
72811
+ (error)="onAudioError(att, $event)"
72812
+ ></audio>
72788
72813
  }
72789
72814
  </div>
72790
72815
  }
@@ -73184,6 +73209,10 @@ class ChatComposerComponent {
73184
73209
  this.recStream = stream;
73185
73210
  const mimeType = this.pickAudioMime();
73186
73211
  this.recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
73212
+ console.log('[ChatComposer] recording start', {
73213
+ picked: mimeType || '(default)',
73214
+ recorderMimeType: this.recorder.mimeType,
73215
+ });
73187
73216
  this.recChunks = [];
73188
73217
  this.recorder.ondataavailable = e => {
73189
73218
  if (e.data.size > 0)
@@ -73210,6 +73239,13 @@ class ChatComposerComponent {
73210
73239
  const blob = new Blob(this.recChunks, { type });
73211
73240
  const ext = type.includes('mp4') ? 'm4a' : 'webm';
73212
73241
  const file = new File([blob], `audio-${this.recSeconds()}s.${ext}`, { type });
73242
+ console.log('[ChatComposer] audio recorded', {
73243
+ type,
73244
+ size: blob.size,
73245
+ chunks: this.recChunks.length,
73246
+ canPlayWebm: this.canPlay('audio/webm'),
73247
+ canPlayMp4: this.canPlay('audio/mp4'),
73248
+ });
73213
73249
  if (blob.size > 0)
73214
73250
  this.addPending(file);
73215
73251
  this.cleanupRecording();
@@ -73240,6 +73276,43 @@ class ChatComposerComponent {
73240
73276
  this.recording.set(false);
73241
73277
  this.recSeconds.set(0);
73242
73278
  }
73279
+ /** Diagnóstico: el navegador no pudo reproducir el adjunto de audio. */
73280
+ onAudioError(where, file, event) {
73281
+ const el = event.target;
73282
+ const err = el.error;
73283
+ const codes = {
73284
+ 1: 'ABORTED',
73285
+ 2: 'NETWORK',
73286
+ 3: 'DECODE',
73287
+ 4: 'SRC_NOT_SUPPORTED',
73288
+ };
73289
+ console.error('[ChatComposer] audio play error', {
73290
+ where,
73291
+ fileType: file.type,
73292
+ fileName: file.name,
73293
+ code: err?.code,
73294
+ reason: err ? codes[err.code] : 'unknown',
73295
+ message: err?.message,
73296
+ canPlayThisType: this.canPlay(file.type),
73297
+ });
73298
+ }
73299
+ onAudioLoaded(where, file, event) {
73300
+ const el = event.target;
73301
+ console.log('[ChatComposer] audio loadedmetadata', {
73302
+ where,
73303
+ fileType: file.type,
73304
+ duration: el.duration,
73305
+ });
73306
+ }
73307
+ /** ¿El navegador declara que puede reproducir este tipo? '' / 'maybe' / 'probably'. */
73308
+ canPlay(type) {
73309
+ try {
73310
+ return document.createElement('audio').canPlayType(type);
73311
+ }
73312
+ catch {
73313
+ return 'error';
73314
+ }
73315
+ }
73243
73316
  ngOnDestroy() {
73244
73317
  this.cleanupRecording();
73245
73318
  for (const att of this.pending()) {
@@ -73270,7 +73343,13 @@ class ChatComposerComponent {
73270
73343
  @for (att of pending(); track att.id) {
73271
73344
  @if (att.kind === 'audio') {
73272
73345
  <div class="audio-chip">
73273
- <audio controls preload="metadata" [src]="att.url"></audio>
73346
+ <audio
73347
+ controls
73348
+ preload="metadata"
73349
+ [src]="att.url"
73350
+ (error)="onAudioError('preview', att.file, $event)"
73351
+ (loadedmetadata)="onAudioLoaded('preview', att.file, $event)"
73352
+ ></audio>
73274
73353
  <button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73275
73354
  <ion-icon name="close-outline" aria-hidden="true" />
73276
73355
  </button>
@@ -73321,21 +73400,22 @@ class ChatComposerComponent {
73321
73400
  ></div>
73322
73401
 
73323
73402
  <div class="actions">
73403
+ <span class="actions-spacer"></span>
73324
73404
  @if (showAttach()) {
73325
73405
  <label class="icon-btn" [attr.aria-label]="t('attach')">
73326
73406
  <ion-icon name="add-outline" aria-hidden="true" />
73327
73407
  <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73328
73408
  </label>
73329
73409
  }
73330
- <span class="actions-spacer"></span>
73410
+ @if (showMic()) {
73411
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73412
+ <ion-icon name="mic-outline" aria-hidden="true" />
73413
+ </button>
73414
+ }
73331
73415
  @if (canSend()) {
73332
73416
  <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73333
73417
  <ion-icon name="arrow-up-outline" aria-hidden="true" />
73334
73418
  </button>
73335
- } @else if (showMic()) {
73336
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73337
- <ion-icon name="mic-outline" aria-hidden="true" />
73338
- </button>
73339
73419
  }
73340
73420
  </div>
73341
73421
  </div>
@@ -73364,7 +73444,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73364
73444
  @for (att of pending(); track att.id) {
73365
73445
  @if (att.kind === 'audio') {
73366
73446
  <div class="audio-chip">
73367
- <audio controls preload="metadata" [src]="att.url"></audio>
73447
+ <audio
73448
+ controls
73449
+ preload="metadata"
73450
+ [src]="att.url"
73451
+ (error)="onAudioError('preview', att.file, $event)"
73452
+ (loadedmetadata)="onAudioLoaded('preview', att.file, $event)"
73453
+ ></audio>
73368
73454
  <button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73369
73455
  <ion-icon name="close-outline" aria-hidden="true" />
73370
73456
  </button>
@@ -73415,21 +73501,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73415
73501
  ></div>
73416
73502
 
73417
73503
  <div class="actions">
73504
+ <span class="actions-spacer"></span>
73418
73505
  @if (showAttach()) {
73419
73506
  <label class="icon-btn" [attr.aria-label]="t('attach')">
73420
73507
  <ion-icon name="add-outline" aria-hidden="true" />
73421
73508
  <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73422
73509
  </label>
73423
73510
  }
73424
- <span class="actions-spacer"></span>
73511
+ @if (showMic()) {
73512
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73513
+ <ion-icon name="mic-outline" aria-hidden="true" />
73514
+ </button>
73515
+ }
73425
73516
  @if (canSend()) {
73426
73517
  <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73427
73518
  <ion-icon name="arrow-up-outline" aria-hidden="true" />
73428
73519
  </button>
73429
- } @else if (showMic()) {
73430
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73431
- <ion-icon name="mic-outline" aria-hidden="true" />
73432
- </button>
73433
73520
  }
73434
73521
  </div>
73435
73522
  </div>