Dananeer Documentation
Welcome to the Dananeer documentation!
Getting Started
The documentation is organized into groups. Each group contains multiple tabs covering different aspects of the service.
Use the sidebar to navigate through the sections, or search (Ctrl+K or Cmd+K) to quickly find a page.
RTC
Complete documentation for Real-Time Communication, including TURN server configuration and WebRTC integration.
TURN Server Configuration
Complete TURN server configuration guide
TURN Server Credentials
TURN server credentials and quick setup
WebRTC Overview
Introduction to WebRTC and key components
Call Lifecycle
Complete call lifecycle from start to finish
Events Reference
All WebRTC signaling events
Configuration
Backend environment variables and network support
API Reference
HTTP endpoints for RTC configuration
Integration Guide
Step-by-step web frontend integration
Unity Integration
Complete Unity game integration guide
Troubleshooting
Common issues and solutions
API Reference
Complete API reference for all events and endpoints
WebRTC Integration Guide
Add voice and video calling to your application using the Dananeer WebRTC API.
Overview
The Dananeer WebRTC API provides real-time voice and video communication through Socket.IO signaling. This guide covers the complete integration process from connection setup to call management.
Architecture
| Component | Purpose |
|---|---|
| Socket.IO | Signaling and event coordination |
| STUN Server | Public IP discovery |
| TURN Server | Relay for restricted networks |
| WebRTC | Peer-to-peer media streaming |
Prerequisites
Before integrating, ensure you have:
- A valid access token from user authentication
- Socket.IO client library installed
- A game session or conversation ID for call identification
Connect to the Server
Establish a Socket.IO connection with your authentication token:
import io from 'socket.io-client';
const socket = io('wss://your-server-url.com', {
auth: {
token: userAccessToken
},
transports: ['websocket']
});
The server extracts user identity from the token automatically.
Join a Call
Step 1: Request Media Access
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
Handle permission denial gracefully in your UI.
Step 2: Join the Call Room
socket.emit('rtc:joinCall', {
callId: gameSession.metadata.roomId,
initialMediaState: {
audioEnabled: true,
videoEnabled: true
}
});
Step 3: Handle Confirmation
socket.on('rtc:callStarted', (data) => {
data.participants.forEach(participant => {
if (participant.userId !== currentUserId) {
createPeerConnection(participant.userId);
}
});
});
Manage Participants
Handle New Participants
socket.on('rtc:participantJoined', (data) => {
createPeerConnection(data.userId);
});
Handle Departures
socket.on('rtc:participantLeft', (data) => {
closePeerConnection(data.userId);
});
Handle Media State Changes
socket.on('rtc:mediaUpdated', (data) => {
updateParticipantUI(data.userId, data.mediaState);
});
Control Media
Mute Microphone
socket.emit('rtc:toggleMute', {
callId: callId,
muted: true
});
Toggle Camera
socket.emit('rtc:toggleVideo', {
callId: callId,
enabled: false
});
Share Screen
socket.emit('rtc:toggleScreenShare', {
callId: callId,
enabled: true
});
All media changes broadcast rtc:mediaUpdated to participants.
Peer Connection Setup
Create Peer Connection
function createPeerConnection(userId) {
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: [
'turn:turn.dananeer.io:3478?transport=udp',
'turn:turn.dananeer.io:3478?transport=tcp',
'turns:turn.dananeer.io:443?transport=tcp'
],
username: 'dananeer_turn',
credential: 'rZBSJ0z2mrDU4ja6j9DT5yPG'
}
]
});
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
pc.ontrack = (event) => {
displayRemoteVideo(userId, event.streams[0]);
};
pc.onicecandidate = (event) => {
if (event.candidate) {
socket.emit('rtc:candidate', {
callId: callId,
toUserId: userId,
candidate: event.candidate
});
}
};
peerConnections[userId] = pc;
return pc;
}
Send Offer
async function createOffer(userId, pc) {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
socket.emit('rtc:offer', {
callId: callId,
toUserId: userId,
sdp: pc.localDescription.sdp
});
}
Handle Incoming Offer
socket.on('rtc:offer', async (data) => {
const pc = peerConnections[data.fromUserId] || createPeerConnection(data.fromUserId);
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'offer', sdp: data.sdp })
);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
socket.emit('rtc:answer', {
callId: data.callId,
toUserId: data.fromUserId,
sdp: pc.localDescription.sdp
});
});
Handle Answer
socket.on('rtc:answer', async (data) => {
const pc = peerConnections[data.fromUserId];
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: data.sdp })
);
});
Handle ICE Candidates
socket.on('rtc:candidate', async (data) => {
const pc = peerConnections[data.fromUserId];
if (pc && data.candidate) {
await pc.addIceCandidate(new RTCIceCandidate(data.candidate));
}
});
Leave a Call
socket.emit('rtc:leaveCall', { callId: callId });
localStream.getTracks().forEach(track => track.stop());
Object.values(peerConnections).forEach(pc => pc.close());
Error Handling
socket.on('rtc:error', (error) => {
switch (error.code) {
case 'UNAUTHORIZED':
break;
case 'FORBIDDEN':
break;
case 'CALL_FULL':
break;
case 'CALL_NOT_FOUND':
break;
}
});
Complete Implementation
class WebRTCCall {
constructor(socket, callId) {
this.socket = socket;
this.callId = callId;
this.localStream = null;
this.peerConnections = {};
this.setupListeners();
}
async join() {
this.localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
this.socket.emit('rtc:joinCall', {
callId: this.callId,
initialMediaState: { audioEnabled: true, videoEnabled: true }
});
}
setupListeners() {
this.socket.on('rtc:callStarted', (data) => {
data.participants.forEach(p => {
if (p.userId !== currentUserId) {
this.createPeerConnection(p.userId);
}
});
});
this.socket.on('rtc:participantJoined', (data) => {
this.createPeerConnection(data.userId);
});
this.socket.on('rtc:participantLeft', (data) => {
this.closePeerConnection(data.userId);
});
this.socket.on('rtc:offer', (data) => this.handleOffer(data));
this.socket.on('rtc:answer', (data) => this.handleAnswer(data));
this.socket.on('rtc:candidate', (data) => this.handleCandidate(data));
}
createPeerConnection(userId) {
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: [
'turn:turn.dananeer.io:3478?transport=udp',
'turn:turn.dananeer.io:3478?transport=tcp',
'turns:turn.dananeer.io:443?transport=tcp'
],
username: 'dananeer_turn',
credential: 'rZBSJ0z2mrDU4ja6j9DT5yPG'
}
]
});
this.localStream.getTracks().forEach(track => {
pc.addTrack(track, this.localStream);
});
pc.ontrack = (event) => displayRemoteVideo(userId, event.streams[0]);
pc.onicecandidate = (event) => {
if (event.candidate) {
this.socket.emit('rtc:candidate', {
callId: this.callId,
toUserId: userId,
candidate: event.candidate
});
}
};
this.peerConnections[userId] = pc;
this.createOffer(userId, pc);
return pc;
}
async createOffer(userId, pc) {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
this.socket.emit('rtc:offer', {
callId: this.callId,
toUserId: userId,
sdp: pc.localDescription.sdp
});
}
async handleOffer(data) {
const pc = this.peerConnections[data.fromUserId] || this.createPeerConnection(data.fromUserId);
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'offer', sdp: data.sdp })
);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
this.socket.emit('rtc:answer', {
callId: this.callId,
toUserId: data.fromUserId,
sdp: pc.localDescription.sdp
});
}
async handleAnswer(data) {
const pc = this.peerConnections[data.fromUserId];
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: data.sdp })
);
}
async handleCandidate(data) {
const pc = this.peerConnections[data.fromUserId];
if (pc && data.candidate) {
await pc.addIceCandidate(new RTCIceCandidate(data.candidate));
}
}
toggleMute(muted) {
this.socket.emit('rtc:toggleMute', { callId: this.callId, muted });
}
toggleVideo(enabled) {
this.socket.emit('rtc:toggleVideo', { callId: this.callId, enabled });
}
closePeerConnection(userId) {
if (this.peerConnections[userId]) {
this.peerConnections[userId].close();
delete this.peerConnections[userId];
}
}
leave() {
this.socket.emit('rtc:leaveCall', { callId: this.callId });
this.localStream.getTracks().forEach(track => track.stop());
Object.values(this.peerConnections).forEach(pc => pc.close());
this.peerConnections = {};
}
}
TURN Server Credentials
Configure TURN servers for reliable WebRTC connections across all network types.
Quick Setup
Add TURN server credentials to your RTCPeerConnection:
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: [
'turn:turn.dananeer.io:3478?transport=udp',
'turn:turn.dananeer.io:3478?transport=tcp',
'turns:turn.dananeer.io:443?transport=tcp'
],
username: 'dananeer_turn',
credential: 'rZBSJ0z2mrDU4ja6j9DT5yPG'
}
]
});
Credentials
| Field | Value |
|---|---|
| Username | dananeer_turn |
| Password | rZBSJ0z2mrDU4ja6j9DT5yPG |
Server URLs
Production:
turn:turn.dananeer.io:3478?transport=udpturn:turn.dananeer.io:3478?transport=tcpturns:turn.dananeer.io:443?transport=tcp
IP Address: 165.22.71.105
Why TURN Servers
TURN servers relay media when direct peer-to-peer connections fail. This occurs on:
- Mobile networks (4G, 5G)
- Corporate firewalls
- Symmetric NAT configurations
- Restrictive network policies
WebRTC attempts direct connection first. If unsuccessful, it automatically falls back to TURN relay. Always configure both STUN and TURN servers.
Complete Example
function createPeerConnection(userId) {
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: [
'turn:turn.dananeer.io:3478?transport=udp',
'turn:turn.dananeer.io:3478?transport=tcp',
'turns:turn.dananeer.io:443?transport=tcp'
],
username: 'dananeer_turn',
credential: 'rZBSJ0z2mrDU4ja6j9DT5yPG'
}
]
});
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
pc.ontrack = (event) => {
displayRemoteVideo(userId, event.streams[0]);
};
pc.onicecandidate = (event) => {
if (event.candidate) {
socket.emit('rtc:candidate', {
callId: callId,
toUserId: userId,
candidate: event.candidate
});
}
};
return pc;
}
Test Connection
Verify TURN server connectivity:
- Open https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
- Enter server details:
- URL:
turn:turn.dananeer.io:3478 - Username:
dananeer_turn - Password:
rZBSJ0z2mrDU4ja6j9DT5yPG
- URL:
- Click "Gather candidates"
- Verify "relay" candidates appear in results
For comprehensive testing, visit turn.dananeer.io and use the test server tool.
Related Documentation
- TURN Server Configuration - Complete configuration guide
- WebRTC Configuration - Backend environment variables
- Integration Guide - Implementation steps
Notes
- Credentials are for client-side WebRTC configuration only
- Include all three transport types (UDP, TCP, TLS) for maximum compatibility
- Server is pre-configured and operational
- TLS transport (port 443) is required for 4G/5G networks
- For backend configuration, see Server Configuration
API Reference
Complete reference for WebRTC signaling events and HTTP endpoints.
HTTP Endpoints
GET /api/v1/rtc/config
Returns TURN/STUN server configuration.
Authentication: Bearer token required
{
"turnUrls": [
"turn:turn.dananeer.io:3478?transport=udp",
"turn:turn.dananeer.io:3478?transport=tcp",
"turns:turn.dananeer.io:443?transport=tcp"
],
"turnUsername": "dananeer_turn",
"turnCredential": "rZBSJ0z2mrDU4ja6j9DT5yPG",
"stunUrls": ["stun:stun.l.google.com:19302"],
"maxParticipants": 4
}
Example
const response = await fetch('/api/v1/rtc/config', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const config = await response.json();
const pc = new RTCPeerConnection({
iceServers: [
...config.stunUrls.map(url => ({ urls: url })),
{
urls: config.turnUrls,
username: config.turnUsername,
credential: config.turnCredential
}
]
});
Client Events
Events sent from client to server.
rtc:joinCall
Join a call room.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
initialMediaState.audioEnabled |
boolean | No | Audio enabled (default: true) |
initialMediaState.videoEnabled |
boolean | No | Video enabled (default: true) |
rtc:leaveCall
Leave a call room.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
rtc:offer
Send WebRTC SDP offer.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
toUserId |
string | Yes | Target user ID |
sdp |
string | Yes | SDP offer string |
rtc:answer
Send WebRTC SDP answer.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
toUserId |
string | Yes | Target user ID |
sdp |
string | Yes | SDP answer string |
rtc:candidate
Send ICE candidate.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
toUserId |
string | Yes | Target user ID |
candidate |
RTCIceCandidateInit | Yes | ICE candidate |
rtc:toggleMute
Toggle microphone.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
muted |
boolean | Yes | Mute state |
rtc:toggleVideo
Toggle camera.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
enabled |
boolean | Yes | Video enabled |
rtc:toggleScreenShare
Toggle screen sharing.
| Parameter | Type | Required | Description |
|---|---|---|---|
callId |
string | Yes | Call identifier |
enabled |
boolean | Yes | Screen share enabled |
Server Events
Events received from server.
rtc:callStarted
Confirms successful call join.
| Field | Type | Description |
|---|---|---|
callId |
string | Call identifier |
participants |
array | Current participants with media states |
rtc:participantJoined
New participant joined.
| Field | Type | Description |
|---|---|---|
callId |
string | Call identifier |
userId |
string | New participant ID |
mediaState |
object | Initial media state |
rtc:participantLeft
Participant left the call.
| Field | Type | Description |
|---|---|---|
callId |
string | Call identifier |
userId |
string | Departed participant ID |
rtc:mediaUpdated
Media state changed.
| Field | Type | Description |
|---|---|---|
callId |
string | Call identifier |
userId |
string | Participant ID |
mediaState |
object | Updated media state |
rtc:offer (received)
Incoming WebRTC offer.
| Field | Type | Description |
|---|---|---|
callId |
string | Call identifier |
fromUserId |
string | Sender ID |
sdp |
string | SDP offer |
rtc:answer (received)
Incoming WebRTC answer.
| Field | Type | Description |
|---|---|---|
callId |
string | Call identifier |
fromUserId |
string | Sender ID |
sdp |
string | SDP answer |
rtc:candidate (received)
Incoming ICE candidate.
| Field | Type | Description |
|---|---|---|
callId |
string | Call identifier |
fromUserId |
string | Sender ID |
candidate |
RTCIceCandidateInit | ICE candidate |
rtc:error
Error occurred.
| Field | Type | Description |
|---|---|---|
code |
string | Error code |
message |
string | Error message |
details |
object | Additional details |
Error Codes
| Code | Description |
|---|---|
UNAUTHORIZED |
Authentication required |
FORBIDDEN |
Access denied |
CALL_FULL |
Maximum participants reached |
CALL_NOT_FOUND |
Call does not exist |
INVALID_PAYLOAD |
Invalid request data |
INTERNAL_ERROR |
Server error |
Room Format
Socket.IO rooms follow the pattern: rtc:${callId}
The server manages room membership automatically on join and leave events.
Related Documentation
- Events Reference - All signaling events
- Call Lifecycle - Complete call flow
- Integration Guide - Implementation steps
TURN Server Configuration
Complete configuration guide for the Dananeer TURN server.
What is a TURN Server?
A TURN (Traversal Using Relays around NAT) server is a critical component for WebRTC applications. It acts as a relay server that forwards media traffic between peers when direct peer-to-peer connections are not possible due to network restrictions.
When is TURN needed?
- 4G/LTE mobile networks (carrier restrictions)
- 5G networks with strict NAT
- Corporate firewalls
- Symmetric NAT environments
- Restrictive network policies
How it works: When WebRTC cannot establish a direct connection, it automatically falls back to using the TURN server as an intermediary, ensuring reliable communication across all network types.
Server Configuration
Domain
turn.dananeer.io
IP Address
165.22.71.105
Ports
- UDP:
3478- For fast LAN/WiFi connections - TCP:
3478- For moderate firewalls - TLS:
443- For 4G/5G and restrictive networks
Transport Types Supported
- UDP (User Datagram Protocol) - Fastest, lowest latency
- TCP (Transmission Control Protocol) - More reliable
- TLS (Transport Layer Security) - Most secure, works everywhere
Backend Configuration
Add these environment variables to your backend .env file:
# STUN servers (optional - defaults to Google STUN servers)
RTC_STUN_URLS=stun:stun.l.google.com:19302,stun:stun1.l.google.com:19302
# TURN servers - Include all three transport types for maximum compatibility
# Supports: 4G, 5G, Firewalls, Symmetric NAT, Corporate Networks
RTC_TURN_URLS=turn:turn.dananeer.io:3478?transport=udp,turn:turn.dananeer.io:3478?transport=tcp,turns:turn.dananeer.io:443?transport=tcp
# TURN server authentication (required if TURN_URLS is set)
RTC_TURN_USERNAME=dananeer_turn
RTC_TURN_CREDENTIAL=rZBSJ0z2mrDU4ja6j9DT5yPG
# Maximum participants per call (optional, default: 4)
RTC_MAX_PARTICIPANTS=4
Configuration Details
RTC_STUN_URLS (Optional)
STUN (Session Traversal Utilities for NAT) servers for discovering public IP addresses.
Default: Google STUN servers are used automatically if not specified.
RTC_TURN_URLS (Optional)
TURN server URLs with transport types. Include all three for maximum compatibility:
turn:turn.dananeer.io:3478?transport=udp- For LAN/WiFiturn:turn.dananeer.io:3478?transport=tcp- For moderate firewallsturns:turn.dananeer.io:443?transport=tcp- For 4G/5G and restrictive networks
Default: Empty array (no TURN servers). Works for LAN/WiFi only.
RTC_TURN_USERNAME (Required if TURN_URLS set)
TURN server username for authentication.
Current value: dananeer_turn
RTC_TURN_CREDENTIAL (Required if TURN_URLS set)
TURN server password/credential for authentication.
Note: Keep this secure and never commit to version control.
RTC_MAX_PARTICIPANTS (Optional)
Maximum number of participants allowed in a WebRTC call.
Default: 4
Range: 2-10 (enforced automatically)
Network Support
This TURN server configuration supports all network types:
| Network Type | Transport |
|---|---|
| LAN Networks | UDP transport |
| WiFi Networks | UDP/TCP transport |
| 4G/LTE Networks | TLS transport |
| 5G Networks | TLS transport |
| Restrictive Firewalls | TLS transport |
| Symmetric NAT | TURN relay |
| Corporate Networks | TLS transport |
API Endpoint
Your backend exposes the RTC configuration via HTTP endpoint:
GET /api/v1/rtc/config
Response:
{
"turnUrls": [
"turn:turn.dananeer.io:3478?transport=udp",
"turn:turn.dananeer.io:3478?transport=tcp",
"turns:turn.dananeer.io:443?transport=tcp"
],
"turnUsername": "dananeer_turn",
"turnCredential": "rZBSJ0z2mrDU4ja6j9DT5yPG",
"stunUrls": [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302"
],
"maxParticipants": 4
}
Frontend Usage
In your frontend WebRTC code, fetch the configuration and use it:
const config = await fetch('/api/v1/rtc/config', {
headers: { Authorization: `Bearer ${token}` }
});
const pc = new RTCPeerConnection({
iceServers: [
...config.stunUrls.map(url => ({ urls: url })),
...config.turnUrls.map(url => ({
urls: url,
username: config.turnUsername,
credential: config.turnCredential
}))
]
});
Related Documentation
- TURN Server Credentials - Quick setup guide
- Integration Guide - Complete implementation
- API Reference - HTTP endpoints
WebRTC Overview
Complete guide to WebRTC implementation, lifecycle, and configuration.
What is WebRTC?
WebRTC (Web Real-Time Communication) is a technology that enables peer-to-peer communication between browsers and mobile applications. It allows real-time audio, video, and data exchange without requiring plugins or additional software.
Key Components
- STUN Server: Discovers public IP addresses for direct peer-to-peer connections
- TURN Server: Relays media traffic when direct connections fail (4G, firewalls, NAT)
- Signaling: WebSocket-based event exchange for connection setup
- ICE (Interactive Connectivity Establishment): Framework for finding the best connection path
Architecture
WebRTC uses a combination of signaling (Socket.IO) and peer-to-peer media streaming (WebRTC API) to enable real-time communication.
Architecture Diagram
The WebRTC architecture consists of:
| Component | Purpose |
|---|---|
| Socket.IO | Signaling server for event exchange and connection coordination |
| STUN Server | Discovers public IP addresses for direct peer-to-peer connections |
| TURN Server | Relays media traffic when direct connections fail |
| WebRTC API | Browser/application API for peer-to-peer media streaming |
Next Steps
Continue reading to learn about:
- Call Lifecycle - Understanding the complete call flow
- Events Reference - All signaling events
- Integration Guide - Step-by-step implementation
- TURN Server Configuration - Server setup and credentials
Call Lifecycle
Understanding the complete WebRTC call lifecycle from start to finish.
1. Join/Start Call
User initiates or joins a call by emitting rtc:joinCall with callId and optional initial media state. Backend creates the call room if it doesn't exist and adds the user as a participant. If other participants exist, they receive rtc:participantJoined event.
2. Call Started Event
After joining, the client receives rtc:callStarted event with the callId and list of all current participants. This allows the client to synchronize with existing participants.
3. ICE Configuration
Clients fetch RTC configuration via GET /api/v1/rtc/config to get STUN/TURN servers and create RTCPeerConnection (Web) or PeerConnection (Unity) with ICE servers.
4. Create Offer
Caller creates an offer using createOffer() and sets it as local description. The SDP is sent via rtc:offer event to the target user.
5. Receive Offer & Create Answer
Receiver sets the offer as remote description, creates an answer with createAnswer(), sets it as local description, and sends it via rtc:answer event.
6. ICE Candidate Exchange
As ICE candidates are discovered, they are exchanged via rtc:candidate events. This continues until a connection is established or all candidates are exhausted.
7. Connection Established
When ICE connection state becomes connected, media streams flow directly between peers (or via TURN relay if needed).
8. Media State Updates
Participants can toggle audio/video/screen sharing via rtc:toggleMute, rtc:toggleVideo, rtc:toggleScreenShare. Changes are broadcast via rtc:mediaUpdated.
9. Leave Call
User emits rtc:leaveCall or disconnects. Backend removes them from the room and broadcasts rtc:participantLeft to remaining participants. When the last participant leaves, the room is automatically cleaned up.
Related Documentation
- Events Reference - All lifecycle events
- Integration Guide - Implementation steps
- Troubleshooting - Common issues
Events Reference
Complete reference for all WebRTC signaling events.
Client â Server Events (Subscribed)
| Event | Payload | Description |
|---|---|---|
rtc:ping |
{} |
Ping server, receive pong with config |
rtc:joinCall |
{ callId, initialMediaState? } |
Join or start a call (idempotent - creates call if doesn't exist) |
rtc:leaveCall |
{ callId } |
Leave the current call |
rtc:offer |
{ callId, toUserId, sdp } |
Send WebRTC offer SDP |
rtc:answer |
{ callId, toUserId, sdp } |
Send WebRTC answer SDP |
rtc:candidate |
{ callId, toUserId, candidate } |
Send ICE candidate |
rtc:toggleMute |
{ callId, muted } |
Toggle audio mute state |
rtc:toggleVideo |
{ callId, enabled } |
Toggle video enabled state |
rtc:toggleScreenShare |
{ callId, enabled } |
Toggle screen sharing |
Server â Client Events (Published)
| Event | Payload | Description |
|---|---|---|
rtc:pong |
{ ts, rtc: { maxParticipants }, echo } |
Response to ping |
rtc:callStarted |
{ callId, participants } |
Call started, includes all participants |
rtc:participantJoined |
{ callId, userId, mediaState } |
New participant joined |
rtc:participantLeft |
{ callId, userId } |
Participant left the call |
rtc:offer |
{ callId, fromUserId, toUserId, sdp } |
Relayed offer from another participant |
rtc:answer |
{ callId, fromUserId, toUserId, sdp } |
Relayed answer from another participant |
rtc:candidate |
{ callId, fromUserId, toUserId, candidate } |
Relayed ICE candidate |
rtc:mediaUpdated |
{ callId, userId, mediaState } |
Participant media state changed |
rtc:error |
{ code, message, details? } |
Error occurred |
Related Documentation
- Call Lifecycle - How events are used in the lifecycle
- Integration Guide - Implementation examples
- API Reference - Complete API documentation
Configuration
Backend environment variables and network support configuration.
Backend Environment Variables
RTC_STUN_URLS (Optional)
STUN server URLs (comma-separated). Defaults to Google STUN servers if not set.
RTC_STUN_URLS=stun:stun.l.google.com:19302,stun:stun1.l.google.com:19302
RTC_TURN_URLS (Optional)
TURN server URLs with transport types. Required for 4G/5G networks.
RTC_TURN_URLS=turn:turn.dananeer.io:3478?transport=udp,turn:turn.dananeer.io:3478?transport=tcp,turns:turn.dananeer.io:443?transport=tcp
RTC_TURN_USERNAME (Required)
TURN server username. Required if TURN_URLS is set.
RTC_TURN_USERNAME=dananeer_turn
RTC_TURN_CREDENTIAL (Required)
TURN server password. Required if TURN_URLS is set.
RTC_TURN_CREDENTIAL=rZBSJ0z2mrDU4ja6j9DT5yPG
RTC_MAX_PARTICIPANTS (Optional)
Maximum participants per call. Default: 4, Range: 2-10.
RTC_MAX_PARTICIPANTS=4
Network Support
| Network Type | Transport |
|---|---|
| LAN Networks | UDP transport (fastest) |
| WiFi Networks | UDP/TCP transport |
| 4G/LTE Networks | TLS transport (port 443) |
| 5G Networks | TLS transport (port 443) |
| Firewalls | TLS transport (port 443) |
| Symmetric NAT | TURN relay |
| Corporate Networks | TLS transport (port 443) |
Related Documentation
- TURN Server Configuration - Complete server setup guide
- API Reference - HTTP endpoints
- Integration Guide - Implementation steps
API Reference
HTTP endpoint for retrieving RTC configuration.
Get RTC Configuration
Retrieve STUN/TURN server configuration for WebRTC peer connections.
GET /api/v1/rtc/config
Headers:
Authorization: Bearer <access_token>
Response: 200 OK
{
"turnUrls": [
"turn:turn.dananeer.io:3478?transport=udp",
"turn:turn.dananeer.io:3478?transport=tcp",
"turns:turn.dananeer.io:443?transport=tcp"
],
"turnUsername": "dananeer_turn",
"turnCredential": "rZBSJ0z2mrDU4ja6j9DT5yPG",
"stunUrls": [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302"
],
"maxParticipants": 4
}
Example Usage
const response = await fetch('/api/v1/rtc/config', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const config = await response.json();
const pc = new RTCPeerConnection({
iceServers: [
...config.stunUrls.map(url => ({ urls: url })),
...config.turnUrls.map(url => ({
urls: url,
username: config.turnUsername,
credential: config.turnCredential
}))
]
});
Related Documentation
- Configuration - Backend environment variables
- Integration Guide - Implementation steps
- TURN Server Configuration - Server setup
Integration Guide
Step-by-step guide for integrating WebRTC into your web frontend.
Web Frontend Implementation
1. Fetch RTC Configuration
async function fetchRtcConfig() {
const response = await fetch('/api/v1/rtc/config', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
return await response.json();
}
2. Initialize RTCPeerConnection
const config = await fetchRtcConfig();
const iceServers = [
...config.stunUrls.map(url => ({ urls: url })),
...config.turnUrls.map(url => ({
urls: url,
username: config.turnUsername,
credential: config.turnCredential
}))
];
const pc = new RTCPeerConnection({
iceServers
});
3. Handle ICE Candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
socket.emit('rtc:candidate', {
callId: currentCallId,
toUserId: targetUserId,
candidate: event.candidate
});
}
};
4. Handle Offer/Answer
socket.on('rtc:offer', async (data) => {
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'offer', sdp: data.sdp })
);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
socket.emit('rtc:answer', {
callId: data.callId,
toUserId: data.fromUserId,
sdp: pc.localDescription.sdp
});
});
socket.on('rtc:answer', async (data) => {
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: data.sdp })
);
});
5. Handle Media Streams
pc.ontrack = (event) => {
const remoteStream = event.streams[0];
remoteVideoElement.srcObject = remoteStream;
};
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then(stream => {
localVideoElement.srcObject = stream;
stream.getTracks().forEach(track => {
pc.addTrack(track, stream);
});
});
Complete Implementation Example
Here's a complete example combining all the steps:
class WebRTCCall {
constructor(socket, callId) {
this.socket = socket;
this.callId = callId;
this.localStream = null;
this.peerConnections = {};
this.setupListeners();
}
async join() {
this.localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
this.socket.emit('rtc:joinCall', {
callId: this.callId,
initialMediaState: { audioEnabled: true, videoEnabled: true }
});
}
setupListeners() {
this.socket.on('rtc:callStarted', (data) => {
data.participants.forEach(p => {
if (p.userId !== currentUserId) {
this.createPeerConnection(p.userId);
}
});
});
this.socket.on('rtc:participantJoined', (data) => {
this.createPeerConnection(data.userId);
});
this.socket.on('rtc:participantLeft', (data) => {
this.closePeerConnection(data.userId);
});
this.socket.on('rtc:offer', (data) => this.handleOffer(data));
this.socket.on('rtc:answer', (data) => this.handleAnswer(data));
this.socket.on('rtc:candidate', (data) => this.handleCandidate(data));
}
async createPeerConnection(userId) {
const config = await fetch('/api/v1/rtc/config', {
headers: { Authorization: `Bearer ${accessToken}` }
}).then(r => r.json());
const pc = new RTCPeerConnection({
iceServers: [
...config.stunUrls.map(url => ({ urls: url })),
...config.turnUrls.map(url => ({
urls: url,
username: config.turnUsername,
credential: config.turnCredential
}))
]
});
this.localStream.getTracks().forEach(track => {
pc.addTrack(track, this.localStream);
});
pc.ontrack = (event) => displayRemoteVideo(userId, event.streams[0]);
pc.onicecandidate = (event) => {
if (event.candidate) {
this.socket.emit('rtc:candidate', {
callId: this.callId,
toUserId: userId,
candidate: event.candidate
});
}
};
this.peerConnections[userId] = pc;
this.createOffer(userId, pc);
return pc;
}
async createOffer(userId, pc) {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
this.socket.emit('rtc:offer', {
callId: this.callId,
toUserId: userId,
sdp: pc.localDescription.sdp
});
}
async handleOffer(data) {
const pc = this.peerConnections[data.fromUserId] || await this.createPeerConnection(data.fromUserId);
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'offer', sdp: data.sdp })
);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
this.socket.emit('rtc:answer', {
callId: this.callId,
toUserId: data.fromUserId,
sdp: pc.localDescription.sdp
});
}
async handleAnswer(data) {
const pc = this.peerConnections[data.fromUserId];
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: data.sdp })
);
}
async handleCandidate(data) {
const pc = this.peerConnections[data.fromUserId];
if (pc && data.candidate) {
await pc.addIceCandidate(new RTCIceCandidate(data.candidate));
}
}
toggleMute(muted) {
this.socket.emit('rtc:toggleMute', { callId: this.callId, muted });
}
toggleVideo(enabled) {
this.socket.emit('rtc:toggleVideo', { callId: this.callId, enabled });
}
closePeerConnection(userId) {
if (this.peerConnections[userId]) {
this.peerConnections[userId].close();
delete this.peerConnections[userId];
}
}
leave() {
this.socket.emit('rtc:leaveCall', { callId: this.callId });
this.localStream.getTracks().forEach(track => track.stop());
Object.values(this.peerConnections).forEach(pc => pc.close());
this.peerConnections = {};
}
}
Related Documentation
- Call Lifecycle - Complete call flow
- Events Reference - All signaling events
- API Reference - HTTP endpoints
- Unity Integration - Unity game integration
Unity Integration
Complete guide for integrating WebRTC into Unity applications.
Overview
Unity applications can integrate with the WebRTC backend using Socket.IO client libraries and Unity's WebRTC package. The backend API is platform-agnostic and works with any WebRTC-compatible client.
Required Packages
Unity WebRTC package is available through Unity Package Manager. You'll also need a Socket.IO client library compatible with Unity (C#) for WebSocket signaling. Popular options include SocketIOClient or similar C# Socket.IO implementations.
Platform Support
Unity WebRTC supports multiple platforms including Windows, macOS, Linux, iOS, and Android. This makes it ideal for cross-platform game development with voice communication features.
Integration Steps
- Install Unity WebRTC Package: Add the Unity WebRTC package through Package Manager using the Git URL or Unity Registry.
- Add Socket.IO Client: Integrate a Socket.IO client library for C# to handle WebSocket signaling with the backend.
- Fetch RTC Configuration: Make an HTTP GET request to
/api/v1/rtc/configendpoint with your access token to retrieve STUN/TURN server configuration. - Initialize PeerConnection: Create a Unity WebRTC PeerConnection instance with the ICE servers from the configuration response.
- Connect Socket.IO: Establish WebSocket connection to the backend with JWT token for authentication.
- Join Call: Emit
rtc:joinCallevent with thecallIdto join or start a call. - Handle Signaling: Listen for
rtc:offer,rtc:answer, andrtc:candidateevents and process them with Unity WebRTC APIs. - Add Audio Track: Capture audio from Unity's audio system and add it to the peer connection.
- Handle Remote Audio: Receive remote audio tracks and route them to Unity's audio playback system.
- Manage Lifecycle: Handle call lifecycle events (
rtc:callStarted,rtc:participantJoined,rtc:participantLeft) and update your game UI accordingly.
Complete Code Implementation
Below are complete, production-ready C# code examples for integrating TURN server and WebRTC in Unity.
Step 1: RTC Configuration Model
using System;
using System.Collections.Generic;
[Serializable]
public class RtcConfigResponse
{
public List<string> stunUrls;
public List<string> turnUrls;
public string turnUsername;
public string turnCredential;
public int maxParticipants;
}
Step 2: Fetch TURN Server Configuration
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using Newtonsoft.Json;
public class RtcConfigManager : MonoBehaviour
{
private string apiBaseUrl = "https://your-api-server.com";
private string accessToken = "your-access-token";
public async void FetchRtcConfig(System.Action<RtcConfigResponse> onSuccess, System.Action<string> onError)
{
string url = $"{apiBaseUrl}/api/v1/rtc/config";
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
request.SetRequestHeader("Authorization", $"Bearer {accessToken}");
request.SetRequestHeader("Content-Type", "application/json");
var operation = request.SendWebRequest();
while (!operation.isDone)
await System.Threading.Tasks.Task.Yield();
if (request.result == UnityWebRequest.Result.Success)
{
try
{
RtcConfigResponse config = JsonConvert.DeserializeObject<RtcConfigResponse>(
request.downloadHandler.text);
Debug.Log($"[RTC Config] STUN: {config.stunUrls.Count}, TURN: {config.turnUrls.Count}");
onSuccess?.Invoke(config);
}
catch (Exception e)
{
Debug.LogError($"[RTC Config] Parse error: {e.Message}");
onError?.Invoke(e.Message);
}
}
else
{
Debug.LogError($"[RTC Config] Request failed: {request.error}");
onError?.Invoke(request.error);
}
}
}
public RTCIceServer[] BuildIceServers(RtcConfigResponse config)
{
List<RTCIceServer> iceServers = new List<RTCIceServer>();
// Add STUN servers
foreach (string stunUrl in config.stunUrls)
{
iceServers.Add(new RTCIceServer { urls = new string[] { stunUrl } });
Debug.Log($"[ICE] Added STUN: {stunUrl}");
}
// Add TURN servers with credentials
foreach (string turnUrl in config.turnUrls)
{
iceServers.Add(new RTCIceServer
{
urls = new string[] { turnUrl },
username = config.turnUsername,
credential = config.turnCredential
});
Debug.Log($"[ICE] Added TURN: {turnUrl}");
}
return iceServers.ToArray();
}
}
Step 3: WebRTC Manager with TURN Detection
using System.Collections;
using UnityEngine;
using Unity.WebRTC;
using SocketIOClient;
public class UnityWebRtcManager : MonoBehaviour
{
[Header("Configuration")]
public string apiBaseUrl = "https://your-api-server.com";
public string socketUrl = "wss://your-api-server.com";
public string accessToken = "your-access-token";
private SocketIO socket;
private Dictionary<string, RTCPeerConnection> peerConnections = new Dictionary<string, RTCPeerConnection>();
private RtcConfigManager configManager;
private string currentCallId;
public async void InitializeConnection(string callId)
{
currentCallId = callId;
configManager = GetComponent<RtcConfigManager>();
configManager.FetchRtcConfig(
onSuccess: (config) => {
Debug.Log($"[RTC] Config received: {config.turnUrls.Count} TURN servers");
ConnectSocketIO(config);
},
onError: (error) => Debug.LogError($"[RTC] Failed: {error}")
);
}
private RTCPeerConnection CreatePeerConnection(string userId, RtcConfigResponse config)
{
RTCIceServer[] iceServers = configManager.BuildIceServers(config);
var pc = new RTCPeerConnection(ref iceServers);
pc.OnIceConnectionChange = (state) => {
Debug.Log($"[{userId}] ICE Connection State: {state}");
if (state == RTCIceConnectionState.Connected ||
state == RTCIceConnectionState.Completed)
{
Debug.Log($"[{userId}] â
Connection established!");
}
else if (state == RTCIceConnectionState.Failed)
{
Debug.LogError($"[{userId}] â Connection failed!");
}
};
pc.OnIceCandidate = (candidate) => {
if (candidate != null)
{
bool isTurnRelay = candidate.Type == RTCIceCandidateType.Relay ||
candidate.Candidate.Contains("relay");
Debug.Log($"[{userId}] ICE Candidate: {candidate.Type} " +
(isTurnRelay ? "đ TURN RELAY" : ""));
// Send candidate via Socket.IO
socket.EmitAsync("rtc:candidate", new
{
callId = currentCallId,
toUserId = userId,
candidate = new
{
candidate = candidate.Candidate,
sdpMid = candidate.SdpMid,
sdpMLineIndex = candidate.SdpMLineIndex
}
});
}
else
{
Debug.Log($"[{userId}] ICE candidate gathering completed");
}
};
pc.OnTrack = (e) => {
Debug.Log($"[{userId}] Remote track received: {e.Track.Kind}");
// Handle remote audio/video track
};
peerConnections[userId] = pc;
StartCoroutine(CreateOffer(pc, userId));
return pc;
}
// Additional implementation: ConnectSocketIO, JoinCall, CreateOffer,
// HandleOffer, HandleAnswer, HandleCandidate methods
// See full code in documentation
}
Key Considerations for Unity
- Threading: WebRTC operations in Unity run on background threads. Ensure UI updates happen on Unity's main thread.
- Audio Handling: Use Unity's AudioSource components for playback and Unity WebRTC's audio capture APIs for microphone input.
- Permissions: Request microphone permissions on mobile platforms (Android/iOS) before attempting to capture audio.
- Network Configuration: Ensure TURN servers with TLS transport are configured for mobile networks (4G/5G) to work reliably.
- Event Handling: Socket.IO events should be processed asynchronously and integrated with Unity's event system.
- Error Handling: Implement proper error handling for network failures, ICE connection failures, and authentication errors.
- State Management: Track call state, participant lists, and media state changes to keep your game synchronized.
Backend Compatibility
The backend WebRTC API is fully compatible with Unity clients. All events, payloads, and lifecycle management work identically whether connecting from a web browser or Unity application. The only difference is the WebRTC implementation library used on the client side.
Testing Unity Integration
When testing Unity integration, ensure you test on the target platforms (especially mobile) as WebRTC behavior can vary between platforms. Test with different network conditions including WiFi, 4G, and 5G to verify TURN server configuration is working correctly.
Related Documentation
- TURN Server Configuration - Complete server setup
- TURN Server Credentials - Authentication details
- TURN Connection Logs - Testing and debugging
- Call Lifecycle - Understanding the call flow
- Events Reference - All signaling events
- Troubleshooting - Common issues and solutions
đ Note: Complete Code Examples
For complete C# code examples, TURN server configuration details, and step-by-step implementation guide, see the comprehensive documentation below. The implementation includes:
- Complete RTC configuration fetching with UnityWebRequest
- Full WebRTC manager implementation with Socket.IO
- TURN server connection and ICE candidate handling
- Platform-specific considerations (Android, iOS, Desktop)
- Debugging and logging best practices
Troubleshooting
Common issues and solutions for WebRTC integration.
Common Issues
Connection Fails on 4G Network
Problem: WebRTC connection fails on mobile 4G networks.
Solution: Ensure TURN servers with TLS transport are configured:
RTC_TURN_URLS=turns:turn.dananeer.io:443?transport=tcp
No Audio/Video
Problem: Connection established but no media streams.
Solution: Check that tracks are added to peer connection and remote stream handlers are set up correctly.
ICE Connection Stuck on "checking"
Problem: ICE connection state remains "checking" indefinitely.
Solution: Verify STUN/TURN servers are accessible and credentials are correct. Check browser console for ICE candidate errors.
High Latency
Problem: High latency in media streams.
Solution: Check if TURN relay is being used (check ICE connection type). For LAN/WiFi, ensure UDP transport is prioritized.
Debugging
pc.oniceconnectionstatechange = () => {
console.log('ICE Connection State:', pc.iceConnectionState);
};
pc.onconnectionstatechange = () => {
console.log('Connection State:', pc.connectionState);
};
pc.onicegatheringstatechange = () => {
console.log('ICE Gathering State:', pc.iceGatheringState);
};
Related Documentation
- Configuration - Backend environment variables
- Integration Guide - Step-by-step implementation
- TURN Server Configuration - Server setup
TURN Connection Logs
Comprehensive logging and monitoring for TURN server connections. Test your TURN server configuration and review all connection events.
Test TURN Server Connection
Connection Logs
Log Information
The logs capture all TURN connection events including:
- ICE Candidate Gathering: All discovered candidates (host, srflx, relay)
- Connection States: ICE connection state, peer connection state, and gathering state changes
- TURN Server Usage: When TURN relay is used vs direct connection
- Connection Statistics: RTP/RTCP statistics, bitrate, packet loss, jitter
- Errors: Connection failures, authentication errors, and other issues
- Timestamps: All events are timestamped for correlation
Note: Logs are stored in memory and cleared when the page is refreshed. Use "Export Logs" to save them.
Related Documentation
- TURN Server Configuration - Server setup guide
- TURN Server Credentials - Authentication details
- Troubleshooting - Common issues and solutions