Simple one-to-one WebRTC video/voice and data channels
—
Add, remove, and manage audio/video streams with support for track-level operations, dynamic stream modification, and advanced WebRTC transceiver management.
Add and remove complete MediaStreams from the peer connection.
/**
* Add a MediaStream to the connection
* @param stream - MediaStream to add (from getUserMedia, etc.)
*/
peer.addStream(stream: MediaStream): void;
/**
* Remove a MediaStream from the connection
* @param stream - MediaStream to remove
*/
peer.removeStream(stream: MediaStream): void;Usage Examples:
const Peer = require('simple-peer');
// Get user media
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
// Create peer with initial stream
const peer = new Peer({
initiator: true,
stream: stream // Constructor option
});
// Or add stream after creation
const peer2 = new Peer();
peer2.addStream(stream);
// Remove stream later
setTimeout(() => {
peer.removeStream(stream);
}, 10000);
});
// Multiple streams
Promise.all([
navigator.mediaDevices.getUserMedia({ video: true }),
navigator.mediaDevices.getDisplayMedia({ video: true })
]).then(([cameraStream, screenStream]) => {
const peer = new Peer({
initiator: true,
streams: [cameraStream, screenStream] // Constructor option
});
// Or add individually
// peer.addStream(cameraStream);
// peer.addStream(screenStream);
});Manage individual MediaStreamTracks for fine-grained control over media.
/**
* Add a MediaStreamTrack to the connection
* @param track - MediaStreamTrack to add
* @param stream - MediaStream to attach the track to
*/
peer.addTrack(track: MediaStreamTrack, stream: MediaStream): void;
/**
* Remove a MediaStreamTrack from the connection
* @param track - MediaStreamTrack to remove
* @param stream - MediaStream the track was attached to
*/
peer.removeTrack(track: MediaStreamTrack, stream: MediaStream): void;
/**
* Replace a MediaStreamTrack with another track
* @param oldTrack - Track to replace
* @param newTrack - New track to use
* @param stream - MediaStream the old track was attached to
*/
peer.replaceTrack(oldTrack: MediaStreamTrack, newTrack: MediaStreamTrack, stream: MediaStream): void;Usage Examples:
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
const peer = new Peer({ initiator: true });
const videoTrack = stream.getVideoTracks()[0];
const audioTrack = stream.getAudioTracks()[0];
// Add individual tracks
peer.addTrack(videoTrack, stream);
peer.addTrack(audioTrack, stream);
// Replace video track (switch camera)
navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
}).then(newStream => {
const newVideoTrack = newStream.getVideoTracks()[0];
peer.replaceTrack(videoTrack, newVideoTrack, stream);
});
// Remove audio track (mute)
peer.removeTrack(audioTrack, stream);
});Advanced WebRTC transceiver management for complex media scenarios.
/**
* Add an RTCRtpTransceiver to the connection
* @param kind - Media kind (commonly 'audio' or 'video', but accepts any string)
* @param init - RTCRtpTransceiverInit options
*/
peer.addTransceiver(kind: string, init?: RTCRtpTransceiverInit): void;
interface RTCRtpTransceiverInit {
direction?: 'sendrecv' | 'sendonly' | 'recvonly' | 'inactive';
streams?: MediaStream[];
sendEncodings?: RTCRtpEncodingParameters[];
}Usage Examples:
const peer = new Peer({ initiator: true });
// Add receive-only transceiver
peer.addTransceiver('video', {
direction: 'recvonly'
});
// Add transceiver with specific streams
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
peer.addTransceiver('audio', {
direction: 'sendrecv',
streams: [stream]
});
});
// Add transceiver with encoding parameters
peer.addTransceiver('video', {
direction: 'sendonly',
sendEncodings: [
{ rid: 'high', maxBitrate: 1000000 },
{ rid: 'low', maxBitrate: 200000, scaleResolutionDownBy: 2 }
]
});Handle incoming media streams and tracks from the remote peer.
// Receive complete MediaStream
peer.on('stream', (stream: MediaStream) => void);
// Receive individual MediaStreamTrack
peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => void);Usage Examples:
const peer = new Peer();
// Handle incoming streams
peer.on('stream', stream => {
console.log('Received stream with', stream.getTracks().length, 'tracks');
// Display video stream
const video = document.querySelector('video');
if ('srcObject' in video) {
video.srcObject = stream;
} else {
video.src = URL.createObjectURL(stream); // Older browsers
}
video.play();
});
// Handle individual tracks
peer.on('track', (track, stream) => {
console.log('Received track:', track.kind, track.id);
if (track.kind === 'video') {
const video = document.createElement('video');
video.srcObject = new MediaStream([track]);
video.play();
document.body.appendChild(video);
} else if (track.kind === 'audio') {
const audio = document.createElement('audio');
audio.srcObject = new MediaStream([track]);
audio.play();
}
});Dynamically add, remove, and modify media streams during an active connection.
Usage Examples:
const peer1 = new Peer({ initiator: true });
const peer2 = new Peer();
// Setup signaling
peer1.on('signal', data => peer2.signal(data));
peer2.on('signal', data => peer1.signal(data));
// Start without media
peer1.on('connect', () => {
console.log('Connected - adding media later');
});
// Add media after connection
function addCamera() {
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
peer1.addStream(stream);
return stream;
}).then(stream => {
// Store for later removal
window.currentStream = stream;
});
}
// Remove media
function removeCamera() {
if (window.currentStream) {
peer1.removeStream(window.currentStream);
window.currentStream.getTracks().forEach(track => track.stop());
window.currentStream = null;
}
}
// Switch camera
function switchCamera() {
if (window.currentStream) {
const videoTrack = window.currentStream.getVideoTracks()[0];
navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
}).then(newStream => {
const newVideoTrack = newStream.getVideoTracks()[0];
peer1.replaceTrack(videoTrack, newVideoTrack, window.currentStream);
// Stop old track
videoTrack.stop();
// Update stream reference
window.currentStream.removeTrack(videoTrack);
window.currentStream.addTrack(newVideoTrack);
});
}
}
// Toggle audio
function toggleAudio(enabled) {
if (window.currentStream) {
const audioTrack = window.currentStream.getAudioTracks()[0];
if (audioTrack) {
audioTrack.enabled = enabled;
}
}
}Handle screen sharing streams with proper setup and cleanup.
Usage Examples:
const peer = new Peer({ initiator: true });
async function startScreenShare() {
try {
// Get screen share stream
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true
});
// Add to peer connection
peer.addStream(screenStream);
// Handle screen share ending
screenStream.getVideoTracks()[0].addEventListener('ended', () => {
console.log('Screen sharing ended');
peer.removeStream(screenStream);
// Optionally switch back to camera
return navigator.mediaDevices.getUserMedia({ video: true });
});
return screenStream;
} catch (err) {
console.error('Screen sharing failed:', err);
}
}
// Replace camera with screen share
async function switchToScreenShare() {
const cameraStream = window.currentCameraStream;
const screenStream = await startScreenShare();
if (cameraStream && screenStream) {
const cameraVideoTrack = cameraStream.getVideoTracks()[0];
const screenVideoTrack = screenStream.getVideoTracks()[0];
// Replace camera with screen
peer.replaceTrack(cameraVideoTrack, screenVideoTrack, cameraStream);
// Stop camera
cameraVideoTrack.stop();
}
}Handle media-related errors and track failures.
// Media-specific error codes
interface MediaError extends Error {
code: 'ERR_ADD_TRANSCEIVER' | 'ERR_SENDER_REMOVED' | 'ERR_SENDER_ALREADY_ADDED' |
'ERR_TRACK_NOT_ADDED' | 'ERR_REMOVE_TRACK' | 'ERR_UNSUPPORTED_REPLACETRACK';
}Usage Examples:
const peer = new Peer({ initiator: true });
peer.on('error', err => {
switch (err.code) {
case 'ERR_ADD_TRANSCEIVER':
console.error('Failed to add transceiver:', err.message);
break;
case 'ERR_SENDER_REMOVED':
console.error('Track sender was removed:', err.message);
break;
case 'ERR_SENDER_ALREADY_ADDED':
console.error('Track already added to stream:', err.message);
break;
case 'ERR_TRACK_NOT_ADDED':
console.error('Cannot remove track - was never added:', err.message);
break;
case 'ERR_UNSUPPORTED_REPLACETRACK':
console.error('replaceTrack not supported in this browser');
break;
}
});
// Safe track operations
function safeAddTrack(peer, track, stream) {
try {
peer.addTrack(track, stream);
return true;
} catch (err) {
console.error('Failed to add track:', err.message);
return false;
}
}
function safeReplaceTrack(peer, oldTrack, newTrack, stream) {
try {
peer.replaceTrack(oldTrack, newTrack, stream);
return true;
} catch (err) {
console.error('Failed to replace track:', err.message);
// Fallback: remove old and add new
try {
peer.removeTrack(oldTrack, stream);
peer.addTrack(newTrack, stream);
return true;
} catch (fallbackErr) {
console.error('Fallback also failed:', fallbackErr.message);
return false;
}
}
}