How to Download

How to Download WebRTC Video Streams

Protocol: WebRTC Transport: RTP/RTCP over UDP/TCP Signaling: WebSocket, SIP, custom protocols Applications: Video calls, live streaming, peer-to-peer communication

Devin Schumacher
15 min read

How to Download WebRTC Video Streams

Protocol: WebRTC
Transport: RTP/RTCP over UDP/TCP
Signaling: WebSocket, SIP, custom protocols
Applications: Video calls, live streaming, peer-to-peer communication

Overview

WebRTC (Web Real-Time Communication) is an open-source project that enables real-time communication of audio, video, and data in web browsers and mobile applications. Unlike traditional streaming protocols like HLS/M3U8, DASH, or RTMP, WebRTC provides direct peer-to-peer communication with minimal latency, making it ideal for video calls and interactive live streaming.

WebRTC Architecture

Core Components

// WebRTC connection components
const webrtcComponents = {
  // Media capture
  getUserMedia: 'Access camera/microphone',
  getDisplayMedia: 'Screen sharing',
  
  // Peer connection
  RTCPeerConnection: 'Main connection interface',
  RTCDataChannel: 'Data transfer channel',
  
  // Signaling
  RTCSessionDescription: 'SDP offer/answer',
  RTCIceCandidate: 'Network path discovery'
};

// Basic WebRTC setup
async function setupWebRTC() {
  // Get user media
  const stream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
  });
  
  // Create peer connection
  const peerConnection = new RTCPeerConnection({
    iceServers: [
      { urls: 'stun:stun.l.google.com:19302' }
    ]
  });
  
  // Add stream to connection
  stream.getTracks().forEach(track => {
    peerConnection.addTrack(track, stream);
  });
  
  return { stream, peerConnection };
}

Detection Methods

1. WebRTC API Monitoring

// Monitor WebRTC usage on page
class WebRTCMonitor {
  constructor() {
    this.connections = new Map();
    this.streams = new Map();
    this.setupMonitoring();
  }
  
  setupMonitoring() {
    // Monitor RTCPeerConnection creation
    const originalRTCPeerConnection = window.RTCPeerConnection;
    
    window.RTCPeerConnection = function(configuration) {
      const connection = new originalRTCPeerConnection(configuration);
      const connectionId = Date.now().toString() + Math.random().toString(36);
      
      console.log('WebRTC PeerConnection created:', connectionId, configuration);
      
      this.connections.set(connectionId, {
        connection,
        configuration,
        createdAt: new Date(),
        tracks: [],
        candidates: []
      });
      
      this.instrumentPeerConnection(connection, connectionId);
      
      return connection;
    }.bind(this);
    
    // Monitor getUserMedia
    this.monitorMediaDevices();
    
    // Monitor screen sharing
    this.monitorScreenShare();
  }
  
  instrumentPeerConnection(connection, connectionId) {
    const connectionInfo = this.connections.get(connectionId);
    
    // Monitor track addition
    const originalAddTrack = connection.addTrack;
    connection.addTrack = function(track, stream) {
      console.log('Track added to WebRTC connection:', track.kind, track.label);
      connectionInfo.tracks.push({
        track,
        stream,
        addedAt: new Date()
      });
      return originalAddTrack.call(this, track, stream);
    };
    
    // Monitor ICE candidates
    connection.addEventListener('icecandidate', (event) => {
      if (event.candidate) {
        connectionInfo.candidates.push(event.candidate);
        console.log('ICE candidate:', event.candidate.candidate);
      }
    });
    
    // Monitor connection state
    connection.addEventListener('connectionstatechange', () => {
      console.log('WebRTC connection state:', connection.connectionState);
      connectionInfo.state = connection.connectionState;
    });
    
    // Monitor data channels
    connection.addEventListener('datachannel', (event) => {
      console.log('Data channel created:', event.channel.label);
      this.instrumentDataChannel(event.channel, connectionId);
    });
  }
  
