Webrtc là gì? | TopDev

  1. Cấp quyền truy cập vào thư mục gốc của cơ sở dữ liệu Firebase
    var database = firebase.database().ref();
  2. Hàm sendMessage để thêm cơ sở dữ liệu vào Firebase: database.on('child_added', readMessage);
  3. Tạo yourId random: var yourId = Math.floor(Math.random()*1000000000);
  4. Khai báo máy chủ sử dụng: Hai máy chủ STUN sử dụng ở đây là của Google và Firefox, bạn cũng có thể thêm nhiều STUN khác tùy thích:
    var servers = {'iceServers': [
        {'urls': 'stun:stun.services.mozilla.com'},
        {'urls': 'stun:stun.l.google.com:19302'}
    ]};

    Note: STUN là gì? STUN (Session Traversal Utilities for NAT) là một giao thức mạng cho phép các máy khách tìm ra địa chỉ công khai của mình, loại NAT mà chúng đang đứng sau và cổng phía Internet được NAT gắn liền với cổng nội bộ nào đó. Thông tin này được sử dụng để thiết lập giao tiếp UDP giữa 2 host mà đều nằm sau NAT router. Giao thức STUN được định nghĩa trong RFC 5389.

  5. Tạo một object PeerConnection var pc = new RTCPeerConnection(servers);
  6. Chờ đợi đối tượng ICE Candidate được tạo trên máy tính của bạn:
    pc.onicecandidate = (event => event.candidate ? sendMessage(yourId, JSON.stringify({'ice': event.candidate})) : console.log('Sent All Ice') );

    Chức năng này sẽ được gọi nhiều lần, một lần cho mỗi ICE Candidate (khó dịch sang tiếng Việt nên để nguyên) được tạo ra. Khi một ICE Candidate được tạo ra thì hàm này sẽ biến object thành một chuỗi. Sau đó, nó gửi chuỗi này cho bạn bè của bạn thông qua Firebase. Máy tính bạn bè của bạn cũng sẽ thực hiện tương tự như trên.

    ICE (Interactive Communication Establishment) nôm na dễ hiểu là một giao thức được cùng để thiết lập phiên media dựa trên UDP đi qua NAT một cách nhanh nhất. ICE sẽ tìm đường tốt nhất để kết nối giữa các peer, nó thử tất cả khả năng có thể kết nối một cách song song và lựa chọn con đường hiệu quả nhất.

  7. Khi bạn và người nhận nhận được một ICE Candidate dưới dạng chuỗi được gửi từ Firebase, thì JSON.stringify sẽ chuyển đổi chuỗi trở lại thành đối tượng ICE Candidate.
  8. Chờ đợi cho các đối tượng Offer, Answer, ICE Candidates được gửi:pc.onaddstream = (event => friendsVideo.srcObject = event.stream);
  9. Event onaddstream được gọi và đặt friendsVideo.srcObject thành object MediaStream. Thao tác này sẽ hiển thị video người kia trên máy tính của bạn và ngược lại. friendsVideo được gọi đến từ HTML.
  10. Thêm dữ liệu vào Firebase bằng hàm sendMessage :
    function sendMessage(senderId, data) {
        var msg = database.push({ sender: senderId, message: data });
        msg.remove();
    }
  11. Thêm hàm show camera của mình showMyFace:
    function showMyFace() {
         navigator.mediaDevices.getUserMedia({audio:true, video:true})
             .then(stream => yourVideo.srcObject = stream)
             .then(stream => pc.addStream(stream));
    }

    Khi gọi hàm getUserMedia, trình duyệt sẽ yêu cầu quyền truy cập camera. Nó sẽ trả về một đối tượng MediaStream cái mà bạn có thể đặt bằng yourVideo.srcObject. Đoạn này có chức năng hiển thị mặt của bạn trên chính máy tính của bạn. Sau đó, ta cần thêm cùng một đối tượng MediaStream vào đối tượng PeerConnection của bạn. Trên máy tính đối phương thực hiện cuộc gọi cũng thực hiện tương tự. Hàm này được gọi ngay khi tải trang, vì vậy bạn sẽ thấy khuôn mặt của mình ngay khi load trang.

  12. Hàm showFriendsFace:
    function showFriendsFace() {
         pc.createOffer()
             .then(offer => pc.setLocalDescription(offer) )
             .then(() => sendMessage(yourId, JSON.stringify({'sdp': pc.localDescription})) );
    }

    Tạo đối tượng Offer bằng cách gọi pc.createOffer(). Đặt local description cho offer này bằng cách gọi pc.setLocalDescription(offer). Cuối cùng gửi đối tượng Offer cho bạn của bạn bằng cách gọi sendMessage.

  13. Hàm readMessage
    function readMessage(data) {
         var msg = JSON.parse(data.val().message);
         var sender = data.val().sender;
         if (sender != yourId) {
             if (msg.ice != undefined) {
                 pc.addIceCandidate(new RTCIceCandidate(msg.ice));
             } else if (msg.sdp.type == "offer") {
                 pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
                     .then(() => pc.createAnswer())
                     .then(answer => pc.setLocalDescription(answer))
                     .then(() => sendMessage(yourId, JSON.stringify({'sdp': pc.localDescription})));
             } else if (msg.sdp.type == "answer") {
                 pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
             }
         }
    };

    Người nhận cuộc gọi có thể đọc được tin nhắn thông qua hàm readMessage. Với kiểu tin nhắn là offer thì bạn đã gửi cho người nhận một đối tượng Offer mà bạn đã tạo. Người kia sẽ thiết lập mô tả từ xa (remote decription) của họ cho đối tượng Offer mà bạn đã gửi cho họ bằng cách gọi pc.setRemoteDescription(new RTCSessionDescription(msg.sdp)). Người nhận sẽ tạo một đối tượng Answer bằng cách gọi đến pc.createAnswer(). Hàm này trả về một đối tượng Answer mà bạn sẽ thiết lập ở mô tả local. Người nhận làm được điều này bằng cách gọi đến pc.setLocalDescription(answer). Sau đó, người nhận lấy đối tượng Answer và gửi nó cho bạn bằng cách gọi sendMessage.

    Bởi vì kiểu của tin nhắn bây giờ đã là answer nên đoạn sau sẽ thực hiện pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));

  14.  Toàn bộ code trên:
    var config = {
        apiKey: "<API_KEY>",
        authDomain: "<PROJECT_ID>.firebaseapp.com",
        databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
        projectId: "<PROJECT_ID>",
        storageBucket: "<BUCKET>.appspot.com",
        messagingSenderId: "<SENDER_ID>",
      };
      firebase.initializeApp(config);
    
    var database = firebase.database().ref();
    var yourVideo = document.getElementById("yourVideo");
    var friendsVideo = document.getElementById("friendsVideo");
    var yourId = Math.floor(Math.random()*1000000000);
    var servers = {'iceServers': [
        {'urls': 'stun:stun.services.mozilla.com'},
        {'urls': 'stun:stun.l.google.com:19302'}
    ]};
    var pc = new RTCPeerConnection(servers);
    pc.onicecandidate = (event => event.candidate?sendMessage(yourId, JSON.stringify({'ice': event.candidate})):console.log("Sent All Ice") );
    pc.onaddstream = (event => friendsVideo.srcObject = event.stream);
    
    function sendMessage(senderId, data) {
        var msg = database.push({ sender: senderId, message: data });
        msg.remove();
    }
    
    function readMessage(data) {
         var msg = JSON.parse(data.val().message);
         var sender = data.val().sender;
         if (sender != yourId) {
             if (msg.ice != undefined) {
                 pc.addIceCandidate(new RTCIceCandidate(msg.ice));
             } else if (msg.sdp.type == "offer") {
                 pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
                     .then(() => pc.createAnswer())
                     .then(answer => pc.setLocalDescription(answer))
                     .then(() => sendMessage(yourId, JSON.stringify({'sdp': pc.localDescription})));
             } else if (msg.sdp.type == "answer") {
                 pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
             }
         }
    };
    
    database.on('child_added', readMessage);
    
    function showMyFace() {
         navigator.mediaDevices.getUserMedia({audio:true, video:true})
             .then(stream => yourVideo.srcObject = stream)
             .then(stream => pc.addStream(stream));
    }
    
    function showFriendsFace() {
         pc.createOffer()
             .then(offer => pc.setLocalDescription(offer) )
             .then(() => sendMessage(yourId, JSON.stringify({'sdp': pc.localDescription})) );
    }