Giao tiếp theo thời gian thực bằng WebRTC

1. Giới thiệu

WebRTC là một dự án mã nguồn mở cho phép giao tiếp theo thời gian thực về âm thanh, video và dữ liệu trong các ứng dụng Web và ứng dụng gốc.

WebRTC có một số API JavaScript – hãy nhấp vào các đường liên kết để xem bản minh hoạ.

Tôi có thể sử dụng WebRTC ở đâu?

Trong Firefox, Opera và trong Chrome trên máy tính cũng như Android. WebRTC cũng có sẵn cho các ứng dụng gốc trên iOS và Android.

Hoạt động báo hiệu là gì?

WebRTC sử dụng RTCPeerConnection để truyền dữ liệu phát trực tiếp giữa các trình duyệt, nhưng cũng cần một cơ chế để điều phối hoạt động giao tiếp và gửi thông báo điều khiển, một quy trình được gọi là báo hiệu. WebRTC không chỉ định các phương thức và giao thức báo hiệu. Trong lớp học lập trình này, bạn sẽ sử dụng Socket.IO để nhắn tin, nhưng có nhiều lựa chọn thay thế.

STUN và TURN là gì?

WebRTC được thiết kế để hoạt động ngang hàng, vì vậy, người dùng có thể kết nối bằng tuyến đường trực tiếp nhất có thể. Tuy nhiên, WebRTC được xây dựng để đối phó với mạng thực tế: các ứng dụng khách cần phải đi qua các cổng NAT và tường lửa, đồng thời mạng ngang hàng cần có các phương án dự phòng trong trường hợp kết nối trực tiếp không thành công. Trong quy trình này, các API WebRTC sử dụng máy chủ STUN để lấy địa chỉ IP của máy tính và máy chủ TURN để hoạt động như máy chủ chuyển tiếp trong trường hợp giao tiếp ngang hàng không thành công. (WebRTC trong thực tế giải thích chi tiết hơn.)

WebRTC có an toàn không?

Mã hoá là bắt buộc đối với tất cả các thành phần WebRTC và các API JavaScript của thành phần này chỉ có thể được dùng từ các nguồn gốc bảo mật (HTTPS hoặc localhost). Các cơ chế báo hiệu không được xác định theo tiêu chuẩn WebRTC, vì vậy, bạn phải đảm bảo sử dụng các giao thức bảo mật.

2. Tổng quan

Tạo một ứng dụng để nhận video và chụp ảnh nhanh bằng webcam, sau đó chia sẻ ảnh và video đó ngang hàng qua WebRTC. Trong quá trình này, bạn sẽ tìm hiểu cách sử dụng các API WebRTC cốt lõi và thiết lập một máy chủ nhắn tin bằng Node.js.

Kiến thức bạn sẽ học được

  • Lấy video từ webcam
  • Phát trực tiếp video bằng RTCPeerConnection
  • Truyền dữ liệu bằng RTCDataChannel
  • Thiết lập dịch vụ báo hiệu để trao đổi thông báo
  • Kết hợp kết nối ngang hàng và báo hiệu
  • Chụp ảnh và chia sẻ ảnh qua kênh dữ liệu

Bạn cần có

  • Chrome 47 trở lên
  • Web Server for Chrome hoặc sử dụng máy chủ web mà bạn chọn.
  • Mã mẫu
  • Trình chỉnh sửa văn bản
  • Kiến thức cơ bản về HTML, CSS và JavaScript

3. Nhận mã mẫu

Tải mã xuống

Nếu đã quen với git, bạn có thể tải mã cho lớp học lập trình này xuống từ GitHub bằng cách sao chép mã:

git clone https://github.com/googlecodelabs/webrtc-web

Ngoài ra, hãy nhấp vào nút sau để tải tệp .zip của mã xuống:

Mở tệp zip đã tải xuống. Thao tác này sẽ giải nén một thư mục dự án (adaptive-web-media) chứa một thư mục cho mỗi bước của lớp học lập trình này, cùng với tất cả tài nguyên bạn sẽ cần.

Bạn sẽ thực hiện tất cả công việc lập trình trong thư mục có tên là work.

Các thư mục step-nn chứa phiên bản hoàn chỉnh cho từng bước của lớp học lập trình này. Các thông tin này chỉ mang tính tham khảo.

Cài đặt và xác minh máy chủ web

Mặc dù bạn có thể sử dụng máy chủ web của riêng mình, nhưng lớp học lập trình này được thiết kế để hoạt động hiệu quả với Máy chủ web Chrome. Nếu chưa cài đặt ứng dụng đó, bạn có thể cài đặt từ Cửa hàng Chrome trực tuyến.

6ddeb4aee53c0f0e.png

Sau khi cài đặt ứng dụng Web Server for Chrome, hãy nhấp vào lối tắt Ứng dụng Chrome trên thanh dấu trang, trang Thẻ mới hoặc trong Trình chạy ứng dụng:

1d2b4aa977ab7e24.png

Nhấp vào biểu tượng Máy chủ web:

27fce4494f641883.png

Tiếp theo, bạn sẽ thấy hộp thoại này, cho phép bạn định cấu hình máy chủ web cục bộ:

Ảnh chụp màn hình lúc 11:48:14 ngày 18 tháng 2 năm 2016

Nhấp vào nút CHỌN THƯ MỤC rồi chọn thư mục work mà bạn vừa tạo. Thao tác này sẽ cho phép bạn xem công việc đang tiến hành trong Chrome thông qua URL được làm nổi bật trong hộp thoại Máy chủ web trong phần URL của máy chủ web.