  monitorMediaDevices() {
    const originalGetUserMedia = navigator.mediaDevices.getUserMedia;
    
    navigator.mediaDevices.getUserMedia = async function(constraints) {
      console.log('getUserMedia called with constraints:', constraints);
      
      const stream = await originalGetUserMedia.call(this, constraints);
      
      const streamId = stream.id;
      this.streams.set(streamId, {
        stream,
        constraints,
        createdAt: new Date(),
        tracks: stream.getTracks().map(track => ({
          kind: track.kind,
          label: track.label,
          settings: track.getSettings()
        }))
      });
      
      console.log('Media stream created:', streamId, stream.getTracks().length, 'tracks');
      
      return stream;
    }.bind(this);
  }
  
  monitorScreenShare() {
    if (!navigator.mediaDevices.getDisplayMedia) return;
    
    const originalGetDisplayMedia = navigator.mediaDevices.getDisplayMedia;
    
    navigator.mediaDevices.getDisplayMedia = async function(constraints) {
      console.log('Screen sharing started with constraints:', constraints);
      
      const stream = await originalGetDisplayMedia.call(this, constraints);
      
      this.streams.set(stream.id, {
        stream,
        constraints,
        type: 'screen-share',
        createdAt: new Date()
      });
      
      console.log('Screen share stream created:', stream.id);
      
      return stream;
    }.bind(this);
  }
  
  instrumentDataChannel(channel, connectionId) {
    const originalSend = channel.send;
    
    channel.send = function(data) {
      console.log('Data sent via WebRTC:', data.length || data.size || data.toString().length, 'bytes');
      return originalSend.call(this, data);
    };
    
    channel.addEventListener('message', (event) => {
      console.log('Data received via WebRTC:', event.data.length || event.data.size, 'bytes');
    });
  }
  
  getActiveConnections() {
    const active = [];
    
    this.connections.forEach((info, id) => {
      if (info.connection.connectionState === 'connected') {
        active.push({
          id,
          state: info.connection.connectionState,
          tracks: info.tracks.length,
          candidates: info.candidates.length,
          createdAt: info.createdAt
        });
      }
    });
    
    return active;
  }
  
  getActiveStreams() {
    const active = [];
    
    this.streams.forEach((info, id) => {
      const stream = info.stream;
      if (stream.active) {
        active.push({
          id,
          type: info.type || 'camera',
          tracks: stream.getTracks().map(track => ({
            kind: track.kind,
            enabled: track.enabled,
            muted: track.muted,
            readyState: track.readyState
          })),
          createdAt: info.createdAt
        });
      }
    });
    
    return active;
  }
}

// Initialize WebRTC monitoring
const webrtcMonitor = new WebRTCMonitor();

2. SDP Analysis

// Analyze SDP (Session Description Protocol) offers/answers
function analyzeSDP(sdpString) {
  const lines = sdpString.split('\n');
  const analysis = {
    mediaTypes: [],
    codecs: [],
    iceServers: [],
    candidates: []
  };
  
  lines.forEach(line => {
    // Media types (m=video, m=audio, m=application)
    if (line.startsWith('m=')) {
      const parts = line.split(' ');
      analysis.mediaTypes.push({
        type: parts[0].substring(2), // Remove 'm='
        port: parts[1],
        protocol: parts[2]
      });
    }
    
    // Codecs (a=rtpmap)
    if (line.startsWith('a=rtpmap:')) {
      const codecInfo = line.substring(9); // Remove 'a=rtpmap:'
      analysis.codecs.push(codecInfo);
    }
    
    // ICE candidates (a=candidate)
    if (line.startsWith('a=candidate:')) {
      const candidate = line.substring(12); // Remove 'a=candidate:'
      analysis.candidates.push(candidate);
    }
  });
  
  return analysis;
}

