This entry is part 4 of 5 in the series #1 WebRTC Code Labs

Mục đích của bài lab này là viết một chương trình sử dụng WebRTC API RTCPeerConnection để gọi video và audio. Nội dung thực hành của bài này là thiết lập kết nối giữa 2 peer với nhau trên cùng một trang. Ví dụ: Bạn là người gọi được coi là 1 peer, và tôi là người nhận cuộc gọi cũng được coi là 1 peer. Bài lab này thường không được sử dụng trong thực tế nhưng giúp chúng ta hiểu được RTCPeerConnection hoạt động như thế nào trong một ứng dụng WebRTC !

Chú ý: Thuật ngữ peer ở đây chỉ hai đối tượng người dùng ở hai thiết bị đầu cuối có thể liên lạc trực tiếp với nhau.

Nội dung của bài lab:

  1. Tạo một trang web HTML5 mới. (Mới hoàn toàn 😀 )
  2. Thêm 2 thẻ video và 3 nút: Mở camera, Gọi và Kết thúc
  3. Viết mã JavaScript gọi WebRTC API thứ 2: RTCPeerConnection
  4. Xem trang web từ localhost
  5. Gợi ý tự khám phá

Bước 1: Tạo tài liệu mới HTML5

Hãy tạo một trang HTML5 đặt tên là wcl-session03.html và có nội dung sau:

<!DOCTYPE html>
<html>

<head>

<meta name="keywords" content="JavaScript, WebRTC" />
<meta name="description" content="WebRTC codelab" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1">

<title>WebRTC codelab: Session 03</title>

<style>
</style>

</head>

<body>
// Body here


<script>
// Script here

</script>
</body>

</html>

Bước 2: Bố trí hiển thị HTML5

Hãy chèn 2 thẻ Video và 3 thẻ Button tên là Start, CallHang Up. Vào trong thẻ body. Mục đích của bước này là tạo các nút bấm để có thể khởi động, thực hiện một cuộc gọi hay là kết thúc cuộc gọi !

        <video id="localVideo" autoplay></video>
        <video id="remoteVideo" autoplay></video>

        <div>
          <button id="startButton">Start</button>
          <button id="callButton">Call</button>
          <button id="hangupButton">Hang Up</button>
        </div>

Bước 3: Khai báo các biến chương trình

Bước này bạn sẽ viết một đoạn mã JavaScript, khai báo các biến toàn cục gồm localStream lưu video stream camera của bạn; localPeerConnection để lưu trữ thông tin liên lạc của bạn như SDP, ICE, IP, PORT; remotePeerConnection lưu thông tin liên lạc của người nhận cuộc gọi bên đầu kia. Và khởi tạo các biến lưu giữ handle của các nút điều khiển startButton, callButton, hangupButton

Khởi tạo đoạn chương trình điều khiển bằng mã JavaScript trong thẻ script như sau:


var localStream, localPeerConnection, remotePeerConnection;

var localVideo = document.getElementById("localVideo");
var remoteVideo = document.getElementById("remoteVideo");

var startButton = document.getElementById("startButton");
var callButton = document.getElementById("callButton");
var hangupButton = document.getElementById("hangupButton");
startButton.disabled = false;
callButton.disabled = true;
hangupButton.disabled = true;
startButton.onclick = start;
callButton.onclick = call;
hangupButton.onclick = hangup;

Bước 4: Điều khiển local Video và Audio stream

Bước này bạn sẽ tạo các đoạn mã điều khiển là các hàm xử lý khi nhận được video Stream từ local, video stream từ remote với cách thức hoạt động tương tự bài lab trước Truy cập sử dụng webcam bằng WebRTC getUserMedia. gotLocalDescription là một hàm gọi lại khi bạn thực hiện một đề nghị cuộc gọi (tức là khởi tạo cuộc gọi) localPeerConnection.createOffer(gotLocalDescription); và khi createOffer thành công bạn có được thông tin cuộc gọi của chính bạn!

