본문 바로가기

Computer science/WebRTC

WebRTC - multiconnection

There are roughly three ways to connect multi users in WebRTC

1. Full Mesh Network

2. Small world network

3. Hierarchical Network

 

With full mesh network, a peer are connected to all other peers. Thus, it should be looking like this.

The adventages of using this network is a peer is able to access all other peers. That means, one can see six videos(including itself) at the same time. On the other hand, networks could be overloaded because networks need n * (n - 1) / 2 connections. If 30 computers form Full Mesh Network, there should be 435 connections and one user should handle 29 incoming media streams. This could be a huge fatality because the applications which implement full mesh network forces their users high load of data stream. 

 

Nevertheless, I'm going to implement the full mesh network here. Ths basic concept is simple. An incoming user requests existing user list to the server and makes RTCPeerConnection for each users. With RTCPeerConnection we just made, offer sdp to each user using createSDPOffer function. Then, existing users get a message from a new guy through the server and answer to the offer using makeAnswer. Now that they share SDP, they know which address they should send messages to. How about exisiting users then? They can simply create new RTCPeerConnection with a socketID as a key.

// All users connected to the server get messages
socket.on("joined", function (
  room: string,
  socketId: string,
  clientsInRoom: any
) {
  console.log(`${socketId} joined ${room}`);
  if (!isChannelReady) { // new users
    console.log(typeof connectedUsers);
    connectedUsers = Object.assign({}, clientsInRoom.sockets);
    delete connectedUsers[sessionId];
    remoteStreams = Object.assign({}, clientsInRoom.sockets);
    delete remoteStreams[sessionId];
    isTrackAdded = Object.assign({}, clientsInRoom.sockets);
    for (const bool in isTrackAdded) {
      isTrackAdded[bool] = false;
    }
  } else { // existing users
    connectedUsers[socketId] = new RTCPeerConnection(rtcIceServerConfiguration);
    if (remoteStreams === undefined) {
      remoteStreams = {};
      isTrackAdded = {};
    } else {
      remoteStreams[socketId] = new MediaStream();
      isTrackAdded[socketId] = false;
    }
    addingListenerOnPC(socketId);
  }
});

function addingListenerOnPC(user: string) {
  // setLocalDescription() calls icecandidate events
  // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/icecandidate_event
  connectedUsers[user].addEventListener("icecandidate", (event) => {
    console.log("icecandidate event: ", event);
    if (event.candidate) {
      sendMessage({
        type: "candidate",
        label: event.candidate.sdpMLineIndex,
        id: event.candidate.sdpMid,
        candidate: event.candidate.candidate,
        sendTo: user,
        sendFrom: sessionId,
        room: room,
      });
    } else {
      console.log("End of candidates.");
    }
  });

  // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/track_event
  connectedUsers[user].ontrack = (event) => {
    console.log("Remote stream added.");
    remoteStreams[user] = event.streams[0];
    const video = document.createElement("video");
    video.srcObject = remoteStreams[user];
    video.autoplay = true;
    video.playsinline = true;
    remoteVideos.appendChild(video);
  };

  // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/negotiationneeded_event
  connectedUsers[user].addEventListener("negotiationneeded", createSDPOffer);

  if (!(localStream === undefined || isTrackAdded[user])) {
    localStream
      .getTracks()
      .forEach(async (track) =>
        connectedUsers[user].addTrack(track, localStream)
      );
    console.log("localStream added on the RTCPeerConnection");
    isTrackAdded[user] = true;
  }
}

 

Creating a PeerConnection and an offer is pretty much same but for every existing users

function createPeerConnection() {
  for (const user in connectedUsers) {
    connectedUsers[user] = new RTCPeerConnection(rtcIceServerConfiguration);

    addingListenerOnPC(user);
    console.log("Created RTCPeerConnection");
  }
}

async function createSDPOffer() {
  try {
    for (const user in connectedUsers) {
      if (connectedUsers[user].localDescription !== null) {
        continue;
      }
      console.log("offering sdp");
      const offer = await connectedUsers[user].createOffer();
      connectedUsers[user].setLocalDescription(offer);
      sendMessage({
        type: "offer",
        sendTo: user,
        sendFrom: sessionId,
        sdp: connectedUsers[user].localDescription,
        room: room,
      });

      console.log("offer created for a user: ", connectedUsers[user]);
    }
  } catch (e) {
    console.error("Failed to create pc session description", e);
  }
}

 

To send and receive messages, I use sendMessage function.

// client side
function sendMessage(message: any) {
  console.log("Client sending message: ", message);
  socket.emit("message", message);
}

// server side
socket.on("message", function (message) {
  log("Client said: ", message);
  if (message.sendTo === undefined) {
    socket.to(message.room).emit("message", message);
  } else {
    ioServer.to(message.sendTo).emit("message", message);
  }
});

// an example of a message
sendMessage({
type: "candidate",
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
sendTo: user,
sendFrom: sessionId,
room: room,
});

 

'Computer science > WebRTC' 카테고리의 다른 글

WebRTC 참고 자료 모음  (0) 2020.07.10
webRTC 용어 정리  (0) 2020.07.10
WebRTC - other parts  (0) 2020.07.04
WebRTC - peer connections  (0) 2020.07.02
WebRTC - Media capture and constraints  (0) 2020.07.02