@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-sdkNote: 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 usingjoinFromApiResponse)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 IDoptions.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 microphonepublishScreenShare()- Start screen sharingstopCamera()- Stop camera streamstopMicrophone()- Stop microphone streamstopScreenShare()- Stop screen sharingchangeCamera(deviceId)- Switch to different camerachangeMicrophone(deviceId)- Switch to different microphonemuteCamera()- Mute cameraunmuteCamera()- Unmute cameramuteMicrophone()- Mute microphoneunmuteMicrophone()- Unmute microphone
Device Management
getDevices()- Get available media devicesgetLocalStream(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 serverisInRoom()- Check if currently in a roomisCameraActive()- Check if camera is activeisMicrophoneActive()- Check if microphone is activeisCameraMuted()- Check if camera is mutedisMicrophoneMuted()- Check if microphone is mutedgetParticipantCount()- 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 serverdisconnected- Disconnected from servererror- Connection error
Room Events:
room:joined- Successfully joined roomroom:left- Left the roomparticipant:joined- New participant joinedparticipant:left- Participant leftparticipant:updated- Participant state changed
Media Events:
stream:added- New media stream availablestream:removed- Media stream removedlocal-stream:added- Local stream startedlocal-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 errorError 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 buildLicense
MIT
