preface

Hello, everyone. I’m a sea monster.

Recently, the company needs the technology of video streaming in its project, so I studied WebRTC and saw the framework of Peer-.js. I made a small Demo with it. Today, I will share with you a simple one.

What is the WebRTC

WebRTC (Web Real Time Communication), also known as network real-time Communication, allows Web applications to directly transmit any data to each other without an intermediate server, such as video streams, audio streams, file streams, and common data. It has also become a set of browser specifications, providing the following capabilities:

  • Capture video and audio streams
  • Audio and video communication
  • The communication of arbitrary data

These three functions correspond to three apis:

  • MediaStream (also known as getUserMedia)
  • RTCPeerConnection
  • RTCDataChannel

While these apis look simple, they are very complex to use. Not only do you have to understand the concepts of “candidate” and “ICE”, but you also have to write a lot of callbacks to implement end-to-end communication. And because different browsers have different support for WebRTC, adapter.js is also required for compatibility.

Therefore, in order to make it easier to use WebRTC to do end-to-end transmission, peer-.js makes the underlying API calls and compatibility, simplifying the whole end-to-end implementation process.

The following uses it to realize a video chat room.

The article code is available on the Github project, or you can view it by clicking on the preview link.

Project initialization

To create a react project, use create-react-app:

create-react-app react-chatroom
Copy the code

Remove some useless files and leave only app.js. To make the interface look better, here we can use ANTD as the UI library:

npm i antd
Copy the code

Finally, add CSS to index.js:

import 'antd/dist/antd.css'
Copy the code

layout

Install peer. Js:

npm i peerjs
Copy the code

Write the layout of the entire page:

const App = () = > {
  const [loading, setLoading] = useState(true);

  const [localId, setLocalId] = useState(' ');
  const [remoteId, setRemoteId] = useState(' ');

  const [messages, setMessages] = useState([]);
  const [customMsg, setCustomMsg] = useState(' ');

  const currentCall = useRef();
  const currentConnection = useRef();

  const peer = useRef()

  const localVideo = useRef();
  const remoteVideo = useRef();

  useEffect(() = > {
    createPeer()

    return () = > {
      endCall()
    }
  }, [])

  // End the call
  const endCall = () = > {}

  // Create a local Peer
  const createPeer = () = > {}

  // Start a conversation
  const callUser = async() = > {}// Send text
  const sendMsg = () = > {}

  return (
    <div className={styles.container}>
      <h1>The local Peer ID: {localId | |<Spin spinning={loading} />}</h1>

      <div>
        <Space>
          <Input value={remoteId} onChange={e= >SetRemoteId (e.target.value)} placeholder=" placeholder ";<Button type="primary" onClick={callUser}>Video call</Button>
          <Button type="primary" danger onClick={endCall}>End calls</Button>
        </Space>
      </div>

      <Row gutter={16} className={styles.live}>
        <Col span={12}>
          <h2>Local camera</h2>
          <video controls autoPlay ref={localVideo} muted />
        </Col>
        <Col span={12}>
          <h2>Remote camera</h2>
          <video controls autoPlay ref={remoteVideo} />
        </Col>
      </Row>

      <h1>Send a message</h1>
      <div>
        <h2>The message list</h2>
        <List
          itemLayout="horizontal"
          dataSource={messages}
          renderItem={msg= > (
            <List.Item key={msg.id}>
              <div>
                <span>{msg.type === 'local' ? <Tag color="red">I</Tag> : <Tag color="green">The other party</Tag>}</span>
                <span>{msg.data}</span>
              </div>
            </List.Item>)} / ><h2>Custom message</h2>
        <TextArea
          placeholder="Send custom content"
          value={customMsg}
          onChange={e= > setCustomMsg(e.target.value)}
          onEnter={sendMsg}
          rows={4}
        />
        <Button
          disabled={! customMsg}
          type="primary"
          onClick={sendMsg}
          style={{ marginTop: 16}} >send</Button>
      </div>
    </div>
  );
}
Copy the code

The effect is as follows:

Creating a Local Peer

Since we want to connect to someone else’s Peer, we need to create a Peer when loading the page.

