3.8 KiB
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:<participantId> 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
AudioFxPluginviaAbstractAudioFxPlugin— every parameter/preset hook works out of the box. io.inputs[0].count === 0per 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
processBlockkeeps the chain numerically safe before the engine adapter takes over. - Participant id baked into plugin metadata for OTel + UI labelling.
Usage: Basic
import { createStreamSourcePlugin } from "@suaveplan/react-audio-stream/session";
const plugin = createStreamSourcePlugin({ participantId: "alice" });
plugin.setSourceStream(remoteAliceStream);
Usage: Advanced
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—AbstractAudioFxPluginsubclass implementingStreamSourcePluginPort.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.