How video calls work
Players start a video call from a contact's video button or from a video call in Recents. Voice stays on pma-voice; the picture from each phone's selfie camera goes through a TURN relay, so players never see each other's IP address.
Group video calls hold up to four people. Players start one with the video button at the top of a group conversation in Messages, and can join a group's call while it is on. Voice-only group calls hold up to eight. Clips Live streams go through the same relay.
Until a relay is set up, the video call button on a contact is greyed out, the group video button in Messages is hidden, and video calls are refused. Voice calls are not affected.
Choose one relay in config/calls_server.lua, which is loaded on the server only:
Config.CallRelay = {
relay = 'coturn', -- 'coturn' | 'cloudflare' | 'metered' | 'static'
...
}| Relay | Cost | What you need |
|---|---|---|
coturn | Free | Your own coturn server with a public IP |
cloudflare | Paid per GB | A Cloudflare Realtime TURN key |
metered | Paid per GB | A Metered.ca TURN app |
static | Depends on the provider | Any TURN server with a fixed username and password |
Each call gets its own temporary relay login made on your server; your secret, token or API key never reaches players. To keep your relay choice through updates, set it in a file in config/local/server/, such as Config.CallRelay.relay = 'cloudflare'.
Expect about 1 Mbps through the relay per active video call at good signal. In a group video call every pair of people has its own link: three people make 3 links, four people 6 links, about 6 Mbps.
Option A: coturn
coturn is the standard open-source TURN server. It needs a public IP and a few open ports. It can run on the same machine as FiveM or on a small separate VPS. You can use any ports; these examples use 41820 instead of the common 3478.
Linux (Ubuntu or Debian)
sudo apt install coturn
sudo sed -i 's/#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn
openssl rand -hex 32The last command prints your shared secret: copy it. Then replace the settings file, keeping the original:
sudo mv /etc/turnserver.conf /etc/turnserver.conf.orig
sudo nano /etc/turnserver.confPaste this, replacing the secret and hostname:
listening-port=41820
# Optional TURNS, see "Strict networks" below
# tls-listening-port=41821
fingerprint
use-auth-secret
static-auth-secret=PASTE_THE_SECRET_HERE
realm=turn.yourserver.com
# Video goes over this UDP range (~2 ports per active call; per link in a group video call)
min-port=49160
max-port=49200
no-cli
no-multicast-peers
# Never relay into private networks (protects your server's LAN)
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
# Only if the machine is behind NAT:
# external-ip=PUBLIC_IP/PRIVATE_IPStart coturn and open its ports:
sudo systemctl enable --now coturn
sudo systemctl restart coturn
sudo systemctl status coturn --no-pager
sudo ufw allow 41820/udp
sudo ufw allow 41820/tcp
sudo ufw allow 49160:49200/udpThe status should say active (running). If it does not, sudo journalctl -u coturn -n 30 shows why.
The range 49160 to 49200 gives about 20 calls at once. Widen min-port and max-port (and the firewall rule) if you expect more, counting a four-person group video call as six.
Windows (Docker)
coturn is made for Linux, so on Windows run it with Docker Desktop. Save the turnserver.conf above next to this file, with external-ip=YOUR_PUBLIC_IP set in it:
services:
coturn:
image: coturn/coturn
restart: unless-stopped
ports:
- "41820:41820/udp"
- "41820:41820/tcp"
- "49160-49200:49160-49200/udp"
volumes:
- ./turnserver.conf:/etc/coturn/turnserver.conf:rodocker compose up -d
New-NetFirewallRule -DisplayName "coturn" -Direction Inbound -Protocol UDP -LocalPort 41820,49160-49200 -Action Allow
New-NetFirewallRule -DisplayName "coturn tcp" -Direction Inbound -Protocol TCP -LocalPort 41820 -Action AllowIf the server sits behind a router, forward the same ports to it.
Connect the phone to coturn
In config/calls_server.lua:
relay = 'coturn',
coturn = {
host = 'turn.yourserver.com', -- or the public IP
port = 41820, -- same as listening-port
tlsPort = nil, -- same as tls-listening-port, if you set one
secret = '', -- leave empty and use server.cfg
ttlSeconds = 3600,
},And in server.cfg, the same secret as static-auth-secret:
set NOVA_TURN_SECRET "PASTE_THE_SECRET_HERE"You can keep every coturn value out of the config file instead: leave host and secret empty and set NOVA_TURN_HOST, NOVA_TURN_PORT and NOVA_TURN_SECRET in server.cfg. Use set, not setr: setr sends the value to players.
Strict networks. Some school, office and ISP networks block unusual UDP ports. For those players, also enable TURNS over TCP: add tls-listening-port, cert= and pkey= (for example your Let's Encrypt certificate for the hostname) to turnserver.conf, open that TCP port, and set tlsPort in the phone config. Port 443 gets through almost everywhere, if nothing else on the machine uses it.
Option B: Cloudflare
- In the Cloudflare dashboard, open Realtime → TURN Server and create a TURN key. Copy its Turn Token ID and API Token.
- In
config/calls_server.lua, setrelay = 'cloudflare'andcloudflare.keyId = 'your-token-id'. - In
server.cfg:set NOVA_TURN_CLOUDFLARE_TOKEN "your-api-token".
Option C: Metered
- At metered.ca, create a TURN app and copy your app name (the part before
.metered.live) and a TURN API key. - In
config/calls_server.lua, setrelay = 'metered'andmetered.appName = 'yourapp'. - In
server.cfg:set NOVA_TURN_METERED_KEY "your-api-key".
Option D: another provider
If your provider gives a fixed URL, username and password, set relay = 'static' and fill static.urls (for example { 'turn:turn.example.com:3478?transport=udp' }), username and credential. Fixed credentials cannot expire per call, so prefer the options above.
Test the relay
- Restart the resource and run
phoneadmin relayin the server console. It shows whether the relay is configured and prints a temporary test login.phoneadmin selftest reportalso checks it ([PASS] video call relay coturn). - Open the WebRTC Trickle ICE page, add the printed URL, username and password, and click Gather candidates. A row with type relay means the relay works.
- Make a video call in game: open a contact and tap the video button.
Picture quality and battery
The picture follows the call's signal bars, using the worse of the two phones. It stays clear at three bars or more and gets choppier, smaller and glitchier at two bars or fewer. Tune this per bar count in Config.VideoCalls.quality in config/communication.lua.
In a group video call, each phone splits its bitrate between the people it sends to, never below Config.GroupCalls.minVideoBitsPerSecond (120 kbps). The other group call limits are in the same Config.GroupCalls; see Group calls. Config.VideoCalls.enabled = false turns video calls off entirely.
Video calls start on speaker, use mobile data (about 5 MB per minute on each side, free on Wi-Fi) and drain the battery faster than voice calls. In a group, each person pays for their own data, and running out of data or signal only takes that person out. Low Power Mode turns video calls off. Putting the phone away pauses the player's camera.
Troubleshooting
| Problem | Fix |
|---|---|
| Video buttons are greyed out or missing | The relay is not configured. phoneadmin relay says what is missing |
| "Video couldn't connect" | The relay cannot be reached. Check the ports are open in the firewall and router, external-ip if behind NAT, and that host and port match turnserver.conf |
phoneadmin relay prints relay API HTTP 401 | The Cloudflare token or Metered key is wrong |
| Works for most players but not one | Their network blocks your UDP port. Set up TURNS over TCP (see Strict networks) |