first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
# `stream-source-plugin`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```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.