const createPeer = () = > {
  peer.current = new Peer();
  peer.current.on("open".(id) = > {
    setLocalId(id)
    setLoading(false)});// Pure data transmission
  peer.current.on('connection'.(connection) = > {
    // Accept data from the other party
    connection.on('data'.(data) = > {
      setMessages((curtMessages) = > [
        ...curtMessages,
        { id: curtMessages.length + 1.type: 'remote', data }
      ])
    })

    // Record the current connection
    currentConnection.current = connection
  })

  // Media transfer
  peer.current.on('call'.async (call) => {
    if (window.confirm('Whether or not to accept${call.peer}? `)) {
      // Get the local stream
      const stream = await navigator.mediaDevices.getUserMedia({ video: true.audio: true })
      localVideo.current.srcObject = stream
      localVideo.current.play()

      / / response
      call.answer(stream)

      // Listen to the video stream and update it to remoteVideo
      call.on('stream'.(stream) = > {
        remoteVideo.current.srcObject = stream;
        remoteVideo.current.play()
      })

      currentCall.current = call
    } else {
      call.close()
    }
  })
}
Copy the code

It mainly does the following things:

  • New a Peer instance and listen for many events on this instance
  • Listening to theopenEvent to update local after opening channellocalId
  • Listening to theconnectEvent. After the connection is successful, all messages of the Peer are updated tomessagesAn array of
  • Listening to thecallEvent when the Peermake call
    • getUserMediaCapture local audio and video streams and update tolocalVideo
    • Listening to thestreamEvent to update the audio and video stream of the Peer toremoteVideo

The whole creation and listening process is complete. But don’t forget to close the link after the page closes:

useEffect(() = > {
  createPeer()

  return () = > {
    endCall()
  }
}, [])

const endCall = () = > {
  if (currentCall.current) {
    currentCall.current.close()
  }
}
Copy the code

Send out invitations

If the page is to be used as the sender, then the Peer needs to complete the make a call task and write to callUser:

const callUser = async() = > {// Get the local video stream
  const stream = await navigator.mediaDevices.getUserMedia({ video: true.audio: true })
  localVideo.current.srcObject = stream
  localVideo.current.play()

  // Data transfer
  const connection = peer.current.connect(remoteId);
  currentConnection.current = connection
  connection.on('open'.() = > {
    message.info('Connected')})// Multimedia transfer
  const call = peer.current.call(remoteId, stream)
  call.on("stream".(stream) = > {
    remoteVideo.current.srcObject = stream;
    remoteVideo.current.play()
  });
  call.on("error".(err) = > {
    console.error(err);
  });
  call.on('close'.() = > {
    endCall()
  })

  currentCall.current = call
}
Copy the code

The following events are mainly done here:

  • Capture local audio and video streams and update tolocalVideo
  • throughremote peer idConnecting the Peer
  • throughremote peer idTo each othermake a callAnd listen for thiscallThe content of the
    • Listening to thestreamEvent that updates the stream sent by the other party toremoteVideo
    • Listening to theerrorIncident, report to Qyak
    • Listening to thecloseEvent, close at any time

In general, the process of creating a Peer is similar to the one above, the only difference is that the previous process is new Peer() and answer, and here is connect and call.

Send text

In addition to audio and video streams, it can also transmit plain text. Here we will improve sendMsg:

const sendMsg = () = > {
  // Send custom content
  if(! currentConnection.current) { message.warn('Not linked yet')}if(! customMsg) {return;
  }
  currentConnection.current.send(customMsg)
  setMessages((curtMessages) = > [
    ...curtMessages,
    { id: curtMessages.length + 1.type: 'local'.data: customMsg }
  ])
  setCustomMsg(' ');
}
Copy the code

We can simply call the current connection to send().

The effect

Step 1, open two pages A and B.

Step 2: Enter the peer ID of page B (receiver) in the input box of page A (initiator) and click “Video Call”.

Step 3: Click Confirm on page B (recipient) :

Then you can complete the video call:

conclusion

In general, it is more convenient to use peer-.js to do end-to-end information exchange.

One feature of P2P is that data can be transferred between two points without the need for an intermediate server. However, not all cases can be “completely off the server.” In some cases, such as communication blocked by a firewall, a mediation server is required to connect the two ends and then start end-to-end communication. By default, you can connect to its mediation Server (data transfer does not go through this Server). Of course, you can also use its PeerServer to create your own Server.

const { PeerServer } = require('peer');

const peerServer = PeerServer({ port: 9000.path: '/myapp' });
Copy the code
<script>
    const peer = new Peer('someid', {
      host: 'localhost'.port: 9000.path: '/myapp'
    });
</script>
Copy the code

The WebRTC API also has a number of security limitations:

In conclusion, end-to-end technology can be useful in some scenarios where real time is required. Using peer-.js, end-to-end communication can be easily realized.