Trong mục Options (Tuỳ chọn), hãy đánh dấu vào hộp bên cạnh Automatically show index.html (Tự động hiện index.html) như minh hoạ dưới đây:

Screen Shot 2016-02-18 at 11.56.30 AM.png

Sau đó, hãy dừng và khởi động lại máy chủ bằng cách trượt nút có nhãn Web Server: STARTED (Máy chủ web: ĐÃ BẬT) sang trái rồi sang phải.

Screen Shot 2016-02-18 at 12.22.18 PM.png

Bây giờ, hãy truy cập vào trang web công việc của bạn trong trình duyệt web bằng cách nhấp vào URL máy chủ web được đánh dấu. Bạn sẽ thấy một trang có dạng như sau, tương ứng với work/index.html:

18a705cb6ccc5181.png

Rõ ràng là ứng dụng này chưa làm gì thú vị. Cho đến nay, đây chỉ là một cấu trúc tối thiểu mà chúng ta đang sử dụng để đảm bảo máy chủ web của bạn hoạt động đúng cách. Bạn sẽ thêm chức năng và các tính năng bố cục trong các bước tiếp theo.

4. Truyền trực tuyến video từ webcam

Kiến thức bạn sẽ học được

Trong bước này, bạn sẽ tìm hiểu cách:

  • Nhận luồng video từ webcam.
  • Thao tác với chế độ phát trực tiếp.
  • Sử dụng CSS và SVG để thao tác với video.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-01.

Một chút HTML...

Thêm phần tử video và phần tử script vào index.html trong thư mục work:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <video autoplay playsinline></video>

  <script src="js/main.js"></script>

</body>

</html>

...và một chút JavaScript

Thêm đoạn mã sau vào main.js trong thư mục js:

'use strict';

// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
  video: true,
};

// Video element where stream will be placed.
const localVideo = document.querySelector('video');

// Local stream that will be reproduced on the video.
let localStream;

// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

Dùng thử

Mở index.html trong trình duyệt, bạn sẽ thấy nội dung như sau (tất nhiên là có cả hình ảnh từ webcam của bạn!):

9297048e43ed0f3d.png

Cách hoạt động

Sau lệnh gọi getUserMedia(), trình duyệt sẽ yêu cầu người dùng cấp quyền truy cập vào camera (nếu đây là lần đầu tiên quyền truy cập camera được yêu cầu cho nguồn gốc hiện tại). Nếu thành công, một MediaStream sẽ được trả về. Phần tử này có thể được một phần tử đa phương tiện sử dụng thông qua thuộc tính srcObject:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);


}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Đối số constraints cho phép bạn chỉ định loại nội dung nghe nhìn cần lấy. Trong ví dụ này, chỉ có video vì âm thanh bị tắt theo mặc định:

const mediaStreamConstraints = {
  video: true,
};

Bạn có thể sử dụng các điều kiện ràng buộc cho các yêu cầu bổ sung, chẳng hạn như độ phân giải video:

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

Quy cách MediaTrackConstraints liệt kê tất cả các loại ràng buộc tiềm ẩn, mặc dù không phải trình duyệt nào cũng hỗ trợ tất cả các lựa chọn. Nếu độ phân giải được yêu cầu không được camera hiện đang chọn hỗ trợ, thì getUserMedia() sẽ bị từ chối bằng OverconstrainedError và người dùng sẽ không được nhắc cấp quyền truy cập vào camera của họ.

Nếu getUserMedia() thành công, luồng video từ webcam sẽ được đặt làm nguồn của phần tử video:

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Điểm thưởng

  • Đối tượng localStream được truyền đến getUserMedia() nằm trong phạm vi toàn cục, vì vậy, bạn có thể kiểm tra đối tượng đó từ bảng điều khiển trình duyệt: mở bảng điều khiển, nhập stream rồi nhấn Return. (Để xem bảng điều khiển trong Chrome, hãy nhấn tổ hợp phím Ctrl-Shift-J hoặc Command-Option-J nếu bạn đang dùng máy Mac.)
  • localStream.getVideoTracks() trả về giá trị nào?
  • Thử gọi cho localStream.getVideoTracks()[0].stop().
  • Hãy xem đối tượng ràng buộc: điều gì sẽ xảy ra khi bạn thay đổi đối tượng này thành {audio: true, video: true}?
  • Phần tử video có kích thước bao nhiêu? Làm cách nào để lấy kích thước tự nhiên của video từ JavaScript, thay vì kích thước hiển thị? Sử dụng Công cụ của Chrome cho nhà phát triển để kiểm tra.
  • Hãy thử thêm bộ lọc CSS vào phần tử video. Ví dụ:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Hãy thử thêm bộ lọc SVG. Ví dụ:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Kiến thức bạn học được

Trong bước này, bạn đã tìm hiểu cách:

  • Lấy video từ webcam.
  • Đặt điều kiện ràng buộc về nội dung nghe nhìn.
  • Thay đổi phần tử video.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-01.

Mẹo

  • Đừng quên thuộc tính autoplay trên phần tử video. Nếu không, bạn sẽ chỉ thấy một khung hình duy nhất!
  • Có nhiều lựa chọn khác cho các điều kiện ràng buộc getUserMedia(). Hãy xem bản minh hoạ tại webrtc.github.io/samples/src/content/peerconnection/constraints. Như bạn thấy, có rất nhiều mẫu WebRTC thú vị trên trang web đó.

