hjf.io

📞 Phone a Friend - Building Zoom in less than 100 LoC

26th Jan - 2021

Being cheap has been an adventure with this blog. I love the constraints that this puts what you can do. Currently, I host it for free on Netlify. I’ve got this idea of ‘hacks’ on the blog: Small SPAs to flex my technical ability and show off to everybody else what I can do.

Recently, I’ve been very interested with sending video and audio over the web. Historically I would have used a websocket connection and shared frames of a video (though I’ve still no idea about audio). That was, until I found out about Peer.js.

Before I continue chatting about Peer.js - a note on WebRTC. It is a Web API that allows for Peer to Peer communication over the web. There are STUN and TURN servers that are used to co-ordinate, but I don’t know much about those - because Peer.js.

This library makes it easy to do anything via WebRTC. It handles signalling and coordination. In fact, it handles it so well that I’ve implemented audio/video sharing, chat and screen sharing.

Here’s how easy it is to make and answer call:

async function call(id) {
  const opts = {audio: true, video: true}
  const stream = await navigator.mediaDevices.getUserMedia(opts)
  const call = peer.call(id, stream)
  call.on('stream', remote => {
    const vid = document.querySelector('video')
    vid.srcObject = remote
  })
}

// answer a call
peer.on('call', async dial => {
  const opts = {audio: true, video: true}
  const stream = await navigator.mediaDevices.getUserMedia(opts)
  dial.answer(stream)
  dial.on('stream', remote => 
    const vid = document.querySelector('video')
    vid.srcObject = remote
  })
})

Of course, you need an id to pass in to call(). You set this when you instantiate a new peer: const peer = new Peer(myID). After this, it’s magic.

You can also create a data connection. I’ve used this for chat:

function getDataConn(id) {
  return new Promise(res => {
    const conn = peer.connect(id)
    conn.on('open', () => res(conn))
  })
}

const conn = await getDataConn(id)
conn.send('some message')

We can also get a handle on the screen - this is a MediaStream, which we can pass to peer.on('call')

const opts = {video: {cursor: 'always'}, audio: false}
const capture = await navigator.mediaDevices.getDisplayMedia(opts)

With this in mind, we can build a chat app. And I did! It’s below. If you don’t want to share this page with a friend, I’ve hosted it at phone.hjf.io. I’ve disabled chat here, because it works better with a larger screen (and I can’t figure out how to bundle my css with mdx…).

To call: put your partner’s ID in the box and hit enter.