// Monitor SDP in WebRTC connections
function monitorSDP() {
  const originalCreateOffer = RTCPeerConnection.prototype.createOffer;
  const originalCreateAnswer = RTCPeerConnection.prototype.createAnswer;
  const originalSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription;
  const originalSetRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription;
  
  RTCPeerConnection.prototype.createOffer = async function(options) {
    const offer = await originalCreateOffer.call(this, options);
    console.log('WebRTC Offer SDP:', analyzeSDP(offer.sdp));
    return offer;
  };
  
  RTCPeerConnection.prototype.createAnswer = async function(options) {
    const answer = await originalCreateAnswer.call(this, options);
    console.log('WebRTC Answer SDP:', analyzeSDP(answer.sdp));
    return answer;
  };
  
  RTCPeerConnection.prototype.setLocalDescription = async function(description) {
    console.log('Setting local description:', description.type);
    if (description.sdp) {
      console.log('Local SDP analysis:', analyzeSDP(description.sdp));
    }
    return originalSetLocalDescription.call(this, description);
  };
  
  RTCPeerConnection.prototype.setRemoteDescription = async function(description) {
    console.log('Setting remote description:', description.type);
    if (description.sdp) {
      console.log('Remote SDP analysis:', analyzeSDP(description.sdp));
    }
    return originalSetRemoteDescription.call(this, description);
  };
}

// Initialize SDP monitoring
monitorSDP();

3. Network Traffic Analysis

// Monitor WebRTC network statistics
async function monitorWebRTCStats(peerConnection) {
  if (!peerConnection) return;
  
  const stats = await peerConnection.getStats();
  const analysis = {
    inbound: {},
    outbound: {},
    candidates: []
  };
  
  stats.forEach(report => {
    switch (report.type) {
      case 'inbound-rtp':
        analysis.inbound[report.mediaType] = {
          bytesReceived: report.bytesReceived,
          packetsReceived: report.packetsReceived,
          packetsLost: report.packetsLost,
          jitter: report.jitter,
          codecId: report.codecId
        };
        break;
        
      case 'outbound-rtp':
        analysis.outbound[report.mediaType] = {
          bytesSent: report.bytesSent,
          packetsSent: report.packetsSent,
          retransmittedPacketsSent: report.retransmittedPacketsSent,
          codecId: report.codecId
        };
        break;
        
      case 'candidate-pair':
        if (report.state === 'succeeded') {
          analysis.candidates.push({
            localCandidateId: report.localCandidateId,
            remoteCandidateId: report.remoteCandidateId,
            bytesSent: report.bytesSent,
            bytesReceived: report.bytesReceived,
            currentRoundTripTime: report.currentRoundTripTime
          });
        }
        break;
    }
  });
  
  return analysis;
}

// Continuous stats monitoring
function startStatsMonitoring(peerConnection, interval = 1000) {
  const statsInterval = setInterval(async () => {
    try {
      const stats = await monitorWebRTCStats(peerConnection);
      console.log('WebRTC Stats:', stats);
      
      // Stop monitoring if connection is closed
      if (peerConnection.connectionState === 'closed') {
        clearInterval(statsInterval);
      }
    } catch (error) {
      console.error('Stats monitoring error:', error);
    }
  }, interval);
  
  return statsInterval;
}

Recording and Capture Methods

1. MediaRecorder API

// Record WebRTC streams using MediaRecorder
class WebRTCRecorder {
  constructor(stream, options = {}) {
    this.stream = stream;
    this.chunks = [];
    this.recorder = null;
    this.options = {
      mimeType: 'video/webm; codecs=vp8,opus',
      videoBitsPerSecond: 2500000,
      ...options
    };
  }
  
  async startRecording() {
    try {
      this.recorder = new MediaRecorder(this.stream, this.options);
      
      this.recorder.addEventListener('dataavailable', (event) => {
        if (event.data && event.data.size > 0) {
          this.chunks.push(event.data);
          console.log('Recorded chunk:', event.data.size, 'bytes');
        }
      });
      
      this.recorder.addEventListener('stop', () => {
        console.log('Recording stopped, total chunks:', this.chunks.length);
      });
      
      this.recorder.start(1000); // Record in 1-second chunks
      console.log('WebRTC recording started');
      
      return true;
    } catch (error) {
      console.error('Failed to start recording:', error);
      return false;
    }
  }
  
  stopRecording() {
    if (this.recorder && this.recorder.state !== 'inactive') {
      this.recorder.stop();
      return true;
    }
    return false;
  }
  
  downloadRecording(filename = `webrtc_recording_${Date.now()}.webm`) {
    if (this.chunks.length === 0) {
      console.warn('No recorded data available');
      return false;
    }
    
    const blob = new Blob(this.chunks, { type: this.options.mimeType });
    const url = URL.createObjectURL(blob);
    
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    
    URL.revokeObjectURL(url);
    return true;
  }
  