Phương pháp hay nhất

  • Đảm bảo phần tử video không tràn ra ngoài vùng chứa. Chúng tôi đã thêm widthmax-width để đặt kích thước ưu tiên và kích thước tối đa cho video. Trình duyệt sẽ tự động tính toán chiều cao:
video {
  max-width: 100%;
  width: 320px;
}

Tiếp theo

Bạn có video, nhưng làm cách nào để phát trực tuyến? Hãy tìm hiểu ở bước tiếp theo!

5. Phát trực tiếp video bằng RTCPeerConnection

Kiến thức bạn sẽ học được

Trong bước này, bạn sẽ tìm hiểu cách:

  • Loại bỏ sự khác biệt giữa các trình duyệt bằng shim WebRTC, adapter.js.
  • Sử dụng RTCPeerConnection API để phát trực tuyến video.
  • Điều khiển hoạt động ghi hình và phát trực tuyến nội dung nghe nhìn.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-2.

RTCPeerConnection là gì?

RTCPeerConnection là một API để thực hiện các cuộc gọi WebRTC nhằm truyền phát video và âm thanh, đồng thời trao đổi dữ liệu.

Ví dụ này thiết lập một kết nối giữa hai đối tượng RTCPeerConnection (còn gọi là các peer) trên cùng một trang.

Không có nhiều ứng dụng thực tế, nhưng hữu ích để tìm hiểu cách hoạt động của RTCPeerConnection.

Thêm các thành phần video và nút điều khiển

Trong index.html, hãy thay thế phần tử video duy nhất bằng 2 phần tử video và 3 nút:

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


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

Một phần tử video sẽ hiển thị luồng từ getUserMedia() và phần tử còn lại sẽ hiển thị cùng một video được phát trực tuyến thông qua RTCPeerconnection. (Trong một ứng dụng thực tế, một phần tử video sẽ hiển thị luồng cục bộ và phần tử còn lại sẽ hiển thị luồng từ xa.)

Thêm shim adapter.js

Thêm một đường liên kết đến phiên bản hiện tại của adapter.js ở phía trên đường liên kết đến main.js:

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

Index.html hiện sẽ có dạng như sau:

<!DOCTYPE html>
<html>

<head>
  <title>Realtime communication with WebRTC</title>
  <link rel="stylesheet" href="css/main.css" />
</head>

<body>
  <h1>Realtime communication with WebRTC</h1>

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

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

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

Cài đặt mã RTCPeerConnection

Thay thế main.js bằng phiên bản trong thư mục step-02.

Gọi điện

Mở index.html, nhấp vào nút Start (Bắt đầu) để lấy video từ webcam và nhấp vào Call (Gọi) để thực hiện kết nối ngang hàng. Bạn sẽ thấy cùng một video (từ webcam) trong cả hai phần tử video. Xem bảng điều khiển của trình duyệt để xem nhật ký WebRTC.

Cách hoạt động

Bước này có nhiều việc cần làm...

WebRTC sử dụng RTCPeerConnection API để thiết lập một kết nối phát trực tuyến video giữa các ứng dụng WebRTC, còn được gọi là các thiết bị ngang hàng.

Trong ví dụ này, hai đối tượng RTCPeerConnection nằm trên cùng một trang: pc1pc2. Không có nhiều ứng dụng thực tế, nhưng phù hợp để minh hoạ cách hoạt động của các API.

Việc thiết lập cuộc gọi giữa các thành phần ngang hàng WebRTC bao gồm 3 việc:

  • Tạo một RTCPeerConnection cho mỗi đầu của cuộc gọi và ở mỗi đầu, hãy thêm luồng cục bộ từ getUserMedia().
  • Nhận và chia sẻ thông tin mạng: các điểm cuối kết nối tiềm năng được gọi là các ứng viên ICE.
  • Nhận và chia sẻ nội dung mô tả cục bộ và từ xa: siêu dữ liệu về nội dung nghe nhìn cục bộ ở định dạng SDP.

Hãy tưởng tượng rằng Alice và Bob muốn sử dụng RTCPeerConnection để thiết lập một cuộc trò chuyện video.

Trước tiên, Alice và Bob trao đổi thông tin mạng. Cụm từ "tìm ứng viên" đề cập đến quy trình tìm giao diện mạng và cổng bằng cách sử dụng khung ICE.

  1. Alice tạo một đối tượng RTCPeerConnection bằng trình xử lý onicecandidate (addEventListener('icecandidate')). Điều này tương ứng với đoạn mã sau trong main.js:
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice gọi getUserMedia() và thêm luồng được truyền vào đó:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
  1. Trình xử lý onicecandidate ở bước 1 được gọi khi các mạng ứng cử viên xuất hiện.
  2. Alice gửi dữ liệu đề xuất được chuyển đổi tuần tự cho Bob. Trong một ứng dụng thực tế, quy trình này (được gọi là truyền tín hiệu) diễn ra thông qua một dịch vụ nhắn tin – bạn sẽ tìm hiểu cách thực hiện việc đó trong một bước sau. Tất nhiên, trong bước này, hai đối tượng RTCPeerConnection nằm trên cùng một trang và có thể giao tiếp trực tiếp mà không cần đến tin nhắn bên ngoài.
  3. Khi Bob nhận được một thông báo đề xuất từ Alice, anh ta sẽ gọi addIceCandidate() để thêm đề xuất vào nội dung mô tả của thiết bị ngang hàng từ xa:
function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

