# @suaveplan/react-audio-stream — hooks + components (chunk C) - **Target path**: `packages/ui/react/react-audio-stream/` - **Tier**: browser/react - **Date**: 2026-05-23 - **Status**: PROPOSED — chunk C of the 4-chunk `enterprise-react-audio-stream` split. ## Why Chunks A (session-core) and B (transports) deliver the headless engine — the FSM, transports, source-plugin bridge, error codes, telemetry. This chunk delivers the React surface that consumers actually touch: a Provider, 6 hooks, 4 accessible components. Transport-share concerns (the 7th hook and its component indicator) live in chunk D so this chunk stays disjoint. ## What changes 1. **``** — `src/provider/stream-provider.tsx` wires a `StreamSession` instance into React context. `sessionFactory` injectable (consumer creates the session with their chosen transport adapter); StrictMode-safe singleton; surfaces `'state-changed'`/`'interruption'` events into hook subscribers via a typed event bus. 2. **Hooks** (each in its own feature dir with `.ts` + `.types.ts` + `.test.tsx` + `.md` + `index.ts`): - `useStreamSession()` — returns the session + current FSM state + lifecycle methods (`connect`, `join`, `leave`, `disconnect`) - `useStreamRoom()` — returns `{ roomId, isJoined, joinedAt, participants }` derived from session state - `useStreamPeers()` — returns live `Peer[]` (id + display name + connection state + RTT) - `useStreamMixer()` — exposes per-peer `setVolume`, `setMute`, `setSolo` (DAW solo semantics — when any peer is soloed, every non-soloed peer's track-volume → 0); reads from each peer chain's `setTrackVolume`/`setTrackMute`/`setTrackSolo` - `useStreamLevels(peerId?)` — returns realtime peak/RMS for the local mic or the named peer; piggybacks on `@suaveplan/audio-analysis-spectrum`'s level computation via the peer chain's analyzer slot - `useStreamRecording()` — `start()`, `stop()` returning a `Blob`; mixes the active session output (or a subset of peers) into a single audio file via `audio-engine-browser`'s offline render - `useStreamPermissions()` — wraps `navigator.mediaDevices.getUserMedia` permission state with the typed `MediaPermissionState` enum 3. **Components** (each with `.tsx` + `.types.ts` + `.test.tsx` + `.stories.tsx` + `.md` + `index.ts`): - `` — controlled wrapper exposing room state + join/leave controls; render-prop pattern for layout flexibility - `` — per-peer mixer strip with level meter + mute/solo buttons + volume slider; delegates to `useStreamMixer()` - `` — record/stop button with duration display; delegates to `useStreamRecording()` - `` — permission state banner with explicit request button; ARIA-live for state changes 4. **Subpath exports** — `package.json` adds `./provider`, `./hooks`, `./hooks/use-stream-session` (and 5 more), `./components`, `./components/stream-room` (and 3 more). ## Requirements landed by this chunk From the original spec: - Per-peer stream mixer (full — `` + `useStreamMixer`) - Subpath exports for `./provider`, `./hooks/*`, `./components/*` ## Impact - ~35 files (6 hooks × 5 + 4 components × 6 + provider + barrel touches). - Imports `StreamSession`, `StreamSourcePlugin`, `ReactAudioStreamError` from chunk A — local-mirror types in tests if A hasn't merged. - Imports nothing from chunk B (consumers wire transports themselves via the session factory). - No spec fold / no archive — chunk D performs both.