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

@unboundcx/video-sdk-client

v2.0.12

Published

Framework-agnostic WebRTC video meeting SDK powered by mediasoup

Readme

Video Meeting SDK

A standalone, framework-agnostic SDK for building video conferencing applications with mediasoup.

Features

  • 🎥 Camera, microphone, and screen sharing
  • 👥 Multi-party video conferencing
  • 🔄 Real-time media stream management
  • 🎨 Virtual backgrounds with blur and custom images (MediaPipe)
  • 📡 Simulcast for adaptive bitrate (3 quality layers)
  • 🎯 Simple, promise-based API
  • 📦 Framework agnostic (works with React, Vue, Svelte, vanilla JS)
  • 🔌 Socket.io + mediasoup integration
  • 🖼️ No UI dependencies - bring your own interface
  • ✅ Mac, Windows, Linux support (latest mediasoup v3.16+)

Installation

npm install @unboundcx/video-sdk

Note: This SDK connects to Unbound's managed video server infrastructure. You don't need to set up your own media servers.

Quick Start

Option 1: Using with Unbound SDK (Recommended)

import { VideoMeetingClient } from '@unboundcx/video-sdk';
import { UnboundSDK } from '@unboundcx/sdk';

// Initialize SDK
const sdk = new UnboundSDK('your-namespace', 'api.yourdomain.com');

// Initialize Video Client
const client = new VideoMeetingClient({ debug: true });

// Join using Unbound SDK API response
const joinResponse = await sdk.video.joinRoom('room-123', 'password', '[email protected]');
await client.joinFromApiResponse(joinResponse);

// Publish local media
const videoStream = await client.publishCamera({ resolution: '720p' });
await client.publishMicrophone();

// Listen for remote participants
client.on('stream:added', ({ participantId, stream, type }) => {
    const videoElement = document.getElementById(`video-${participantId}`);
    videoElement.srcObject = stream;
});

// Leave meeting
await client.leaveRoom();
await client.disconnect();

Option 2: Direct Integration (Custom Backend)

If you're building your own backend (not using Unbound's video server):

import { VideoMeetingClient } from '@unboundcx/video-sdk';

// Initialize - no serverUrl needed, will be provided by your join API
const client = new VideoMeetingClient({ debug: true });

// Your custom API should return the same format as Unbound SDK
const joinResponse = await yourApi.joinVideoRoom('room-123');
// joinResponse should include: { videoRoom, server, participant, authorization }

await client.joinFromApiResponse(joinResponse);

// Publish local camera
const videoStream = await client.publishCamera();

// Listen for remote participants
client.on('participant:joined', (participant) => {
    console.log('Participant joined:', participant.id);
});

client.on('stream:added', ({ participantId, stream, type }) => {
    const videoElement = document.getElementById(`video-${participantId}`);
    videoElement.srcObject = stream;
});

// Leave meeting
await client.leaveRoom();
await client.disconnect();

Note: This SDK is designed to work with Unbound's video server infrastructure. For custom backends, you'll need to implement compatible server-side APIs.

API Documentation

Constructor

new VideoMeetingClient(options)

Options:

  • serverUrl (string, optional) - WebSocket server URL (not required if using joinFromApiResponse)
  • debug (boolean, optional) - Enable debug logging

Methods

Connection

joinFromApiResponse(joinResponse) ⭐ Recommended

  • Joins a meeting using the response from api.video.joinRoom()
  • Automatically extracts server URL, auth token, and room info
  • Connects to video server and joins room in one call
  • Returns: Promise<Object> - Full join data including videoRoom, participant, server info

Example:

const joinResponse = await api.video.joinRoom('room-123', 'password', '[email protected]');
await client.joinFromApiResponse(joinResponse);

// Access join data
const videoRoom = client.getVideoRoom();
const participant = client.getCurrentParticipant();

connect(authToken)

  • Connect to server (manual method)
  • Returns: Promise<void>

disconnect()

  • Disconnect from server
  • Returns: Promise<void>

joinRoom(roomId, options?)

  • Join a meeting room (manual method, requires connect() first)
  • Returns: Promise<Object> - Room data

leaveRoom()

  • Leave current room
  • Returns: Promise<void>

Local Media

publishCamera(options?) - Start publishing camera

  • options.resolution - '480p', '720p', '1080p' (default: '720p')
  • options.frameRate - Frame rate (default: 24)
  • options.deviceId - Specific camera device ID
  • options.background - Virtual background options:
    • { type: 'blur', blurLevel: 8 } - Blur background
    • { type: 'image', imageUrl: '/path/to/image.jpg' } - Virtual background
    • { type: 'none' } - No effect (default)
  • options.simulcast - Enable simulcast (default: true for video)
  • Returns: Promise<MediaStream>

Example:

// Publish with blur background
await client.publishCamera({
  resolution: '720p',
  background: { type: 'blur', blurLevel: 8 }
});

// Publish with virtual background
await client.publishCamera({
  background: {
    type: 'image',
    imageUrl: '/images/backgrounds/office.jpg'
  }
});

updateCameraBackground(options) - Change background on active camera

  • options.type - 'none', 'blur', or 'image'
  • options.blurLevel - Blur amount in pixels (default: 8)
  • options.imageUrl - Background image URL
  • Returns: Promise<MediaStream>