Các thành phần ngang hàng WebRTC cũng cần tìm hiểu và trao đổi thông tin về nội dung nghe nhìn cục bộ và từ xa, chẳng hạn như độ phân giải và khả năng của bộ mã hoá và giải mã. Quá trình báo hiệu để trao đổi thông tin cấu hình nội dung nghe nhìn diễn ra bằng cách trao đổi các blob siêu dữ liệu, được gọi là đề nghịphản hồi, bằng cách sử dụng định dạng Giao thức mô tả phiên, còn gọi là SDP:

  1. Alice chạy phương thức RTCPeerConnection createOffer(). Lời hứa được trả về cung cấp một RTCSessionDescription: Nội dung mô tả phiên cục bộ của Alice:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. Nếu thành công, Alice sẽ đặt nội dung mô tả cục bộ bằng cách sử dụng setLocalDescription() rồi gửi nội dung mô tả phiên này cho Bob thông qua kênh báo hiệu của họ.
  2. Bob đặt nội dung mô tả mà Alice gửi cho anh làm nội dung mô tả từ xa bằng cách sử dụng setRemoteDescription().
  3. Bob chạy phương thức RTCPeerConnection createAnswer(), truyền cho phương thức này nội dung mô tả từ xa mà anh nhận được từ Alice, nhờ đó, một phiên cục bộ có thể được tạo ra và tương thích với phiên của Alice. Lời hứa createAnswer() truyền một RTCSessionDescription: Bob đặt lời hứa đó làm nội dung mô tả cục bộ và gửi cho Alice.
  4. Khi Alice nhận được nội dung mô tả phiên của Bob, cô ấy sẽ đặt nội dung đó làm nội dung mô tả từ xa bằng setRemoteDescription().
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}
  1. Ping!

Điểm thưởng

  1. Hãy xem chrome://webrtc-internals. Thao tác này cung cấp số liệu thống kê và dữ liệu gỡ lỗi WebRTC. (Bạn có thể xem danh sách đầy đủ các URL của Chrome tại chrome://about.)
  2. Tạo kiểu cho trang bằng CSS:
  • Đặt các video cạnh nhau.
  • Tạo các nút có cùng chiều rộng và văn bản lớn hơn.
  • Đảm bảo bố cục hoạt động trên thiết bị di động.
  1. Trong bảng điều khiển Công cụ cho nhà phát triển của Chrome, hãy xem localStream, localPeerConnectionremotePeerConnection.
  2. Trên bảng điều khiển, hãy xem localPeerConnectionpc1.localDescription. Định dạng SDP trông như thế nào?

Kiến thức bạn học được

Trong bước này, bạn đã tìm hiểu cách:

  • Loại bỏ sự khác biệt giữa các trình duyệt bằng shim WebRTC, adapter.js.
  • Sử dụng RTCPeerConnection API để phát trực tuyến video.
  • Điều khiển hoạt động ghi hình và phát trực tuyến nội dung nghe nhìn.
  • Chia sẻ thông tin về nội dung nghe nhìn và mạng giữa các thiết bị ngang hàng để cho phép thực hiện cuộc gọi WebRTC.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-2.

Mẹo

  • Có rất nhiều điều cần tìm hiểu trong bước này! Để tìm các tài nguyên khác giải thích chi tiết hơn về RTCPeerConnection, hãy xem webrtc.org. Trang này bao gồm các đề xuất về khung JavaScript – nếu bạn muốn sử dụng WebRTC nhưng không muốn xử lý các API.
  • Tìm hiểu thêm về shim adapter.js trong kho lưu trữ adapter.js trên GitHub.
  • Bạn muốn xem ứng dụng trò chuyện video tốt nhất thế giới trông như thế nào? Hãy xem AppRTC, ứng dụng chuẩn của dự án WebRTC cho các cuộc gọi WebRTC: app, code. Thời gian thiết lập cuộc gọi dưới 500 mili giây.

Phương pháp hay nhất

  • Để đảm bảo mã của bạn có thể hoạt động trong tương lai, hãy sử dụng các API mới dựa trên Promise và bật khả năng tương thích với những trình duyệt không hỗ trợ các API này bằng cách sử dụng adapter.js.

Tiếp theo

Bước này cho biết cách dùng WebRTC để truyền phát video giữa các thiết bị ngang hàng – nhưng lớp học lập trình này cũng nói về dữ liệu!

Trong bước tiếp theo, hãy tìm hiểu cách truyền trực tuyến dữ liệu tuỳ ý bằng RTCDataChannel.

6. Sử dụng RTCDataChannel để trao đổi dữ liệu

Kiến thức bạn sẽ học được

  • Cách trao đổi dữ liệu giữa các điểm cuối (thiết bị ngang hàng) WebRTC.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-03.

Cập nhật HTML

Trong bước này, bạn sẽ sử dụng các kênh dữ liệu WebRTC để gửi văn bản giữa hai phần tử textarea trên cùng một trang. Điều này không hữu ích lắm, nhưng cho thấy cách WebRTC có thể được dùng để chia sẻ dữ liệu cũng như phát trực tuyến video.

Xoá các phần tử video và nút khỏi index.html rồi thay thế bằng đoạn mã HTML sau:

<textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>

<div id="buttons">
  <button id="startButton">Start</button>
  <button id="sendButton">Send</button>
  <button id="closeButton">Stop</button>
</div>

Một textarea sẽ dùng để nhập văn bản, textarea còn lại sẽ hiển thị văn bản khi được truyền trực tuyến giữa các thiết bị ngang hàng.

