npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@truncatetechnologies/streamsdk

v1.0.9

Published

Drop-in video call and live streaming SDK for React

Readme

StreamSDK — React / Web

npm version license bundle size

Drop-in React SDK for real-time video calls and live streaming.


Features

  • Video calls — multi-participant SFU rooms via Mediasoup + Socket.IO
  • Live streaming — WHIP publisher and WHEP viewer over WebRTC
  • Spotlight layout — pinned participant > stable speaker lock (3s, 700ms debounce) > first participant
  • Simulcast — 3 encoding layers at 100 k / 300 k / 900 k bps
  • High-quality audio — Opus stereo, FEC, DTX, 48 kHz; echoCancellation + noiseSuppression + autoGainControl
  • In-call chat — text messages, location sharing, image upload (auto-compressed to 800 px)
  • Real-time stream interactions — floating emoji reactions (SSE), admin chat, stream settings
  • Auto-reconnect — exponential backoff (3–10 s), up to 10 retries, 8 s track-arrival timeout
  • Headless hooks — bring your own UI with useVideoCall, usePublisher, useViewer, useStreamInteraction
  • Drop-in componentsVideoCallRoom, LivePublisher, LiveViewer with full built-in UI
  • JWT-embedded config — server URL, media URL, and ICE servers can be embedded in your auth token

Installation

npm install @truncatetechnologies/streamsdk

Install peer dependencies based on which features you use:

# Video calls (VideoCallRoom / useVideoCall)
npm install react mediasoup-client socket.io-client

# Live streaming only (LivePublisher, LiveViewer, usePublisher, useViewer)
npm install react

| Peer dependency | Version | Required for | |---|---|---| | react | >= 17.0.0 | All features | | mediasoup-client | >= 3.0.0 | Video calls | | socket.io-client | >= 4.0.0 | Video calls |


Quick Start

1. Video call room (drop-in)

import { StreamSDKClient, VideoCallRoom } from '@truncatetechnologies/streamsdk';

const client = new StreamSDKClient({
  token: 'your-jwt-from-server',
  serverUrl: 'https://api.streamsdk.io',
});

export default function CallPage() {
  return (
    <VideoCallRoom
      client={client}
      roomId="room-abc123"
      displayName="Alice"
      onLeave={() => window.location.href = '/'}
    />
  );
}

2. Live publisher (drop-in)

import { StreamSDKClient, LivePublisher } from '@truncatetechnologies/streamsdk';

const client = new StreamSDKClient({
  token: 'your-jwt-from-server',
  serverUrl: 'https://api.streamsdk.io',
});

export default function BroadcastPage() {
  return (
    <LivePublisher
      client={client}
      streamId="my-stream-id"
      onEnd={() => window.location.href = '/dashboard'}
    />
  );
}

3. Live viewer (drop-in)

import { StreamSDKClient, LiveViewer } from '@truncatetechnologies/streamsdk';

const client = new StreamSDKClient({
  token: 'your-jwt-from-server',
  serverUrl: 'https://api.streamsdk.io',
});

export default function WatchPage() {
  return (
    <LiveViewer
      client={client}
      streamId="my-stream-id"
    />
  );
}

Headless Hooks

All drop-in components are built on top of headless hooks. Use the hooks directly when you need full control over the UI.

useVideoCall

import { useVideoCall } from '@truncatetechnologies/streamsdk';

function MyCallUI({ client, roomId }) {
  const {
    localRef, localStream, remoteStreams,
    micOn, camOn, speaking, muteStatus,
    toggleMic, toggleCam, leaveCall,
    messages, unreadCount, markChatOpen,
    sendTextMessage, sendLocation, sendImage,
    connected, error,
  } = useVideoCall(client, roomId, { displayName: 'Alice', enableChat: true });

  return (
    <div>
      <video ref={localRef} autoPlay playsInline muted />
      {Object.entries(remoteStreams).map(([id, stream]) => (
        <RemoteVideo key={id} stream={stream} speaking={speaking[id]} />
      ))}
      <button onClick={toggleMic}>{micOn ? 'Mute' : 'Unmute'}</button>
      <button onClick={toggleCam}>{camOn ? 'Stop video' : 'Start video'}</button>
      <button onClick={leaveCall}>Leave</button>
    </div>
  );
}

usePublisher

import { usePublisher } from '@truncatetechnologies/streamsdk';

function MyPublisherUI({ client, streamId }) {
  const {
    videoRef, status, loading, elapsed,
    micOn, camOn,
    startStream, stopStream, toggleMic, toggleCam,
  } = usePublisher(client, streamId);

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />
      <p>Status: {status} — {elapsed}s</p>
      {status !== 'live'
        ? <button onClick={startStream} disabled={loading}>Go Live</button>
        : <button onClick={stopStream}>End Stream</button>
      }
    </div>
  );
}

useViewer

import { useViewer } from '@truncatetechnologies/streamsdk';

