<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged webrtc</title>
<link>https://coderlegion.com/tag/webrtc</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to build a browser‑based virtual webcam</title>
<link>https://coderlegion.com/18878/how-to-build-a-browser-based-virtual-webcam</link>
<description>&lt;h3 id=&quot;what-if-we-could-make-our-video-calls-even-better-&quot;&gt;What if we could make our video calls even better?&lt;/h3&gt;
&lt;p&gt;With softer colors, better resolution, a higher frame rate, and a more flattering angle, our video calls would be much more enjoyable. But the laptop webcam falls short of all these expectations, while the camera in our pocket already offers ten times better quality.&lt;/p&gt;
&lt;p&gt;The challenge is how to use that phone camera for video calls without installing additional drivers, pushing video through another cloud server, or building a native app for the phone. Let's explore building a Chrome extension and a mobile PWA.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;make-a-website-see-the-virtual-camera-&quot;&gt;Make a website see the virtual camera ️&lt;/h3&gt;
&lt;p&gt;Most video conferencing platforms rely on the same browser API, MediaDevices, to capture video. To inject a virtual camera into a website, two things need to happen: the website must believe there is a new device, and calls for that device should return a live stream from the phone's camera.&lt;/p&gt;
&lt;p&gt;The camera must be injected before the website collects a list of devices. Therefore, the content script must be injected before any page scripts run, i.e., at &lt;code&gt;document_start&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It should intercept &lt;code&gt;navigator.mediaDevices&lt;/code&gt; and override both &lt;code&gt;getUserMedia&lt;/code&gt; and &lt;code&gt;enumerateDevices&lt;/code&gt;. The overridden &lt;code&gt;enumerateDevices&lt;/code&gt; adds a virtual entry to the device list, so the new camera appears in any selector the page uses. The overridden &lt;code&gt;getUserMedia&lt;/code&gt; checks the requested device ID: if it matches the virtual device, the call returns a stream from the phone; otherwise, it falls through to the original function.&lt;/p&gt;
&lt;p&gt;As an additional measure, it is possible to dispatch an event about changing the device list. In some cases, if the site takes this event into account, it helps ensure the virtual camera is picked up.&lt;/p&gt;
&lt;p&gt;What we have now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export function monkeyPatchMediaDevices({fakeDeviceInfo, onFakeSelected}: PatchMediaDevices): void {
  const mediaDevices = window.navigator.mediaDevices;
  const enumerateDevicesFn = mediaDevices.enumerateDevices.bind(mediaDevices);
  const getUserMediaFn = mediaDevices.getUserMedia.bind(mediaDevices);

  mediaDevices.enumerateDevices = async () =&amp;gt; {
    const devices = await enumerateDevicesFn();
    return [...devices, fakeDeviceInfo];
  };

  mediaDevices.getUserMedia = async (constraints) =&amp;gt; {
    if (checkDeviceConstraints(constraints, fakeDeviceInfo)) {
      return onFakeSelected(constraints);
    }

    return await getUserMediaFn(constraints);
  };

  mediaDevices.dispatchEvent(new Event('devicechange'));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The end result: On any page where the extension has permission to run, the virtual camera appears as an option in a browser-based video app, without any site-specific integration.&lt;/p&gt;
&lt;h3 id=&quot;webrtc-without-a-dedicated-server&quot;&gt;WebRTC without a dedicated server&lt;/h3&gt;
&lt;p&gt;WebRTC requires signaling – a way for two peers to exchange connection offers, answers, and ICE candidates before they can communicate directly. What if we cannot afford a dedicated server?&lt;/p&gt;
&lt;p&gt;An alternative is to use Google Drive's &lt;code&gt;appdata&lt;/code&gt; folder as a mailbox. Both the desktop extension and the mobile PWA write and read signaling messages to the same hidden folder, which is invisible to users and accessible only by the extension.&lt;/p&gt;
&lt;p&gt;The exchange follows a request-response pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt; Desktop                  Drive                   Mobile
   ┃                        ┃                        ┃
   ┃ 1. Write offer         ┃                        ┃
   ┃ └────────────────────&amp;gt; ┃                        ┃
   ┃                        ┃                        ┃
   ┃ 2. QR (fileId+token)   ┃                        ┃
   ┃ └──────────────────────╂───────┐                ┃
   ┃                        ┃       │                ┃
   ┃ 3. Poll for answer     ┃       │                ┃
   ┃ ├────────────────────&amp;gt; ┃       │                ┃
   ┃ ┊                      ┃       │                ┃
   ┃ ┊                      ┃       │                ┃ 4. Scan QR
   ┃ ┊                      ┃       └──────────────&amp;gt; ┃
   ┃ ┊                      ┃                        ┃
   ┃ ┊                      ┃                        ┃ 5. Read offer
   ┃ ┊                      ┃ ─────────────────────&amp;gt; ┃
   ┃ ┊                      ┃                        ┃
   ┃ ┊                      ┃                        ┃ 6. Write answer
   ┃ ┊                      ┃ &amp;lt;──────────────────────╂─┘
   ┃ ˅                      ┃                        ┃
   ┃ 7. Read answer         ┃                        ┃
   ┃ &amp;lt;───────────────────── ┃                        ┃
   ┃                        ┃                        ┃
   ┃ 8. Delete all files    ┃                        ┃
   ┃ └────────────────────&amp;gt; ┃                        ┃
   ┃                        ┃                        ┃
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To avoid a separate authorization step on the mobile side and ensure that access to the file will be preserved, a Google OAuth token can be embedded directly into the QR code. This means that the QR code contains everything needed to authenticate with Drive, so scanning the QR code is the only action required to establish a connection. A new QR scan is needed for each new connection, but after the WebRTC handshake is complete, the connection can continue without interruption for any duration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;/**
 * Offerer uploads its description and calls back the business part 
 */
public async sendOffer(description: RTCSessionDescriptionInit): Promise&amp;lt;void&amp;gt; {
  const fileCreationResponse = await driveGapi.files.create({
    media: new Blob([JSON.stringify(description)], {type: 'application/json'}),
    name: `${this.options.sessionId}-offer.json`,
    mimeType: 'application/json',
    parents: ['appDataFolder'],
  });

  this.options.onDescriptionCreated(fileCreationResponse.id);
}

/**
 * Answerer downloads the offer and returns it to the business part 
 */
public async checkOffer(): Promise&amp;lt;RTCSessionDescriptionInit&amp;gt; {
  const offerFileBlob = await driveGapi.files.download(this.options.driveId);
  const offerFileJson = await offerFileBlob.text();
  return JSON.parse(offerFileJson) as RTCSessionDescriptionInit;
}

/**
 * Answerer uploads its description 
 */
public async sendAnswer(description: RTCSessionDescriptionInit): Promise&amp;lt;void&amp;gt; {
  const offerFileMeta = await driveGapi.files.get(this.options.driveId);
  const fileCreationResponse = await driveGapi.files.create({
    media: new Blob([JSON.stringify(description)], {type: 'application/json'}),
    name: offerFileMeta.name.replace('offer', 'answer'),
    mimeType: 'application/json',
    parents: ['appDataFolder'],
  });
}

/**
 * Offerer downloads the answer and returns it to the business part 
 */
public async checkAnswer(): Promise&amp;lt;RTCSessionDescriptionInit&amp;gt; {
  const listFilesResponse = await driveGapi.files.list({
    pageSize: 2,
    query: `name contains '${this.options.sessionId}'`,
    spaces: 'appDataFolder',
  });

  const listFiles = listFilesResponse?.files ?? [];
  const answerFileMeta = listFiles.find((file) =&amp;gt; file.name.endsWith('-answer.json'));

  if (answerFileMeta) {
    const answerFileBlob = await driveGapi.files.download(answerFileMeta.id);
    const answerFileJson = await answerFileBlob.text();
    const answerDescription = JSON.parse(answerFileJson) as RTCSessionDescriptionInit;

    for (const file of listFiles) {
      await driveGapi.files.delete(file.id);
    }

    return answerDescription;
  }

  return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;stick-it-together&quot;&gt;Stick it together&lt;/h3&gt;
&lt;p&gt;To avoid making the user scan a QR code for every call, we can keep a long-lived WebRTC connection alive and reuse it. The ideal place for this connection would be the service worker. Since WebRTC cannot be used there, it's necessary to set up offscreen.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let offscreenSettingUp: Promise&amp;lt;void&amp;gt;;
export async function setupOffscreenDocument(filename = 'offscreen.html'): Promise&amp;lt;void&amp;gt; {
  const offscreenUrl = chrome.runtime.getURL(filename);
  if (await hasOffscreenDocument(offscreenUrl)) {
    return Promise.resolve();
  }

  await (offscreenSettingUp ||= chrome.offscreen.createDocument({
    reasons: ['WEB_RTC'],
    url: offscreenUrl,
    justification: 'WebRTC stuff',
  }));

  offscreenSettingUp = null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, while there is no way to stream video from offscreen to content script, the only solution is to connect content script to mobile directly. The offscreen document assists with the offer/answer exchange but stays out of the media path.&lt;/p&gt;
&lt;p&gt;The architecture that makes it work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt; ┌────────────┐     ┌────────────────┐     ┌────────────────────┐
 │ Side Panel ├───&amp;gt; │ Service Worker │ &amp;lt;───┤ Offscreen Document │
 │  (React)   │ &amp;lt;───┤  (dispatcher)  ├───&amp;gt; │   (WebRTC host)    │
 └────────────┘     └───────┬────────┘     └───────┬────────────┘
                            │ ▲                    │ ▲
                            │ │                    │ │
                            │ │                    │ │
                            ▼ │                    ▼ │
                    ┌─────────┴──────┐     ┌─────────┴──────┐
                    │ Content Script │ &amp;lt;───┤     Mobile     │
                    │ (monkey-patch) │     │    (camera)    │
                    └────────────────┘     └────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Side Panel&lt;/strong&gt;: The UI. A React component that displays the Google Sign-in, the QR code, and device status. All inputs go to the service worker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Service Worker&lt;/strong&gt;: A lightweight message dispatcher. This component never touches video. It creates an offscreen document, stores user settings, and proxies messages between the side panel, the offscreen document, and the content script(s).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Offscreen Document&lt;/strong&gt;: The hidden WebRTC manager. It owns the long-term RTCPeerConnection and uses it to establish new ephemeral connections.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Content Script&lt;/strong&gt;: Injected into each page. Its only job is to inject the virtual camera with the phone stream, using the monkey patch described above. The script receives the stream from the mobile device and exposes it to the page.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// Configuring peer for receiving video
peer.addTransceiver('video', {direction: 'recvonly'});
peer.addEventListener('track', ({transceiver}) =&amp;gt; {
  const track = transceiver.receiver.track;
  track.addEventListener('mute', () =&amp;gt; {
    this.options.onStream(null);
  });
  track.addEventListener('unmute', () =&amp;gt; {
    this.options.onStream(new MediaStream([track]));
  });
});

// Setting up the stream in the video element
public setStream(targetStream?: MediaStream): void {
  if (targetStream) {
    const videoElement = window.document.createElement('video');
    videoElement.srcObject = targetStream;

    videoElement
      .play()
      .then(() =&amp;gt; (this.targetVideo = videoElement));

    return;
  }

  this.disposeTargetStream();
}

// Drawing the video element or a stub animation
private loop(streamOptions: FakeMediaStreamOptions): void {
  window.requestAnimationFrame((time) =&amp;gt; {
    if (this.targetVideo) {
      this.canvas.drawVideo(this.targetVideo);
    } else {
      this.canvas.drawStub(time);
    }

    this.loop(streamOptions);
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-this-architecture-enables&quot;&gt;What this architecture enables&lt;/h3&gt;
&lt;p&gt;The solutions chosen in each problem section produced capabilities that weren't designed independently. Instead, they emerged from the overall architecture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minimal permissions&lt;/strong&gt;. The OAuth token stored in the QR code only has two scopes: read and write access to the app's own hidden Drive folder. It doesn't have permission to see files outside that folder or access email, contacts, or other user data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebRTC provides built-in security guarantees&lt;/strong&gt;. Media streams are end-to-end encrypted regardless of the signaling method. The video stays inside the user's WiFi network or, in a fully wired setup, is transferred via USB tethering, which provides a stable wired connection that bypasses Wi-Fi entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flexibility beyond the webcam&lt;/strong&gt;. The peer-to-peer nature of the connection allows for any type of data to be transmitted through the same channel. This includes images, screen captures, and even completely different video sources, which can replace the camera feed without affecting the signaling or transport. The architecture views video as just another type of payload among many.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-platform compatibility&lt;/strong&gt;. The desktop side is a Chrome extension, meaning it works on all operating systems that support a Chromium-based browser, such as Windows, macOS, Linux, and ChromeOS. The mobile side is a PWA, which doesn't require installation from an app store, and works on both Android and iOS. The only exceptions are platform-specific WebRTC behaviors (for example, how iOS Safari handles background streaming differently), but the core connection model remains consistent across all platforms.&lt;/p&gt;
&lt;p&gt;The concrete result of this path is &lt;a rel=&quot;nofollow&quot; href=&quot;https://extroid.alittle.pro?utm_source=coderlegion&amp;amp;utm_medium=referral&amp;amp;utm_content=virtual_webcam&quot;&gt;Extroid&lt;/a&gt;, a &lt;a rel=&quot;nofollow&quot; href=&quot;https://chromewebstore.google.com/detail/kefmmlpligadeljhhpbhlgfoaomnaplb?utm_source=coderlegion&amp;amp;utm_medium=referral&amp;amp;utm_content=virtual_webcam&quot;&gt;Chrome extension that turns your phone into a wireless webcam&lt;/a&gt; for any browser-based video call. Extroid is designed for browser-based video calls. See the &lt;a rel=&quot;nofollow&quot; href=&quot;https://extroid.alittle.pro/compatibility?utm_source=coderlegion&amp;amp;utm_medium=referral&amp;amp;utm_content=virtual_webcam&quot;&gt;compatibility guide&lt;/a&gt; for supported browsers, meeting apps, and unsupported desktop apps. It works without the need for cloud servers or drivers, and there's no need for a native app on your phone. I'd love to hear your feedback!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://extroid.alittle.pro/phone-camera-google-meet&quot;&gt;Use your phone camera in Google Meet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://extroid.alittle.pro/camera-check&quot;&gt;Check your browser camera before a call&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/a-zhelonkin/Extroid&quot;&gt;Official Extroid resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18878/how-to-build-a-browser-based-virtual-webcam</guid>
<pubDate>Mon, 25 May 2026 12:07:57 +0000</pubDate>
</item>
<item>
<title>We power screen recording inside SaaS products.</title>
<link>https://coderlegion.com/11267/we-power-screen-recording-inside-saas-products</link>
<description>&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://eyedolise.github.io/chromescreenrecord&quot;&gt;https://eyedolise.github.io/chromescreenrecord&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Building reliable screen recording into a product looks simple—until you actually do it. Browser limitations, performance tradeoffs, codec issues, resolution caps, and edge cases across devices quickly turn it into a time sink that steals focus from your core roadmap. SCR4K exists so SaaS teams don’t have to fight those battles. We provide a production-ready recording engine that’s already been tested in the real world, so teams can ship faster without compromising quality or stability.&lt;/p&gt;
&lt;p&gt;Our recording engine is battle-tested by over 2,300+ users through our flagship Chrome extension, with no watermarks and consistent 4K output—plus an upgrade path beyond Chrome’s native limits up to 8K via our SaaS pipeline. The same engine can be embedded directly into platforms built with React, Angular, or custom stacks, giving teams full control over the user experience while avoiding the cost and risk of building recording infrastructure from scratch.&lt;/p&gt;
&lt;p&gt;Instead of selling a generic tool, we license a proven system: source code, integration support, and a commercial-ready recording core designed to scale with your product. Teams use SCR4K to power onboarding, bug reporting, customer support, demos, and internal workflows—without reinventing the wheel. Try the engine yourself, or integrate it directly into your platform. Either way, you’re starting with something that already works.&lt;/p&gt;
&lt;h2 id=&quot;saas-b2bsaas-saasbuilders-startupengineering-productengineering-devtools-softwarearchitecture-webdevelopment-webrtc-techfounders&quot;&gt;SaaS #B2BSaaS #SaaSBuilders #StartupEngineering #ProductEngineering #DevTools #SoftwareArchitecture #WebDevelopment #WebRTC #TechFounders&lt;/h2&gt;
&lt;p&gt;If you want to test the recording engine yourself, you can try the SCR4K &lt;a rel=&quot;nofollow&quot; href=&quot;https://chromewebstore.google.com/detail/screen-capture-recorder-4/cffpojocacfloplffkpecnmhfldddegd?hl=en&quot;&gt;Chrome extension here&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11267/we-power-screen-recording-inside-saas-products</guid>
<pubDate>Sun, 08 Feb 2026 19:01:13 +0000</pubDate>
</item>
<item>
<title>WebRTC - How does it work ?</title>
<link>https://coderlegion.com/1904/webrtc-how-does-it-work</link>
<description>&lt;h2 id=&quot;what-is-webrtc-&quot;&gt;What is WebRTC ?&lt;/h2&gt;
&lt;p&gt;WebRTC stands for Web Real Time Communication -&amp;gt; It allows audio, video and streaming communication to work inside&lt;br&gt;
webpages by allowing direct peer-to-peer communication, eliminating the need to install plugins or download native apps.&lt;/p&gt;
&lt;p&gt;Is supported by Apple, Google, Microsoft, Mozilla and Opera, and is an standard way to implement peer-to-peer&lt;br&gt;
communication.&lt;/p&gt;
&lt;h2 id=&quot;high-level-flow-of-webrtc&quot;&gt;High level flow of WebRTC&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A wants to connect to B&lt;/li&gt;
&lt;li&gt;A finds out all possible ways that the public can connect to it&lt;/li&gt;
&lt;li&gt;B finds out all possible ways that the public can connect to it&lt;/li&gt;
&lt;li&gt;A and B signal the session information (supported codecs, mode of communication) via other means (long polling, short polling, websockets)&lt;/li&gt;
&lt;li&gt;A connects to B via the most optimal path&lt;/li&gt;
&lt;li&gt;A &amp;amp; B can now exchange their supported media&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;components-at-works&quot;&gt;Components at works&lt;/h2&gt;
&lt;p&gt;WebRTC is fairly complicated and it requires lot of components to work in harmony to be able to establish a P2P&lt;br&gt;
connection. We should know about following components before we go ahead and create a WebRTC app.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;NAT, types of NAT&lt;/li&gt;
&lt;li&gt;STUN / TURN servers&lt;/li&gt;
&lt;li&gt;Signaling, Session Description Protocol (SDP)&lt;/li&gt;
&lt;li&gt;ICE, trickle ICE&lt;/li&gt;
&lt;li&gt;JS API -&amp;gt; RTCPeerConnection, RTCDataChannel, createOffer(), createAnswer(), setLocalDescription(), setRemoteDescription(), ...so on.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-is-nat-&quot;&gt;What is NAT ?&lt;/h3&gt;
&lt;p&gt;NAT stands for Network Address Translation, any device that is connected to the internet, is connected via a router,&lt;br&gt;
so there are 2 networks that are at play here, one is the public network that is the internet and the other one is the&lt;br&gt;
private network which is basically your LAN, so each device connected to a router that is connected to the internet, has&lt;br&gt;
a public IP and a private IP.&lt;/p&gt;
&lt;p&gt;You can check your public IP by visiting &lt;a rel=&quot;nofollow&quot; href=&quot;https://whatismyipaddress.com/&quot;&gt;whatismyipaddress.com&lt;/a&gt;&lt;br&gt;
and if you want to check your private IP open up a terminal (windows / Linux) and run ipconfig(windows) or ifconfig (Linux)&lt;br&gt;
and you will get your local IP address.&lt;/p&gt;
&lt;p&gt;So, if you want to connect your device (for ex. private IP: 10.0.0.2:8992) to a server / different device (public IP: 4.4.4.4:80) on internet&lt;br&gt;
you would want a public IP address to connect to it, since router will not allow anyone to access the private network,&lt;br&gt;
this is to avoid unsolicited connections - its a basic firewall.&lt;/p&gt;
&lt;p&gt;Who assigns the public IP to your device ? Who make sure that you get back the response from the internet to your device ?&lt;/p&gt;
&lt;p&gt;The Router. Router acts like a middleman between your device and the public network / internet.&lt;/p&gt;
&lt;p&gt;Here is how it works:&lt;/p&gt;
&lt;p&gt;A(private IP:10.0.0.2:8992) wants to connect to B (public IP: 4.4.4.4:80)&lt;/p&gt;
&lt;p&gt;A creates a request packet like this&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Port &lt;/th&gt;&lt;th&gt;Public IP &lt;/th&gt;&lt;th&gt;Request &lt;/th&gt;&lt;th&gt;Destination IP &lt;/th&gt;&lt;th&gt;Destination port&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;8992 &lt;/td&gt;&lt;td&gt;10.0.0.2  &lt;/td&gt;&lt;td&gt;GET/    &lt;/td&gt;&lt;td&gt;4.4.4.4        &lt;/td&gt;&lt;td&gt;80&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A forwards the packet to the router -&amp;gt; Router has to decide whether this is a packet on same network or external network ?&lt;/p&gt;
&lt;p&gt;Router checks this by a process called subnet matching,&lt;/p&gt;
&lt;p&gt;If 2 devices are from same subnet (xxx.xxx.xxx.y) then there is no need to create a NAT table entry, but if not, a entry in NAT table will be created.&lt;/p&gt;
&lt;p&gt;This decision is made by router with following logic&lt;/p&gt;
&lt;p&gt;10.0.0.1 &amp;amp;&amp;amp; 255.255.255.0 -&amp;gt; 10.0.0.0 (lets say this is the gateway and the subnet is 10.0.0.0)&lt;/p&gt;
&lt;p&gt;if the IP belongs to same network we should get same subnet for example 10.0.0.0 , else we would get other subnet and&lt;br&gt;
that means that we are trying to connect to an IP outside the private network.&lt;/p&gt;
&lt;h3 id=&quot;types-of-nat&quot;&gt;Types of NAT&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;One-to-one NAT&lt;/li&gt;
&lt;li&gt;Address Restricted NAT&lt;/li&gt;
&lt;li&gt;Port Restricted NAT&lt;/li&gt;
&lt;li&gt;Symmetric NAT -&amp;gt; WebRTC does not like this one -&amp;gt; neither does any real time gaming app&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;one-to-one-nat-full-cone-nat-&quot;&gt;One to One NAT (Full Cone NAT)&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Int port &lt;/th&gt;&lt;th&gt;Int IP   &lt;/th&gt;&lt;th&gt;External IP &lt;/th&gt;&lt;th&gt;External port &lt;/th&gt;&lt;th&gt;Dest IP &lt;/th&gt;&lt;th&gt;Dest port&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;8992     &lt;/td&gt;&lt;td&gt;10.0.0.2 &lt;/td&gt;&lt;td&gt;5.5.5.5     &lt;/td&gt;&lt;td&gt;3333          &lt;/td&gt;&lt;td&gt;4.4.4.4 &lt;/td&gt;&lt;td&gt;80&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Outbound: You send a packet to any server.&lt;br&gt;
Inbound: &lt;em&gt;Any external IP &amp;amp; port&lt;/em&gt; can send to your mapped public IP / port, once it's open.&lt;/p&gt;
&lt;p&gt;✅ Most permissive&lt;br&gt;
 Good for P2P, pretty bad for security&lt;/p&gt;
&lt;h4 id=&quot;address-restricted-nat&quot;&gt;Address Restricted NAT&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Int port &lt;/th&gt;&lt;th&gt;Int IP   &lt;/th&gt;&lt;th&gt;External IP &lt;/th&gt;&lt;th&gt;External port &lt;/th&gt;&lt;th&gt;Dest IP &lt;/th&gt;&lt;th&gt;Dest port&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;8992     &lt;/td&gt;&lt;td&gt;10.0.0.2 &lt;/td&gt;&lt;td&gt;5.5.5.5     &lt;/td&gt;&lt;td&gt;3333          &lt;/td&gt;&lt;td&gt;4.4.4.4 &lt;/td&gt;&lt;td&gt;80&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Outbound: You send to an external IP.&lt;br&gt;
Inbound: &lt;em&gt;Only that same IP&lt;/em&gt; can respond to your public IP/port - any port from that IP is allowed.&lt;/p&gt;
&lt;p&gt;✅ Moderate restriction&lt;br&gt;
 Safer than full cone&lt;/p&gt;
&lt;h4 id=&quot;port-restricted-nat&quot;&gt;Port restricted NAT&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Int port &lt;/th&gt;&lt;th&gt;Int IP   &lt;/th&gt;&lt;th&gt;External IP &lt;/th&gt;&lt;th&gt;External port &lt;/th&gt;&lt;th&gt;Dest IP &lt;/th&gt;&lt;th&gt;Dest port&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;8992     &lt;/td&gt;&lt;td&gt;10.0.0.2 &lt;/td&gt;&lt;td&gt;5.5.5.5     &lt;/td&gt;&lt;td&gt;3333          &lt;/td&gt;&lt;td&gt;4.4.4.4 &lt;/td&gt;&lt;td&gt;80&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Outbound: You send to an external IP &amp;amp; port.&lt;br&gt;
Inbound: &lt;em&gt;Only that exact IP &amp;amp; port&lt;/em&gt; can respond.&lt;/p&gt;
&lt;p&gt;✅ More restrictive&lt;br&gt;
 Tighter control on incoming traffic&lt;/p&gt;
&lt;h4 id=&quot;symmetric-nat&quot;&gt;Symmetric NAT&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Int port &lt;/th&gt;&lt;th&gt;Int IP   &lt;/th&gt;&lt;th&gt;External IP &lt;/th&gt;&lt;th&gt;External port &lt;/th&gt;&lt;th&gt;Dest IP &lt;/th&gt;&lt;th&gt;Dest port&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;8992     &lt;/td&gt;&lt;td&gt;10.0.0.2 &lt;/td&gt;&lt;td&gt;5.5.5.5     &lt;/td&gt;&lt;td&gt;3333          &lt;/td&gt;&lt;td&gt;4.4.4.4 &lt;/td&gt;&lt;td&gt;80&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Outbound: Every outbound (IP: port) gets a unique mapping.&lt;br&gt;
Inbound: &lt;em&gt;Only the exact IP &amp;amp; port you contacted via the specific mapping&lt;/em&gt; can respond.&lt;/p&gt;
&lt;p&gt; Most restrictive&lt;br&gt;
❌ Hardest for P2P&lt;/p&gt;
&lt;h4 id=&quot;stun-session-traversal-utilities-for-nat-&quot;&gt;STUN (Session Traversal utilities for NAT)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;STUN server reflects back the public IP address:port combo back to the peer.&lt;/li&gt;
&lt;li&gt;STUN has simply one role, get a request from a peer and reflect back its public ip: port&lt;/li&gt;
&lt;li&gt;Works well with full cone, address/ port restricted NAT types&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does not work well with Symmetric NAT -&amp;gt; Since every outbound request gets a new mapping in NAT table,&lt;br&gt;
the public IP:port pair, returned by the STUN server could not be used reliably to create a P2P connection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Around 80 - 85% of devices are behind NATs where STUN works just fine, only few devices (corporate networks, some mobile carriers, hotel / public wifi) are using symmetric NAT&lt;br&gt;
for that we need to make sure we use TURN servers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple way to remember this&lt;/p&gt;
&lt;p&gt;You ask to a STUN server: Who am I ? (what is my public IP:port ?)&lt;br&gt;
STUN server: This is who you are (public IP: port)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;STUN servers are inexpensive and relatively cheap to maintain and you will find a lot of public STUN servers as well&lt;br&gt;
that you could use in your application, implementing a STUN server from scratch too, would not be too complex either.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;turn-traversal-using-relays-around-nat-&quot;&gt;TURN (Traversal using Relays around NAT)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;As we discussed above, in about 10 - 15% cases its possible that the peers use symmetric NAT, that makes it impossible&lt;br&gt;
to create a P2P connection with STUN servers&lt;/li&gt;
&lt;li&gt;TURN comes into picture in such cases, TURN server act as a relay server between 2 peers, esentially&lt;ul&gt;
&lt;li&gt;A sends data to a TURN server,&lt;/li&gt;
&lt;li&gt;TURN server sends that data to peer B&lt;/li&gt;
&lt;li&gt;Peer B sends data to TURN server&lt;/li&gt;
&lt;li&gt;TURN server forwards that to peer A&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now the connection is no longer a truly peer-to-peer anymore, latency is higher now, and as you can expect, bandwidth&lt;br&gt;
costs for the TURN servers are higher since they are handling a lot of data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typically a WebRTC client will try to connect peers via the STUN method, and if that fails it will fallback to TURN&lt;br&gt;
servers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ice-interactive-connectivity-establishment-candidates&quot;&gt;ICE (Interactive connectivity establishment) candidates&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;So we have a bunch of STUNs and TURN servers setup now&lt;/li&gt;
&lt;li&gt;We want to find what works i.e. is STUN enough or do I need to fallback on TURN ?&lt;/li&gt;
&lt;li&gt;This process of determining best possible way to connect to peers is ICE gathering,&lt;/li&gt;
&lt;li&gt;Browser tries out multiple connections until it finds a working one, and once that is done, it creates a list of&lt;br&gt;
servers through which the peers can communicate&lt;/li&gt;
&lt;li&gt;Once the discovery is complete, we will have ICE candidates that need to be shared via signaling with other peer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;sdp-session-description-protocol-&quot;&gt;SDP (Session Description protocol)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Session Description Protocol (SDP) is a plain string that describes peers to one another,&lt;/li&gt;
&lt;li&gt;What kind of media options, security options are supported by both peers, what kind of connection is being requested&lt;br&gt;
by another peer.&lt;/li&gt;
&lt;li&gt;One of the important parts of WebRTC, since this will be exchanged via signaling among peers to even start the process&lt;br&gt;
of ICE gathering,&lt;/li&gt;
&lt;li&gt;Goal is to generate a SDP and send it to the other party, how its done depends on your method of signaling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;signaling&quot;&gt;Signaling&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Signaling typically means sending the required information via some channel to both peers (exchanging information)&lt;/li&gt;
&lt;li&gt;Now this can be done via almost anything (WhatsApp, QR code, Web sockets, HTTP), web sockets are a popular choice&lt;/li&gt;
&lt;li&gt;What do we typically signal ? SDP, ICE candidates -&amp;gt; these need to be exchanged before a connection can be successfully&lt;br&gt;
established between two peers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;putting-everything-together-so-close-to-connecting-the-peers-now-&quot;&gt;Putting everything together... So close to connecting the peers now!&lt;/h4&gt;
&lt;p&gt;Finally we understand how all the components play in the WebRTC game, now its time to connect&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A wants to connect to B&lt;/li&gt;
&lt;li&gt;A creates an &quot;offer&quot;, it puts all the information about sessions, data channel in the SDP&lt;/li&gt;
&lt;li&gt;A signals the SDP to B&lt;/li&gt;
&lt;li&gt;At the same time, ICE gathering process starts for A, &lt;code&gt;onicecandidate&lt;/code&gt; event handler is called once an ICE candidate is&lt;br&gt;
discovered, this needs to be signaled to B separately&lt;/li&gt;
&lt;li&gt;B creates the answer after setting A's offer&lt;/li&gt;
&lt;li&gt;B starts ICE gathering process, signals ICE candidates to A&lt;/li&gt;
&lt;li&gt;A accepts B's answer&lt;/li&gt;
&lt;li&gt;Connection is created.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/1904/webrtc-how-does-it-work</guid>
<pubDate>Sun, 20 Apr 2025 10:11:19 +0000</pubDate>
</item>
</channel>
</rss>