SimpleWebRTC is a component-based Unity wrapper for peer-to-peer audio, video, 360° video and data communication over WebRTC. It builds on WebRTC (FDGS), the community-maintained fork of Unity's WebRTC package.
Version 2.0 splits the former all-in-one WebRTCConnection into small components. Add only what you need, swap the signaling backend, or write your own video sources, sinks and transports without touching the WebRTC core.
New to WebRTC? How WebRTC works explains the basics, using the Loopback sample as a walkthrough.
- Full-mesh peer-to-peer connections with automatic ("perfect") negotiation. Peers can join late and start or stop streams at any time.
- Pluggable signaling: WebSocket (NativeWebSocket or the Meta Voice SDK version), Photon Fusion 2, in-process loopback, or your own transport.
- Rooms, so several apps can share one signaling server.
- Data channels (text and binary, reliable or unreliable, several channels per connection).
- Video from cameras, textures, webcams or as 360° video (mono or stereo over-under).
- Received video shown in RawImages, on renderers or in RenderTextures.
- Audio from AudioSources or the microphone, played back per peer.
- STUN and TURN servers, preferred video codec, bitrate and framerate limits.
- Connection statistics per peer (round trip time, bitrates, FPS, resolution, codecs, packet loss, jitter, direct or TURN-relayed path), with an optional on-screen overlay.
- Unity Visual Scripting nodes and events.
- A compatible browser client, SimpleWebRTC-Web, so web browsers can join the same rooms as your Unity apps.
- Migration tool for SimpleWebRTC 1.x scenes.
- Unity 6000.0 or newer
- WebRTC (FDGS)
com.fdgs.webrtc3.2.0 or newer - For WebSocket signaling, one of:
- NativeWebSocket 2.x (
https://github.com/endel/NativeWebSocket.git#upm-2) - the Meta Voice SDK (
com.meta.xr.sdk.voice), which already contains a NativeWebSocket version
- NativeWebSocket 2.x (
- Optional: Photon Fusion 2, Unity Visual Scripting
UPM packages cannot depend on git packages, so install WebRTC (FDGS) and NativeWebSocket yourself. Without WebRTC (FDGS), SimpleWebRTC is not compiled and offers to install it. Tools > SimpleWebRTC > Check Dependencies shows what is available.
- Open the Package Manager, click on the + sign and select "Add package from git URL".
- Add
https://github.com/FireDragonGameStudio/com.unity.webrtc.fdgs.git#v3.2.0 - Add
https://github.com/endel/NativeWebSocket.git#upm-2(skip this if the Meta Voice SDK is installed) - Add
https://github.com/FireDragonGameStudio/SimpleWebRTC.git?path=/Packages/com.fdgs.simplewebrtc
Install SimpleWebRTC from the Asset Store and add the dependencies above.
SimpleWebRTC only needs a WebSocket server that relays every message to all other connected clients. A ready-to-use Node.js server is in Server~ (also in Runtime/SignalServerCode.txt):
cd Server~
npm install
npm start # ws://localhost:3000, set PORT to change itHost it anywhere that supports WebSockets, then use wss://your-host as the server URL.
WebRTC lets two apps send audio, video and data directly to each other (peer-to-peer) instead of routing everything through a game server. That means low latency and no server bandwidth for the media. To get there, the two peers have to solve three problems:
- Find each other and agree on what they want to send (signaling with an offer and an answer).
- Find a network path between them, even though both are usually behind routers (ICE, with help from STUN and TURN servers).
- Send the media and data, encrypted, over that path.
Before two peers can talk directly, they need to exchange a few small text messages through some channel they can both already reach. WebRTC deliberately does not say what that channel is. A WebSocket server, a multiplayer backend, a LAN broadcast or anything else that delivers text works.
In SimpleWebRTC this channel is a SignalingTransport: WebSocketSignaling, PhotonFusionSignaling, LoopbackSignaling, the UDP broadcast sample, or your own. The signaling channel is only used to set up and change connections. Audio, video and data never go through it.
Each peer describes its side of the session in an SDP text: which audio and video tracks it sends, which codecs it supports (e.g. VP8, H264, Opus), which data channels it opens, and the keys for encryption. One peer sends its description as an offer, and the other replies with an answer. After that, both know what to expect.
Whenever something changes, for example a peer starts its camera, the peers exchange a new offer and answer. SimpleWebRTC does this automatically in every PeerSession using "perfect negotiation". If both peers send an offer at the same moment, the "polite" peer gives way and the collision resolves itself.
Most devices sit behind a router (NAT) and only know their local address, like 192.168.1.20. To find a path anyway, each peer collects ICE candidates: addresses where it might be reachable. It sends them to the other peer through signaling. Both peers then test the candidate pairs directly against each other, and the best pair that works is used.
- host: the local address. This works when both peers are in the same network.
- srflx (server reflexive): the public address of the router, which the peer learns by asking a STUN server. This works for most home and mobile networks.
- relay: an address on a TURN server that forwards all traffic. It's needed when firewalls or strict NATs block every direct path, for example in some corporate or mobile networks.
In SimpleWebRTC you set these servers in the Ice Servers list of the WebRTCPeerManager. It uses a public Google STUN server by default. For apps that must work everywhere, add your own TURN server with a username and credential (e.g. coturn or a hosted TURN service). The WebRTCStatsMonitor shows which candidate types a connection uses, for example host/host udp or relay/srflx udp (TURN).
Once a path is found, the peers send:
- Media tracks: camera or texture video and audio, compressed by a codec such as VP8 or Opus.
VideoSenderandAudioSendersend them;VideoReceiverandAudioReceiverplay them back. - Data channels: any text or binary messages. They can be reliable and ordered like TCP, or unreliable like UDP for fast, frequently updated data such as positions. In SimpleWebRTC that is the
DataChannelcomponent.
Everything is encrypted (DTLS and SRTP), and WebRTC does not allow unencrypted connections.
SimpleWebRTC connects every peer to every other peer (a full mesh). Each peer sends its own stream to every other peer, so upload bandwidth and encoding work grow with the number of peers. This works well for small groups, typically up to about 4 to 6 peers with video. Larger groups need a media server (SFU), which is not part of SimpleWebRTC.
The Loopback sample runs two clients, Alice and Bob, in one scene. The whole connection setup happens on one machine without any server, so it's a good place to watch the steps above. Import it from the Package Manager, press Play, and enable Show Logs on a WebRTCPeerManager to follow along in the Console.
- Connect: both
WebRTCPeerManagers have Connect On Start enabled. TheirLoopbackSignalingcomponents join the in-process channeldefault, which takes the place of a signaling server. - Find each other: the client that connects second broadcasts
join. The other one creates aPeerSessionfor it and replies withwelcome, and the first one creates itsPeerSessionas well. You'll seecreated session [Alice-… -> Bob-…]in the Console. - Add features to the session: each component hooks into the new session.
DataChannelopens the channeldata(id 0), andVideoSenderadds a video transceiver with the camera track, because Stream On Enable is set. - Offer and answer: Bob has the higher peer id, so he is the "impolite" peer and sends the first
offer. Alice sends theanswer, and both exchangecandidatemessages. Both clients run on the same machine, so the local host candidates are enough. That's why the sample has no STUN server configured. - Connected: the Console shows
connection state Connected. EachVideoReceivergets the other client's video track, and itsRawImageVideoSinkshows it in the panel.LoopbackSampleChatsends a message over the data channel every 2 seconds, and the stats overlay shows the VP8 streams withRTT 0 msoverhost/host udp.
To turn this into a real app, put each client on its own device and replace LoopbackSignaling with a transport that reaches other machines. The Example Scenes use WebSocketSignaling with the server from Server~, and the Custom Signaling Transport sample uses UDP broadcast in the local network. Everything else stays the same.
- webrtc.org: the WebRTC project, getting-started guides and samples
- Get started with WebRTC on web.dev: a broader introduction to signaling, ICE, STUN/TURN and security
- WebRTC API on MDN: reference and protocol details
WebRTCPeerManager ── one PeerSession (RTCPeerConnection) per remote peer
├─ SignalingTransport WebSocketSignaling | MetaWebSocketSignaling | PhotonFusionSignaling | LoopbackSignaling | your own
├─ DataChannel any number, each with its own channel id
├─ VideoSender ── VideoSource CameraVideoSource | TextureVideoSource | WebCamVideoSource | CubemapVideoSource | your own
├─ VideoReceiver ── VideoSink RawImageVideoSink | RendererVideoSink | RenderTextureVideoSink | your own
├─ AudioSender AudioSource or microphone
├─ AudioReceiver one AudioSource per peer
└─ WebRTCStatsMonitor connection statistics per peer
All feature components find the WebRTCPeerManager on their GameObject or a parent. Put them on one GameObject, or on children for a cleaner hierarchy.
- Create a GameObject and add
WebRTCPeerManagerandWebSocketSignaling. Assign the signaling component to the manager and set the server URL. - Add a
DataChannel. - To send video, add a
CameraVideoSource(assign a camera) and aVideoSender(assign the source). - To receive video, add a
RawImageVideoSink(assign a RawImage or a parent for spawned images) and aVideoReceiver(add the sink to its list). - For audio, add an
AudioSender(assign an AudioSource) and anAudioReceiver. - Call
Connect()on the manager, or enable Connect On Start. In Play mode the inspector shows buttons for Connect, Start Streaming, Send Test Message and so on.
public class MyClient : MonoBehaviour {
[SerializeField] private WebRTCPeerManager peerManager;
[SerializeField] private DataChannel chat;
[SerializeField] private VideoSender video;
private void Start() {
peerManager.PeerConnected.AddListener(peerId => Debug.Log($"{peerId} connected"));
chat.PeerMessageReceived += (peerId, message) => Debug.Log($"{peerId}: {message}");
peerManager.Connect();
}
public void SayHello() => chat.Send("Hello everyone!");
public void SayHelloTo(string peerId) => chat.SendTo(peerId, "Hello you!");
public void ToggleVideo() => video.ToggleStreaming();
}| Component | Purpose | Main API |
|---|---|---|
WebRTCPeerManager |
Joins a room over the signaling transport and manages one peer connection per remote peer. | Connect(), Disconnect(), SetLocalPeerId(), SetRoom(), SetSignaling(), Sessions, LocalPeerId; events SignalingStateChanged, PeerJoined, PeerConnected, PeerLeft, SessionCreated, SessionClosed |
WebSocketSignaling / MetaWebSocketSignaling |
Signaling over a WebSocket relay server (NativeWebSocket or Meta Voice SDK). | ServerUrl |
PhotonFusionSignaling + PhotonSignalingRelay |
Signaling through a running Photon Fusion 2 session. | |
LoopbackSignaling |
In-process signaling for peers in the same app, e.g. tests. | Channel |
DataChannel |
Pre-negotiated data channel to every peer. All peers need the same channel id. | Send(), SendTo() (string or byte[]); events ChannelOpened, ChannelClosed, MessageReceived, PeerMessageReceived, BytesReceived |
VideoSender |
Streams a VideoSource to all peers, including peers that join later. |
StartStreaming(), StopStreaming(), ToggleStreaming(); bitrate and framerate limits |
VideoReceiver |
Forwards remote videos to its VideoSinks. |
events VideoStarted, VideoStopped, VideoUpdated, VideoRemoved |
AudioSender |
Streams an AudioSource or the microphone. | StartStreaming(), StopStreaming() |
AudioReceiver |
Plays every peer's audio through its own AudioSource (optional prefab for spatial audio or mixer groups). | TryGetAudioSource(); events AudioStarted, AudioStopped |
WebRTCStatsMonitor |
Polls connection statistics for every peer at a fixed interval. Optional on-screen overlay (IMGUI) for debugging. | Stats, TryGetStats(), Summary, ShowOverlay; events StatsUpdated (PeerStats), StatsTextUpdated (formatted text, e.g. for a TextMeshPro text) |
- Signaling backend: derive from
SignalingTransportand implementConnect,DisconnectandSend. Report state changes withSetStateand incoming messages withReceiveMessage; both are safe to call from any thread. The Custom Signaling Transport sample shows a complete example (UdpBroadcastSignaling, LAN without server) and a minimal template. - Video source: derive from
VideoSourceand create aVideoStreamTrackinCreateTrack. - Video display: derive from
VideoSinkand implementSetVideo/RemoveVideo. - Anything per peer: derive from
PeerSessionComponentand useOnSessionCreated/OnSessionClosedto add tracks, transceivers or data channels to everyPeerSession.
Add a WebRTCStatsMonitor next to the WebRTCPeerManager. Every update interval (default 1 second) it reports for each peer:
- the round trip time and the available outgoing bandwidth of the active network path,
- whether the path is direct (host/srflx) or relayed through a TURN server, and the protocol,
- total send and receive bitrate,
- per audio and video stream: direction, codec and encoder/decoder (e.g. VP8 / libvpx, H264 / NvCodec), bitrate, resolution, frame rate, packet loss, jitter, jitter buffer delay, dropped frames, freezes, and why the encoder limits quality (bandwidth or CPU),
- data channel messages and bitrate.
monitor.StatsUpdated += stats => {
Debug.Log($"{stats.PeerId}: {stats.RoundTripTimeMs:0} ms, {stats.ReceiveBitrateKbps:0} kbps in");
foreach (var stream in stats.Streams.Where(s => s.IsVideo)) {
Debug.Log($"{stream.Direction} {stream.Codec} {stream.Width}x{stream.Height} @ {stream.FramesPerSecond:0} fps");
}
};Enable Show Overlay (or use the Toggle Overlay button in Play mode) for a quick on-screen view, or wire StatsTextUpdated to the text of a UI text.
Messages are JSON objects, for example {"v":2,"type":"offer","room":"default","from":"Peer-1a2b","to":"Peer-3c4d","payload":"..."}. The types are join, welcome, leave, offer, answer and candidate. to is * for broadcasts. The server never needs to read the messages.
- Install Photon Fusion 2 and import the Photon Fusion sample.
- Add the
SimpleWebRTC.PhotonFusionassembly to NetworkProjectConfig > Assemblies To Weave. - Put a
PhotonSignalingRelayon a NetworkObject and aPhotonFusionSignalingnext to yourWebRTCPeerManager, and reference them from each other. - Start the Fusion session, then call
Connect()on the manager. Signaling connects as soon as the relay is spawned.
- Install Unity Visual Scripting and import the Visual Scripting sample.
- Regenerate the nodes under Edit > Project Settings > Visual Scripting.
- Add
WebRTCConnectionVisualScriptingEventsto the client GameObject, which has theWebRTCPeerManager. - Add that GameObject as scene variable
ClientConnection, see the sample scene.
2.0 is a breaking release, and the signaling protocol changed, so 1.x and 2.x clients cannot talk to each other.
- Remove the 1.x package (
com.firedragongamestudio.simplewebrtc) in the Package Manager, or delete theAssets/SimpleWebRTCfolder if you imported it as asset. 2.x is calledcom.fdgs.simplewebrtcand uses the same script GUIDs, so the two must not be installed side by side. Then add 2.x as described in Installation. - Update the dependencies: replace
com.unity.webrtcwithcom.fdgs.webrtc, and use NativeWebSocket 2.x. - Open each scene that uses
WebRTCConnectionorWebRTCConnection_Genericand run Tools > SimpleWebRTC > Migrate Open Scenes (or use the button in the old component's inspector). The tool:- creates the new components with your settings (server URL, STUN server, peer id, immersive setup, cameras, audio sources, receiving parents),
- moves the UnityEvent listeners to the matching new events,
- re-targets UnityEvent calls (e.g. buttons) from the old component to the new ones,
- assigns empty references of other scripts when the scene has a single client.
- Photon scenes need Photon Fusion 2 installed before migrating.
- If the old component is on a prefab instance, the new components are added as overrides. Use Overrides > Apply All to update the prefab.
- Update your scripts using this mapping:
1.x (WebRTCConnection) |
2.x |
|---|---|
Connect() / ConnectWebRTC() |
WebRTCPeerManager.Connect(), WebRTC connects automatically |
Disconnect() |
WebRTCPeerManager.Disconnect() |
SetUniquePlayerName() |
WebRTCPeerManager.SetLocalPeerId() |
SendDataChannelMessage() / SendDataChannelMessageToPeer() |
DataChannel.Send() / DataChannel.SendTo() |
Start/StopVideoTransmission() |
VideoSender.StartStreaming() / StopStreaming() |
Start/StopAudioTransmission() |
AudioSender.StartStreaming() / StopStreaming() |
WebSocketConnectionChanged |
WebRTCPeerManager.SignalingStateChanged |
WebRTCConnected |
WebRTCPeerManager.PeerConnected |
DataChannelConnected / DataChannelMessageReceived |
DataChannel.ChannelOpened / MessageReceived |
VideoTransmissionReceived / AudioTransmissionReceived |
VideoReceiver.VideoStarted / AudioReceiver.AudioStarted |
UseImmersiveSetup (sender / receiver) |
CubemapVideoSource / RenderTextureVideoSink |
inspector toggles (StartStopVideoTransmission, ...) |
Play mode buttons in the inspector |
Import the samples from the Samples tab of the package in the Package Manager. Each sample has its own README with setup steps, a description of the scene and things to try.
| Sample | Scenes | Shows | Needs |
|---|---|---|---|
| Loopback | WebRTC-Loopback-NoServer |
Two peers in one scene with video, chat and the stats overlay. Start here. | nothing |
| Example Scenes | WebSocket-TestConnection, WebRTC-SingleClient-STUNConnection, WebRTC-SingleClient-wLobby-STUNConnection, WebRTC-MultipleClients-STUNConnection |
Clients connecting through a WebSocket server: buttons for every feature, a lobby driven from C#, three clients in one scene | signaling server (Server~), NativeWebSocket |
| Custom Signaling Transport | WebRTC-CustomSignaling-UdpBroadcast |
Your own transport: peers find each other in the LAN via UDP broadcast | same network; Android/Quest: multicast permission |
| Immersive Video | ...-ImmersiveSpectator-Sender, ...-ImmersiveSpectator-Receiver |
360° video from a 3D scene to a receiver (e.g. XR headset), camera switching over the data channel | signaling server, NativeWebSocket |
| Photon Fusion 2 | WebRTC-SingleClient-STUNConnection-PhotonFusion |
Signaling through a Photon Fusion 2 session | Photon Fusion 2; migrate the scene once after importing |
| Visual Scripting | WebRTC-SingleClient-STUNConnection-VisualScripting |
Controlling a client from a script graph with keyboard shortcuts | Visual Scripting, Input System, signaling server |
SimpleWebRTC-Web is a browser client for SimpleWebRTC 2.x. It speaks the same signaling protocol, so browsers and Unity apps can join the same room and exchange video, audio and data channel messages. This is handy for testing a Unity app without a second build, for web spectators or remote operators, or for mixed groups of desktop, mobile, XR and browser users.
What it can do: camera or screen sharing, microphone audio, chat over the data channel (to everyone or to one peer), codec selection (VP8, VP9, H264, AV1), a max bitrate setting, live per-peer statistics and per-peer muting.
git clone https://github.com/FireDragonGameStudio/SimpleWebRTC-Web.git
cd SimpleWebRTC-Web
npm install
npm run server # signaling relay on ws://localhost:3000 (or use Server~ from this package)
npm run dev # web client on https://localhost:5173Open the web client, enter the signaling server URL and room, and connect. You can also preset both in the URL, e.g. https://localhost:5173/?server=wss://your-host&room=lobby.
- Use the same server URL and room on the
WebSocketSignaling/WebRTCPeerManagerin Unity and in the web client. Both relay servers work, the one from the web client repository and the one inServer~. - For chat, the Unity client needs a
DataChannelwith labeldataand channel id0, which is the component's default. - Video codecs must match. Unity prefers VP8 by default, which every browser supports. If you choose another codec in the browser, make sure the Unity platform supports it too.
- Browsers only allow
ws://connections from HTTPS pages tolocalhost. For other devices, host the signaling server with TLS and usewss://, or start the web client withnpm run dev:httpfor local testing. - The Example Scenes (e.g.
WebRTC-SingleClient-STUNConnection) and the Custom Signaling Transport sample are good counterparts. The UDP broadcast transport doesn't reach browsers, though; use WebSocket signaling for that.
SimpleWebRTC 1.x clients can't connect to the 2.x web client. For 1.x, use the 1.x state of SimpleWebRTC-Web.
These videos show SimpleWebRTC 1.x. The concepts are the same, but the components changed in 2.0.
This project is licensed under the MIT License.
Contributions are welcome! Feel free to submit pull requests or report issues.


