@edclass/ivs-providers
v1.3.12
Published
React providers and a publication-centric core for Amazon IVS Real-Time stages. **One API** — the old v1/v2/v3 layering is gone (1.0.0).
Keywords
Readme
@edclass/ivs-providers
React providers and a publication-centric core for Amazon IVS Real-Time stages. One API — the old v1/v2/v3 layering is gone (1.0.0).
Architecture
┌───────────────────────────────────────────────────────────┐
│ SURFACE PROVIDERS (src/providers.tsx) │
│ OwnStreamProvider · useViewerStage · CallStreamProvider │
└──────────────┬────────────────────────────┬───────────────┘
│ publish via │ join/state via
┌──────────────▼──────────────┐ ┌──────────▼───────────────┐
│ PUBLICATION (src/publication)│ │ STAGE ENGINE (src/stage) │
│ PublicationManager │ │ registry (refcounted │
│ PublisherPlane (single- │ │ acquire/release, token │
│ destination per source) │ │ rotation), engine hook, │
│ driver → stage registry │ │ EDIvsStage/Strategy │
└──────────────────────────────┘ └──────────────────────────┘
┌───────────────────────────────────────────────────────────┐
│ DEVICE (src/device, src/hooks) — capture + controls ONLY │
│ EDDeviceManagerProvider · useEDUserMedia/useEDDisplayMedia│
│ NEVER publishes. No stage pointers. │
└───────────────────────────────────────────────────────────┘Core rules:
- A capture source is published to AT MOST ONE stage (
PublisherPlane, keyed bysourceOwnerId). Moving it is end-before-publish — a camera can never be live on two stages, and ending a call can never leave the camera on the callee's stage. - The device layer never publishes. It captures (
userMedia.mediaStreamsingleton — tracks swapped in place, so gate onuserMedia.cameraOn, never on stream truthiness) and controls (mute/switch/screen-share prompt). - One stage ARN hosts BOTH participant groups (
user+displaytokens). Rosters are group-filtered internally; a surface only ever sees its own group's participants.
The three surfaces
OwnStreamProvider — resident broadcast (student uplink, teacher on-duty)
<OwnStreamProvider
joinData={joinData} // room + per-group stage configs
cameraStream={userMedia.cameraOn ? userMedia.mediaStream : null}
screenStream={screenStream} // gesture-provided; null = not sharing
shouldPublish={policy} // e.g. in-slot latch, on-duty, call-active
subscribe={false} // optional publish-only uplink; default true
screenSubscriptionFilter={fn} // optional display-roster policy; MUST be
// identity-stable (useCallback)
autoJoin={Boolean(joinData)}
sourceOwnerId={localUser.id} // ALWAYS the local user, never the room host
>Both subscription knobs COMPOSE across every surface holding the
registry-shared stage: the stage subscribes only while no holder passes
subscribe={false}, and a remote participant must pass every registered
screenSubscriptionFilter. Most-restrictive wins — a publish-only surface
denies subscriptions for all co-holders of that room's stages, so don't mount
one on a room another surface needs to watch.
useOwnStream() → { camera, screen, cameraPlane, screenPlane, onCall }.
camera/screen are stage values (connectState, publishState,
getParticipants, on/off, mute toggles, updateStreamsToPublish).
useViewerStage — subscribe-only
const user = useViewerStage(joinData, 'user')
const display = useViewerStage(joinData, 'display')Joins on data, auto-rejoins on disconnect, releases on unmount. No manual join/leave — never rebuild one (that's the flap class the rewrite killed).
CallStreamProvider — on-demand staff call
<CallStreamProvider
callData={callData ?? null} // the STUDENT's room while a call runs
cameraStream={userMedia.cameraOn ? userMedia.mediaStream : null}
screenStream={callScreenStream}
audioOnly={voiceOnly}
sourceId={staffUser.id} // bare owner id; same id as OwnStream for a
> // teacher = call auto-cuts/restores broadcastuseCallStream() → { call, screen, cameraPlane, screenPlane, active }.
Setting callData joins + publishes; clearing it stops the planes and leaves.
Unmount always stops the planes (no publication is ever left behind in the
singleton PublicationManager).
Device layer
<EDDeviceManagerProvider>
const { userMedia, displayMedia, stopDevices } = useEDDeviceManager()
await userMedia.startUserMedia() // capture camera+mic
const s = await displayMedia.startScreenShare() // gesture-driven promptJoin data shape
const joinData: EDIvsJoinData = {
roomId: 'your-room-id', // room identity (not necessarily a stage ARN)
ownerId: 'room-owner-id', // whose room this is (isOwner filters)
stageConfigs: {
user: { token, participantId, participantGroup: 'user' },
display: { token, participantId, participantGroup: 'display' },
},
}Extras
usePatrolSlot(wanted)— hard cap (12) on concurrent patrol/grid stage connections; a card only fetches join data while it holds a slot.PublicationManager.publicationsForStage(key)— the safeguarding "who is publishing to this stage" query.AudienceViewController(src/publication/audience.ts) — warm-standby primary/secondary presence rotation (shared teacher stages).
Testing
pnpm vitest run — pure unit tests, no mock frameworks (hand-rolled fakes).
pnpm build (= tsc -b && vite build) is the type gate; plain
tsc --noEmit does NOT check the referenced project.