Other Methods:

  • publishMicrophone(options?) - Start publishing microphone
  • publishScreenShare() - Start screen sharing
  • stopCamera() - Stop camera stream
  • stopMicrophone() - Stop microphone stream
  • stopScreenShare() - Stop screen sharing
  • changeCamera(deviceId) - Switch to different camera
  • changeMicrophone(deviceId) - Switch to different microphone
  • muteCamera() - Mute camera
  • unmuteCamera() - Unmute camera
  • muteMicrophone() - Mute microphone
  • unmuteMicrophone() - Unmute microphone

Device Management

  • getDevices() - Get available media devices
  • getLocalStream(type) - Get local stream by type

State and Info

  • getState() - Get current SDK state ('disconnected', 'connecting', 'connected', 'joining', 'in-room')
  • isConnected() - Check if connected to server
  • isInRoom() - Check if currently in a room
  • isCameraActive() - Check if camera is active
  • isMicrophoneActive() - Check if microphone is active
  • isCameraMuted() - Check if camera is muted
  • isMicrophoneMuted() - Check if microphone is muted
  • getParticipantCount() - Get number of remote participants

Join Data Access (when using joinFromApiResponse)

  • getVideoRoom() - Get video room info (id, name, friendlyName, waitingRoom, etc.)
  • getCurrentParticipant() - Get current participant info (id, name, email, isHost, etc.)
  • getServerInfo() - Get server info (url, serverId, socketPort)
  • getJoinData() - Get full join data (videoRoom, participant, server, authorization)

Events

client.on('event-name', (data) => {
    // Handle event
});

Connection Events:

  • connected - Connected to server
  • disconnected - Disconnected from server
  • error - Connection error

Room Events:

  • room:joined - Successfully joined room
  • room:left - Left the room
  • participant:joined - New participant joined
  • participant:left - Participant left
  • participant:updated - Participant state changed

Media Events:

  • stream:added - New media stream available
  • stream:removed - Media stream removed
  • local-stream:added - Local stream started
  • local-stream:removed - Local stream stopped

State Events:

  • state:changed - SDK state changed

Example: Complete Video Call

import { VideoMeetingClient } from '@yourcompany/video-sdk';

class VideoCall {
    constructor() {
        this.client = new VideoMeetingClient({
            serverUrl: 'wss://video.example.com',
            debug: true
        });

        this.setupEventListeners();
    }

    setupEventListeners() {
        // Handle remote streams
        this.client.on('stream:added', ({ participantId, stream, type }) => {
            this.addRemoteStream(participantId, stream, type);
        });

        this.client.on('stream:removed', ({ participantId, type }) => {
            this.removeRemoteStream(participantId, type);
        });

        // Handle participants
        this.client.on('participant:joined', (participant) => {
            this.addParticipantUI(participant);
        });

        this.client.on('participant:left', ({ participantId }) => {
            this.removeParticipantUI(participantId);
        });

        // Handle errors
        this.client.on('error', (error) => {
            console.error('SDK Error:', error);
            this.showErrorMessage(error.message);
        });
    }

    async start(roomId, authToken) {
        try {
            // Connect to server
            await this.client.connect(authToken);

            // Join room
            await this.client.joinRoom(roomId);

            // Start local media
            const videoStream = await this.client.publishCamera({
                resolution: '720p'
            });
            const audioStream = await this.client.publishMicrophone();

            // Display local video
            this.showLocalVideo(videoStream);

        } catch (error) {
            console.error('Failed to start call:', error);
            throw error;
        }
    }

    async toggleCamera() {
        if (this.client.isCameraMuted()) {
            await this.client.unmuteCamera();
        } else {
            await this.client.muteCamera();
        }
    }

    async toggleMicrophone() {
        if (this.client.isMicrophoneMuted()) {
            await this.client.unmuteMicrophone();
        } else {
            await this.client.muteMicrophone();
        }
    }

    async startScreenShare() {
        try {
            await this.client.publishScreenShare();
        } catch (error) {
            if (error.name === 'NotAllowedError') {
                console.log('Screen share permission denied');
            }
        }
    }

    async end() {
        await this.client.leaveRoom();
        await this.client.disconnect();
    }

    // UI methods (implement based on your framework)
    addRemoteStream(participantId, stream, type) { }
    removeRemoteStream(participantId, type) { }
    addParticipantUI(participant) { }
    removeParticipantUI(participantId) { }
    showLocalVideo(stream) { }
    showErrorMessage(message) { }
}

// Usage
const videoCall = new VideoCall();
await videoCall.start('room-123', 'auth-token-xyz');

Architecture

The SDK is composed of several managers:

  • MediasoupManager - Manages mediasoup Device and Transports
  • LocalMediaManager - Handles camera, microphone, screen share
  • RemoteMediaManager - Manages remote participant streams
  • ConnectionManager - Socket.io connection and signaling

State Machine

disconnected -> connecting -> connected -> joining -> in-room -> leaving -> connected
                     ↓                          ↓
                   error                      error

Error Handling

All errors are wrapped in custom error classes:

try {
    await client.publishCamera();
} catch (error) {
    if (error.name === 'DeviceNotFoundError') {
        console.log('Camera not found');
    } else if (error.name === 'PermissionDeniedError') {
        console.log('Camera permission denied');
    } else if (error.name === 'ConnectionError') {
        console.log('Connection failed');
    }
}

Development

# Install dependencies
npm install

# Run tests
npm test

# Build
npm run build

License

MIT