  getRecordingBlob() {
    if (this.chunks.length === 0) return null;
    return new Blob(this.chunks, { type: this.options.mimeType });
  }
  
  clearRecording() {
    this.chunks = [];
  }
}

// Usage example
async function recordWebRTCCall() {
  try {
    // Get user media
    const stream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: true
    });
    
    // Create recorder
    const recorder = new WebRTCRecorder(stream, {
      mimeType: 'video/webm; codecs=vp9,opus',
      videoBitsPerSecond: 3000000
    });
    
    // Start recording
    await recorder.startRecording();
    
    // Stop after 30 seconds (example)
    setTimeout(() => {
      recorder.stopRecording();
      recorder.downloadRecording();
    }, 30000);
    
    return recorder;
  } catch (error) {
    console.error('Recording setup failed:', error);
  }
}

2. Screen Capture Recording

// Record WebRTC screen sharing
class ScreenShareRecorder extends WebRTCRecorder {
  constructor(options = {}) {
    super(null, options); // Stream will be set later
    this.displayStream = null;
    this.audioStream = null;
  }
  
  async startScreenRecording(includeAudio = true) {
    try {
      // Get screen capture
      this.displayStream = await navigator.mediaDevices.getDisplayMedia({
        video: {
          mediaSource: 'screen',
          width: { max: 1920 },
          height: { max: 1080 },
          frameRate: { max: 30 }
        }
      });
      
      // Optionally add microphone audio
      if (includeAudio) {
        try {
          this.audioStream = await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: false
          });
        } catch (audioError) {
          console.warn('Could not capture audio:', audioError);
        }
      }
      
      // Combine streams
      const combinedStream = new MediaStream();
      
      this.displayStream.getVideoTracks().forEach(track => {
        combinedStream.addTrack(track);
      });
      
      if (this.audioStream) {
        this.audioStream.getAudioTracks().forEach(track => {
          combinedStream.addTrack(track);
        });
      }
      
      this.stream = combinedStream;
      
      // Set up stop handler when screen sharing ends
      this.displayStream.getVideoTracks()[0].addEventListener('ended', () => {
        console.log('Screen sharing ended');
        this.stopRecording();
      });
      
      return this.startRecording();
    } catch (error) {
      console.error('Screen recording setup failed:', error);
      return false;
    }
  }
  
  stopScreenRecording() {
    // Stop all tracks
    if (this.displayStream) {
      this.displayStream.getTracks().forEach(track => track.stop());
    }
    
    if (this.audioStream) {
      this.audioStream.getTracks().forEach(track => track.stop());
    }
    
    return this.stopRecording();
  }
}

// Usage
const screenRecorder = new ScreenShareRecorder({
  mimeType: 'video/webm; codecs=vp9,opus',
  videoBitsPerSecond: 5000000
});

// Start screen recording with audio
screenRecorder.startScreenRecording(true);

3. Canvas-Based Recording

// Record WebRTC video to canvas for processing
class CanvasWebRTCRecorder {
  constructor(videoElement, canvasElement) {
    this.video = videoElement;
    this.canvas = canvasElement;
    this.ctx = canvasElement.getContext('2d');
    this.recording = false;
    this.frames = [];
  }
  
  startCanvasRecording(fps = 30) {
    if (this.recording) return false;
    
    this.recording = true;
    this.frames = [];
    
    const interval = 1000 / fps;
    
    const captureFrame = () => {
      if (!this.recording) return;
      
      // Draw video frame to canvas
      this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
      
      // Optional: Apply filters or overlays here
      this.applyFilters();
      
      // Capture frame data
      this.canvas.toBlob(blob => {
        this.frames.push({
          blob,
          timestamp: Date.now()
        });
      }, 'image/webp', 0.8);
      
      setTimeout(captureFrame, interval);
    };
    
    captureFrame();
    return true;
  }
  
