Skip to content
Docs
🔒

Protected Documentation

Please enter your credentials to access the documentation

Invalid username or password. Please try again.
RTC
TURN Server Configuration TURN Server Credentials WebRTC Overview Call Lifecycle Events Reference Configuration API Reference Integration Guide Unity Integration Troubleshooting TURN Connection Logs API Reference

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

Docs / Integration Guide

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 = {};
  }
}
Docs / TURN Server Credentials

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=udp
  • turn:turn.dananeer.io:3478?transport=tcp
  • turns: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:

  1. Open https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
  2. Enter server details:
    • URL: turn:turn.dananeer.io:3478
    • Username: dananeer_turn
    • Password: rZBSJ0z2mrDU4ja6j9DT5yPG
  3. Click "Gather candidates"
  4. 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
Docs / API Reference

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
Docs / TURN Server Configuration

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/WiFi
  • turn:turn.dananeer.io:3478?transport=tcp - For moderate firewalls
  • turns: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
Docs / WebRTC Overview

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
Docs / Call Lifecycle

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
Docs / Events Reference

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
Docs / Configuration

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
Docs / API Reference

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
Docs / Integration Guide

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
Docs / Unity 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

  1. Install Unity WebRTC Package: Add the Unity WebRTC package through Package Manager using the Git URL or Unity Registry.
  2. Add Socket.IO Client: Integrate a Socket.IO client library for C# to handle WebSocket signaling with the backend.
  3. Fetch RTC Configuration: Make an HTTP GET request to /api/v1/rtc/config endpoint with your access token to retrieve STUN/TURN server configuration.
  4. Initialize PeerConnection: Create a Unity WebRTC PeerConnection instance with the ICE servers from the configuration response.
  5. Connect Socket.IO: Establish WebSocket connection to the backend with JWT token for authentication.
  6. Join Call: Emit rtc:joinCall event with the callId to join or start a call.
  7. Handle Signaling: Listen for rtc:offer, rtc:answer, and rtc:candidate events and process them with Unity WebRTC APIs.
  8. Add Audio Track: Capture audio from Unity's audio system and add it to the peer connection.
  9. Handle Remote Audio: Receive remote audio tracks and route them to Unity's audio playback system.
  10. 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
Docs / Troubleshooting

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
Docs / TURN Connection Logs

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 Status
Ready to test
ICE Connection
-
Connection Type
-
Round Trip Time
-

Connection Logs

0 logs
📊
No logs yet
Click "Start Connection Test" to begin logging TURN connection events

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
On this page