Skip to content

feat: add LiveWaveform component and mic/screen capture strategies#308

Open
abroa01 wants to merge 4 commits into
mainfrom
feat/live-waveform-and-capture-strategies
Open

feat: add LiveWaveform component and mic/screen capture strategies#308
abroa01 wants to merge 4 commits into
mainfrom
feat/live-waveform-and-capture-strategies

Conversation

@abroa01

@abroa01 abroa01 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What

Adds two reusable, brand-agnostic media primitives to @mieweb/ui:

  1. LiveWaveform — a real-time volume visualizer for an existing MediaStream.
  2. media/capture — a pluggable capture-strategy layer: createCapture(mode) returning MicrophoneCapture or ScreenCapture, plus the RecordingCapture / CaptureMode / CaptureResult types.

Minor, additive release: 0.6.1 → 0.7.0. No existing component is modified or removed.

Why

These were built in the Ozwell app because the library's existing AudioRecorder is a self-contained component that owns the microphone, buffers the whole recording in memory, and only hands back a finished Blob at the end. That model can't support Ozwell's recorder, which needs to:

  • stream encrypted audio chunks live (never buffer a full blob),
  • survive navigation (recording owned above the router, controlled from a floating dock),
  • capture screen as well as microphone.

Rather than keep these capabilities app-local, we're upstreaming the genuinely reusable, framework-agnostic pieces so any consumer can build a real recorder on top of the library — and so the app can delete its duplicates.

Gaps this fills in the library

Capability Before After
Visualize an external MediaStream ❌ (only a private AnalyserNode-based visualizer inside AudioRecorder) LiveWaveform
Screen capture (getDisplayMedia) ❌ none anywhere ScreenCapture
System + mic audio mixing / video-only fallback
Swap capture source without a component rewrite createCapture(mode)

Details

  • LiveWaveform takes a MediaStream you already have (so it can share a recorder's stream without contending for the device), draws vertically-centered rounded bars, is DPR-aware, and uses a ResizeObserver. Bar color defaults to the theme token --color-primary-500 (fallback #27aae1), so it adapts to the active brand with no config.
  • media/capture is pure TypeScript (no React), SSR-safe (MediaRecorder/navigator are only touched inside methods; MIME probing guards typeof MediaRecorder). ScreenCapture mixes display + mic audio into one track via an AudioContext and falls back to video-only when no audio track is granted.
  • Registers the browser globals these use (ResizeObserver, MediaStreamTrack, DOMException) in the ESLint allowlist.

Storybook (UI to review)

New story: Components/Images & Media/LiveWaveform with Default, CustomColor, and Idle. The demo builds a synthetic oscillator-backed stream behind a "Play tone" button, so the waveform animates in Storybook without requesting microphone permission. Existing AudioRecorder / AudioPlayer stories are unchanged.

media/capture is non-visual (no story) — it's a service layer.

Testing

  • pnpm typecheck
  • pnpm lint (new files) ✅
  • pnpm build ✅ — dist exports LiveWaveform, createCapture, CaptureMode, MicrophoneCapture, ScreenCapture
  • pnpm test ✅ 249 tests pass (no regressions)

Downstream

Consumed by the Ozwell app, which removes its local LiveWaveform and services/audio/capture/ copies and imports both from @mieweb/ui.

Adds two reusable, brand-agnostic media primitives extracted from the Ozwell recorder feature, where they were built in-app because the library's self-contained AudioRecorder (mic-only, buffers a final Blob) could not support a streaming, navigation-surviving, mic-or-screen recorder.

LiveWaveform: real-time volume visualizer that takes an existing MediaStream (unlike AudioRecorder's private AnalyserNode-based visualizer), so it can share a recorder's stream. Bar color defaults to the theme's --color-primary-500, adapting to any brand. Exported as a component + tree-shakeable subpath.

media/capture: a pluggable RecordingCapture strategy (createCapture) with MicrophoneCapture (getUserMedia) and ScreenCapture (getDisplayMedia + system/mic audio mixing via AudioContext, with a video-only fallback). Pure TS, SSR-safe. Fills a real gap: the library previously had no screen capture at all.

Also registers the browser globals (ResizeObserver, MediaStreamTrack, DOMException) these use in the ESLint allowlist. Minor/additive: bumps 0.6.1 -> 0.7.0.
Copilot AI review requested due to automatic review settings July 9, 2026 17:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new, reusable media primitives to @mieweb/ui: a React waveform visualizer for an external MediaStream, plus a TypeScript-only capture strategy layer for microphone and screen recording. This extends the library toward streaming/long-lived recording pipelines without modifying existing components.

Changes:

  • Adds LiveWaveform component with Storybook stories for default/custom/idle rendering.
  • Introduces media/capture strategies (MicrophoneCapture, ScreenCapture) and helpers (createCapture, MIME picking, error normalization).
  • Wires new exports/entries and updates lint globals + package version.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tsup.config.ts Adds a dedicated entrypoint for components/LiveWaveform builds.
src/media/capture/types.ts Defines CaptureMode, CaptureResult, and RecordingCapture contract.
src/media/capture/ScreenCapture.ts Implements display capture with optional mic capture and mixed audio via AudioContext.
src/media/capture/mime.ts Adds MIME probing helper and capture error normalization.
src/media/capture/MicrophoneCapture.ts Implements microphone-only capture strategy and MIME selection.
src/media/capture/index.ts Exposes createCapture plus public capture exports.
src/index.ts Re-exports LiveWaveform and media/capture from the package root.
src/components/LiveWaveform/LiveWaveform.tsx Implements the canvas-based analyser waveform renderer with DPR + ResizeObserver.
src/components/LiveWaveform/LiveWaveform.stories.tsx Adds Storybook stories including a synthetic oscillator-based demo stream.
src/components/LiveWaveform/index.ts Provides component entry export for tree-shaking.
package.json Bumps version 0.6.10.7.0.
eslint.config.js Allow-lists additional browser globals used by the new code.

Comment thread src/media/capture/mime.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploying ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: caf3796
Status: ✅  Deploy successful!
Preview URL: https://b3a16bb6.ui-6d0.pages.dev
Branch Preview URL: https://feat-live-waveform-and-captu.ui-6d0.pages.dev

View logs

typeof DOMException !== 'undefined' before instanceof, so toCaptureError doesn't throw a ReferenceError in SSR/Node/test environments and mask the original error. Addresses PR review feedback.
Copilot AI review requested due to automatic review settings July 9, 2026 17:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Comment thread src/components/LiveWaveform/LiveWaveform.tsx Outdated
Comment thread src/media/capture/ScreenCapture.ts
Copilot AI review requested due to automatic review settings July 9, 2026 17:48
…rror edge cases

LiveWaveform: feature-detect ResizeObserver; when unavailable, fall back to the one-time resize() already performed instead of throwing (older browsers, some webviews/test runtimes).

ScreenCapture: wrap audio mixing in try/catch — on failure, close the partial AudioContext and degrade to video-only instead of rejecting start() and leaving the already-granted screen share running. Also only report audioCaptured: true when the mixed destination actually yields audio tracks. Addresses PR review feedback.
@abroa01
abroa01 removed the request for review from Copilot July 9, 2026 17:53
@abroa01 abroa01 self-assigned this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants