Android Apps

How to Make a Video Calling App with WebRTC (Free One-Page Code)

Make a working video call in one HTML page with WebRTC, then learn how calls really connect (signalling, STUN and TURN), why they fail on mobile data, how to put it in an Android app, and what a real video calling app needs.

Two phones on a video call connected through WebRTC with a TURN server

Every video calling app, from WhatsApp to Imo, rests on the same technology: WebRTC. It is built into every modern browser and is available for Android, and it sends video directly between two devices whenever it can. What makes a call app hard is not the video itself; it is getting two phones on different networks to find each other.

This guide starts with a working video call in one HTML page that you can test today on two devices. Then it explains how a call really connects, why calls fail on mobile data, how to turn it into an Android app, and what a real video calling app needs.

Try it: a video call in one page

  1. Save the code below as index.html and upload it to any website with HTTPS. Browsers only allow the camera on HTTPS pages (and on localhost while you test).
  2. Open the page on two devices, for example your computer and your phone. Allow the camera and microphone.
  3. Each page shows Your ID. Copy the ID from one device, paste it on the other as “Friend's ID”, and press Call.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Video Call</title>
<style>
  body { margin: 0; font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; }
  main { max-width: 900px; margin: 0 auto; padding: 20px; }
  input { font: inherit; padding: 10px; border-radius: 8px; border: 0; width: 260px; max-width: 100%; }
  button { font: inherit; font-weight: 700; padding: 10px 18px; border: 0; border-radius: 8px;
           background: #22c55e; color: #052e16; cursor: pointer; margin: 4px 4px 4px 0; }
  #hangup { background: #ef4444; color: #fff; }
  .videos { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 16px; }
  video { width: 100%; background: #020617; border-radius: 12px; aspect-ratio: 4 / 3; object-fit: cover; }
  @media (max-width: 600px) { .videos { grid-template-columns: 1fr; } }
</style>
<script src="https://unpkg.com/peerjs@1.5.4/dist/peerjs.min.js"></script>
</head>
<body>
<main>
  <h1>Video call</h1>
  <p>Your ID: <b id="myId">connecting ...</b> <button id="copy">Copy</button></p>
  <p>Send your ID to a friend, or paste theirs and press Call.</p>
  <input id="friendId" placeholder="Friend's ID">
  <button id="call">Call</button>
  <button id="hangup">Hang up</button>
  <p id="status"></p>
  <div class="videos">
    <video id="local" autoplay muted playsinline></video>
    <video id="remote" autoplay playsinline></video>
  </div>
</main>

<script>
const peer = new Peer();   // gets an ID from the free PeerJS server, which passes the call setup
let localStream = null;
let currentCall = null;

function status(text) { document.getElementById('status').textContent = text; }

// Ask for the camera and microphone once, and show your own video
async function camera() {
  if (!localStream) {
    localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    document.getElementById('local').srcObject = localStream;
  }
  return localStream;
}

// Show the other person's video when it arrives
function follow(call) {
  currentCall = call;
  call.on('stream', stream => {
    document.getElementById('remote').srcObject = stream;
    status('Connected');
  });
  call.on('close', () => {
    document.getElementById('remote').srcObject = null;
    status('Call ended');
  });
}

peer.on('open', id => { document.getElementById('myId').textContent = id; });
peer.on('error', err => status('Error: ' + err.type));

// Someone is calling you: answer with your camera
peer.on('call', async call => {
  call.answer(await camera());
  follow(call);
});

// You call someone
document.getElementById('call').onclick = async () => {
  const id = document.getElementById('friendId').value.trim();
  if (!id) return status("Paste your friend's ID first");
  status('Calling ...');
  follow(peer.call(id, await camera()));
};

document.getElementById('hangup').onclick = () => { if (currentCall) currentCall.close(); };
document.getElementById('copy').onclick = () =>
  navigator.clipboard.writeText(document.getElementById('myId').textContent);
</script>
</body>
</html>
PartWhat it does
new Peer()Connects to the free PeerJS server, which gives the page an ID and passes the call setup between the two devices
getUserMediaAsks for the camera and microphone and shows your own video (muted, so you do not hear yourself)
peer.call(id, stream)Calls the other ID and sends your video
peer.on('call') and answer()Answers an incoming call with your video. A real app shows a ringing screen first.
call.on('stream')The other person's video arrives and is shown
playsinlinePlays the video inside the page on phones instead of full screen

The free PeerJS server is meant for testing. For a real app you run your own signalling server, as explained below.

How a WebRTC call really connects

PieceIts job
MediaThe camera and microphone, compressed and encrypted by WebRTC itself
Signalling serverPasses the call setup (who is calling, the video formats, the network addresses) between the two devices. WebRTC does not include one; you choose it: PeerJS, your own WebSocket server, or an XMPP server such as ejabberd.
STUN serverTells each device its public address, so they can try to connect directly
TURN serverRelays the video when a direct connection is impossible

Once the setup is done, the video goes straight from one device to the other when the networks allow it, so the server only carries a few kilobytes per call.

Why calls fail on mobile data

Calls between two devices on the same Wi-Fi almost always work. The real test is one phone on Wi-Fi and the other on mobile data. Mobile networks put many phones behind one public address, and a direct connection often fails there. Then the call needs a TURN server, which relays the video between the two phones.

Without your own TURN server, a video app “works in testing” and fails for a large share of real users. The usual choice is coturn on a small VPS; our guide on installing ejabberd and a TURN server on Ubuntu sets it up step by step. TURN carries the whole video when it relays, so its bandwidth is the main running cost of a call app.

Turning it into an Android app

  • The quick way: a WebView. Load the page in a WebView, add the CAMERA, RECORD_AUDIO and MODIFY_AUDIO_SETTINGS permissions, ask for them at runtime, and grant the page's request in the WebView's onPermissionRequest. Good for a test; it cannot ring when the app is closed.
  • The real way: native WebRTC. Use the WebRTC library for Android with your own signalling. That gives proper call screens, audio routing to the speaker or a Bluetooth headset, and calls that keep running in the background.

What a real video calling app needs

  • Accounts and contacts, instead of copying IDs
  • Your own signalling server and your own TURN server
  • Ringing: a high-priority push notification wakes the phone and shows an incoming call screen with Accept and Decline
  • A foreground service during the call (with the phone call, camera and microphone service types on Android 14 and newer), so the call survives the screen turning off
  • Audio routing between earpiece, speaker and Bluetooth, and a reconnect when the network changes
  • Chat, call history, and for public apps report and block

Common problems

ProblemCause and fix
The camera does not startThe page is on http://. Browsers only allow the camera on HTTPS (or localhost).
Connected, but the other video is blackThe video element lacks autoplay and playsinline, or the browser blocked autoplay. Tap the video or add both attributes.
Works on Wi-Fi, fails on mobile dataNo TURN server for the relay. Set up coturn and add it to the ICE servers.
Error: peer-unavailableThe ID is wrong, or the other page was closed and got a new ID
Echo or feedbackBoth devices are in the same room. Use headphones; WebRTC's echo cancellation is on by default.

The full version: ready video call app source codes

Our chat app source codes are complete video calling apps for Android, built on WebRTC with XMPP signalling on ejabberd 24.12 and a TURN server, and each comes with a ready server folder and a step-by-step server guide:

Where to go next

Questions people ask

How do I make a video calling app?
Use WebRTC for the video, a signalling server to set up each call, a STUN server so devices find their public address, and a TURN server to relay calls that cannot connect directly. The one-page example in this guide does a real call with PeerJS for the signalling.
What is WebRTC?
WebRTC is the open technology inside every modern browser and available for Android that sends audio and video directly between two devices, encrypted, with low delay. Most video calling apps are built on it.
Do I need a server for a WebRTC video call?
Yes, two kinds. A signalling server passes the call setup between the devices, and a TURN server relays the video when they cannot connect directly. The video itself goes device to device whenever the networks allow it.
Why does my video call work on Wi-Fi but not on mobile data?
Mobile networks often block direct connections between phones. The call then needs a TURN server to relay it. Install coturn on a VPS and add it to the ICE servers.
What is the difference between STUN and TURN?
STUN only tells a device its public address so the two devices can try to connect directly. TURN relays the whole call when a direct connection fails, so it uses much more bandwidth.
Why does the camera not work on my video call page?
Browsers only allow the camera and microphone on HTTPS pages, and on localhost while testing. Put the page on a site with an SSL certificate.
Can I make a video calling app in Android Studio?
Yes. For a quick test, load a WebRTC page in a WebView and grant camera and microphone permissions. For a real app, use the native WebRTC library with your own signalling, incoming call notifications and a foreground service during calls.
Is the free PeerJS server good for a real app?
It is meant for testing. For a real app run your own signalling server, such as PeerServer, your own WebSocket server or an XMPP server like ejabberd, plus your own TURN server.
How much does it cost to run a video calling app?
Signalling needs only a small VPS. The main cost is TURN bandwidth, because relayed calls carry the full video through your server. Direct calls cost almost nothing.
Is there ready source code for a video calling app?
Yes. Our Android chat app source codes include video calls on WebRTC with ejabberd 24.12 and a TURN server, plus chat, and come with a ready server folder and setup guide.

Written by Habib Baloch

I build Android apps, websites and Ubuntu servers, and write down exactly how I did it.

Ask me about this guide →