index.html hiện sẽ có dạng như sau:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
  <textarea id="dataChannelReceive" disabled></textarea>

  <div id="buttons">
    <button id="startButton">Start</button>
    <button id="sendButton">Send</button>
    <button id="closeButton">Stop</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Cập nhật JavaScript

Thay thế main.js bằng nội dung của step-03/js/main.js.

Thử truyền trực tuyến dữ liệu giữa các thiết bị ngang hàng: mở index.html, nhấn vào Start (Bắt đầu) để thiết lập kết nối ngang hàng, nhập một số văn bản vào textarea ở bên trái, sau đó nhấp vào Send (Gửi) để truyền văn bản bằng các kênh dữ liệu WebRTC.

Cách hoạt động

Mã này sử dụng RTCPeerConnection và RTCDataChannel để cho phép trao đổi tin nhắn văn bản.

Phần lớn mã trong bước này giống với mã trong ví dụ về RTCPeerConnection.

Hàm sendData()createConnection() có hầu hết mã mới:

function createConnection() {
  dataChannelSend.placeholder = '';
  var servers = null;
  pcConstraint = null;
  dataConstraint = null;
  trace('Using SCTP based data channels');
  // For SCTP, reliable and ordered delivery is true by default.
  // Add localConnection to global scope to make it visible
  // from the browser console.
  window.localConnection = localConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created local peer connection object localConnection');

  sendChannel = localConnection.createDataChannel('sendDataChannel',
      dataConstraint);
  trace('Created send data channel');

  localConnection.onicecandidate = iceCallback1;
  sendChannel.onopen = onSendChannelStateChange;
  sendChannel.onclose = onSendChannelStateChange;

  // Add remoteConnection to global scope to make it visible
  // from the browser console.
  window.remoteConnection = remoteConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created remote peer connection object remoteConnection');

  remoteConnection.onicecandidate = iceCallback2;
  remoteConnection.ondatachannel = receiveChannelCallback;

  localConnection.createOffer().then(
    gotDescription1,
    onCreateSessionDescriptionError
  );
  startButton.disabled = true;
  closeButton.disabled = false;
}

function sendData() {
  var data = dataChannelSend.value;
  sendChannel.send(data);
  trace('Sent Data: ' + data);
}

Cú pháp của RTCDataChannel cố tình tương tự như WebSocket, với phương thức send() và sự kiện message.

Hãy lưu ý cách sử dụng dataConstraint. Bạn có thể định cấu hình các kênh dữ liệu để cho phép chia sẻ nhiều loại dữ liệu – ví dụ: ưu tiên việc phân phối đáng tin cậy hơn hiệu suất. Bạn có thể tìm hiểu thêm thông tin về các lựa chọn tại Mạng lưới nhà phát triển của Mozilla.

Điểm thưởng

  1. Với SCTP, giao thức mà các kênh dữ liệu WebRTC sử dụng, chế độ phân phối dữ liệu đáng tin cậy và có thứ tự sẽ được bật theo mặc định. Khi nào RTCDataChannel cần cung cấp dữ liệu một cách đáng tin cậy và khi nào hiệu suất quan trọng hơn – ngay cả khi điều đó có nghĩa là mất một số dữ liệu?
  2. Sử dụng CSS để cải thiện bố cục trang và thêm một thuộc tính phần giữ chỗ vào vùng văn bản "dataChannelReceive".
  3. Kiểm thử trang trên thiết bị di động.

Kiến thức bạn học được

Trong bước này, bạn đã tìm hiểu cách:

  • Thiết lập kết nối giữa hai thành phần ngang hàng WebRTC.
  • Trao đổi dữ liệu văn bản giữa các thiết bị ngang hàng.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-03.

Tìm hiểu thêm

Tiếp theo

Bạn đã tìm hiểu cách trao đổi dữ liệu giữa các thành phần ngang hàng trên cùng một trang, nhưng làm cách nào để thực hiện việc này giữa các máy khác nhau? Trước tiên, bạn cần thiết lập một kênh báo hiệu để trao đổi thông báo siêu dữ liệu. Hãy tìm hiểu cách thực hiện trong bước tiếp theo!

7. Thiết lập dịch vụ báo hiệu để trao đổi thông báo

Kiến thức bạn sẽ học được

Trong bước này, bạn sẽ tìm hiểu cách:

  • Sử dụng npm để cài đặt các phần phụ thuộc của dự án như được chỉ định trong package.json
  • Chạy một máy chủ Node.js và sử dụng node-static để phân phát các tệp tĩnh.
  • Thiết lập dịch vụ nhắn tin trên Node.js bằng Socket.IO.
  • Bạn có thể dùng tính năng này để tạo "phòng" và trao đổi tin nhắn.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-04.

Khái niệm

Để thiết lập và duy trì cuộc gọi WebRTC, các ứng dụng WebRTC (người dùng ngang hàng) cần trao đổi siêu dữ liệu:

  • Thông tin về mạng (đề xuất).
  • Đề xuấttrả lời các thông báo cung cấp thông tin về nội dung nghe nhìn, chẳng hạn như độ phân giải và bộ mã hoá/giải mã.

Nói cách khác, cần phải trao đổi siêu dữ liệu trước khi có thể truyền trực tiếp âm thanh, video hoặc dữ liệu. Quá trình này được gọi là truyền tín hiệu.

Trong các bước trước, các đối tượng RTCPeerConnection của người gửi và người nhận nằm trên cùng một trang, vì vậy, "truyền tín hiệu" chỉ đơn giản là việc truyền siêu dữ liệu giữa các đối tượng.

Trong một ứng dụng thực tế, RTCPeerConnection của người gửi và người nhận chạy trong các trang web trên các thiết bị khác nhau và bạn cần một cách để chúng giao tiếp siêu dữ liệu.

Để làm việc này, bạn sử dụng một máy chủ báo hiệu: một máy chủ có thể truyền thông báo giữa các ứng dụng WebRTC (các thành phần ngang hàng). Các thông báo thực tế là văn bản thuần tuý: các đối tượng JavaScript được chuyển đổi thành chuỗi.

Điều kiện tiên quyết: Cài đặt Node.js

Để chạy các bước tiếp theo của lớp học lập trình này (các thư mục step-04 đến step-06), bạn sẽ cần chạy một máy chủ trên localhost bằng Node.js.

Bạn có thể tải xuống và cài đặt Node.js từ đường liên kết này hoặc thông qua trình quản lý gói mà bạn muốn.

Sau khi cài đặt, bạn sẽ có thể nhập các phần phụ thuộc cần thiết cho các bước tiếp theo (chạy npm install), cũng như chạy một máy chủ localhost nhỏ để thực thi lớp học lập trình (chạy node index.js). Các lệnh này sẽ được chỉ ra sau, khi cần thiết.

Giới thiệu về ứng dụng

WebRTC sử dụng API JavaScript phía máy khách, nhưng để sử dụng trong thực tế, WebRTC cũng cần một máy chủ báo hiệu (nhắn tin), cũng như máy chủ STUN và TURN. Bạn có thể tìm hiểu thêm tại đây.

Trong bước này, bạn sẽ tạo một máy chủ báo hiệu Node.js đơn giản bằng cách sử dụng mô-đun Node.js Socket.IO và thư viện JavaScript để nhắn tin. Kinh nghiệm sử dụng Node.js và Socket.IO sẽ hữu ích nhưng không bắt buộc; các thành phần nhắn tin rất đơn giản.

Trong ví dụ này, máy chủ (ứng dụng Node.js) được triển khai trong index.js và máy khách chạy trên máy chủ đó (ứng dụng web) được triển khai trong index.html.

Ứng dụng Node.js trong bước này có 2 nhiệm vụ.

Trước tiên, tính năng này đóng vai trò là một trung gian chuyển tiếp tin nhắn:

socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

Thứ hai, ứng dụng này quản lý "phòng" trò chuyện video WebRTC:

if (numClients === 0) {
  socket.join(room);
  socket.emit('created', room, socket.id);
} else if (numClients === 1) {
  socket.join(room);
  socket.emit('joined', room, socket.id);
  io.sockets.in(room).emit('ready');
} else { // max two clients
  socket.emit('full', room);
}

Ứng dụng WebRTC đơn giản của chúng tôi sẽ cho phép tối đa 2 người dùng chia sẻ một phòng.

HTML và JavaScript

Cập nhật index.html để có dạng như sau:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Bạn sẽ không thấy gì trên trang ở bước này: tất cả hoạt động ghi nhật ký đều được thực hiện trên bảng điều khiển của trình duyệt. (Để xem bảng điều khiển trong Chrome, hãy nhấn tổ hợp phím Ctrl-Shift-J hoặc Command-Option-J nếu bạn đang dùng máy Mac.)

Thay thế js/main.js bằng nội dung sau:

'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

socket.on('log', function(array) {
  console.log.apply(console, array);
});

Thiết lập Socket.IO để chạy trên Node.js

Trong tệp HTML, có thể bạn đã thấy rằng bạn đang sử dụng tệp Socket.IO:

<script src="/socket.io/socket.io.js"></script>

Ở cấp cao nhất của thư mục work, hãy tạo một tệp có tên là package.json với nội dung sau:

{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Đây là một tệp kê khai ứng dụng cho Node Package Manager (npm) biết những phần phụ thuộc dự án cần cài đặt.

Để cài đặt các phần phụ thuộc (chẳng hạn như /socket.io/socket.io.js), hãy chạy lệnh sau từ thiết bị đầu cuối dòng lệnh, trong thư mục work:

npm install

Bạn sẽ thấy một nhật ký cài đặt có nội dung kết thúc như sau:

3ab06b7bcc7664b9.png

Như bạn có thể thấy, npm đã cài đặt các phần phụ thuộc được xác định trong package.json.

Tạo một tệp mới index.js ở cấp cao nhất của thư mục work (không nằm trong thư mục js) rồi thêm mã sau:

'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // for a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

});

Từ dòng lệnh, hãy chạy lệnh sau trong thư mục work:

node index.js

Trên trình duyệt, hãy mở localhost:8080.

Mỗi khi mở URL này, bạn sẽ được nhắc nhập tên phòng. Để tham gia cùng một phòng, hãy chọn cùng một tên phòng mỗi lần, chẳng hạn như "foo".

Mở một trang thẻ mới rồi mở lại localhost:8080. Chọn cùng một tên phòng.

Mở localhost:8080 trong thẻ hoặc cửa sổ thứ ba. Chọn lại tên phòng đó.

Kiểm tra bảng điều khiển trong từng thẻ: bạn sẽ thấy nhật ký từ JavaScript ở trên.

Điểm thưởng

  1. Có thể sử dụng những cơ chế nhắn tin thay thế nào? Bạn có thể gặp phải những vấn đề gì khi sử dụng WebSocket "thuần tuý"?
  2. Những vấn đề nào có thể liên quan đến việc mở rộng quy mô ứng dụng này? Bạn có thể phát triển một phương pháp để kiểm thử hàng nghìn hoặc hàng triệu yêu cầu về phòng cùng lúc không?
  3. Ứng dụng này sử dụng một lời nhắc JavaScript để lấy tên phòng. Tìm cách lấy tên phòng từ URL. Ví dụ: localhost:8080/foo sẽ cho tên phòng là foo.

