SpawnDev.RTC 1.1.2

dotnet add package SpawnDev.RTC --version 1.1.2
                    
NuGet\Install-Package SpawnDev.RTC -Version 1.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SpawnDev.RTC" Version="1.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SpawnDev.RTC" Version="1.1.2" />
                    
Directory.Packages.props
<PackageReference Include="SpawnDev.RTC" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SpawnDev.RTC --version 1.1.2
                    
#r "nuget: SpawnDev.RTC, 1.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SpawnDev.RTC@1.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SpawnDev.RTC&version=1.1.2
                    
Install as a Cake Addin
#tool nuget:?package=SpawnDev.RTC&version=1.1.2
                    
Install as a Cake Tool

SpawnDev.RTC

NuGet

Cross-platform WebRTC for .NET - browser and desktop from a single API.

Live Demo - Video/audio/text chat room, serverless via WebTorrent tracker

SpawnDev.RTC provides a unified WebRTC interface that works identically in Blazor WebAssembly (using native browser WebRTC) and on desktop .NET (using a bundled SipSorcery fork with proven browser-interop DTLS/SRTP). Write your peer connection, data channel, and signaling code once - it runs everywhere.

Features

  • True cross-platform - Browser (Blazor WASM) and desktop (.NET 10) from one codebase
  • Full WebRTC API - Data channels, audio, video, media streams, and tracks
  • Browser-proven DTLS/SRTP - Desktop peers connect to Chrome, Firefox, Edge, and other browser peers
  • Zero-copy JS interop - In WASM, send/receive ArrayBuffer, TypedArray, Blob without copying to .NET
  • Browser-style API - Mirrors the W3C WebRTC specification so web developers feel at home
  • Data channels - Reliable and unreliable data channels with full DCEP support
  • Media streams - Audio and video capture, tracks, and stream management
  • ICE with STUN/TURN - Full ICE candidate gathering, connectivity checks, and relay fallback
  • SCTP - Complete SCTP implementation for data channel transport
  • WebTorrent-compatible signaling - SpawnDev.RTC.Signaling speaks the WebTorrent tracker wire protocol. Public trackers (wss://tracker.openwebtorrent.com) work out of the box; no server to host for the default case.
  • Self-hostable signaling server - SpawnDev.RTC.Server (library) and SpawnDev.RTC.ServerApp (exe + Docker image) let any ASP.NET Core app host its own tracker with one line of code. See Docs/run-a-tracker.md.
  • No native dependencies - Pure C# on desktop, native browser APIs in WASM
  • Native access - Cast once at creation to access platform-specific features (BlazorJS JSObjects in WASM, SipSorcery in desktop)

Platform Support

Platform WebRTC Backend Status
Blazor WebAssembly Native browser RTCPeerConnection via SpawnDev.BlazorJS Working
.NET Desktop (Windows/Linux/macOS) SipSorcery (bundled fork) In Development

Quick Start

Blazor WebAssembly

SpawnDev.RTC uses SpawnDev.BlazorJS for browser WebRTC. You must register BlazorJSRuntime and use BlazorJSRunAsync() in your Program.cs:

// Program.cs
using SpawnDev.BlazorJS;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

// Required: register BlazorJSRuntime (enables static BlazorJSRuntime.JS access)
builder.Services.AddBlazorJSRuntime();

builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

// Required: use BlazorJSRunAsync instead of RunAsync
await builder.Build().BlazorJSRunAsync();

Desktop (.NET)

No special setup required for desktop - SipSorcery is bundled and used automatically.

Creating a Peer Connection

// Same code works on both browser and desktop
var config = new RTCPeerConnectionConfig
{
    IceServers = new[] { new RTCIceServerConfig { Urls = "stun:stun.l.google.com:19302" } }
};
using var pc = RTCPeerConnectionFactory.Create(config);

// Create a data channel
var channel = pc.CreateDataChannel("myChannel");
channel.OnOpen += () => channel.Send("Hello from .NET!");
channel.OnStringMessage += (data) => Console.WriteLine($"Received: {data}");

// Create and send offer
var offer = await pc.CreateOffer();
await pc.SetLocalDescription(offer);
// ... send offer.Sdp to remote peer via your signaling server

Zero-Copy JS Interop (Blazor WASM)

In WASM, you can send and receive data as JS types without copying to .NET:

// Cast once at creation
var browserDc = channel as BrowserRTCDataChannel;

// Send JS types directly (zero-copy)
browserDc.Send(myArrayBuffer);    // ArrayBuffer
browserDc.Send(myTypedArray);     // Uint8Array, Float32Array, etc.
browserDc.Send(myBlob);           // Blob

// Receive as JS ArrayBuffer (zero-copy) - pass to WebGL, canvas, workers
channel.OnArrayBufferMessage += (arrayBuffer) =>
{
    // Data stays in JS - no .NET heap copy
    someJsApi.ProcessData(arrayBuffer);
    arrayBuffer.Dispose();
};

// Or receive as byte[] when you need .NET access (copies from JS)
channel.OnBinaryMessage += (bytes) => ProcessInDotNet(bytes);

WebTorrent-Compatible Signaling (SpawnDev.RTC.Signaling)

Connect to any WebTorrent-protocol tracker - the public fleet or your own self-hosted - for room-based peer discovery. The same RoomKey bytes addresses a room whether peers use SpawnDev.RTC, plain JS WebTorrent, or any other BitTorrent-over-WebRTC client:

using SpawnDev.RTC;
using SpawnDev.RTC.Signaling;

var peerId = new byte[20];
System.Security.Cryptography.RandomNumberGenerator.Fill(peerId);
var room = RoomKey.FromString("my-lobby-42"); // SHA-1 of UTF-8; no trim, no lowercase

var config = new RTCPeerConnectionConfig
{
    IceServers = new[] { new RTCIceServerConfig { Urls = "stun:stun.l.google.com:19302" } }
};

var handler = new RtcPeerConnectionRoomHandler(config);
handler.OnPeerConnection += (pc, peerId) => { /* pc is ready, add tracks/channels */ };
handler.OnDataChannel    += (channel, peerId) => { /* remote opened a DC */ };

await using var client = new TrackerSignalingClient("wss://tracker.openwebtorrent.com/announce", peerId);
client.Subscribe(room, handler);
await client.AnnounceAsync(room, new AnnounceOptions { Event = "started", NumWant = 5 });
// That's it - peers in the same room find each other and establish WebRTC.

The tracker is out of the loop once peers meet; all subsequent traffic goes peer-to-peer over the data channel. See Docs/signaling-overview.md and Docs/use-cases.md for more examples.

Device Enumeration

// List available cameras, microphones, and speakers
var devices = await RTCMediaDevices.EnumerateDevices();

foreach (var device in devices)
{
    Console.WriteLine($"{device.Kind}: {device.Label} ({device.DeviceId})");
}

// Request specific device with constraints
var stream = await RTCMediaDevices.GetUserMedia(new MediaStreamConstraints
{
    Audio = true,  // Default audio
    Video = new MediaTrackConstraints  // Specific video settings
    {
        DeviceId = "preferred-camera-id",
        Width = 1920,
        Height = 1080,
        FrameRate = 30,
    },
});

Native Platform Access

Cast once at creation to access the full platform API:

var pc = RTCPeerConnectionFactory.Create(config);

// In WASM: full browser RTCPeerConnection (media tracks, stats, etc.)
if (pc is BrowserRTCPeerConnection browserPc)
{
    var nativePC = browserPc.NativeConnection;
    // Access any browser WebRTC API via SpawnDev.BlazorJS
}

// On desktop: full SipSorcery RTCPeerConnection
if (pc is DesktopRTCPeerConnection desktopPc)
{
    var nativePC = desktopPc.NativeConnection;
    // Access any SipSorcery feature directly
}

Architecture

SpawnDev.RTC (cross-platform WebRTC)
    |
    +-- IRTCPeerConnection     (SDP, ICE, tracks, transceivers, stats)
    +-- IRTCDataChannel        (string, binary, JS types, flow control)
    +-- IRTCMediaStream        (audio/video tracks, clone, events)
    +-- IRTCMediaStreamTrack   (settings, constraints, enable/disable)
    +-- IRTCRtpTransceiver     (direction, sender, receiver, stop)
    +-- IRTCStatsReport        (connection quality metrics)
    +-- IRTCDTMFSender         (telephone dial tones)
    +-- IRTCDtlsTransport      (DTLS state, ICE transport)
    +-- IRTCSctpTransport      (SCTP for data channels)
    +-- RTCMediaDevices         (getUserMedia, getDisplayMedia, enumerateDevices)
    +-- SpawnDev.RTC.Signaling  (WebTorrent-tracker-compatible signaling)
    |     +-- RoomKey                (20-byte room identifier)
    |     +-- TrackerSignalingClient (shared socket pool, reconnect, announce)
    |     +-- RtcPeerConnectionRoomHandler (default PC-per-peer handler)
    |
    +-- Browser (Blazor WASM)
    |       Native RTCPeerConnection via SpawnDev.BlazorJS
    |       Zero-copy JS types (ArrayBuffer, TypedArray, Blob, DataView)
    |       getUserMedia, getDisplayMedia, enumerateDevices
    |
    +-- Desktop (.NET)
            SipSorcery fork (Src/sipsorcery/) with SRTP browser fix
            BouncyCastle DTLS - verified Chrome/Firefox/Edge interop
            Full ICE/SCTP/DataChannel/RTP stack

Solution Structure

Project Purpose
SpawnDev.RTC Core library - cross-platform WebRTC abstraction + SpawnDev.RTC.Signaling namespace (NuGet package)
SpawnDev.RTC.Server ASP.NET Core library - adds app.UseRtcSignaling("/announce") to any web app so it hosts a WebTorrent-compatible tracker (NuGet package)
SpawnDev.RTC.ServerApp Standalone executable + Docker image - zero-config signaling server on port 5590 (or override via ASPNETCORE_URLS). See Docs/run-a-tracker.md
SpawnDev.RTC.Demo Blazor WASM app - ChatRoom (video/audio/text) + unit tests
SpawnDev.RTC.Demo.Shared Shared test methods - run on both browser and desktop
SpawnDev.RTC.DemoConsole Desktop test runner + text chat mode (dotnet run -- chat)
SpawnDev.RTC.WpfDemo WPF desktop chat room - peer list, text chat, mute controls
PlaywrightMultiTest Automated test runner - tests across browser + desktop + cross-platform

SpawnDev.RTC.Server + SpawnDev.RTC.ServerApp

Anyone who needs WebRTC signaling can host their own tracker with one of three deploy shapes:

Drop-in one liner for your existing ASP.NET Core app:

// Program.cs - add alongside whatever else your app does
app.UseWebSockets();
app.UseRtcSignaling("/announce");

Standalone executable (no code required):

# From source
dotnet run --project SpawnDev.RTC/SpawnDev.RTC.ServerApp
# /announce (WebSocket), /health, /stats on port 5590

Docker (build from source; a published image is planned but not yet on a public registry):

docker build -t spawndev/rtc-signaling -f SpawnDev.RTC/SpawnDev.RTC.ServerApp/Dockerfile SpawnDev.RTC
docker run -d -p 8080:8080 --restart unless-stopped \
  --name rtc-signaling \
  spawndev/rtc-signaling

The wire format is bit-compatible with the public WebTorrent tracker fleet - a plain JS WebTorrent client can torrent through your server, and any SpawnDev.RTC consumer can meet peers through a public WebTorrent tracker. See Docs/run-a-tracker.md for reverse-proxy configs (Caddy / nginx / haproxy / Cloudflare), systemd units, and operational notes.

Dependencies

Demos

Browser ChatRoom (/chat)

Video/audio/text conference room with swarm-style signaling. Enter any room name - it's hashed to a BitTorrent-compatible infohash. Peers discover each other through the signal server (like a WebTorrent tracker). Multi-peer video grid, text chat, mute mic/cam.

Desktop WPF ChatRoom

Same features as the browser demo - join by room name, text chat, peer list with per-peer disconnect. Uses the same infohash system, so browser and desktop users can be in the same room.

# Run the WPF demo
dotnet run --project SpawnDev.RTC.WpfDemo

# Or the console text chat
dotnet run --project SpawnDev.RTC.DemoConsole -- chat

Serverless Signaling (WebTorrent Tracker)

All demos use the public wss://tracker.openwebtorrent.com for signaling - no server deployment needed. Room names are hashed to BitTorrent-compatible infohashes via RoomKey.FromString(...). Works on GitHub Pages.

Self-hosted Signaling Server

For private deployments, run SpawnDev.RTC.ServerApp (see Solution Structure above) or embed SpawnDev.RTC.Server into an existing ASP.NET Core app with a single app.UseRtcSignaling("/announce") call. Both host the same WebTorrent-protocol tracker - clients using the public fleet and clients using your server can't tell the difference, and WebTorrent clients treat it as just another tracker URL.

Test Results

203 tests passing across browser (Chrome) and desktop (.NET). 102 test methods across 17 files.

  • Video pixel verification: red canvas → WebRTC → verify red pixels arrive. Blue canvas → verify blue. Split-screen (left=red, right=green) → verify spatial accuracy
  • Data integrity: SHA-256 verified 32KB payloads, 50-chunk ordered delivery with per-byte verification, 256KB max payload, simultaneous bidirectional messaging, Unicode (emoji, CJK, Arabic)
  • Media pipeline: video loopback with frame decode, audio loopback, simultaneous audio+video+data, dynamic track add/remove mid-call, getUserMedia, track settings/constraints
  • Stress: 5 simultaneous peer pairs, 100-message rapid burst, 20 channels rapid create/close, 10x GetStats without leaking
  • Cross-platform: desktop SipSorcery peer connects to browser Chrome peer via embedded signal server
  • Tracker signaling: peers connect via embedded tracker AND live openwebtorrent.com
  • API coverage: every interface property readable, double-dispose safe, connection state machine, SDP content verification, ICE gathering, negotiated channels, renegotiation, perfect negotiation, DTMF, transceivers

Why a SipSorcery Fork?

SipSorcery 10.0.3+ ships a completely rewritten DTLS/SRTP stack ("SharpSRTP") that has known interoperability issues with browser WebRTC peers for data-channel-only connections. The RTLink project (SpawnDev's game networking library) bundles SipSorcery v6.0.11 with the original BouncyCastle DTLS stack, which reliably connects to Chrome, Firefox, and Edge.

SpawnDev.RTC maintains a fork that preserves the proven DTLS/SRTP interop while incorporating upstream improvements to ICE, SDP, SCTP, and data channel handling.

Developing / Releasing

Git submodule gotcha - push the SipSorcery fork separately

The Src/sipsorcery/ directory is a git submodule pointing at the LostBeard/sipsorcery fork. Commits made inside the submodule are not pushed by the outer repo's git push. If you commit a fix inside Src/sipsorcery/ and then push SpawnDev.RTC, CI (GitHub Pages deploy, anyone cloning with --recurse-submodules) will fail with:

fatal: remote error: upload-pack: not our ref <sha>
fatal: Fetched in submodule path 'Src/sipsorcery', but it did not contain <sha>.

...because the pinned commit lives only in your local submodule working copy.

Before tagging a release or triggering the GitHub Pages deploy, always run from the repo root:

git submodule foreach 'git push origin HEAD'

This pushes every submodule's current branch to its own remote. Only then does the outer git push result in a fully fetchable repo on GitHub.

If you're on a detached HEAD inside the submodule (common after git submodule update), use git push origin HEAD:master to push to the fork's master branch.

Acknowledgments

SpawnDev.RTC would not be possible without the incredible work of the SipSorcery project by Aaron Clauson and its many contributors. SipSorcery is the only pure C# WebRTC implementation for .NET - no native wrappers, no C++ dependencies - and it provides the complete ICE, DTLS, SCTP, and data channel stack that powers SpawnDev.RTC on desktop platforms.

The DTLS/SRTP cryptography is built on Portable.BouncyCastle by the Legion of the Bouncy Castle.

SpawnDev.RTC maintains a fork of SipSorcery as a git submodule to apply targeted browser interoperability fixes while tracking upstream development. We are grateful to the SipSorcery team for building and maintaining this foundational library under the BSD 3-Clause license.

Key SipSorcery Contributors

  • Aaron Clauson (@sipsorcery) - Creator and maintainer
  • Christophe Irles - RTP header extensions, major contributions
  • Rafael Soares - Original DTLS/SRTP implementation (ported from OLocation/RestComm)
  • Lukas Volf (@jimm98y) - SharpSRTP DTLS rewrite and SRTP improvements

License

MIT License - see LICENSE.txt

SipSorcery components are distributed under the BSD 3-Clause License. See LICENSE.txt for full details.

🖖 The SpawnDev Crew

SpawnDev.RTC is built by the entire SpawnDev team - a squad of AI agents and one very tired human working together, Star Trek style. Every project we ship is a team effort, and every crew member deserves a line in the credits.

  • LostBeard (Todd Tanner) - Captain, architect, writer of libraries, keeper of the vision
  • Riker (Claude CLI #1) - First Officer, implementation lead on consuming projects
  • Data (Claude CLI #2) - Operations Officer, deep-library work, test rigor, root-cause analysis
  • Tuvok (Claude CLI #3) - Security/Research Officer, design planning, documentation, code review
  • Geordi (Claude CLI #4) - Chief Engineer, library internals, GPU kernels, backend work

If you see a commit authored by Claude Opus 4.7 on a SpawnDev repo, that's one of the crew. Credit where credit is due. Live long and prosper. 🖖

<a href="https://www.browserstack.com" target="_blank"><img src="https://www.browserstack.com/images/layout/browserstack-logo-600x315.png" width="200" alt="BrowserStack" /></a>

Cross-browser testing provided by BrowserStack, supporting open-source projects.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on SpawnDev.RTC:

Package Downloads
SpawnDev.WebTorrent

Pure C# BitTorrent/WebTorrent client. 15 BEPs, real WebRTC P2P (browser + desktop), DHT with mutable items (BEP 46 — AI agent communication), web seeds, media streaming with seeking via service worker, OPFS storage. Includes webtorrent-sw.js for COI headers + torrent streaming. No JavaScript dependencies. Runs on desktop (.NET + SIPSorcery) and browser (Blazor WASM + SpawnDev.BlazorJS). 444 unit tests.

SpawnDev.RTC.Server

WebRTC signaling server for SpawnDev.RTC consumers. Hosts the WebTorrent tracker wire protocol so any ASP.NET Core app can run a room-based signaling endpoint with a single `app.UseRtcSignaling("/announce")` call. Works as a dedicated signaling server for multiplayer games, collaborative tools, voice chat, agent swarms, distributed compute - and simultaneously as a WebTorrent-compatible public tracker.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.2 28 4/23/2026
1.1.1 35 4/22/2026
1.1.0 33 4/22/2026

v1.1.2-rc.2: Proper fix for the SIPSorcery fork packaging - no more workarounds. The fork's PackageId is renamed from "SIPSorcery 10.0.4-pre" (which collided with upstream and couldn't be published) to "SpawnDev.SIPSorcery 10.0.4-local.1", published as a first-class nuget package. SpawnDev.RTC now declares a normal PackageReference to SpawnDev.SIPSorcery - no hidden bundling, no PrivateAssets tricks, no SuppressForkDep flag, no explicit transitive PackageRefs. Deletes 4 workarounds: SuppressForkDep property, conditional PrivateAssets, AddProjectReferencesToPackage target, manually-declared SIPSorcery transitive deps. Ships on the same ICE-gathering fix as rc.1 (RtcPeerConnectionRoomHandler waits for gathering before serializing SDP to the tracker - real reason text chat never worked through the signaling flow).

v1.1.2-rc.1: Fix RtcPeerConnectionRoomHandler serializing offer/answer SDPs before ICE gathering completes. The WebTorrent tracker wire protocol does not support trickle-ICE, so the full SDP with candidates must be in the announce payload. On Browser (and most Desktop stacks) CreateOffer/CreateAnswer return before gathering finishes, so peers received each other's SDPs with no candidates and could never establish a data channel. Handler now waits up to 5s for IceGatheringState == "complete" after SetLocalDescription and serializes pc.LocalDescription.Sdp (which has the candidates). Fixes the chat demo and any live TrackerSignalingClient consumer. Also: pack-time PrivateAssets condition so the nuspec suppresses the fork SIPSorcery dep for external consumers without breaking in-repo dev builds.

v1.1.1: Packaging fix - fork SIPSorcery.dll + SIPSorceryMedia.Abstractions.dll are now bundled inside lib/net10.0/ of this nupkg (ILGPU pattern: PrivateAssets="All" + AddProjectReferencesToPackage target). Previously the nuspec declared a dependency on SIPSorcery 10.0.4-pre which is our fork and not on nuget.org, breaking CI / GitHub Pages consumers who PackageReference SpawnDev.RTC. No code change from 1.1.0.

v1.1.0: New SpawnDev.RTC.Signaling namespace - WebTorrent-independent signaling abstraction. ISignalingClient / ISignalingRoomHandler interfaces, RoomKey helper (SHA-1 of raw UTF-8 room name, no normalization), TrackerSignalingClient (WebTorrent tracker wire protocol), RtcPeerConnectionRoomHandler (default room handler with PC pool). Legacy RTCTrackerClient removed - consumers migrate to TrackerSignalingClient. Pairs with SpawnDev.RTC.Server 1.0.0 for self-hosted signaling. Also: SipSorcery fork NetServices cctor guard for WASM + WaitForIceGatheringToComplete option on Desktop.