WebRTC JavaScript Code Samples

repository·gh-pages·Indexed 12 days ago

https://github.com/webrtc/samples

A collection of JavaScript code samples demonstrating WebRTC APIs and capabilities. Includes examples for using RTCPeerConnection, capturing streams from canvas and video elements via captureStream(), applying video filters, recording with MediaRecorder, and configuring MediaStreamTrack content hints.

Tokens
47K
Snippets
123
Records
143
Agent score
94%

What's inside WebRTC Samples

  1. What the Chrome WebRTC Network Limiter does

    gh-pages

    The Chrome WebRTC Network Limiter is an extension that configures WebRTC traffic routing options within Chrome's privacy settings. It allows you to restrict WebRTC from using specific IP addresses or protocols to enhance privacy.

    Specifically, it can prevent WebRTC from using:

    • Private IP addresses that are not visible to the public internet (e.g., 192.168.1.2).
    • Public IP addresses associated with network interfaces not used for standard web traffic (e.g., an ISP-provided address when you are browsing via a VPN).
    • Non-proxied UDP traffic: By requiring WebRTC traffic to go through proxy servers, it effectively disables UDP (since most proxies do not handle UDP), forcing traffic through available proxy configurations.
  2. Run WebRTC samples locally

    gh-pages

    To run the WebRTC JavaScript code samples on your local machine, install the dependencies and start the development server using npm. Once started, open your web browser to the local URL provided in the terminal output.

    npm install && npm start
  3. Diagnose TURN server connectivity and authentication

    gh-pages

    You can use heuristics to determine if a TURN server is reachable or if authentication failed based on the types of ICE candidates gathered:

    • Authentication Failed: If you have a srflx (server reflexive) candidate but no relay candidate, it suggests that the binding response was received but the TURN authentication failed.
    • Not Reachable: If you have neither srflx nor relay candidates (and you are using a TURN server), the TURN server might be down or the client's access to the port is blocked.
  4. Measure audio impairment using getStats()

    gh-pages

    Audio impairment during WebRTC renegotiation can be measured by monitoring concealedSamples in the inbound-rtp statistics.

    1. Identify the audio transceiver: pc.getTransceivers().find(tr => tr.receiver.track.kind == 'audio').
    2. Retrieve stats: audioTransceiver.receiver.getStats().
    3. Access concealedSamples: Iterate through the stats to find the inbound-rtp report and read the concealedSamples property.
    4. Calculate delta: Compare the concealedSamples count before and after the renegotiation event to determine the impairment level.
    async function getAudioImpairment(audioTransceiver) {
      const stats = await audioTransceiver.receiver.getStats();
      let currentImpairment;
      stats.forEach(stat => {
        if (stat.type == 'inbound-rtp') {
          currentImpairment = stat.concealedSamples;
        }
      });
      return currentImpairment;
    }
  5. Implement a video processing pipeline using Pipeline, MediaStreamSource, FrameTransform, and MediaStreamSink

    gh-pages

    To build a video transformation workflow using Insertable Streams, you can use the Pipeline class to orchestrate three main components: a Source, a Transform, and a Sink.

    Core Components

    1. MediaStreamSource: Responsible for providing the raw input. Implement getMediaStream() to return a Promise<MediaStream>.
    2. FrameTransform: Responsible for the actual video manipulation. Implement init() for setup and transform(frame, controller) to process each VideoFrame. Use the controller to queue the output frame.
    3. MediaStreamSink: Responsible for consuming the processed stream. Implement setMediaStream(stream) to receive the transformed MediaStream.

    Orchestration with Pipeline

    The Pipeline class manages the lifecycle and connection of these components. You update the pipeline by calling updateSource(source), updateTransform(transform), and updateSink(sink). The pipeline automatically handles initialization, resource cleanup via destroy(), and stream re-routing when components change.

    // Example conceptual usage
    const pipeline = new Pipeline();
    
    // 1. Set the source (e.g., a camera)
    await pipeline.updateSource(myMediaStreamSource);
    
    // 2. Set the transform (e.g., a grayscale filter)
    await pipeline.updateTransform(myFrameTransform);
    
    // 3. Set the sink (e.g., a video element)
    await pipeline.updateSink(myMediaStreamSink);
  6. Analyze video frames using Insertable Streams

    gh-pages

    You can intercept and analyze encoded video frames in a WebRTC stream by using the createEncodedStreams() method on an RTCRtpReceiver. This method returns an object containing a readable stream and a writable stream.

    To analyze frames without disrupting the playback, you can use a TransformStream to process the readable side and then pipe the results to the writable side. This allows you to inspect metadata like keyframe indicators, timestamps, and synchronization sources.

    In the provided implementation, the videoAnalyzer function is used as a transform logic to detect keyframes by checking the lowest value bit in the first byte of the encoded data (per RFC 6386).

    function gotRemoteTrack(e) {
      // Access the encoded streams from the receiver
      const frameStreams = e.receiver.createEncodedStreams();
    
      // Pipe the readable stream through a TransformStream for analysis,
      // then pipe to the writable stream to continue the flow.
      frameStreams.readable.pipeThrough(new TransformStream({
        transform: videoAnalyzer
      }))
      .pipeTo(frameStreams.writable);
    
      remoteVideo.srcObject = e.streams[0];
    }
  7. How Insertable Streams video processing works

    gh-pages

    The video processing pipeline follows a producer-transformer-consumer model using the WebRTC Insertable Streams API:

    1. Producer: A MediaStreamTrackProcessor reads frames from a source MediaStreamTrack and exposes them via a ReadableStream of VideoFrame objects.
    2. Transformer: A TransformStream is created using a FrameTransformFn. This stream sits between the processor and the generator, applying logic (like WebGL, Canvas2D, or WebCodecs) to each frame.
    3. Consumer: A MediaStreamTrackGenerator provides a WritableStream that receives the processed frames. This generator then produces a new MediaStreamTrack which can be used in a RTCPeerConnection or displayed in a <video> element.

    Data Flow: Source Track $\rightarrow$ MediaStreamTrackProcessor $\rightarrow$ TransformStream $\rightarrow$ MediaStreamTrackGenerator $\rightarrow$ Processed Track

  8. Set preferred video codec via transceiver preferences

    gh-pages

    You can force a specific video codec by manipulating the codec list in an RTCRtpTransceiver.

    1. Get capabilities: RTCRtpReceiver.getCapabilities('video').
    2. Find the desired codec: Search the codecs array for a matching mimeType (e.g., 'video/VP8').
    3. Reorder the list: Move the preferred codec to the front of the array.
    4. Apply preferences: Call e.transceiver.setCodecPreferences(codecs) within the ontrack event.
    pc2.ontrack = (e) => {
      if (e.track.kind === 'video') {
        const {codecs} = RTCRtpReceiver.getCapabilities('video');
        const selectedCodecIndex = codecs.findIndex(c => c.mimeType === preferredVideoCodecMimeType);
        const selectedCodec = codecs[selectedCodecIndex];
        codecs.splice(selectedCodecIndex, 1);
        codecs.unshift(selectedCodec);
        e.transceiver.setCodecPreferences(codecs);
      }
    };
  9. Handle ICE Candidates for Peer Connection

    gh-pages

    To establish a connection, you must exchange ICE candidates between peers.

    1. On the local side: Listen to the onicecandidate event on the RTCPeerConnection. When a candidate is generated, send its details (candidate string, sdpMid, and sdpMLineIndex) to the remote peer via your signaling server.
    2. On the remote side: When a candidate is received via signaling, add it to the local peer connection using pc.addIceCandidate(candidate).

    Note: If the signaling message contains a null candidate, call pc.addIceCandidate(null) to signal the end of the candidate gathering process.

    // Local side: capturing candidates
    pc.onicecandidate = e => {
      if (e.candidate) {
        const message = {
          type: 'candidate',
          candidate: e.candidate.candidate,
          sdpMid: e.candidate.sdpMid,
          sdpMLineIndex: e.candidate.sdpMLineIndex
        };
        signaling.postMessage(message);
      }
    };
    
    // Remote side: adding candidates
    async function handleCandidate(candidate) {
      if (!candidate.candidate) {
        await pc.addIceCandidate(null);
      } else {
        await pc.addIceCandidate(candidate);
      }
    }
  10. Check camera capabilities for Pan, Tilt, and Zoom

    gh-pages

    Before attempting to control PTZ (Pan, Tilt, Zoom) features, verify that the hardware supports them by checking the track's settings and capabilities.

    1. track.getSettings(): Returns the current active settings. Use this to check if a property like zoom is currently active.
    2. track.getCapabilities(): Returns the range of supported values (min, max, step) for the hardware.

    Example logic:

    const capabilities = track.getCapabilities();
    const settings = track.getSettings();
    
    if ('zoom' in settings) {
      console.log('Min zoom:', capabilities.zoom.min);
      console.log('Max zoom:', capabilities.zoom.max);
      console.log('Current zoom:', settings.zoom);
    }
  11. Monitor DataChannel bitrate using getStats()

    gh-pages

    You can calculate the real-time bitrate of a WebRTC connection by inspecting the getStats() report.

    To calculate the bitrate:

    1. Call pc.getStats().
    2. Iterate through the reports to find a transport type report.
    3. Use the selectedCandidatePairId from the transport report to find the corresponding candidate-pair report.
    4. Calculate the difference in bytesReceived and the difference in timestamp between two consecutive polling intervals.
    5. Formula: bitrate = (bytesNow - bytesPrev) * 8 / (timestampNow - timestampPrev) (result in bits per second).

    Note: The timestamp used for calculation should be the one provided in the RTC stats report to ensure accuracy relative to the network stack's timing.

    async function displayStats() {
      const stats = await pc2.getStats();
      let activeCandidatePair;
      
      stats.forEach(report => {
        if (report.type === 'transport') {
          activeCandidatePair = stats.get(report.selectedCandidatePairId);
        }
      });
    
      if (activeCandidatePair) {
        const bytesNow = activeCandidatePair.bytesReceived;
        const timestampNow = activeCandidatePair.timestamp;
        
        const bitrate = Math.round((bytesNow - bytesPrev) * 8 / (timestampNow - timestampPrev));
        
        bytesPrev = bytesNow;
        timestampPrev = timestampNow;
      }
    }