  applyFilters() {
    // Example: Apply a simple filter
    const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
    const data = imageData.data;
    
    // Grayscale filter example
    for (let i = 0; i < data.length; i += 4) {
      const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
      data[i] = avg;     // Red
      data[i + 1] = avg; // Green  
      data[i + 2] = avg; // Blue
      // Alpha stays the same
    }
    
    this.ctx.putImageData(imageData, 0, 0);
    
    // Add timestamp overlay
    this.ctx.fillStyle = 'white';
    this.ctx.font = '16px Arial';
    this.ctx.fillText(new Date().toLocaleTimeString(), 10, 30);
  }
  
  stopCanvasRecording() {
    this.recording = false;
    console.log('Canvas recording stopped, captured frames:', this.frames.length);
    return this.frames.length;
  }
  
  async exportAsVideo() {
    if (this.frames.length === 0) return null;
    
    // This is a simplified example
    // Real implementation would need a library like ffmpeg.wasm
    console.log('Export functionality would need additional libraries');
    
    return null;
  }
  
  downloadFrames() {
    this.frames.forEach((frame, index) => {
      const url = URL.createObjectURL(frame.blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `frame_${index.toString().padStart(4, '0')}.webp`;
      a.click();
      URL.revokeObjectURL(url);
    });
  }
}

Advanced WebRTC Techniques

1. Data Channel File Transfer

// Transfer files over WebRTC data channel
class WebRTCFileTransfer {
  constructor(peerConnection) {
    this.pc = peerConnection;
    this.dataChannel = null;
    this.fileQueue = [];
    this.chunkSize = 16384; // 16KB chunks
  }
  
  setupDataChannel() {
    this.dataChannel = this.pc.createDataChannel('fileTransfer', {
      ordered: true
    });
    
    this.dataChannel.addEventListener('open', () => {
      console.log('File transfer channel opened');
      this.processFileQueue();
    });
    
    this.dataChannel.addEventListener('message', (event) => {
      this.handleIncomingData(event.data);
    });
    
    this.dataChannel.addEventListener('error', (error) => {
      console.error('Data channel error:', error);
    });
  }
  
  async sendFile(file) {
    if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
      this.fileQueue.push(file);
      return false;
    }
    
    const fileInfo = {
      name: file.name,
      size: file.size,
      type: file.type,
      chunks: Math.ceil(file.size / this.chunkSize)
    };
    
    // Send file metadata
    this.dataChannel.send(JSON.stringify({
      type: 'fileInfo',
      data: fileInfo
    }));
    
    // Send file in chunks
    const arrayBuffer = await file.arrayBuffer();
    
    for (let i = 0; i < fileInfo.chunks; i++) {
      const start = i * this.chunkSize;
      const end = Math.min(start + this.chunkSize, file.size);
      const chunk = arrayBuffer.slice(start, end);
      
      this.dataChannel.send(JSON.stringify({
        type: 'fileChunk',
        chunkIndex: i,
        totalChunks: fileInfo.chunks,
        data: Array.from(new Uint8Array(chunk))
      }));
      
      // Small delay to prevent overwhelming the channel
      await new Promise(resolve => setTimeout(resolve, 10));
    }
    
    console.log(`File sent: ${file.name} (${file.size} bytes)`);
    return true;
  }
  
  handleIncomingData(data) {
    try {
      const message = JSON.parse(data);
      
      switch (message.type) {
        case 'fileInfo':
          this.handleFileInfo(message.data);
          break;
        case 'fileChunk':
          this.handleFileChunk(message);
          break;
      }
    } catch (error) {
      console.error('Error handling incoming data:', error);
    }
  }
  
  handleFileInfo(fileInfo) {
    console.log('Incoming file:', fileInfo.name, fileInfo.size, 'bytes');
    this.incomingFile = {
      info: fileInfo,
      chunks: [],
      receivedChunks: 0
    };
  }
  
  handleFileChunk(chunkMessage) {
    if (!this.incomingFile) return;
    
    this.incomingFile.chunks[chunkMessage.chunkIndex] = new Uint8Array(chunkMessage.data);
    this.incomingFile.receivedChunks++;
    
    console.log(`Received chunk ${chunkMessage.chunkIndex + 1}/${chunkMessage.totalChunks}`);
    
    // Check if all chunks received
    if (this.incomingFile.receivedChunks === this.incomingFile.info.chunks) {
      this.assembleFile();
    }
  }
  