function MyViewerUI({ client, streamId }) {
  const {
    videoRef, containerRef, status, elapsed,
    muted, isFullscreen,
    toggleMute, toggleFullscreen,
  } = useViewer(client, streamId);

  return (
    <div ref={containerRef}>
      <video ref={videoRef} autoPlay playsInline muted />
      <p>Status: {status}</p>
      <button onClick={toggleMute}>{muted ? 'Unmute' : 'Mute'}</button>
      <button onClick={toggleFullscreen}>Fullscreen</button>
    </div>
  );
}

useStreamInteraction

import { useStreamInteraction } from '@truncatetechnologies/streamsdk';

function StreamOverlay({ client, streamId }) {
  const {
    chatMessages, floatingReactions, settings,
    sendReaction, sendChat, updateSettings,
  } = useStreamInteraction(client, streamId);

  return (
    <div>
      {floatingReactions.map(r => (
        <span key={r.id} style={{ left: `${r.x}%` }}>{r.emoji}</span>
      ))}
      <button onClick={() => sendReaction('👍')}>React</button>
      <button onClick={() => sendChat('Hello viewers!')}>Send chat</button>
    </div>
  );
}

StreamSDKClient

Configure once and pass to any hook or component.

import { StreamSDKClient } from '@truncatetechnologies/streamsdk';

const client = new StreamSDKClient({
  token: 'your-jwt',
  serverUrl: 'https://api.streamsdk.io',
  mediaUrl: 'https://stream.streamsdk.io',
  streamKey: 'optional-ingest-key',
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});

Constructor options

| Prop | Type | Default | Description | |---|---|---|---| | token | string | '' | JWT auth token. Config embedded in the token payload takes the lowest precedence (see JWT token config). | | serverUrl | string | 'https://api.streamsdk.io' | Base URL for the API server (signaling, WHIP, WHEP, SSE). Also accepted as apiUrl (alias). | | mediaUrl | string | 'https://stream.streamsdk.io' | Base URL for the media server. Currently reserved for future use. | | streamKey | string | '' | Optional ingest key sent as the x-stream-key header on WHIP requests. | | iceServers | RTCIceServer[] | [{ urls: 'stun:stun.l.google.com:19302' }] | ICE server configuration passed to every RTCPeerConnection. |


API Reference

useVideoCall return values

| Value | Type | Description | |---|---|---| | localRef | React.RefObject<HTMLVideoElement> | Attach to the local <video> element. | | localStream | MediaStream \| null | The local camera/microphone stream. | | remoteStreams | { [peerId: string]: MediaStream } | Map of remote peer IDs to their streams. | | micOn | boolean | Whether the local microphone is active. | | camOn | boolean | Whether the local camera is active. | | speaking | { [peerId: string]: boolean } | Real-time speaking state per peer. 700 ms on-threshold, 1800 ms off-threshold. | | muteStatus | { [peerId: string]: boolean } | Mute state broadcast by each remote peer. | | toggleMic | () => void | Toggle local microphone on/off. | | toggleCam | () => void | Toggle local camera on/off. | | leaveCall | () => void | Stop all tracks and disconnect from the room. | | messages | Message[] | In-call chat history. Each message has type, text, senderName, timestamp, isMe. | | unreadCount | number | Number of unread messages received while chat is closed. | | markChatOpen | (open: boolean) => void | Notify the hook that the chat panel is open/closed (resets unread count). | | sendTextMessage | (text: string) => void | Broadcast a text message to all participants. | | sendLocation | () => void | Request the browser's geolocation and broadcast coordinates. | | sendImage | (file: File) => void | Compress and broadcast an image (resized to max 800 px, JPEG 75%). | | connected | boolean | Whether the SFU transport is fully established. | | error | Error \| null | Set if the boot sequence fails (e.g. camera denied, SFU unreachable). |

Options passed as the third argument to useVideoCall(client, roomId, options):

| Option | Type | Default | Description | |---|---|---|---| | displayName | string | '' | Display name shown to other participants. | | enableChat | boolean | true | Subscribe to and emit chat events. Set to false to disable all chat functionality. |


usePublisher return values

| Value | Type | Description | |---|---|---| | videoRef | React.RefObject<HTMLVideoElement> | Attach to the publisher's <video> element (local preview, mirrored). | | status | 'idle' \| 'starting' \| 'live' \| 'error' | Current stream state. | | loading | boolean | true while the WHIP handshake is in progress. | | elapsed | number | Seconds elapsed since the stream went live. Resets to 0 when stopped. | | micOn | boolean | Whether the microphone track is enabled. | | camOn | boolean | Whether the camera track is enabled. | | startStream | () => Promise<void> | Acquire media, negotiate WHIP, and set status to 'live'. | | stopStream | () => Promise<void> | Send WHIP DELETE, stop tracks, and reset to 'idle'. | | toggleMic | () => void | Enable/disable the audio track without renegotiating. | | toggleCam | () => void | Enable/disable the video track without renegotiating. |


useViewer return values

