WebRTC Cheatsheet
Media Streams and Tracks
Use this WebRTC reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
MediaStream
A MediaStream is a container of MediaStreamTrack objects (video and/or audio). Streams flow from sources (camera, mic, screen, canvas, audio element) through RTCPeerConnection to remote peers.
// Get stream from getUserMedia const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); // Inspect the stream console.log(stream.id); // unique UUID console.log(stream.active); // true if any tracks are live console.log(stream.getTracks()); // all tracks (audio + video) console.log(stream.getVideoTracks()); // [MediaStreamTrack, …] console.log(stream.getAudioTracks()); // [MediaStreamTrack, …]
MediaStreamTrack
const [videoTrack] = stream.getVideoTracks(); const [audioTrack] = stream.getAudioTracks(); // Key properties videoTrack.id; // UUID videoTrack.kind; // 'video' | 'audio' videoTrack.label; // device label, e.g. 'Built-in FaceTime Camera' videoTrack.enabled; // mute/unmute without stopping videoTrack.muted; // true if track is muted by the source (read-only) videoTrack.readyState; // 'live' | 'ended' // Mute (sends silence/black frame) — does NOT release hardware audioTrack.enabled = false; // mute audioTrack.enabled = true; // unmute // Stop — releases hardware, readyState → 'ended' videoTrack.stop();
MediaStreamTrack Events
track.onmute = () => console.log('track muted remotely'); track.onunmute = () => console.log('track unmuted'); track.onended = () => console.log('track ended');
Building a Composite Stream
// Combine audio from one source with video from another const videoStream = await navigator.mediaDevices.getUserMedia({ video: true }); const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true }); const combined = new MediaStream([ ...videoStream.getVideoTracks(), ...audioStream.getAudioTracks(), ]);
Track Constraints and Settings
const [track] = stream.getVideoTracks(); // What the browser actually delivered const settings = track.getSettings(); // { width, height, frameRate, deviceId, facingMode, aspectRatio, … } // What the browser can deliver (range) const caps = track.getCapabilities(); // { width: { min, max }, height: { min, max }, frameRate: { min, max }, … } // What constraints were applied const constraints = track.getConstraints(); // Apply new constraints at runtime await track.applyConstraints({ frameRate: { max: 15 } }); await track.applyConstraints({ width: 640, height: 480 });
Adding / Removing Tracks
// Add a track to the peer connection const sender = pc.addTrack(videoTrack, stream); // Remove (triggers renegotiation via onnegotiationneeded) pc.removeTrack(sender); // Swap track without renegotiation await sender.replaceTrack(newTrack); // Pass null to "black-hole" the sender without removing it await sender.replaceTrack(null);
Attaching Streams to HTML Elements
// Video element const videoEl = document.querySelector('video'); videoEl.srcObject = stream; videoEl.autoplay = true; videoEl.playsInline = true; // required on iOS videoEl.muted = true; // muted autoplays; unmuted requires gesture // Audio element const audioEl = document.querySelector('audio'); audioEl.srcObject = audioStream; audioEl.play(); // Mirror local camera (flip horizontally with CSS) videoEl.style.transform = 'scaleX(-1)';
Streams from Non-Camera Sources
Canvas as a Video Source
const canvas = document.getElementById('myCanvas'); const canvasStream = canvas.captureStream(30); // 30 fps const [canvasTrack] = canvasStream.getVideoTracks(); pc.addTrack(canvasTrack, canvasStream);
Audio Element as a Source
const audioCtx = new AudioContext(); const source = audioCtx.createMediaElementSource(document.getElementById('audio')); const dest = audioCtx.createMediaStreamDestination(); source.connect(dest); const [processedAudioTrack] = dest.stream.getAudioTracks(); pc.addTrack(processedAudioTrack, dest.stream);
Web Audio API Pipeline
const audioCtx = new AudioContext(); const micSource = audioCtx.createMediaStreamSource(stream); // Add effects const gainNode = audioCtx.createGain(); gainNode.gain.value = 1.5; const dest = audioCtx.createMediaStreamDestination(); micSource.connect(gainNode).connect(dest); const processedStream = dest.stream;
MediaStreamTrackGenerator (Insertable Streams)
// Transform video frames before sending const { readable, writable } = new MediaStreamTrackProcessor({ track: videoTrack }); const { readable: outReadable, writable: outWritable } = new MediaStreamTrackGenerator({ kind: 'video' }); const transformer = new TransformStream({ async transform(videoFrame, controller) { // Manipulate the frame controller.enqueue(videoFrame); videoFrame.close(); } }); readable.pipeThrough(transformer).pipeTo(outWritable); // Send the processed track pc.addTrack(outReadable.getReader().read().value, stream);
RTCRtpSender — Encoding Control
const sender = pc.getSenders().find(s => s.track?.kind === 'video'); // Read current encoding parameters const params = sender.getParameters(); // Adjust bitrate and framerate params.encodings[0].maxBitrate = 1_000_000; // 1 Mbps params.encodings[0].maxFramerate = 24; params.encodings[0].scaleResolutionDownBy = 1; // 1 = full resolution, 2 = half // Priority hint params.encodings[0].networkPriority = 'high'; // 'very-low'|'low'|'medium'|'high' params.encodings[0].priority = 'high'; await sender.setParameters(params);
RTCRtpReceiver — Received Track Access
pc.ontrack = ({ track, streams, receiver }) => {
// track — the MediaStreamTrack
// streams — associated MediaStream array
// receiver — RTCRtpReceiver
remoteVideo.srcObject = streams[0];
// Read receiver stats
receiver.getStats().then(stats => stats.forEach(r => console.log(r)));
track.onunmute = () => {
// Remote unmuted their track
remoteVideo.play();
};
};Checking Track Quality / Stats
const statsReport = await pc.getStats(); statsReport.forEach(report => { if (report.type === 'inbound-rtp' && report.kind === 'video') { console.log({ fps: report.framesPerSecond, decoded: report.framesDecoded, dropped: report.framesDropped, width: report.frameWidth, height: report.frameHeight, packetsLost: report.packetsLost, jitter: report.jitter, freezeCount: report.freezeCount, freezeDuration: report.totalFreezesDuration, }); } });
Common Media Stream Gotchas
track.enabled = falsemutes by sending silence/black;track.stop()terminates the track — stopped tracks cannot be restarted.stream.activeistrueas long as any track is live; once all tracks end, it becomesfalse.- Tracks survive streams — a track can be in multiple streams simultaneously; stopping the stream object does nothing — stop the tracks.
- Remote tracks start muted until data arrives; wait for
track.onunmutebefore relying onvideoEl.videoWidth. - Canvas
captureStream(0)—0fps means frames only onrequestAnimationFrame; use a positive value for continuous output. replaceTrack(null)keeps the sender but sends nothing; use it to temporarily pause without SDP renegotiation.- iOS Safari requires
playsInline,muted, andautoplayon<video>for local preview, and a user tap to start audio on remote.