  assembleFile() {
    const { info, chunks } = this.incomingFile;
    const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
    
    const assembledFile = new Uint8Array(totalSize);
    let offset = 0;
    
    chunks.forEach(chunk => {
      assembledFile.set(chunk, offset);
      offset += chunk.length;
    });
    
    // Create blob and download
    const blob = new Blob([assembledFile], { type: info.type });
    const url = URL.createObjectURL(blob);
    
    const a = document.createElement('a');
    a.href = url;
    a.download = info.name;
    a.click();
    
    URL.revokeObjectURL(url);
    
    console.log(`File received and downloaded: ${info.name}`);
    this.incomingFile = null;
  }
  
  processFileQueue() {
    while (this.fileQueue.length > 0 && this.dataChannel?.readyState === 'open') {
      const file = this.fileQueue.shift();
      this.sendFile(file);
    }
  }
}

2. Multi-Stream Management

// Manage multiple WebRTC streams
class WebRTCStreamManager {
  constructor() {
    this.connections = new Map();
    this.streams = new Map();
    this.recordingState = new Map();
  }
  
  async addVideoStream(streamId, constraints) {
    try {
      const stream = await navigator.mediaDevices.getUserMedia(constraints);
      this.streams.set(streamId, stream);
      
      console.log(`Video stream added: ${streamId}`);
      return stream;
    } catch (error) {
      console.error(`Failed to add video stream ${streamId}:`, error);
      return null;
    }
  }
  
  async addScreenStream(streamId) {
    try {
      const stream = await navigator.mediaDevices.getDisplayMedia({
        video: true,
        audio: true
      });
      
      this.streams.set(streamId, stream);
      
      // Handle screen share end
      stream.getVideoTracks()[0].addEventListener('ended', () => {
        this.removeStream(streamId);
      });
      
      console.log(`Screen stream added: ${streamId}`);
      return stream;
    } catch (error) {
      console.error(`Failed to add screen stream ${streamId}:`, error);
      return null;
    }
  }
  
  createPeerConnection(connectionId, configuration = {}) {
    const defaultConfig = {
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' }
      ]
    };
    
    const config = { ...defaultConfig, ...configuration };
    const pc = new RTCPeerConnection(config);
    
    this.connections.set(connectionId, pc);
    
    // Set up event handlers
    pc.addEventListener('icecandidate', (event) => {
      if (event.candidate) {
        console.log(`ICE candidate for ${connectionId}:`, event.candidate.candidate);
      }
    });
    
    pc.addEventListener('track', (event) => {
      console.log(`Track received on ${connectionId}:`, event.track.kind);
    });
    
    console.log(`Peer connection created: ${connectionId}`);
    return pc;
  }
  
  addStreamToConnection(streamId, connectionId) {
    const stream = this.streams.get(streamId);
    const connection = this.connections.get(connectionId);
    
    if (!stream || !connection) {
      console.error('Stream or connection not found');
      return false;
    }
    
    stream.getTracks().forEach(track => {
      connection.addTrack(track, stream);
    });
    
    console.log(`Stream ${streamId} added to connection ${connectionId}`);
    return true;
  }
  
  async startRecording(streamId, options = {}) {
    const stream = this.streams.get(streamId);
    if (!stream) {
      console.error(`Stream not found: ${streamId}`);
      return false;
    }
    
    const recorder = new WebRTCRecorder(stream, options);
    const success = await recorder.startRecording();
    
    if (success) {
      this.recordingState.set(streamId, recorder);
      console.log(`Recording started for stream: ${streamId}`);
    }
    
    return success;
  }
  
  stopRecording(streamId) {
    const recorder = this.recordingState.get(streamId);
    if (!recorder) {
      console.error(`No recording found for stream: ${streamId}`);
      return false;
    }
    
    recorder.stopRecording();
    recorder.downloadRecording(`${streamId}_recording.webm`);
    
    this.recordingState.delete(streamId);
    console.log(`Recording stopped for stream: ${streamId}`);
    return true;
  }
  
  removeStream(streamId) {
    const stream = this.streams.get(streamId);
    if (stream) {
      stream.getTracks().forEach(track => track.stop());
      this.streams.delete(streamId);
      console.log(`Stream removed: ${streamId}`);
    }
    
    // Stop any recording
    if (this.recordingState.has(streamId)) {
      this.stopRecording(streamId);
    }
  }
  
  closeConnection(connectionId) {
    const connection = this.connections.get(connectionId);
    if (connection) {
      connection.close();
      this.connections.delete(connectionId);
      console.log(`Connection closed: ${connectionId}`);
    }
  }
  
  getStats() {
    return {
      streams: this.streams.size,
      connections: this.connections.size,
      recordings: this.recordingState.size,
      streamList: Array.from(this.streams.keys()),
      connectionList: Array.from(this.connections.keys())
    };
  }
}

