by Pivec Labs
TutorialPeer-to-Peer Video Call

Peer-to-Peer Video Call

⏱ 15–20 min Uses the shipped OneToOneCallDemo scene

Prerequisites: NetCrafter Live installed; com.unity.webrtc installed (Package Manager) — version 3.0.0 or later if either side of the call is Android, see the note under Part 4 below; your own self-hosted coturn + rendezvous server up and running (see the Server Deployment Guide or just run the Server Setup Wizard); a second machine, build, or Editor instance to actually place a call to.

What you'll build: Nothing — this tutorial runs the demo scene that already ships with the package (Examples/OneToOneCallDemo/) and explains what it's doing, so you can see a real NetCrafterLiveConnection integration working before you build your own UI around it.

01

Open the demo scene#

text
Assets/PivecLabs/NetCrafterLive/Examples/OneToOneCallDemo/OneToOneCallDemo.unity

Double-click it in the Project window like any other scene. Select the Read Me First GameObject in the Hierarchy to see the same summary in the Inspector.

The scene has one NetCrafterLiveConnection GameObject and one OneToOneCallDemoController GameObject that wires a plain UI to it — that controller script is the actual reference implementation this tutorial walks through. Open OneToOneCallDemoController.cs alongside this tutorial if you want to read the wiring while you go.

Everything except the two video previews themselves — title, buttons, toggles, status/code/error labels — lives in a collapsible settings panel, visible by default. The Hide Settings/Show Settings button in the top corner (a sibling of the panel, so it's never hidden along with it) toggles it; the video previews stay on screen either way. The two previews normally sit on a screen-space Canvas as RawImages — see Part 6 below for switching them to world-space instead.

02

Get a server running#

If you haven't already: open Tools → Pivec Labs → NetCrafter Live, run the Server Setup Wizard tab, and get through its Finish step. That writes your Relay Host, Relay Port, Rendezvous URL, TURN Username, and Shared Secret directly into the project's Relay Settings — nothing to copy-paste by hand.

Already have a server? Confirm the Relay Settings tab shows a real Relay Host and Rendezvous URL, not blank fields.

03

Assign Relay Settings on the demo's component#

Select the NetCrafterLiveConnection GameObject in the Hierarchy. In its Inspector, click Assign Project's Relay Settings. This is the one manual step every fresh scene needs — the demo scene ships with connectionSettings intentionally unassigned, the same as any GameObject you'll add this component to yourself.

If you skip this step and press Host/Join anyway, you'll see a clear error in the on-screen error label and the Console (connectionSettings is not assigned...) rather than a silent failure.

04

Host a call#

Press Play. Click Host. Within a couple of seconds:

  • The Join code label fills in with a short code — this came from JoinCodeReady.
  • Status moves from New → Connecting while it waits for someone to join.
  • Your own camera feed appears in the local preview panel, driven by LocalVideoTextureAvailable — this is what the other peer is about to receive.

Share that join code however you like (voice, chat, screen) with whoever's joining.

Android as the sending side requires com.unity.webrtc 3.0.0 or later. Earlier pre-release versions of the package (confirmed on 3.0.0-pre.8) have a hardware video encoder bug on Android that prevents sending video — the call negotiates and connects normally, but no video ever reaches the other side from the Android device. Receiving video on Android, and Android hosting/joining a call where a PC is the one sending video, are both unaffected. If you're on an older package version and see this, update to 3.0.0 or later via Package Manager first before troubleshooting anything else.

05

Join from the other side#

On a second machine, a second build, or a second Unity Editor instance (File → Open Project in New Window works for local testing — remember to enable Run In Background in Player Settings on both instances), open the same demo scene, assign Relay Settings there too, paste the join code into the input field, and click Join.

Once negotiation completes on both sides:

  • Status reaches Connected (green) on both ends.
  • Each side's remote preview panel fills in with the other peer's video, driven by RemoteVideoTextureAvailable.
  • Audio plays automatically through a hidden AudioSource — no wiring needed unless you want to route it somewhere specific (see the API Reference's remoteAudioOutput field).

Click Leave on either side to hang up cleanly — this calls connection.Leave(), which tears down the peer connection and clears both preview textures.

06

Try the other options#

Three toggles sit in the settings panel, next to the Host/Join buttons:

  • Use RenderTexture source — switches videoSource from the default Webcam to RenderTexture. The demo scene already has a small rotating-cube camera rig wired up for this (DemoRenderTextureSource/DemoRotatingCube) so you can test this path without a real webcam at all. In your own scene, you'd point customRenderTexture at your own game camera's output instead.
  • Force-Relay — sets forceRelay = true before the next Host/Join, restricting the connection to TURN-relayed candidates only (no direct/STUN). Worth understanding why this exists and when you'd actually want it — see the Player Hosting Guide rather than duplicating that explanation here. Short version: it proves your relay path actually works end to end, and it's the setting most likely to expose a stale TURN credential if one's gone stale (also covered there).
  • World-space preview — flips connection.videoDisplayMode between ScreenSpace2D (default) and WorldSpace3D. With it on, the local/remote previews move from the screen-space RawImages to two draggable 3D panels in the scene instead — click and drag one around with the mouse; it tilts slightly as you drag it near its edges. The toggle itself is just a thin wrapper around connection.SetVideoDisplayMode() — see the Player Hosting Guide's display-modes section for the full pattern (including how to react to VideoDisplayModeChanged from your own script) and the API Reference for the enum itself.
07

How it works#

OneToOneCallDemoController does nothing NetCrafterLiveConnection doesn't already expose publicly — it's a thin UI layer over:

csharp
hostButton.onClick.AddListener(() => connection.Host());
joinButton.onClick.AddListener(() => connection.Join(joinCodeInput.text));
connection.ConnectionStateChanged += state => /* update status label */;
connection.JoinCodeReady += code => /* show the code */;
connection.LocalVideoTextureAvailable += tex => localPreview.texture = tex;
connection.RemoteVideoTextureAvailable += tex => remotePreview.texture = tex;
connection.VideoDisplayModeChanged += mode => /* swap screen-space <-> world-space preview objects */;
connection.ErrorOccurred += message => /* show the error */;

That's genuinely the whole integration. Everything else in the controller script is just Unity UI bookkeeping (button listeners, toggle wiring, clearing labels on Leave).

08

Where to go from here#