first commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# stream-provider
|
||||
|
||||
## Purpose
|
||||
|
||||
`<StreamProvider>` is the singleton React surface that owns the live `StreamSession` for
|
||||
a tree. It calls the caller-supplied `sessionFactory` exactly once per provider instance,
|
||||
subscribes to the seven session event channels (`state-changed`, `interruption`,
|
||||
`peers-changed`, `mixer-changed`, `levels-changed`, `recording-changed`,
|
||||
`permission-changed`), and increments a render-bump counter on every tick so descendant
|
||||
hooks observe a fresh snapshot of session state.
|
||||
|
||||
The factory pattern keeps `@suaveplan/react-audio-stream` transport-agnostic: the
|
||||
consumer decides whether the underlying transport is WebRTC, WebSocket, Trystero, or a
|
||||
test fake, and the provider never imports any transport directly. Cleanup on unmount
|
||||
calls `session.disconnect()` so the audio engine + socket are torn down deterministically.
|
||||
|
||||
## Features
|
||||
|
||||
- StrictMode-safe singleton via `useRef`-guarded lazy construction.
|
||||
- Caller-injected `sessionFactory` — zero transport coupling at provider tier.
|
||||
- Render-bump counter fans seven event channels into one re-render path.
|
||||
- Cleanup-on-unmount disconnects the session and detaches every listener.
|
||||
- Memoised context value — only the `version` field changes per tick.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { StreamProvider } from "@suaveplan/react-audio-stream/provider";
|
||||
import { createStreamSession } from "./my-session-factory";
|
||||
|
||||
export function App({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<StreamProvider sessionFactory={() => createStreamSession({ transport: "webrtc" })}>
|
||||
{children}
|
||||
</StreamProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```tsx
|
||||
import { StreamProvider } from "@suaveplan/react-audio-stream/provider";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function ScopedRoom({ roomId, children }: { roomId: string; children: React.ReactNode }) {
|
||||
const factory = useMemo(
|
||||
() => () => createStreamSession({ transport: "trystero", initialRoomId: roomId }),
|
||||
[roomId]
|
||||
);
|
||||
return <StreamProvider sessionFactory={factory}>{children}</StreamProvider>;
|
||||
}
|
||||
```
|
||||
|
||||
Keying the provider on `roomId` (`<StreamProvider key={roomId} …>`) forces a fresh
|
||||
session when the room changes, because the singleton is per-instance.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `StreamProviderProps` — `{ sessionFactory: StreamSessionFactory; children?: ReactNode }`
|
||||
- `StreamProvider(props)` — renders a `StreamReactContext.Provider` with `{ session, version }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The seven event subscriptions all funnel into a single `setVersion((v) => v + 1)` call,
|
||||
which collapses bursts into a single React render pass thanks to automatic batching. The
|
||||
disconnect call on unmount is wrapped in `Promise.resolve` so the same code path handles
|
||||
both sync and async session implementations, and the rejection branch swallows errors
|
||||
(any failure already surfaced as an `interruption` event before the listener detach).
|
||||
Reference in New Issue
Block a user