Files
skills/docs/ui/react/react-audio-stream/src/session/stream-source-plugin/stream-source-plugin.md
2026-05-29 08:20:56 +01:00

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 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

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 — 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.