| Value | Type | Description | |---|---|---| | videoRef | React.RefObject<HTMLVideoElement> | Attach to the viewer's <video> element. | | containerRef | React.RefObject<HTMLDivElement> | Attach to the container element for fullscreen requests. | | status | 'connecting' \| 'live' \| 'waiting' \| 'reconnecting' \| 'error' | Current playback state. | | elapsed | number | Seconds elapsed since the stream became live. Resets on reconnect. | | muted | boolean | Whether the video element is muted. Starts true (required for autoplay). | | isFullscreen | boolean | Whether the container is currently in fullscreen mode. | | toggleMute | () => void | Toggle audio mute on the video element. | | toggleFullscreen | () => void | Enter or exit fullscreen using the Fullscreen API. |

Reconnect behavior: exponential backoff starting at 3 s, capped at 10 s, maximum 10 retries. A 8 s timer fires if the WebRTC connection opens but no media tracks arrive, triggering an automatic reconnect.


useStreamInteraction return values

| Value | Type | Description | |---|---|---| | chatMessages | ChatMessage[] | Admin chat messages received via SSE. Each has id, message, senderName, timestamp. Capped at the last 50. | | floatingReactions | FloatingReaction[] | Active floating emoji animations. Each has id, emoji, x (CSS % from left). Auto-removed after 3.5 s. | | settings | { reactionsEnabled: boolean } | Stream settings synced in real time from the server. | | sendReaction | (emoji: string) => void | POST an emoji reaction. Visible to all viewers as a floating animation. | | sendChat | (message: string) => void | POST a chat message (publisher/admin only). Appears to all viewers. | | updateSettings | (patch: Partial<Settings>) => void | PATCH stream settings (publisher/admin only). Changes broadcast to all viewers immediately. |


Component Props

VideoCallRoom

<VideoCallRoom
  client={client}
  roomId="room-abc123"
  displayName="Alice"
  theme="dark"
  chat={true}
  onLeave={() => {}}
/>

| Prop | Type | Default | Description | |---|---|---|---| | client | StreamSDKClient | required | Configured client instance. | | roomId | string | required | Unique room identifier. All participants with the same ID join the same call. | | displayName | string | '' | Name shown on your tile and sent in chat messages. | | theme | 'dark' \| 'light' | 'dark' | Background color scheme. | | chat | boolean | true | Show the in-call chat panel and controls. | | onLeave | () => void | window.history.back() | Called after the local user leaves the call. |

Layout behavior:

  • 1 participant: full-screen single tile.
  • 2 participants: remote participant large, local in a picture-in-picture overlay (tap/click to swap).
  • 3+ participants: spotlight tile (pinned > stable speaker > first) with a scrollable filmstrip.

LivePublisher

<LivePublisher
  client={client}
  streamId="my-stream-id"
  onEnd={() => {}}
/>

| Prop | Type | Default | Description | |---|---|---|---| | client | StreamSDKClient | required | Configured client instance. | | streamId | string | required | Unique stream identifier. Sent to POST /stream/whip/{streamId}. | | onEnd | () => void | undefined | Called after the stream is stopped and the WHIP session is deleted. |

Protocol details: H264 codec, max bitrate 1.5 Mbps, 30 fps. Sends x-stream-key header if client.streamKey is set.


LiveViewer

<LiveViewer
  client={client}
  streamId="my-stream-id"
/>

| Prop | Type | Default | Description | |---|---|---|---| | client | StreamSDKClient | required | Configured client instance. | | streamId | string | required | Stream to subscribe to. Sent to POST /stream/whep/{streamId}. |

Status overlays: Shows a spinner for connecting and reconnecting states, a waiting indicator when the stream is offline (404), and an error message if all retries are exhausted.


JWT Token Config

StreamSDKClient reads configuration from the JWT payload so you can provision tokens server-side without hardcoding URLs in your frontend.

Embed a config object in the token's first segment (before signing):

// Server-side token payload
{
  "sub": "user-123",
  "exp": 1800000000,
  "config": {
    "apiUrl": "https://api.your-domain.com",
    "mediaUrl": "https://stream.your-domain.com",
    "iceServers": [
      { "urls": "stun:stun.your-domain.com:3478" },
      {
        "urls": "turn:turn.your-domain.com:3478",
        "username": "user",
        "credential": "pass"
      }
    ]
  }
}

Then initialize the client with only the token:

const client = new StreamSDKClient({ token: tokenFromYourServer });
// client.serverUrl  → "https://api.your-domain.com"
// client.mediaUrl   → "https://stream.your-domain.com"
// client.iceServers → [...] from token

Precedence (highest to lowest):

  1. Constructor props (serverUrl, mediaUrl, iceServers)
  2. config.* fields inside the JWT payload
  3. Built-in defaults (https://api.streamsdk.io, https://stream.streamsdk.io, Google STUN)

TypeScript

The SDK ships as compiled JavaScript (ESM + CJS). TypeScript declaration files (.d.ts) are on the roadmap. In the meantime, you can add inline @ts-ignore comments or use JSDoc types where needed.

See https://streamsdk.io for the latest type information as it becomes available.


Links


MIT License. Copyright (c) Truncate Technologies.