// Usage
const streamManager = new WebRTCStreamManager();

// Add camera stream
streamManager.addVideoStream('camera', { video: true, audio: true });

// Add screen share
streamManager.addScreenStream('screen');

// Create connection and add streams
streamManager.createPeerConnection('peer1');
streamManager.addStreamToConnection('camera', 'peer1');

// Start recording
streamManager.startRecording('camera');

Common Issues and Solutions

1. Connection Issues

// Handle WebRTC connection failures
function handleConnectionIssues(peerConnection) {
  peerConnection.addEventListener('iceconnectionstatechange', () => {
    const state = peerConnection.iceConnectionState;
    console.log('ICE connection state:', state);
    
    switch (state) {
      case 'disconnected':
        console.warn('Connection disconnected, attempting to reconnect...');
        // Implement reconnection logic
        break;
        
      case 'failed':
        console.error('Connection failed, trying with different ICE servers');
        // Try with TURN servers
        restartConnection(peerConnection);
        break;
        
      case 'closed':
        console.log('Connection closed');
        // Clean up resources
        break;
    }
  });
}

async function restartConnection(peerConnection) {
  try {
    // Restart ICE
    await peerConnection.restartIce();
    console.log('ICE restart initiated');
  } catch (error) {
    console.error('Failed to restart ICE:', error);
  }
}

2. Media Constraints Issues

// Handle media access issues gracefully
async function requestMediaWithFallback() {
  const constraints = [
    // Ideal constraints
    { video: { width: 1920, height: 1080 }, audio: true },
    
    // Fallback constraints
    { video: { width: 1280, height: 720 }, audio: true },
    { video: { width: 640, height: 480 }, audio: true },
    { video: true, audio: true },
    { audio: true }, // Audio only
  ];
  
  for (const constraint of constraints) {
    try {
      const stream = await navigator.mediaDevices.getUserMedia(constraint);
      console.log('Media obtained with constraints:', constraint);
      return stream;
    } catch (error) {
      console.warn('Failed with constraints:', constraint, error.message);
    }
  }
  
  throw new Error('Could not obtain any media stream');
}

3. Recording Format Issues

// Handle different browser recording capabilities
function getOptimalRecordingOptions() {
  const options = [];
  
  // Try different MIME types in order of preference
  const mimeTypes = [
    'video/webm; codecs=vp9,opus',
    'video/webm; codecs=vp8,opus',
    'video/webm; codecs=h264,opus',
    'video/mp4; codecs=h264,aac',
    'video/webm',
    'video/mp4'
  ];
  
  for (const mimeType of mimeTypes) {
    if (MediaRecorder.isTypeSupported(mimeType)) {
      options.push({
        mimeType,
        videoBitsPerSecond: 2500000,
        audioBitsPerSecond: 128000
      });
    }
  }
  
  return options[0] || {}; // Return best option or empty object
}

Related Tools

  • Universal streaming video downloader
  • Download Skool platform videos
  • Download Telegram video calls
  • Download Twitch streams
  • Download Kick.com clips

Platform-Specific WebRTC Tools

Many video conferencing and streaming platforms use WebRTC:

  • For online courses
  • Educational content
  • Course videos

YouTube Tutorial

Conclusion

WebRTC represents the cutting edge of real-time communication technology. While it's primarily designed for live interaction rather than downloading, understanding how to capture and record WebRTC streams is essential for preserving video calls, online meetings, and live broadcasts. The tools and techniques covered in this guide will help you successfully record WebRTC content from various platforms.