Kiến thức bạn học được

Trong bước này, bạn đã tìm hiểu cách:

  • Sử dụng npm để cài đặt các phần phụ thuộc của dự án như được chỉ định trong package.json
  • Chạy một máy chủ Node.js để phân phát các tệp tĩnh.
  • Thiết lập dịch vụ nhắn tin trên Node.js bằng socket.io.
  • Bạn có thể dùng tính năng này để tạo "phòng" và trao đổi tin nhắn.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-04.

Tìm hiểu thêm

Tiếp theo

Tìm hiểu cách sử dụng tín hiệu để cho phép 2 người dùng thiết lập kết nối ngang hàng.

8. Kết hợp kết nối ngang hàng và báo hiệu

Kiến thức bạn sẽ học được

Trong bước này, bạn sẽ tìm hiểu cách:

  • Chạy dịch vụ báo hiệu WebRTC bằng Socket.IO chạy trên Node.js
  • Sử dụng dịch vụ đó để trao đổi siêu dữ liệu WebRTC giữa các thiết bị ngang hàng.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-05.

Thay thế HTML và JavaScript

Thay thế nội dung của index.html bằng nội dung sau:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <div id="videos">
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Thay thế js/main.js bằng nội dung của step-05/js/main.js.

Chạy máy chủ Node.js

Nếu không làm theo lớp học lập trình này từ thư mục work, bạn có thể cần cài đặt các phần phụ thuộc cho thư mục step-05 hoặc thư mục đang hoạt động hiện tại. Chạy lệnh sau từ thư mục đang hoạt động:

npm install

Sau khi cài đặt, nếu máy chủ Node.js của bạn chưa chạy, hãy bắt đầu bằng cách gọi lệnh sau trong thư mục work:

node index.js

Đảm bảo bạn đang sử dụng phiên bản index.js từ bước trước để triển khai Socket.IO. Để biết thêm thông tin về Node và Socket IO, hãy xem phần "Thiết lập dịch vụ báo hiệu để trao đổi thông báo".

Trên trình duyệt, hãy mở localhost:8080.

Mở lại localhost:8080 trong một thẻ hoặc cửa sổ mới. Một phần tử video sẽ hiển thị luồng cục bộ từ getUserMedia() và phần tử còn lại sẽ hiển thị video "từ xa" được phát trực tuyến thông qua RTCPeerconnection.

Xem nhật ký trong bảng điều khiển của trình duyệt.

Điểm thưởng

  1. Ứng dụng này chỉ hỗ trợ cuộc trò chuyện video trực tiếp. Bạn có thể thay đổi thiết kế như thế nào để cho phép nhiều người cùng chia sẻ một phòng trò chuyện video?
  2. Ví dụ này có tên phòng foo được mã hoá cứng. Cách nào là phù hợp nhất để bật các tên phòng khác?
  3. Người dùng sẽ chia sẻ tên phòng như thế nào? Hãy thử tạo một cách khác để chia sẻ tên phòng.
  4. Bạn có thể thay đổi ứng dụng như thế nào

Kiến thức bạn học được

Trong bước này, bạn đã tìm hiểu cách:

  • Chạy dịch vụ báo hiệu WebRTC bằng Socket.IO chạy trên Node.js.
  • Sử dụng dịch vụ đó để trao đổi siêu dữ liệu WebRTC giữa các thiết bị ngang hàng.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-05.

Mẹo

  • Bạn có thể xem số liệu thống kê và dữ liệu gỡ lỗi WebRTC tại chrome://webrtc-internals.
  • Bạn có thể dùng test.webrtc.org để kiểm tra môi trường cục bộ cũng như kiểm thử camera và micrô.
  • Nếu bạn gặp vấn đề bất thường với bộ nhớ đệm, hãy thử làm như sau:
  • Thực hiện thao tác làm mới hoàn toàn bằng cách nhấn giữ phím ctrl rồi nhấp vào nút Tải lại
  • Khởi động lại trình duyệt
  • Chạy npm cache clean từ dòng lệnh.

Tiếp theo

Tìm hiểu cách chụp ảnh, lấy dữ liệu hình ảnh và chia sẻ dữ liệu đó giữa các thiết bị ngang hàng từ xa.

9. Chụp ảnh và chia sẻ ảnh qua kênh dữ liệu

Kiến thức bạn sẽ học được

Trong bước này, bạn sẽ tìm hiểu cách:

  • Chụp ảnh và lấy dữ liệu từ ảnh bằng phần tử canvas.
  • Trao đổi dữ liệu hình ảnh với người dùng từ xa.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-06.

Cách hoạt động

Trước đây, bạn đã tìm hiểu cách trao đổi tin nhắn văn bản bằng RTCDataChannel.

Bước này giúp bạn có thể chia sẻ toàn bộ tệp: trong ví dụ này là ảnh chụp qua getUserMedia().

Sau đây là các phần cốt lõi của bước này:

  1. Thiết lập một kênh dữ liệu. Xin lưu ý rằng bạn không thêm bất kỳ luồng nội dung nghe nhìn nào vào kết nối ngang hàng trong bước này.
  2. Ghi lại luồng video từ webcam của người dùng bằng getUserMedia():
