How to Build a WebRTC Voice Call App with PeerJS: Step‑by‑Step Guide

This guide walks through setting up a PeerJS‑based P2P voice call system, covering browser compatibility, Node environment preparation, installing and running the peerjs‑server (via npm or Docker), initializing the Peer object, handling connections, making and receiving calls, and customizing Peer IDs, with full code snippets and screenshots.

Dunmao Tech Hub
Dunmao Tech Hub
Dunmao Tech Hub
How to Build a WebRTC Voice Call App with PeerJS: Step‑by‑Step Guide

Introduction

Recently a project required a web‑based voice call feature. This article shares the deployment process and code for using the open‑source PeerJS P2P voice service.

What is PeerJS?

PeerJS is a JavaScript library built on WebRTC that simplifies peer‑to‑peer communication in browsers, abstracting the complex WebRTC API and enabling easy audio or text data transfer without a middle server.

Environment Preparation

Browser Support

PeerJS works only on browsers that support WebRTC (as shown in the accompanying screenshot).

Node Environment

The tutorial uses Node v12.22.9 and npm 8.5.1; other versions may be incompatible.

peerjs‑server Service

Install the server globally: $ npm install peer -g Start the server: peerjs --port 9000 --key peerjs --path /myapp Or run it with Docker:

$ docker run -p 9000:9000 -d peerjs/peerjs-server

Visit the server URL to verify deployment (screenshot below).

Server deployment result
Server deployment result

Code Steps

Initialize Peer Object

Define the server URL and create a new Peer instance with appropriate host, port, path, and security settings.

const PEER_SERVER = "https://peer.nodcat.com";
const url = new URL(PEER_SERVER);
const peer = new Peer({
  host: url.hostname,
  port: url.port || (url.protocol === "https:" ? 443 : 80),
  path: url.pathname || "/peerjs",
  secure: url.protocol === "https:",
  debug: 3 // enable detailed logs
});

Listen for Connection

When the peer opens, log the generated ID.

peer.on("open", (id) => {
  console.log("My peer ID is: " + id);
  myPeerIdEl.textContent = id;
  connectionStateEl.textContent = "状态: 已连接";
});

Make a Call

Obtain the local audio stream and call the remote peer ID.

stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
mediaConnection = peer.call(partnerId, stream, {
  metadata: { username }
});

Receive a Call

Handle incoming call events, display caller information, and answer with local media.

peer.on("call", (call) => {
  console.log("收到来电", call);
  incomingCallConnection = call;
  if (call.metadata && call.metadata.username) {
    partnerName = call.metadata.username;
    incomingCallerNameEl.textContent = partnerName;
  } else {
    incomingCallerNameEl.textContent = "未知来电";
  }
  // show incoming UI
  connectScreen.classList.add("hidden");
  incomingCallScreen.classList.remove("hidden");

  acceptCallBtn.onclick = async () => {
    try {
      stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
      initAudioAnalyzer(stream);
      call.answer(stream);
      handleCall(call);
      incomingCallScreen.classList.add("hidden");
      callerNameEl.textContent = partnerName;
      callScreen.classList.remove("hidden");
    } catch (err) {
      console.error("无法获取媒体设备:", err);
      alert("无法访问麦克风,请确保已授予权限");
      incomingCallScreen.classList.add("hidden");
      connectScreen.classList.remove("hidden");
    }
  };
});

Handle Audio Stream

When the remote stream arrives, attach it to an audio element and manage autoplay restrictions.

call.on("stream", (remoteStream) => {
  console.log("收到远程流,轨道:", remoteStream.getTracks());
  audioStateEl.textContent = "已接收音频";
  remoteAudio.srcObject = remoteStream;
  const playPromise = remoteAudio.play();
  if (playPromise !== undefined) {
    playPromise.catch(error => {
      console.log("自动播放被阻止:", error);
      callStatusEl.textContent = "点击屏幕以启用声音";
      document.addEventListener("click", enableAudio, { once: true });
    });
  }
});

Custom Peer ID

Peer IDs can be set manually by passing a string to the Peer constructor, allowing backend systems to bind users to specific IDs.

const peer = new Peer("custom-peer-id", {
  host: url.hostname,
  port: url.port || (url.protocol === "https:" ? 443 : 80),
  path: url.pathname || "/peerjs",
  secure: url.protocol === "https:",
  debug: 3
});

Demo Screenshots

Nickname registration page, connection page, and call page are shown below.

Nickname page
Nickname page
Connection page
Connection page
Call page
Call page

Conclusion

PeerJS offers a simple, extensible P2P voice and text server/client that can be deployed with minimal code, providing a solid foundation for adding multi‑party calls, video conferencing, and other real‑time communication features.

Project repository: https://github.com/peers

JavaScriptNode.jsP2PWebRTCPeerJSVoice Call
Dunmao Tech Hub
Written by

Dunmao Tech Hub

Sharing selected technical articles synced from CSDN. Follow us on CSDN: Dunmao.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.