Trong hàm call bạn sẽ thực hiện khởi tạo các thông tin liên lạc PeerConnection. Đồng thời sẽ nhập và xử lý thông tin liên lạc PeerConnection, thông tin kết nối ICECandidate, thông tin mạng và đường truyền SDP khi đạt được kết nối hoặc khởi tạo thành công. Cuối hàm call bạn thấy hàm addStream được gọi để thiết lập video stream sẽ được truyền đi và hàm sự kiện onaddstream sẽ được gọi khi nhận được video stream người nhận (tham số của nó là một hàm gọi lại gotRemoteStream.

Bạn sẽ chú ý là chúng ta đang không sử dụng một máy chủ tín hiệu nào cả, thế nên hãy khai báo var servers = null;. Thêm vào đoạn chương trình đoạn mã xử lý video stream và thiết lập cuộc gọi như sau:

function trace(text) {
  console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}

function gotStream(stream){
  trace("Received local stream");
  localVideo.src = URL.createObjectURL(stream);
  localStream = stream;
  callButton.disabled = false;
}

function gotRemoteStream(event){
  remoteVideo.src = URL.createObjectURL(event.stream);
  trace("Received remote stream");
}

function start() {
  trace("Requesting local stream");
  startButton.disabled = true;
  navigator.getUserMedia = navigator.getUserMedia ||
    navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
  navigator.getUserMedia({audio:true, video:true}, gotStream,
    function(error) {
      trace("navigator.getUserMedia error: ", error);
    });
}

function call() {
  callButton.disabled = true;
  hangupButton.disabled = false;
  trace("Starting call");

  if (localStream.getVideoTracks().length > 0) {
    trace('Using video device: ' + localStream.getVideoTracks()[0].label);
  }
  if (localStream.getAudioTracks().length > 0) {
    trace('Using audio device: ' + localStream.getAudioTracks()[0].label);
  }

  var servers = null;

  localPeerConnection = new webkitRTCPeerConnection(servers);
  trace("Created local peer connection object localPeerConnection");
  localPeerConnection.onicecandidate = gotLocalIceCandidate;

  remotePeerConnection = new webkitRTCPeerConnection(servers);
  trace("Created remote peer connection object remotePeerConnection");
  remotePeerConnection.onicecandidate = gotRemoteIceCandidate;
  remotePeerConnection.onaddstream = gotRemoteStream;

  localPeerConnection.addStream(localStream);
  trace("Added localStream to localPeerConnection");
  localPeerConnection.createOffer(gotLocalDescription);
}

Bước 5: Xử lý thông tin liên lạc Peer Connection

Đơn giản tại bước này là viết các hàm callback sẽ được gọi lại để nhập và xử lý thông tin PeerConnection. Cụ thể các bước logic được mô tả như sau: (1) lấy được thông tin liên lạc của chính bạn gotLocalDescription sẽ được coi là remoteDescript của người nhận bên kia; sau đó (2) thiết lập một hàm callback gotRemoteDescription xử lý khi nhận được thông tin liên lạc của người nhận cuộc gọi đầu bên kia; (3) hàm hangup sẽ được gọi khi bạn kết thúc cuộc gọi và toàn bộ kết nối liên lạc được đóng lại.

Hãy thêm vào chương trình đoạn mã xử lý thông tin Peer Connection như sau:

function gotLocalDescription(description){
  localPeerConnection.setLocalDescription(description);
  trace("Offer from localPeerConnection: \n" + description.sdp);
  remotePeerConnection.setRemoteDescription(description);
  remotePeerConnection.createAnswer(gotRemoteDescription);
}

function gotRemoteDescription(description){
  remotePeerConnection.setLocalDescription(description);
  trace("Answer from remotePeerConnection: \n" + description.sdp);
  localPeerConnection.setRemoteDescription(description);
}

function hangup() {
  trace("Ending call");
  localPeerConnection.close();
  remotePeerConnection.close();
  localPeerConnection = null;
  remotePeerConnection = null;
  hangupButton.disabled = true;
  callButton.disabled = false;
}

Để tìm hiểu về cơ chế đề nghị / trả lời của giao thực SDP các bạn có thể tìm hiểu thêm lại đây:
An Offer/Answer Model with the Session Description Protocol (SDP)

Bước 6: Xử lý thông tin kết nối mạng ICE Candidates

Vẫn tiếp tục thêm vào đoạn chương trình javascript các hàm callback gọi lại khi thu thập được thông tin mạng của người gọi và người nhận. Các thông tin này được sử dụng bởi WebRTC trong quá trình kết nối ngang hàng 2 peer với nhau. Ví dụ như 2 người chát video với nhau trong cùng một phiên.

Ban đầu, ICE sẽ cố gắng để kết nối các peer với nhau một cách trực tiếp, với độ trễ thấp nhất có thể thông qua UDP. Trong quá trình này, các máy chủ có một nhiệm vụ duy nhất là cho phép 1 peer đứng sau NAT có thể tìm ra được địa chỉ ip và cổng public ngoài mạng internet của nó.

Các bạn hãy cố gắng hiểu được ICE Candidates như là các thông tin mạng như ip:port của các peer mà từ đó có thể kết nối được với nhau.

function gotLocalIceCandidate(event){
  if (event.candidate) {
    remotePeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
    trace("Local ICE candidate: \n" + event.candidate.candidate);
  }
}

function gotRemoteIceCandidate(event){
  if (event.candidate) {
    localPeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
    trace("Remote ICE candidate: \n " + event.candidate.candidate);
  }
}

Bước 7: Xem trang web trên dịch duyệt

Hãy nhấn Ctrl + S để lưu lại file HTML5 của bạn đang thực hành phần này và nhập vào trình duyệt trang http://localhost:8000/wcl-session03.html để xem kết quả hiển thị 😀 Chú ý là bạn đang bật máy chủ here đúng không? Nếu đã tắt đi thì bạn hãy bật lại nhé !

Bước 8: Gợi ý tự khám phá

  1. Mở trang giám sát các kết nối WebRTC bằng cách nhập vào trình duyệt địa chỉ: chrome://webrtc-internals
  2. Điều chỉnh CSS cho các nút, thẻ hiện thị video để có kích thước lớn hơn ?
  3. Mở Chrome Dev Tools Console, để theo dõi log và thăm dò các giá trị biến global localStream, localPeerConnection, và retmotePeerConnection.
  4. Gõ vào console lệnh localPeerConnection.localDescription. Để xem mặt mũi SDP nó như thế nào ?

Và cuối cùng, nếu như bạn có bất cứ khó khăn nào trong việc ghép từng mảnh code trong từng bước để thành một file chạy hãy để lại comment tại đây hoặc tải xuống file hoàn thiện tại đây wcl-session03.html

About The Author