# `stream-source-plugin` _(no submodules)_ ## Purpose `StreamSourcePlugin` is the bridge that lets a remote peer's `MediaStream` participate in the audio engine's plugin-chain on equal footing with any other source plugin (microphone, sampler, audio-player). Implementing the `AudioFxPlugin` contract from `@suaveplan/audio-fx-plugin-base` means the chain can be queried, mutated, and rendered without special-case code for peer sources — adding an EQ, compressor, or reverb to a peer's chain is the same `chain.add(plugin)` call as on any local track. Every materialised peer becomes its own `peer:` track with this plugin at slot 0. The plugin is intentionally a thin shell at chunk-A scope: it owns the `MediaStream` reference and outputs silence until the engine's source-stream adapter (chunk B) replaces the silent fallback with the live samples. Keeping the bridge inert at this scope is what allows `@suaveplan/react-audio-stream` to satisfy the stream-tier purity rule — there are no `AudioContext`, `AudioNode`, or `AudioWorklet` references in the package. ## Features - Implements `AudioFxPlugin` via `AbstractAudioFxPlugin` — every parameter/preset hook works out of the box. - `io.inputs[0].count === 0` per the source-plugin protocol, so the chain validator places it at slot 0. - `setSourceStream(stream)` / `getSourceStream()` for orchestration. - Zod-validated options + preset payloads; failures throw `REACT_AUDIO_STREAM.INVALID_OPTIONS`. - Silence fallback in `processBlock` keeps the chain numerically safe before the engine adapter takes over. - Participant id baked into plugin metadata for OTel + UI labelling. ## Usage: Basic ```ts import { createStreamSourcePlugin } from "@suaveplan/react-audio-stream/session"; const plugin = createStreamSourcePlugin({ participantId: "alice" }); plugin.setSourceStream(remoteAliceStream); ``` ## Usage: Advanced ```ts import { createStreamSourcePlugin, type StreamSourcePluginPort, } from "@suaveplan/react-audio-stream/session"; function attachPeer( participantId: string, stream: MediaStream, chain: PluginChain, ): StreamSourcePluginPort { const plugin = createStreamSourcePlugin({ participantId, initialStream: stream, outputChannelCount: 2, }); chain.add(plugin); // slot 0 — source plugin return plugin; } // Later, when the peer ends their stream: function detach(plugin: StreamSourcePluginPort): void { plugin.setSourceStream(undefined); } ``` ## API Reference - `StreamSourcePlugin` — `AbstractAudioFxPlugin` subclass implementing `StreamSourcePluginPort`. - `createStreamSourcePlugin(options): StreamSourcePlugin` — factory. - `StreamSourcePluginOptions` — `{ participantId, initialStream?, outputChannelCount? }`. - `StreamSourcePluginPort` — minimal surface `{ participantId, getSourceStream, setSourceStream }` exposed to orchestration code. ## Implementation Notes The plugin uses `unknown` rather than `MediaStream` as the stream type to stay strictly free of browser-globals at compile time — the package's TS config does not pull DOM lib references for non-test code, and the `unknown` shape lets chunk-B transports narrow at their own seam. The plugin's metadata id is suffixed with the participant id so OTel emissions keyed by plugin id distinguish per-peer rates even inside a single session. `processBlock` writes zeros into every output channel rather than allocating a fresh buffer because `AbstractAudioFxPlugin` already supplies fixed-length output arrays; this keeps the inner loop free of allocations under steady-state playback. The Zod boundary on options + preset matches the convention used by sibling source plugins (`audio-fx-plugin-microphone`) so consumers see consistent error shapes across the family.