Commit Graph

2 Commits

Author SHA1 Message Date
saravanakumardb1
b2e45380ec fix(media-ui): plug AudioContext leak + guard <img> against undefined src
Two latent bugs caught in the audit pass:

1. **AudioContext leak in AudioWaveform.**
   The lazy WebAudio decoder constructed an `AudioContext` per
   mount-with-audioUrl and never called `.close()`. Browsers cap the
   total per page (Chrome ~6, Firefox ~6) so a long-lived session that
   remounted waveforms enough times eventually hit
   `InvalidStateError: cannot create AudioContext` and silently
   stopped decoding.
   Fix: wrap `decodeAudioData` in try/finally and `close()` the
   context in the finally. Errors from close() are swallowed (best-
   effort cleanup).

2. **Undefined img src in ImageGenStream.**
   When `status=streaming` arrived before the first
   `snapshot.partialUrl` (very common during the SSE handshake),
   the component rendered <img src={undefined}> which browsers treat
   as a broken-image icon. Same for status=complete + missing
   finalUrl.
   Fix: compute `visibleSrc` once; only mount <img> if a source
   exists, otherwise show 'Waiting for first frame\u2026' placeholder.
   Also removed the dead `revealedAt` state \u2014 the prior 0.95/1
   opacity dance was imperceptible and contributed nothing.

3. **Bonus: AudioWaveform `bars` clamp.**
   `bars={0}` (or negative / fractional) divided by zero on the
   canvas slot math and rendered an empty waveform. Now clamped to
   `Math.max(1, Math.floor(barsProp))`.

Tests: 10 \u2192 12
  AudioWaveform   \u00b7 bars=0 doesn't crash + canvas still renders
  ImageGenStream  \u00b7 streaming without partialUrl shows placeholder
                    instead of <img src={undefined}>
2026-05-27 18:44:50 -07:00
saravanakumardb1
99e59597d1 feat(media-ui): @bytelyst/media-ui@0.1.0 — Wave 13.G.1/.2/.4
Media surface primitives — ImageGenStream, AudioWaveform, VideoPlayer.
Zero runtime deps (canvas + native <video> + native <img>); PdfPreview
deferred to 0.2.x where it'll lazy-load pdf.js.

──────────────────────────────────────────────────────────────────
<ImageGenStream>  ·  Wave 13.G.1
──────────────────────────────────────────────────────────────────
  - Driven entirely by props (status + snapshot + finalUrl)
  - 4 statuses: idle / streaming / complete / error
  - Streaming: blurred partial image + overlay progress bar +
    step label (e.g. 'denoising 24/50')
  - Complete: drops blur with fade-in
  - Error: muted error placeholder (token-tinted danger)
  - Host pipes any transport (SSE / WebSocket / polling) into
    the snapshot prop — pure presentation

<AudioWaveform>  ·  Wave 13.G.2
  - Canvas render with DPR-aware paint
  - Two sources: pre-computed peaks (cheapest) OR lazy WebAudio
    decode from audioUrl (falls back gracefully w/o AudioContext)
  - Click-to-seek (returns 0..1 position)
  - Progress overlay tints played bars with progressColor
  - Resampling helper handles arbitrary peak-count inputs

<VideoPlayer>  ·  Wave 13.G.4
  - Native <video controls> wrapper — accessible by default
  - Chapter buttons that seek + auto-play (silently swallows
    autoplay rejections)
  - Optional in-memory caption track (aria-live polite); hosts
    that prefer real WebVTT pass <track> via the slot prop
  - Token-tinted; rounded; muted poster background

──────────────────────────────────────────────────────────────────
Quality gates
──────────────────────────────────────────────────────────────────
  ✓ 10/10 tests passing
    - AudioWaveform: 3 cases (canvas size · click-seek math ·
      placeholder when no peaks)
    - ImageGenStream: 4 cases (idle / streaming / complete /
      error)
    - VideoPlayer: 3 cases (src · chapter button list · caption
      rail)
  ✓ tsc build clean
  ✓ Zero new runtime deps; all WebAudio access guarded for SSR

Showcase /futurism/multimodal (MAG.7) lands in the paired showcase
commit.
2026-05-27 17:44:13 -07:00