var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}
  1. Khi người dùng nhấp vào nút Snap (Chụp nhanh), hãy lấy một ảnh chụp nhanh (khung hình video) từ luồng video và hiển thị ảnh đó trong một phần tử canvas:
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}
  1. Khi người dùng nhấp vào nút Gửi, hãy chuyển đổi hình ảnh thành byte rồi gửi qua kênh dữ liệu:
function sendPhoto() {
  // Split data channel message in chunks of this byte length.
  var CHUNK_LEN = 64000;
  var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
    len = img.data.byteLength,
    n = len / CHUNK_LEN | 0;

  console.log('Sending a total of ' + len + ' byte(s)');
  dataChannel.send(len);

  // split the photo and send in chunks of about 64KB
  for (var i = 0; i < n; i++) {
    var start = i * CHUNK_LEN,
      end = (i + 1) * CHUNK_LEN;
    console.log(start + ' - ' + (end - 1));
    dataChannel.send(img.data.subarray(start, end));
  }

  // send the reminder, if any
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}
  1. Bên nhận chuyển đổi các byte thông báo của kênh dữ liệu trở lại thành hình ảnh và hiển thị hình ảnh đó cho người dùng:
function receiveDataChromeFactory() {
  var buf, count;

  return function onmessage(event) {
    if (typeof event.data === 'string') {
      buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
      count = 0;
      console.log('Expecting a total of ' + buf.byteLength + ' bytes');
      return;
    }

    var data = new Uint8ClampedArray(event.data);
    buf.set(data, count);

    count += data.byteLength;
    console.log('count: ' + count);

    if (count === buf.byteLength) {
      // we're done: all data chunks have been received
      console.log('Done. Rendering photo.');
      renderPhoto(buf);
    }
  };
}

function renderPhoto(data) {
  var canvas = document.createElement('canvas');
  canvas.width = photoContextW;
  canvas.height = photoContextH;
  canvas.classList.add('incomingPhoto');
  // trail is the element holding the incoming images
  trail.insertBefore(canvas, trail.firstChild);

  var context = canvas.getContext('2d');
  var img = context.createImageData(photoContextW, photoContextH);
  img.data.set(data);
  context.putImageData(img, 0, 0);
}

Lấy mã

Thay thế nội dung trong thư mục work bằng nội dung trong thư mục step-06. Tệp index.html trong work hiện sẽ có dạng như sau**:**

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <h2>
    <span>Room URL: </span><span id="url">...</span>
  </h2>

  <div id="videoCanvas">
    <video id="camera" autoplay></video>
    <canvas id="photo"></canvas>
  </div>

  <div id="buttons">
    <button id="snap">Snap</button><span> then </span><button id="send">Send</button>
    <span> or </span>
    <button id="snapAndSend">Snap &amp; Send</button>
  </div>

  <div id="incoming">
    <h2>Incoming photos</h2>
    <div id="trail"></div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Nếu không làm theo lớp học lập trình này từ thư mục work, bạn có thể cần cài đặt các phần phụ thuộc cho thư mục step-06 hoặc thư mục đang hoạt động hiện tại. Bạn chỉ cần chạy lệnh sau trong thư mục làm việc:

npm install

Sau khi cài đặt, nếu máy chủ Node.js của bạn chưa chạy, hãy khởi động máy chủ bằng cách gọi lệnh sau từ thư mục work:

node index.js

Đảm bảo rằng bạn đang sử dụng phiên bản index.js triển khai Socket.IO và nhớ khởi động lại máy chủ Node.js nếu bạn thực hiện thay đổi. Để biết thêm thông tin về Node và Socket IO, hãy xem phần "Thiết lập dịch vụ báo hiệu để trao đổi thông báo".

Nếu cần, hãy nhấp vào nút Cho phép để cho phép ứng dụng sử dụng webcam.

Ứng dụng sẽ tạo một mã phòng ngẫu nhiên và thêm mã đó vào URL. Mở URL trên thanh địa chỉ trong một thẻ hoặc cửa sổ trình duyệt mới.

Nhấp vào nút Chụp và gửi, sau đó xem phần Nhận trong thẻ khác ở cuối trang. Ứng dụng sẽ chuyển ảnh giữa các thẻ.

Bạn sẽ thấy như sau:

911b40f36ba6ba8.png

Điểm thưởng

  1. Làm cách nào để bạn có thể thay đổi mã để chia sẻ mọi loại tệp?

Tìm hiểu thêm

Kiến thức bạn học được

  • Cách chụp ảnh và lấy dữ liệu từ ảnh bằng phần tử canvas.
  • Cách trao đổi dữ liệu đó với người dùng từ xa.

Phiên bản hoàn chỉnh của bước này nằm trong thư mục step-06.

10. Xin chúc mừng

Bạn đã tạo một ứng dụng để truyền phát video và trao đổi dữ liệu theo thời gian thực!

Kiến thức bạn học được

Trong lớp học lập trình này, bạn đã tìm hiểu cách:

  • Lấy video từ webcam.
  • Phát trực tuyến video bằng RTCPeerConnection.
  • Truyền dữ liệu bằng RTCDataChannel.
  • Thiết lập một dịch vụ báo hiệu để trao đổi thông báo.
  • Kết hợp kết nối ngang hàng và báo hiệu.
  • Chụp ảnh và chia sẻ ảnh qua kênh dữ liệu.

Các bước tiếp theo

Tìm hiểu thêm

  • Bạn có thể xem nhiều tài nguyên để bắt đầu sử dụng WebRTC trên webrtc.org.