WebRTC - Hướng dẫn nhanh

Web không còn xa lạ với giao tiếp thời gian thực khi WebRTC (Web Real-Time Communication)vào trong chơi. Mặc dù nó đã được phát hành vào tháng 5 năm 2011, nó vẫn đang phát triển và các tiêu chuẩn của nó đang thay đổi. Một tập hợp các giao thức được tiêu chuẩn hóa bởi Giao tiếp thời gian thực trong trình duyệt WEB Nhóm làm việc tạihttp://tools.ietf.org/wg/rtcweb/ sau đó IETF (Internet Engineering Task Force)trong khi các bộ API mới được tiêu chuẩn hóa bởi Nhóm làm việc truyền thông thời gian thực trên web tạihttp://www.w3.org/2011/04/webrtc/ sau đó W3C (World Wide Web Consortium). Với sự xuất hiện của WebRTC, các ứng dụng web hiện đại có thể dễ dàng truyền phát nội dung âm thanh và video cho hàng triệu người.

Sơ đồ cơ bản

WebRTC cho phép bạn thiết lập các kết nối ngang hàng với các trình duyệt web khác một cách nhanh chóng và dễ dàng. Để xây dựng một ứng dụng như vậy từ đầu, bạn sẽ cần nhiều khuôn khổ và thư viện xử lý các vấn đề điển hình như mất dữ liệu, rớt kết nối và truyền tải NAT. Với WebRTC, tất cả những thứ này đều được tích hợp sẵn trong trình duyệt. Công nghệ này không cần bất kỳ plugin hoặc phần mềm của bên thứ ba. Nó có nguồn mở và mã nguồn của nó có sẵn miễn phí tạihttp://www.webrtc.org/.

API WebRTC bao gồm thu thập phương tiện, mã hóa và giải mã âm thanh và video, lớp truyền tải và quản lý phiên.

Chụp phương tiện

Bước đầu tiên là truy cập vào camera và micrô của thiết bị của người dùng. Chúng tôi phát hiện loại thiết bị có sẵn, xin phép người dùng truy cập các thiết bị này và quản lý luồng.

Mã hóa và giải mã âm thanh và video

Việc gửi một luồng dữ liệu âm thanh và video qua Internet không phải là một nhiệm vụ dễ dàng. Đây là nơi mã hóa và giải mã được sử dụng. Đây là quá trình chia nhỏ các khung hình video và sóng âm thanh thành các phần nhỏ hơn và nén chúng. Thuật toán này được gọi làcodec. Có một lượng lớn các codec khác nhau, được duy trì bởi các công ty khác nhau với các mục tiêu kinh doanh khác nhau. Ngoài ra còn có nhiều codec bên trong WebRTC như H.264, iSAC, Opus và VP8. Khi hai trình duyệt kết nối với nhau, chúng sẽ chọn codec được hỗ trợ tối ưu nhất giữa hai người dùng. May mắn thay, WebRTC thực hiện hầu hết các mã hóa đằng sau hậu trường.

Lớp vận chuyển

Tầng vận chuyển quản lý thứ tự các gói, xử lý mất gói và kết nối với những người dùng khác. Một lần nữa, API WebRTC cung cấp cho chúng tôi quyền truy cập dễ dàng vào các sự kiện cho chúng tôi biết khi có sự cố với kết nối.

Quản lý phiên

Quản lý phiên liên quan đến việc quản lý, mở và tổ chức các kết nối. Điều này thường được gọi làsignaling. Nếu bạn chuyển các luồng âm thanh và video cho người dùng thì việc chuyển dữ liệu thế chấp cũng có ý nghĩa. Điều này được thực hiện bởiRTCDataChannel API.

Các kỹ sư từ các công ty như Google, Mozilla, Opera và những công ty khác đã làm rất tốt việc mang trải nghiệm thời gian thực này lên Web.

tính tương thích của trình duyệt web

Các tiêu chuẩn WebRTC là một trong những tiêu chuẩn phát triển nhanh nhất trên web, vì vậy không có nghĩa là mọi trình duyệt đều hỗ trợ tất cả các tính năng giống nhau cùng một lúc. Để kiểm tra xem trình duyệt của bạn có hỗ trợ WebRTC hay không, bạn có thể truy cậphttp://caniuse.com/#feat=rtcpeerconnection. Trong tất cả các hướng dẫn, tôi khuyên bạn nên sử dụng Chrome cho tất cả các ví dụ.

Dùng thử WebRTC

Hãy bắt đầu sử dụng WebRTC ngay bây giờ. Điều hướng trình duyệt của bạn đến trang demo tạihttps://apprtc.appspot.com/

Nhấp vào nút "THAM GIA". Bạn sẽ thấy một thông báo thả xuống.

Nhấp vào nút “Cho phép” để bắt đầu phát trực tuyến video và âm thanh của bạn lên trang web. Bạn sẽ thấy một luồng video của chính mình.

Bây giờ, hãy mở URL bạn hiện đang truy cập trong tab trình duyệt mới và nhấp vào “THAM GIA”. Bạn sẽ thấy hai luồng video - một luồng từ khách hàng đầu tiên của bạn và một luồng khác từ khách hàng thứ hai.

Bây giờ bạn sẽ hiểu tại sao WebRTC là một công cụ mạnh mẽ.

Trường hợp sử dụng

Web thời gian thực mở ra cánh cửa cho một loạt ứng dụng hoàn toàn mới, bao gồm trò chuyện dựa trên văn bản, chia sẻ màn hình và tệp, chơi trò chơi, trò chuyện video, v.v. Ngoài giao tiếp, bạn có thể sử dụng WebRTC cho các mục đích khác như -

  • tiếp thị thời gian thực
  • quảng cáo thời gian thực
  • giao tiếp văn phòng (CRM, ERP, SCM, FFM)
  • Quản lý nhân sự
  • mạng xã hội
  • dịch vụ hẹn hò
  • tư vấn y tế trực tuyến
  • các dịch vụ tài chính
  • surveillance
  • Trò chơi nhiều người chơi
  • phát sóng trực tiếp
  • e-learning

Tóm lược

Bây giờ bạn đã hiểu rõ về thuật ngữ WebRTC. Bạn cũng nên có ý tưởng về những loại ứng dụng nào có thể được xây dựng bằng WebRTC, vì bạn đã thử nó trong trình duyệt của mình. Tóm lại, WebRTC là một công nghệ khá hữu ích.

Kiến trúc WebRTC tổng thể có mức độ phức tạp lớn.

Ở đây bạn có thể tìm thấy ba lớp khác nhau -

  • API for web developers - lớp này chứa tất cả các API nhà phát triển web cần thiết, bao gồm các đối tượng RTCPeerConnection, RTCDataChannel và MediaStrean.

  • API cho các nhà sản xuất trình duyệt

  • API có thể ghi đè, mà các nhà sản xuất trình duyệt có thể kết nối.

Các thành phần truyền tải cho phép thiết lập các kết nối qua nhiều loại mạng khác nhau trong khi công cụ thoại và video là các khuôn khổ chịu trách nhiệm truyền các luồng âm thanh và video từ card âm thanh và máy ảnh vào mạng. Đối với các nhà phát triển Web, phần quan trọng nhất là API WebRTC.

Nếu chúng ta nhìn vào kiến ​​trúc WebRTC từ phía máy khách-máy chủ, chúng ta có thể thấy rằng một trong những mô hình được sử dụng phổ biến nhất được lấy cảm hứng từ Hình thang SIP (Session Initiation Protocol).

Trong mô hình này, cả hai thiết bị đang chạy một ứng dụng web từ các máy chủ khác nhau. Đối tượng RTCPeerConnection định cấu hình các luồng để chúng có thể kết nối với nhau, ngang hàng. Báo hiệu này được thực hiện thông qua HTTP hoặc WebSockets.

Nhưng mô hình được sử dụng phổ biến nhất là Tam giác -

Trong mô hình này, cả hai thiết bị đều sử dụng cùng một ứng dụng web. Nó giúp nhà phát triển web linh hoạt hơn khi quản lý các kết nối của người dùng.

API WebRTC

Nó bao gồm một vài đối tượng javascript chính:

  • RTCPeerConnection
  • MediaStream
  • RTCDataChannel

Đối tượng RTCPeerConnection

Đối tượng này là điểm vào chính của API WebRTC. Nó giúp chúng tôi kết nối với đồng nghiệp, khởi tạo kết nối và đính kèm các luồng phương tiện. Nó cũng quản lý kết nối UDP với người dùng khác.

Nhiệm vụ chính của đối tượng RTCPeerConnection là thiết lập và tạo kết nối ngang hàng. Chúng ta có thể dễ dàng móc các điểm chính của kết nối vì đối tượng này kích hoạt một tập hợp các sự kiện khi chúng xuất hiện. Những sự kiện này cung cấp cho bạn quyền truy cập vào cấu hình kết nối của chúng tôi -

RTCPeerConnection là một đối tượng javascript đơn giản, bạn có thể đơn giản tạo theo cách này -

[code] 
var conn = new RTCPeerConnection(conf); 

conn.onaddstream = function(stream) { 
   // use stream here 
}; 

[/code]

Đối tượng RTCPeerConnection chấp nhận một tham số conf mà chúng ta sẽ trình bày ở phần sau trong các hướng dẫn này. Sự kiện onaddstream được kích hoạt khi người dùng từ xa thêm luồng video hoặc âm thanh vào kết nối ngang hàng của họ.

API MediaStream

Các trình duyệt hiện đại cấp cho nhà phát triển quyền truy cập vào API getUserMedia , còn được gọi là API MediaStream . Có ba điểm chính của chức năng -

  • Nó cấp cho nhà phát triển quyền truy cập vào đối tượng luồng đại diện cho các luồng video và âm thanh

  • Nó quản lý việc lựa chọn thiết bị đầu vào của người dùng trong trường hợp người dùng có nhiều camera hoặc micrô trên thiết bị của mình

  • Nó cung cấp một mức bảo mật yêu cầu người dùng mọi lúc anh ta muốn tìm nạp luồng của

Để kiểm tra API này, hãy tạo một trang HTML đơn giản. Nó sẽ hiển thị một phần tử <video> duy nhất, yêu cầu người dùng cho phép sử dụng máy ảnh và hiển thị luồng trực tiếp từ máy ảnh trên trang. Tạo tệp index.html và thêm -

[code] 
<html>
 
   <head> 
      <meta charset = "utf-8"> 
   </head>
	
   <body> 
      <video autoplay></video> 
      <script src = "client.js"></script> 
   </body> 
	 
</html> 
[/code]

Sau đó, thêm tệp client.js -

[code] 
//checks if the browser supports WebRTC 

function hasUserMedia() { 
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia 
      || navigator.mozGetUserMedia || navigator.msGetUserMedia; 
   return !!navigator.getUserMedia; 
}
 
if (hasUserMedia()) { 
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
      || navigator.mozGetUserMedia || navigator.msGetUserMedia;
		
   //get both video and audio streams from user's camera 
   navigator.getUserMedia({ video: true, audio: true }, function (stream) { 
      var video = document.querySelector('video'); 
		
      //insert stream into the video tag 
      video.src = window.URL.createObjectURL(stream); 
   }, function (err) {}); 
	
}else {
   alert("Error. WebRTC is not supported!"); 
}
[/code]

Bây giờ, hãy mở index.html và bạn sẽ thấy luồng video hiển thị khuôn mặt của bạn.

Nhưng hãy cẩn thận, vì WebRTC chỉ hoạt động ở phía máy chủ. Nếu bạn chỉ mở trang này bằng trình duyệt, nó sẽ không hoạt động. Bạn cần lưu trữ các tệp này trên máy chủ Apache hoặc Node hoặc máy chủ nào bạn muốn.

Đối tượng RTCDataChannel

Cũng như gửi các luồng phương tiện giữa các đồng nghiệp, bạn cũng có thể gửi thêm dữ liệu bằng cách sử dụng API DataChannel . API này đơn giản như API MediaStream. Công việc chính là tạo một kênh đến từ một đối tượng RTCPeerConnection hiện có -

[code] 
var peerConn = new RTCPeerConnection(); 

//establishing peer connection 
//... 
//end of establishing peer connection 
var dataChannel = peerConnection.createDataChannel("myChannel", dataChannelOptions); 

// here we can start sending direct messages to another peer 
[/code]

Đây là tất cả những gì bạn cần, chỉ cần hai dòng mã. Mọi thứ khác được thực hiện trên lớp bên trong của trình duyệt. Bạn có thể tạo kênh tại bất kỳ kết nối ngang hàng nào cho đến khi đóng đối tượng RTCPeerConnectionobject .

Tóm lược

Bây giờ bạn đã nắm chắc kiến ​​trúc WebRTC. Chúng tôi cũng đề cập đến các API MediaStream, RTCPeerConnection và RTCDataChannel. API WebRTC là một mục tiêu di động, vì vậy hãy luôn cập nhật các thông số kỹ thuật mới nhất.

Trước khi bắt đầu xây dựng các ứng dụng WebRTC, chúng ta nên thiết lập môi trường mã hóa của mình. Trước hết, bạn nên có một trình soạn thảo văn bản hoặc IDE để bạn có thể chỉnh sửa HTML và Javascript. Có khả năng bạn đã chọn cái ưa thích khi đọc hướng dẫn này. Đối với tôi, tôi đang sử dụng WebStorm IDE. Bạn có thể tải xuống phiên bản dùng thử của nó tạihttps://www.jetbrains.com/webstorm/. Tôi cũng đang sử dụng Linux Mint làm hệ điều hành mà tôi lựa chọn.

Yêu cầu khác đối với các ứng dụng WebRTC phổ biến là có một máy chủ để lưu trữ các tệp HTML và Javascript. Mã sẽ không hoạt động chỉ bằng cách nhấp đúp vào tệp vì trình duyệt không được phép kết nối với máy ảnh và micrô trừ khi tệp đang được cung cấp bởi một máy chủ thực tế. Điều này được thực hiện rõ ràng là do các vấn đề bảo mật.

Có rất nhiều máy chủ web khác nhau, nhưng trong hướng dẫn này, chúng tôi sẽ sử dụng Node.js với node-static -

  • Chuyến thăm https://nodejs.org/en/ và tải xuống phiên bản Node.js mới nhất.

  • Giải nén nó vào thư mục / usr / local / nodejs.

  • Mở tệp /home/YOUR_USERNAME/.profile và thêm dòng sau vào cuối - xuất PATH = $ PATH: / usr / local / nodejs / bin

  • Bạn có thể khởi động lại máy tính của mình hoặc chạy nguồn /home/YOUR_USERNAME/.profile

  • Bây giờ lệnh nút sẽ có sẵn từ dòng lệnh. Lệnh npm cũng có sẵn. NMP là trình quản lý gói cho Node.js. Bạn có thể tìm hiểu thêm tạihttps://www.npmjs.com/.

  • Mở một thiết bị đầu cuối và chạy sudo npm install -g node-static . Thao tác này sẽ cài đặt máy chủ web tĩnh cho Node.js.

  • Bây giờ điều hướng đến bất kỳ thư mục nào chứa các tệp HTML và chạy lệnh tĩnh bên trong thư mục đó để khởi động máy chủ web của bạn.

  • Bạn có thể điều hướng đến http://localhost:8080 để xem các tệp của bạn.

Có một cách khác để cài đặt nodejs. Chỉ cần chạy sudo apt-get install nodejs trong cửa sổ đầu cuối.

Để kiểm tra cài đặt Node.js của bạn, hãy mở thiết bị đầu cuối của bạn và chạy lệnh nút . Nhập một vài lệnh để kiểm tra cách nó hoạt động -

Node.js chạy các tệp Javascript cũng như các lệnh được nhập trong thiết bị đầu cuối. Tạo tệp index.js với nội dung sau:

console.log(“Testing Node.js”);

Sau đó chạy lệnh chỉ mục nút . Bạn sẽ thấy như sau:

Khi xây dựng máy chủ báo hiệu, chúng tôi sẽ sử dụng thư viện WebSockets cho Node.js. Để cài đặt npm, hãy cài đặt ws trong thiết bị đầu cuối.

Để kiểm tra máy chủ báo hiệu của chúng tôi, chúng tôi sẽ sử dụng tiện ích wscat. Để cài đặt nó, hãy chạy npm install -g wscat trong cửa sổ đầu cuối của bạn.

S.Không Giao thức & Mô tả
1 Giao thức WebRTC

Các ứng dụng WebRTC sử dụng UDP (User Datagram Protocol) làm giao thức truyền tải. Hầu hết các ứng dụng web ngày nay đều được xây dựng với việc sử dụng TCP (Giao thức điều khiển truyền)

2 Giao thức mô tả phiên

SDP là một phần quan trọng của WebRTC. Nó là một giao thức nhằm mục đích mô tả các phiên giao tiếp truyền thông.

3 Tìm tuyến đường

Để kết nối với người dùng khác, bạn nên tìm một đường dẫn rõ ràng xung quanh mạng của chính mình và mạng của người dùng khác. Nhưng rất có thể mạng bạn đang sử dụng có một số cấp độ kiểm soát truy cập để tránh các vấn đề bảo mật.

4 Giao thức truyền điều khiển luồng

Với kết nối ngang hàng, chúng tôi có khả năng gửi dữ liệu video và âm thanh nhanh chóng. Giao thức SCTP ngày nay được sử dụng để gửi dữ liệu blob trên kết nối ngang hàng hiện đang được thiết lập của chúng tôi khi sử dụng đối tượng RTCDataChannel.

Tóm lược

Trong chương này, chúng tôi đã đề cập đến một số công nghệ cho phép kết nối ngang hàng, chẳng hạn như UDP, TCP, STUN, TURN, ICE và SCTP. Bây giờ bạn sẽ có hiểu biết ở mức độ bề mặt về cách hoạt động của SDP và các trường hợp sử dụng của nó.

API MediaStream được thiết kế để dễ dàng truy cập các luồng phương tiện từ máy ảnh và micrô cục bộ. Phương thức getUserMedia () là cách chính để truy cập các thiết bị đầu vào cục bộ.

API có một số điểm chính:

  • Luồng phương tiện thời gian thực được đại diện bởi một đối tượng luồng ở dạng video hoặc âm thanh

  • Nó cung cấp một mức độ bảo mật thông qua quyền của người dùng yêu cầu người dùng trước khi ứng dụng web có thể bắt đầu tìm nạp một luồng

  • Việc lựa chọn thiết bị đầu vào được xử lý bởi API MediaStream (ví dụ: khi có hai máy ảnh hoặc micrô được kết nối với thiết bị)

Mỗi đối tượng MediaStream bao gồm một số đối tượng MediaStreamTrack. Chúng đại diện cho video và âm thanh từ các thiết bị đầu vào khác nhau.

Mỗi đối tượng MediaStreamTrack có thể bao gồm một số kênh (kênh âm thanh bên phải và bên trái). Đây là những phần nhỏ nhất được xác định bởi MediaStream API.

Có hai cách để xuất các đối tượng MediaStream. Đầu tiên, chúng tôi có thể kết xuất đầu ra thành phần tử video hoặc âm thanh. Thứ hai, chúng ta có thể gửi đầu ra cho đối tượng RTCPeerConnection, sau đó gửi nó đến một đối tượng từ xa.

Sử dụng API MediaStream

Hãy tạo một ứng dụng WebRTC đơn giản. Nó sẽ hiển thị một phần tử video trên màn hình, yêu cầu người dùng cho phép sử dụng máy ảnh và hiển thị một luồng video trực tiếp trong trình duyệt. Tạo tệp index.html -

<!DOCTYPE html> 
<html lang = "en">
 
   <head> 
      <meta charset = "utf-8" /> 
   </head> 
	
   <body> 
      <video autoplay></video> 
      <script src = "client.js"></script> 
   </body>
	
</html>

Sau đó, tạo tệp client.js và thêm phần sau;

function hasUserMedia() { 
   //check if the browser supports the WebRTC 
   return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia); 
} 

if (hasUserMedia()) { 
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
      || navigator.mozGetUserMedia; 
		
   //enabling video and audio channels 
   navigator.getUserMedia({ video: true, audio: true }, function (stream) { 
      var video = document.querySelector('video'); 
		
      //inserting our stream to the video tag     
      video.src = window.URL.createObjectURL(stream); 
   }, function (err) {}); 
} else { 
   alert("WebRTC is not supported"); 
}

Ở đây chúng ta tạo hàm hasUserMedia () để kiểm tra xem WebRTC có được hỗ trợ hay không. Sau đó, chúng tôi truy cập hàm getUserMedia trong đó tham số thứ hai là một lệnh gọi lại chấp nhận luồng đến từ thiết bị của người dùng. Sau đó, chúng tôi tải luồng của mình vào phần tử video bằng window.URL.createObjectURL , tạo URL đại diện cho đối tượng được cung cấp trong tham số.

Bây giờ, hãy làm mới trang của bạn, nhấp vào Cho phép và bạn sẽ thấy khuôn mặt của mình trên màn hình.

Hãy nhớ chạy tất cả các tập lệnh của bạn bằng máy chủ web. Chúng tôi đã cài đặt một trong Hướng dẫn Môi trường WebRTC.

API MediaStream

Tính chất

  • MediaStream.active (read only) - Trả về true nếu MediaStream đang hoạt động, hoặc trả về false nếu ngược lại.

  • MediaStream.ended (read only, deprecated)- Trả về true nếu sự kiện đã kết thúc đã được kích hoạt trên đối tượng, có nghĩa là luồng đã được đọc hoàn toàn, hoặc false nếu chưa đạt đến cuối luồng.

  • MediaStream.id (read only) - Một định danh duy nhất cho đối tượng.

  • MediaStream.label (read only, deprecated) - Một số nhận dạng duy nhất được chỉ định bởi tác nhân người dùng.

Bạn có thể xem các thuộc tính trên trông như thế nào trong trình duyệt của tôi -

Trình xử lý sự kiện

  • MediaStream.onactive- Một trình xử lý cho một sự kiện đang hoạt động được kích hoạt khi một đối tượng MediaStream trở nên hoạt động.

  • MediaStream.onaddtrack- Một trình xử lý cho một sự kiện addtrack được kích hoạt khi một đối tượng MediaStreamTrack mới được thêm vào.

  • MediaStream.onended (deprecated)- Một trình xử lý cho một sự kiện đã kết thúc được kích hoạt khi quá trình phát trực tuyến kết thúc.

  • MediaStream.oninactive- Trình xử lý cho một sự kiện không hoạt động được kích hoạt khi một đối tượng MediaStream không hoạt động.

  • MediaStream.onremovetrack- Trình xử lý cho một sự kiện removetrack được kích hoạt khi một đối tượng MediaStreamTrack bị xóa khỏi nó.

Phương pháp

  • MediaStream.addTrack()- Thêm đối tượng MediaStreamTrack được cung cấp làm đối số vào MediaStream. Nếu bản nhạc đã được thêm vào, không có gì xảy ra.

  • MediaStream.clone() - Trả về một bản sao của đối tượng MediaStream với một ID mới.

  • MediaStream.getAudioTracks()- Trả về danh sách các đối tượng MediaStreamTrack âm thanh từ đối tượng MediaStream .

  • MediaStream.getTrackById()- Trả về bản nhạc theo ID. Nếu đối số trống hoặc không tìm thấy ID, nó sẽ trả về null. Nếu một số bản nhạc có cùng một ID, nó sẽ trả về bản nhạc đầu tiên.

  • MediaStream.getTracks()- Trả về danh sách tất cả các đối tượng MediaStreamTrack từ đối tượng MediaStream .

  • MediaStream.getVideoTracks()- Trả về danh sách các đối tượng MediaStreamTrack video từ đối tượng MediaStream .

  • MediaStream.removeTrack()- Loại bỏ đối tượng MediaStreamTrack được cung cấp dưới dạng đối số khỏi MediaStream. Nếu bản nhạc đã bị xóa, không có gì xảy ra.

Để kiểm tra các thay đổi API ở trên, hãy thay đổi index.html theo cách sau:

<!DOCTYPE html> 
<html lang = "en">
 
   <head> 
      <meta charset = "utf-8" /> 
   </head>
	
   <body> 
      <video autoplay></video> 
      <div><button id = "btnGetAudioTracks">getAudioTracks()
         </button></div> 
      <div><button id = "btnGetTrackById">getTrackById()
         </button></div> 
      <div><button id = "btnGetTracks">getTracks()</button></div> 
      <div><button id = "btnGetVideoTracks">getVideoTracks()
         </button></div> 
      <div><button id = "btnRemoveAudioTrack">removeTrack() - audio
         </button></div> 
      <div><button id = "btnRemoveVideoTrack">removeTrack() - video
         </button></div> 
      <script src = "client.js"></script> 
   </body> 
	
</html>

Chúng tôi đã thêm một số nút để thử một số API MediaStream. Sau đó, chúng ta nên thêm các trình xử lý sự kiện cho nút mới tạo của chúng ta. Sửa đổi tệp client.js theo cách này -

var stream;
  
function hasUserMedia() { 
   //check if the browser supports the WebRTC 
   return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia); 
} 
 
if (hasUserMedia()) {
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
      || navigator.mozGetUserMedia; 
		
   //enabling video and audio channels 
   navigator.getUserMedia({ video: true, audio: true }, function (s) { 
      stream = s; 
      var video = document.querySelector('video'); 
		
      //inserting our stream to the video tag     
      video.src = window.URL.createObjectURL(stream); 
   }, function (err) {}); 
} else { 
   alert("WebRTC is not supported"); 
}
  
btnGetAudioTracks.addEventListener("click", function(){ 
   console.log("getAudioTracks"); 
   console.log(stream.getAudioTracks()); 
});
  
btnGetTrackById.addEventListener("click", function(){ 
   console.log("getTrackById"); 
   console.log(stream.getTrackById(stream.getAudioTracks()[0].id)); 
});
  
btnGetTracks.addEventListener("click", function(){ 
   console.log("getTracks()"); 
   console.log(stream.getTracks()); 
});
 
btnGetVideoTracks.addEventListener("click", function(){ 
   console.log("getVideoTracks()"); 
   console.log(stream.getVideoTracks()); 
});

btnRemoveAudioTrack.addEventListener("click", function(){ 
   console.log("removeAudioTrack()"); 
   stream.removeTrack(stream.getAudioTracks()[0]); 
});
  
btnRemoveVideoTrack.addEventListener("click", function(){ 
   console.log("removeVideoTrack()"); 
   stream.removeTrack(stream.getVideoTracks()[0]); 
});

Bây giờ hãy làm mới trang của bạn. Nhấp vào nút getAudioTracks () , sau đó nhấp vào nút removeTrack () - âm thanh . Bản âm thanh bây giờ sẽ bị xóa. Sau đó làm tương tự cho đoạn video.

Nếu bạn nhấp vào nút getTracks (), bạn sẽ thấy tất cả MediaStreamTracks (tất cả các đầu vào video và âm thanh được kết nối). Sau đó nhấp vào getTrackById () để lấy MediaStreamTrack âm thanh.

Tóm lược

Trong chương này, chúng tôi đã tạo một ứng dụng WebRTC đơn giản bằng API MediaStream. Bây giờ bạn sẽ có một cái nhìn tổng quan rõ ràng về các API MediaStream khác nhau giúp cho WebRTC hoạt động.

API RTCPeerConnection là cốt lõi của kết nối ngang hàng giữa mỗi trình duyệt. Để tạo các đối tượng RTCPeerConnection chỉ cần viết

var pc = RTCPeerConnection(config);

trong đó đối số cấu hình chứa ít nhất trên khóa, iceServers. Nó là một mảng các đối tượng URL chứa thông tin về máy chủ STUN và TURN, được sử dụng trong quá trình tìm kiếm các ứng viên ICE. Bạn có thể tìm thấy danh sách các máy chủ STUN công cộng hiện có tại code.google.com

Tùy thuộc vào việc bạn là người gọi hay người gọi mà đối tượng RTCPeerConnection được sử dụng theo một cách hơi khác nhau ở mỗi bên của kết nối.

Đây là một ví dụ về luồng của người dùng -

  • Đăng ký trình xử lý onicecandidate . Nó gửi bất kỳ ứng cử viên ICE nào đến đồng đẳng khác, khi họ được nhận.

  • Đăng ký trình xử lý onaddstream . Nó xử lý việc hiển thị luồng video khi nó được nhận từ máy ngang hàng từ xa.

  • Đăng ký trình xử lý tin nhắn . Máy chủ báo hiệu của bạn cũng phải có một trình xử lý các thông báo nhận được từ máy chủ ngang hàng khác. Nếu thư có chứa đối tượng RTCSessionDescription , thì nó phải được thêm vào đối tượng RTCPeerConnection bằng phương thức setRemoteDescription () . Nếu thư có chứa đối tượng RTCIceCandidate , thì nó phải được thêm vào đối tượng RTCPeerConnection bằng phương thức addIceCandidate () .

  • Sử dụng getUserMedia () để thiết lập luồng phương tiện cục bộ của bạn và thêm nó vào đối tượng RTCPeerConnection bằng phương thức addStream () .

  • Bắt đầu quá trình thương lượng đề nghị / câu trả lời. Đây là bước duy nhất mà luồng của người gọi khác với luồng của người gọi. Người gọi bắt đầu thương lượng bằng cách sử dụng phương thức createOffer () và đăng ký một cuộc gọi lại nhận đối tượng RTCSessionDescription . Sau đó, lệnh gọi lại này sẽ thêm đối tượng RTCSessionDescription này vào đối tượng RTCPeerConnection của bạn bằng cách sử dụng setLocalDescription () . Và cuối cùng, người gọi sẽ gửi RTCSessionDescription này đến máy chủ từ xa bằng cách sử dụng máy chủ báo hiệu. Mặt khác, callee đăng ký cùng một lệnh gọi lại, nhưng trong phương thức createAnswer () . Lưu ý rằng quy trình callee chỉ được bắt đầu sau khi nhận được phiếu mua hàng từ người gọi.

API RTCPeerConnection

Tính chất

  • RTCPeerConnection.iceConnectionState (read only)- Trả về một enum RTCIceConnectionState mô tả trạng thái của kết nối. Sự kiện iceconnectionstatechange được kích hoạt khi giá trị này thay đổi. Các giá trị có thể có -

    • new - đại lý ICE đang đợi các ứng viên ở xa hoặc các địa chỉ tập hợp

    • checking - đại lý ICE có các ứng cử viên từ xa, nhưng họ chưa tìm thấy kết nối

    • connected - nhân viên ICE đã tìm thấy một kết nối có thể sử dụng được, nhưng vẫn đang kiểm tra ứng viên ở xa hơn để có kết nối tốt hơn.

    • completed - nhân viên ICE đã tìm thấy một kết nối có thể sử dụng được và ngừng kiểm tra các ứng viên từ xa.

    • failed - nhân viên ICE đã kiểm tra tất cả các ứng cử viên từ xa nhưng không tìm thấy sự phù hợp cho ít nhất một thành phần.

    • disconnected - ít nhất một thành phần không còn sống.

    • closed - đại lý ICE đã đóng cửa.

  • RTCPeerConnection.iceGatheringState (read only) - Trả về một enum RTCIceGatheringState mô tả trạng thái thu thập ICE cho kết nối -

    • new - đối tượng vừa được tạo.

    • gathering - đại lý ICE đang trong quá trình thu thập các ứng cử viên

    • complete đại lý ICE đã hoàn tất việc thu thập.

  • RTCPeerConnection.localDescription (read only)- Trả về một RTCSessionDescription mô tả phiên cục bộ. Nó có thể là null nếu nó chưa được đặt.

  • RTCPeerConnection.peerIdentity (read only)- Trả về một RTCIdentityAssertion. Nó bao gồm một idp (tên miền) và một tên đại diện cho danh tính của đồng đẳng từ xa.

  • RTCPeerConnection.remoteDescription (read only)- Trả về một RTCSessionDescription mô tả phiên làm việc từ xa. Nó có thể là null nếu nó chưa được đặt.

  • RTCPeerConnection.signalingState (read only)- Trả về một enum RTCSignalingState mô tả trạng thái báo hiệu của kết nối cục bộ. Trạng thái này mô tả ưu đãi SDP. Một sự kiện thay đổi báo hiệu được kích hoạt khi giá trị này thay đổi. Các giá trị có thể có -

    • stable- Trạng thái ban đầu. Không có ưu đãi / trao đổi câu trả lời SDP nào đang diễn ra.

    • have-local-offer - phía địa phương của kết nối đã áp dụng ưu đãi SDP tại địa phương.

    • have-remote-offer - phía từ xa của kết nối đã áp dụng cục bộ ưu đãi SDP.

    • have-local-pranswer - một ưu đãi SDP từ xa đã được áp dụng và một ứng dụng SDP được áp dụng cục bộ.

    • have-remote-pranswer - một SDP địa phương đã được áp dụng và một ứng dụng SDP được áp dụng từ xa.

    • closed - kết nối bị đóng.

Trình xử lý sự kiện

Không. Trình xử lý sự kiện & Mô tả
1

RTCPeerConnection.onaddstream

Trình xử lý này được gọi khi sự kiện dòng bổ sung được kích hoạt. Sự kiện này được gửi khi một MediaStream được thêm vào kết nối này bởi đồng đẳng từ xa.

2

RTCPeerConnection.ondatachannel

Trình xử lý này được gọi khi sự kiện kênh dữ liệu được kích hoạt. Sự kiện này được gửi khi một RTCDataChannel được thêm vào kết nối này.

3

RTCPeerConnection.onicecandidate

Trình xử lý này được gọi khi sự kiện icecandidate được kích hoạt. Sự kiện này được gửi khi một đối tượng RTCIceCandidate được thêm vào tập lệnh.

4

RTCPeerConnection.oniceconnectionstatechange

Trình xử lý này được gọi khi sự kiện iceconnectionstatechange được kích hoạt. Sự kiện này được gửi khi giá trị của iceConnectionState thay đổi.

5

RTCPeerConnection.onidentityresult

Trình xử lý này được gọi khi sự kiện kết quả nhận dạng được kích hoạt. Sự kiện này được gửi khi xác nhận danh tính được tạo trong quá trình tạo phiếu mua hàng hoặc câu trả lời qua getIdentityAssertion ().

6

RTCPeerConnection.onidpassertionerror

Trình xử lý này được gọi khi sự kiện idpassertionerror được kích hoạt. Sự kiện này được gửi khi IdP (Nhà cung cấp danh tính) tìm thấy lỗi trong khi tạo xác nhận danh tính.

7

RTCPeerConnection.onidpvalidation

Trình xử lý này được gọi khi sự kiện idpvalidationerror được kích hoạt. Sự kiện này được gửi khi IdP (Nhà cung cấp danh tính) tìm thấy lỗi trong khi xác thực xác nhận danh tính.

số 8

RTCPeerConnection.onnegotiationneeded

Trình xử lý này được gọi khi sự kiện cần thương lượng được kích hoạt. Sự kiện này được gửi bởi trình duyệt để thông báo việc thương lượng sẽ được yêu cầu vào một thời điểm nào đó trong tương lai.

9

RTCPeerConnection.onpeeridentity

Trình xử lý này được gọi khi sự kiện peridentity được kích hoạt. Sự kiện này được gửi khi danh tính ngang hàng đã được thiết lập và xác minh trên kết nối này.

10

RTCPeerConnection.onremovestream

Trình xử lý này được gọi khi sự kiện báo hiệu thay đổi được kích hoạt. Sự kiện này được gửi khi giá trị của signalState thay đổi.

11

RTCPeerConnection.onsignalingstatechange

Trình xử lý này được gọi khi sự kiện removestream được kích hoạt. Sự kiện này được gửi khi một MediaStream bị xóa khỏi kết nối này.

Phương pháp

Không. Phương pháp & Mô tả
1

RTCPeerConnection()

Trả về một đối tượng RTCPeerConnection mới.

2

RTCPeerConnection.createOffer()

Tạo phiếu mua hàng (yêu cầu) để tìm đồng nghiệp từ xa. Hai tham số đầu tiên của phương thức này là thành công và gọi lại lỗi. Tham số thứ ba tùy chọn là các tùy chọn, như bật luồng âm thanh hoặc video.

3

RTCPeerConnection.createAnswer()

Tạo câu trả lời cho đề nghị mà người ngang hàng ở xa nhận được trong quá trình thương lượng đề nghị / câu trả lời. Hai tham số đầu tiên của phương thức này là thành công và gọi lại lỗi. Tham số thứ ba tùy chọn là các tùy chọn cho câu trả lời được tạo.

4

RTCPeerConnection.setLocalDescription()

Thay đổi mô tả kết nối cục bộ. Mô tả xác định các thuộc tính của kết nối. Kết nối phải có thể hỗ trợ cả mô tả cũ và mới. Phương thức nhận ba tham số, đối tượng RTCSessionDescription, gọi lại nếu thay đổi mô tả thành công, gọi lại nếu thay đổi mô tả không thành công.

5

RTCPeerConnection.setRemoteDescription()

Thay đổi mô tả kết nối từ xa. Mô tả xác định các thuộc tính của kết nối. Kết nối phải có thể hỗ trợ cả mô tả cũ và mới. Phương thức nhận ba tham số, đối tượng RTCSessionDescription, gọi lại nếu thay đổi mô tả thành công, gọi lại nếu thay đổi mô tả không thành công.

6

RTCPeerConnection.updateIce()

Cập nhật quy trình của đại lý ICE về việc ping các ứng viên từ xa và thu thập các ứng viên địa phương.

7

RTCPeerConnection.addIceCandidate()

Cung cấp một ứng cử viên từ xa cho đại lý ICE.

số 8

RTCPeerConnection.getConfiguration()

Trả về một đối tượng RTCConfiguration. Nó đại diện cho cấu hình của đối tượng RTCPeerConnection.

9

RTCPeerConnection.getLocalStreams()

Trả về một mảng kết nối MediaStream cục bộ.

10

RTCPeerConnection.getRemoteStreams()

Trả về một mảng kết nối MediaStream từ xa.

11

RTCPeerConnection.getStreamById()

Trả về MediaStream cục bộ hoặc từ xa theo ID đã cho.

12

RTCPeerConnection.addStream()

Thêm MediaStream làm nguồn video hoặc âm thanh cục bộ.

13

RTCPeerConnection.removeStream()

Loại bỏ MediaStream làm nguồn video hoặc âm thanh cục bộ.

14

RTCPeerConnection.close()

Đóng kết nối.

15

RTCPeerConnection.createDataChannel()

Tạo một RTCDataChannel mới.

16

RTCPeerConnection.createDTMFSender()

Tạo RTCDTMFSender mới, được liên kết với MediaStreamTrack cụ thể. Cho phép gửi tín hiệu điện thoại DTMF (Đa tần số kép) qua kết nối.

17

RTCPeerConnection.getStats()

Tạo một RTCStatsReport mới chứa số liệu thống kê liên quan đến kết nối.

18

RTCPeerConnection.setIdentityProvider()

Đặt IdP. Lấy ba tham số - tên, giao thức được sử dụng để giao tiếp và tên người dùng tùy chọn.

19

RTCPeerConnection.getIdentityAssertion()

Thu thập một xác nhận danh tính. Nó không được mong đợi để đối phó với phương pháp này trong ứng dụng. Vì vậy, bạn có thể gọi nó một cách rõ ràng chỉ để đoán trước nhu cầu.

Thiết lập kết nối

Bây giờ hãy tạo một ứng dụng ví dụ. Đầu tiên, chạy máy chủ báo hiệu mà chúng tôi đã tạo trong hướng dẫn “máy chủ báo hiệu” thông qua “máy chủ nút”.

Sẽ có hai đầu vào văn bản trên trang, một cho đăng nhập và một cho tên người dùng mà chúng tôi muốn kết nối. Tạo tệp index.html và thêm mã sau:

<html lang = "en"> 
   <head> 
      <meta charset = "utf-8" /> 
   </head>
	
   <body> 
	
      <div> 
         <input type = "text" id = "loginInput" /> 
         <button id = "loginBtn">Login</button> 
      </div> 
	
      <div> 
         <input type = "text" id = "otherUsernameInput" />
         <button id = "connectToOtherUsernameBtn">Establish connection</button> 
      </div> 
		
      <script src = "client2.js"></script>
		
   </body>
	
</html>

Bạn có thể thấy rằng chúng tôi đã thêm phần nhập văn bản để đăng nhập, nút đăng nhập, phần nhập văn bản cho tên người dùng ngang hàng khác và nút kết nối với anh ta. Bây giờ, hãy tạo tệp client.js và thêm mã sau:

var connection = new WebSocket('ws://localhost:9090'); 
var name = ""; 
 
var loginInput = document.querySelector('#loginInput'); 
var loginBtn = document.querySelector('#loginBtn'); 
var otherUsernameInput = document.querySelector('#otherUsernameInput'); 
var connectToOtherUsernameBtn = document.querySelector('#connectToOtherUsernameBtn'); 
var connectedUser, myConnection;
  
//when a user clicks the login button 
loginBtn.addEventListener("click", function(event){ 
   name = loginInput.value; 
	
   if(name.length > 0){ 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
  
//handle messages from the server 
connection.onmessage = function (message) { 
   console.log("Got message", message.data);
   var data = JSON.parse(message.data); 
	
   switch(data.type) { 
      case "login": 
         onLogin(data.success); 
         break; 
      case "offer": 
         onOffer(data.offer, data.name); 
         break; 
      case "answer": 
         onAnswer(data.answer); 
         break; 
      case "candidate": 
         onCandidate(data.candidate); 
         break; 
      default: 
         break; 
   } 
};
  
//when a user logs in 
function onLogin(success) { 

   if (success === false) { 
      alert("oops...try a different username"); 
   } else { 
      //creating our RTCPeerConnection object 
		
      var configuration = { 
         "iceServers": [{ "url": "stun:stun.1.google.com:19302" }] 
      }; 
		
      myConnection = new webkitRTCPeerConnection(configuration); 
      console.log("RTCPeerConnection object was created"); 
      console.log(myConnection); 
  
      //setup ice handling
      //when the browser finds an ice candidate we send it to another peer 
      myConnection.onicecandidate = function (event) { 
		
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      }; 
   } 
};
  
connection.onopen = function () { 
   console.log("Connected"); 
};
  
connection.onerror = function (err) { 
   console.log("Got error", err); 
};
  
// Alias for sending messages in JSON format 
function send(message) { 

   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   connection.send(JSON.stringify(message)); 
};

Bạn có thể thấy rằng chúng tôi thiết lập kết nối socket tới máy chủ báo hiệu của chúng tôi. Khi người dùng nhấp vào nút đăng nhập, ứng dụng sẽ gửi tên người dùng của anh ta đến máy chủ. Nếu đăng nhập thành công, ứng dụng sẽ tạo đối tượng RTCPeerConnection và thiết lập trình xử lý onicecandidate để gửi tất cả các icecandidate đã tìm thấy đến đồng đẳng khác. Bây giờ hãy mở trang và thử đăng nhập. Bạn sẽ thấy đầu ra bảng điều khiển sau:

Bước tiếp theo là tạo một phiếu mua hàng cho đồng nghiệp khác. Thêm mã sau vào tệp client.js của bạn -

//setup a peer connection with another user 
connectToOtherUsernameBtn.addEventListener("click", function () { 
 
   var otherUsername = otherUsernameInput.value; 
   connectedUser = otherUsername;
	
   if (otherUsername.length > 0) { 
      //make an offer 
      myConnection.createOffer(function (offer) { 
         console.log(); 
         send({ 
            type: "offer", 
            offer: offer 
         });
			
         myConnection.setLocalDescription(offer); 
      }, function (error) { 
         alert("An error has occurred."); 
      }); 
   } 
}); 
 
//when somebody wants to call us 
function onOffer(offer, name) { 
   connectedUser = name; 
   myConnection.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   myConnection.createAnswer(function (answer) { 
      myConnection.setLocalDescription(answer); 
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("oops...error"); 
   }); 
}
  
//when another user answers to our offer 
function onAnswer(answer) { 
   myConnection.setRemoteDescription(new RTCSessionDescription(answer)); 
} 
 
//when we got ice candidate from another user 
function onCandidate(candidate) { 
   myConnection.addIceCandidate(new RTCIceCandidate(candidate)); 
}

Bạn có thể thấy rằng khi người dùng nhấp vào nút "Thiết lập kết nối", ứng dụng sẽ đưa ra đề nghị SDP cho người ngang hàng khác. Chúng tôi cũng đặt onAnsweronCandidate xử lý. Tải lại trang của bạn, mở nó trong hai tab, đăng nhập với hai người dùng và cố gắng thiết lập kết nối giữa họ. Bạn sẽ thấy đầu ra bảng điều khiển sau:

Bây giờ kết nối ngang hàng được thiết lập. Trong các hướng dẫn tiếp theo, chúng tôi sẽ thêm các luồng video và âm thanh cũng như hỗ trợ trò chuyện văn bản.

WebRTC không chỉ tốt trong việc truyền các luồng âm thanh và video, mà còn bất kỳ dữ liệu tùy ý nào mà chúng tôi có thể có. Đây là lúc đối tượng RTCDataChannel phát huy tác dụng.

API RTCDataChannel

Tính chất

  • RTCDataChannel.label (read only) - Trả về một chuỗi chứa tên kênh dữ liệu.

  • RTCDataChannel.ordered (read only) - Trả về true nếu thứ tự giao tin được đảm bảo hoặc trả về false nếu không được đảm bảo.

  • RTCDataChannel.protocol (read only) - Trả về một chuỗi chứa tên giao thức con được sử dụng cho kênh này.

  • RTCDataChannel.id (read only) - Trả về một id duy nhất cho kênh được đặt khi tạo đối tượng RTCDataChannel.

  • RTCDataChannel.readyState (read only)- Trả về enum RTCDataChannelState đại diện cho trạng thái của kết nối. Các giá trị có thể có -

    • connecting- Cho biết kết nối chưa hoạt động. Đây là trạng thái ban đầu.

    • open - Cho biết kết nối đang chạy.

    • closing- Cho biết kết nối đang trong quá trình tắt. Thư được lưu trong bộ nhớ cache đang trong quá trình gửi hoặc nhận, nhưng không có tác vụ mới tạo nào được chấp nhận.

    • closed - Cho biết kết nối không thể được thiết lập hoặc đã bị tắt.

  • RTCDataChannel.bufferedAmount (read only)- Trả về số lượng byte đã được xếp hàng đợi để gửi. Đây là lượng dữ liệu chưa được gửi qua RTCDataChannel.send ().

  • RTCDataChannel.bufferedAmountLowThreshold- Trả về số byte mà tại đó RTCDataChannel.bufferedAmount được lấy ở mức thấp. Khi RTCDataChannel.bufferedAmount giảm xuống dưới ngưỡng này, sự kiện bufferedamountlow sẽ được kích hoạt.

  • RTCDataChannel.binaryType- Trả về kiểu dữ liệu nhị phân được truyền bởi kết nối. Có thể là “blob” hoặc “arraybuffer”.

  • RTCDataChannel.maxPacketLifeType (read only) - Trả về một đoạn ngắn không dấu cho biết độ dài tính bằng mili giây của cửa sổ khi nhắn tin ở chế độ không đáng tin cậy.

  • RTCDataChannel.maxRetransmits (read only) - Trả về một đoạn ngắn chưa được đánh dấu cho biết số lần tối đa một kênh sẽ truyền lại dữ liệu nếu nó không được phân phối.

  • RTCDataChannel.negotiated (read only) - Trả về boolean cho biết kênh đã được tác nhân người dùng hoặc ứng dụng thương lượng hay chưa.

  • RTCDataChannel.reliable (read only) - Trả về boolean cho biết kết nối có thể gửi tin nhắn ở chế độ không đáng tin cậy.

  • RTCDataChannel.stream (read only) - Từ đồng nghĩa với RTCDataChannel.id

Trình xử lý sự kiện

  • RTCDataChannel.onopen- Trình xử lý sự kiện này được gọi khi sự kiện mở được kích hoạt. Sự kiện này được gửi khi kết nối dữ liệu đã được thiết lập.

  • RTCDataChannel.onmessage- Trình xử lý sự kiện này được gọi khi sự kiện thông báo được kích hoạt. Sự kiện được gửi khi có thông báo trên kênh dữ liệu.

  • RTCDataChannel.onbufferedamountlow- Trình xử lý sự kiện này được gọi khi sự kiện bufferedamoutlow được kích hoạt. Sự kiện này được gửi khi RTCDataChannel.bufferedAmount giảm xuống dưới thuộc tính RTCDataChannel.bufferedAmountLowThreshold.

  • RTCDataChannel.onclose- Trình xử lý sự kiện này được gọi khi sự kiện đóng được kích hoạt. Sự kiện này được gửi khi kết nối dữ liệu đã bị đóng.

  • RTCDataChannel.onerror- Trình xử lý sự kiện này được gọi khi sự kiện lỗi được kích hoạt. Sự kiện này được gửi khi gặp lỗi.

Phương pháp

  • RTCDataChannel.close() - Đóng kênh dữ liệu.

  • RTCDataChannel.send()- Gửi dữ liệu trong tham số qua kênh. Dữ liệu có thể là một blob, một chuỗi, một ArrayBuffer hoặc một ArrayBufferView.

Bây giờ hãy tạo một ví dụ đơn giản. Đầu tiên, chạy máy chủ báo hiệu mà chúng tôi đã tạo trong hướng dẫn “máy chủ báo hiệu” thông qua “máy chủ nút”.

Sẽ có ba đầu vào văn bản trên trang, một đầu vào cho đăng nhập, một cho tên người dùng và một cho thông điệp chúng tôi muốn gửi cho người ngang hàng khác. Tạo tệp index.html và thêm mã sau:

<html lang = "en"> 
   <head> 
      <meta charset = "utf-8" /> 
   </head>
	
   <body> 
      <div> 
         <input type = "text" id = "loginInput" /> 
         <button id = "loginBtn">Login</button> 
      </div> 
		
      <div> 
         <input type = "text" id = "otherUsernameInput" /> 
         <button id = "connectToOtherUsernameBtn">Establish connection</button> 
      </div> 
		
      <div> 
         <input type = "text" id = "msgInput" /> 
         <button id = "sendMsgBtn">Send text message</button> 
      </div> 
		
      <script src = "client.js"></script>
   </body>
	
</html>

Chúng tôi cũng đã thêm ba nút để đăng nhập, thiết lập kết nối và gửi tin nhắn. Bây giờ, hãy tạo tệp client.js và thêm mã sau:

var connection = new WebSocket('ws://localhost:9090'); 
var name = "";

var loginInput = document.querySelector('#loginInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var otherUsernameInput = document.querySelector('#otherUsernameInput'); 
var connectToOtherUsernameBtn = document.querySelector('#connectToOtherUsernameBtn'); 
var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 
var connectedUser, myConnection, dataChannel;
  
//when a user clicks the login button 
loginBtn.addEventListener("click", function(event) { 
   name = loginInput.value; 
	
   if(name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
}); 
 
//handle messages from the server 
connection.onmessage = function (message) { 
   console.log("Got message", message.data); 
   var data = JSON.parse(message.data); 
	
   switch(data.type) { 
      case "login": 
         onLogin(data.success); 
         break; 
      case "offer": 
         onOffer(data.offer, data.name); 
         break; 
      case "answer":
         onAnswer(data.answer); 
         break; 
      case "candidate": 
         onCandidate(data.candidate); 
         break; 
      default: 
         break; 
   } 
}; 
 
//when a user logs in 
function onLogin(success) { 

   if (success === false) { 
      alert("oops...try a different username"); 
   } else { 
      //creating our RTCPeerConnection object 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun.1.google.com:19302" }] 
      }; 
		
      myConnection = new webkitRTCPeerConnection(configuration, { 
         optional: [{RtpDataChannels: true}] 
      }); 
		
      console.log("RTCPeerConnection object was created"); 
      console.log(myConnection); 
  
      //setup ice handling 
      //when the browser finds an ice candidate we send it to another peer 
      myConnection.onicecandidate = function (event) { 
		
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            });
         } 
      }; 
		
      openDataChannel();
		
   } 
};
  
connection.onopen = function () { 
   console.log("Connected"); 
}; 
 
connection.onerror = function (err) { 
   console.log("Got error", err); 
};
  
// Alias for sending messages in JSON format 
function send(message) { 
   if (connectedUser) { 
      message.name = connectedUser; 
   }
	
   connection.send(JSON.stringify(message)); 
};

Bạn có thể thấy rằng chúng tôi thiết lập kết nối socket tới máy chủ báo hiệu của chúng tôi. Khi người dùng nhấp vào nút đăng nhập, ứng dụng sẽ gửi tên người dùng của anh ta đến máy chủ. Nếu đăng nhập thành công, ứng dụng sẽ tạo đối tượng RTCPeerConnection và thiết lập trình xử lý onicecandidate để gửi tất cả các icecandidate đã tìm thấy đến đồng đẳng khác. Nó cũng chạy hàm openDataChannel () để tạo một dataChannel. Lưu ý rằng khi tạo đối tượng RTCPeerConnection, đối số thứ hai trong phương thức khởi tạo tùy chọn: [{RtpDataChannels: true}] là bắt buộc nếu bạn đang sử dụng Chrome hoặc Opera. Bước tiếp theo là tạo một phiếu mua hàng cho đồng nghiệp khác. Thêm mã sau đây để bạn client.js File-

//setup a peer connection with another user 
connectToOtherUsernameBtn.addEventListener("click", function () {
  
   var otherUsername = otherUsernameInput.value;
   connectedUser = otherUsername;
	
   if (otherUsername.length > 0) { 
      //make an offer 
      myConnection.createOffer(function (offer) { 
         console.log(); 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         myConnection.setLocalDescription(offer); 
      }, function (error) { 
         alert("An error has occurred."); 
      }); 
   } 
});
  
//when somebody wants to call us 
function onOffer(offer, name) { 
   connectedUser = name; 
   myConnection.setRemoteDescription(new RTCSessionDescription(offer));
	
   myConnection.createAnswer(function (answer) { 
      myConnection.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("oops...error"); 
   }); 
}

//when another user answers to our offer 
function onAnswer(answer) { 
   myConnection.setRemoteDescription(new RTCSessionDescription(answer)); 
}
  
//when we got ice candidate from another user 
function onCandidate(candidate) { 
   myConnection.addIceCandidate(new RTCIceCandidate(candidate)); 
}

Bạn có thể thấy rằng khi người dùng nhấp vào nút "Thiết lập kết nối", ứng dụng sẽ đưa ra đề nghị SDP cho người ngang hàng khác. Chúng tôi cũng đặt onAnsweronCandidate xử lý. Cuối cùng, hãy triển khai hàm openDataChannel () để tạo ra dataChannel của chúng ta. Thêm mã sau vào tệp client.js của bạn -

//creating data channel 
function openDataChannel() { 

   var dataChannelOptions = { 
      reliable:true 
   }; 
	
   dataChannel = myConnection.createDataChannel("myDataChannel", dataChannelOptions);
	
   dataChannel.onerror = function (error) { 
      console.log("Error:", error); 
   };
	
   dataChannel.onmessage = function (event) { 
      console.log("Got message:", event.data); 
   };  
}
  
//when a user clicks the send message button 
sendMsgBtn.addEventListener("click", function (event) { 
   console.log("send message");
   var val = msgInput.value; 
   dataChannel.send(val); 
});

Ở đây, chúng tôi tạo dataChannel cho kết nối của chúng tôi và thêm trình xử lý sự kiện cho nút "gửi tin nhắn". Bây giờ, hãy mở trang này trong hai tab, đăng nhập bằng hai người dùng, thiết lập kết nối và thử gửi tin nhắn. Bạn sẽ thấy chúng trong đầu ra của bảng điều khiển. Lưu ý rằng ví dụ trên được thử nghiệm trong Opera.

Bây giờ bạn có thể thấy rằng RTCDataChannel là một phần cực kỳ mạnh mẽ của API WebRTC. Có rất nhiều trường hợp sử dụng khác cho đối tượng này, như chơi game ngang hàng hoặc chia sẻ tệp dựa trên torrent.

Hầu hết các ứng dụng WebRTC không chỉ có thể giao tiếp qua video và âm thanh. Họ cần nhiều tính năng khác. Trong chương này, chúng ta sẽ xây dựng một máy chủ báo hiệu cơ bản.

Phát tín hiệu và thương lượng

Để kết nối với người dùng khác, bạn nên biết vị trí của họ trên Web. Địa chỉ IP của thiết bị của bạn cho phép các thiết bị hỗ trợ Internet gửi dữ liệu trực tiếp giữa nhau. Các RTCPeerConnection đối tượng chịu trách nhiệm cho việc này. Ngay sau khi các thiết bị biết cách tìm thấy nhau qua Internet, chúng bắt đầu trao đổi dữ liệu về các giao thức và codec mà mỗi thiết bị hỗ trợ.

Để giao tiếp với người dùng khác, bạn chỉ cần trao đổi thông tin liên lạc và phần còn lại sẽ do WebRTC thực hiện. Quá trình kết nối với người dùng khác còn được gọi là báo hiệu và thương lượng. Nó bao gồm một vài bước -

  • Tạo danh sách các ứng viên tiềm năng cho kết nối ngang hàng.

  • Người dùng hoặc một ứng dụng chọn một người dùng để kết nối.

  • Lớp tín hiệu thông báo cho người dùng khác rằng ai đó muốn kết nối với anh ta. Anh ta có thể chấp nhận hoặc từ chối.

  • Người dùng đầu tiên được thông báo về việc chấp nhận ưu đãi.

  • Người dùng đầu tiên khởi tạo RTCPeerConnection với người dùng khác.

  • Cả hai người dùng trao đổi thông tin phần mềm và phần cứng thông qua máy chủ báo hiệu.

  • Cả hai người dùng trao đổi thông tin vị trí.

  • Kết nối thành công hay không thành công.

Đặc tả WebRTC không chứa bất kỳ tiêu chuẩn nào về trao đổi thông tin. Vì vậy, hãy nhớ rằng phần trên chỉ là một ví dụ về cách báo hiệu có thể xảy ra. Bạn có thể sử dụng bất kỳ giao thức hoặc công nghệ nào bạn thích.

Xây dựng máy chủ

Máy chủ mà chúng tôi sắp xây dựng sẽ có thể kết nối hai người dùng với nhau, những người không nằm trên cùng một máy tính. Chúng tôi sẽ tạo ra cơ chế phát tín hiệu của riêng mình. Máy chủ báo hiệu của chúng tôi sẽ cho phép một người dùng gọi một người khác. Khi người dùng đã gọi người khác, máy chủ sẽ chuyển đề nghị, câu trả lời, các ứng viên ICE giữa họ và thiết lập kết nối WebRTC.

Sơ đồ trên là luồng nhắn tin giữa những người dùng khi sử dụng máy chủ báo hiệu. Trước hết, mỗi người dùng đăng ký với máy chủ. Trong trường hợp của chúng tôi, đây sẽ là một tên người dùng chuỗi đơn giản. Khi người dùng đã đăng ký, họ có thể gọi cho nhau. Người dùng 1 đưa ra đề nghị với số nhận dạng người dùng mà anh ta muốn gọi. Người dùng khác nên trả lời. Cuối cùng, các ứng cử viên ICE được gửi giữa những người dùng cho đến khi họ có thể tạo kết nối.

Để tạo kết nối WebRTC, khách hàng phải có khả năng truyền thông điệp mà không cần sử dụng kết nối ngang hàng WebRTC. Đây là nơi chúng ta sẽ sử dụng HTML5 WebSockets - kết nối ổ cắm hai chiều giữa hai điểm cuối - máy chủ web và trình duyệt web. Bây giờ chúng ta hãy bắt đầu sử dụng thư viện WebSocket. Tạo tệp server.js và chèn mã sau:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 
 
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message){ 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

Dòng đầu tiên yêu cầu thư viện WebSocket mà chúng tôi đã cài đặt. Sau đó, chúng tôi tạo một máy chủ socket trên cổng 9090. Tiếp theo, chúng tôi lắng nghe sự kiện kết nối . Mã này sẽ được thực thi khi người dùng thực hiện kết nối WebSocket với máy chủ. Sau đó, chúng tôi lắng nghe bất kỳ tin nhắn nào do người dùng gửi. Cuối cùng, chúng tôi gửi phản hồi đến người dùng được kết nối nói rằng “Xin chào từ máy chủ”.

Bây giờ hãy chạy máy chủ nút và máy chủ sẽ bắt đầu lắng nghe các kết nối ổ cắm.

Để kiểm tra máy chủ của chúng tôi, chúng tôi sẽ sử dụng tiện ích wscat mà chúng tôi cũng đã cài đặt. Công cụ này giúp kết nối trực tiếp với máy chủ WebSocket và kiểm tra các lệnh. Chạy máy chủ của chúng tôi trong một cửa sổ đầu cuối, sau đó mở một cửa sổ khác và chạy lệnh wscat -c ws: // localhost: 9090 . Bạn sẽ thấy những điều sau ở phía khách hàng:

Máy chủ cũng phải ghi nhật ký người dùng được kết nối -

Đăng ký người dùng

Trong máy chủ báo hiệu của chúng tôi, chúng tôi sẽ sử dụng tên người dùng dựa trên chuỗi cho mỗi kết nối để chúng tôi biết nơi gửi tin nhắn. Hãy thay đổi trình xử lý kết nối của chúng ta một chút -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
	
});

Bằng cách này, chúng tôi chỉ chấp nhận các tin nhắn JSON. Tiếp theo, chúng ta cần lưu trữ tất cả người dùng được kết nối ở đâu đó. Chúng tôi sẽ sử dụng một đối tượng Javascript đơn giản cho nó. Thay đổi đầu tệp của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users
var users = {};

Chúng tôi sẽ thêm một trường loại cho mọi thông báo đến từ máy khách. Ví dụ, nếu người dùng muốn đăng nhập, anh ta sẽ gửi thông báo loại đăng nhập . Hãy định nghĩa nó -

connection.on('message', function(message){
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   }
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false 
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            });
				
         } 
			
         break;
					 
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break; 
   } 
	
});

Nếu người dùng gửi tin nhắn với kiểu đăng nhập , chúng tôi -

  • Kiểm tra xem có ai đã đăng nhập bằng tên người dùng này không

  • Nếu vậy, hãy nói với người dùng rằng anh ta chưa đăng nhập thành công

  • Nếu không có ai đang sử dụng tên người dùng này, chúng tôi thêm tên người dùng làm khóa cho đối tượng kết nối.

  • Nếu một lệnh không được nhận dạng, chúng tôi sẽ gửi một lỗi.

Đoạn mã sau là một chức năng trợ giúp để gửi tin nhắn đến một kết nối. Thêm nó vào tệp server.js -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Hàm trên đảm bảo rằng tất cả các tin nhắn của chúng tôi được gửi ở định dạng JSON.

Khi người dùng ngắt kết nối, chúng ta nên xóa kết nối của nó. Chúng tôi có thể xóa người dùng khi sự kiện đóng được kích hoạt. Thêm mã sau vào trình xử lý kết nối -

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
    } 
});

Bây giờ hãy kiểm tra máy chủ của chúng tôi bằng lệnh đăng nhập. Hãy nhớ rằng tất cả các thư phải được mã hóa ở định dạng JSON. Chạy máy chủ của chúng tôi và cố gắng đăng nhập. Bạn sẽ thấy một cái gì đó như thế này -

Thực hiện cuộc gọi

Sau khi đăng nhập thành công người dùng muốn gọi một người khác. Anh ta nên đưa ra đề nghị cho người dùng khác để đạt được nó. Thêm trình xử lý phiếu mua hàng -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null){ 
      //setting that UserA connected with UserB 
      connection.otherName = data.name; 
		
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      }); 
   }
	
   break;

Đầu tiên, chúng tôi nhận được kết nối của người dùng mà chúng tôi đang cố gắng gọi. Nếu nó tồn tại, chúng tôi gửi cho anh ấy chi tiết cung cấp . Chúng tôi cũng thêm otherName vào đối tượng kết nối . Điều này được thực hiện để đơn giản hóa việc tìm kiếm nó sau này.

Trả lời

Trả lời câu trả lời có một mẫu tương tự mà chúng tôi đã sử dụng trong trình xử lý phiếu mua hàng . Máy chủ của chúng tôi chỉ chuyển qua tất cả các tin nhắn dưới dạng câu trả lời cho người dùng khác. Thêm mã sau vào sau trình cung cấp phiếu mua hàng -

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name; 
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   }
	
   break;

Bạn có thể thấy cách này tương tự như trình xử lý phiếu mua hàng . Lưu ý rằng đoạn mã này theo sau các hàm createOffercreateAnswer trên đối tượng RTCPeerConnection .

Bây giờ chúng ta có thể kiểm tra cơ chế đề nghị / trả lời của mình. Kết nối hai khách hàng cùng một lúc và cố gắng đưa ra đề nghị và câu trả lời. Bạn sẽ thấy những điều sau:

Trong ví dụ này, offeranswer là các chuỗi đơn giản, nhưng trong một ứng dụng thực tế, chúng sẽ được điền bằng dữ liệu SDP.

Ứng viên ICE

Phần cuối cùng là xử lý ứng cử viên ICE giữa những người dùng. Chúng tôi sử dụng cùng một kỹ thuật chỉ truyền thông điệp giữa những người dùng. Sự khác biệt chính là các thông báo ứng viên có thể xảy ra nhiều lần cho mỗi người dùng theo bất kỳ thứ tự nào. Thêm trình xử lý ứng viên -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name]; 
	
   if(conn != null) {
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   }
	
   break;

Nó sẽ hoạt động tương tự như trình xử lý phiếu mua hàngcâu trả lời .

Rời khỏi kết nối

Để cho phép người dùng của chúng tôi ngắt kết nối với người dùng khác, chúng tôi nên triển khai chức năng treo máy. Nó cũng sẽ yêu cầu máy chủ xóa tất cả các tham chiếu của người dùng. Thêmleave người xử lý -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null; 
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   } 
	
   break;

Điều này cũng sẽ gửi cho người dùng khác sự kiện nghỉ việc để anh ta có thể ngắt kết nối ngang hàng của mình cho phù hợp. Chúng ta cũng nên xử lý trường hợp người dùng làm rớt kết nối của mình khỏi máy chủ báo hiệu. Hãy sửa đổi trình xử lý gần của chúng ta -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }  
      } 
   } 
});

Bây giờ nếu kết nối chấm dứt, người dùng của chúng tôi sẽ bị ngắt kết nối. Sự kiện đóng sẽ được kích hoạt khi người dùng đóng cửa sổ trình duyệt của mình trong khi chúng tôi vẫn ở trạng thái đề xuất , câu trả lời hoặc trạng thái ứng viên .

Máy chủ báo hiệu hoàn chỉnh

Đây là toàn bộ mã của máy chủ báo hiệu của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	
   //when server gets a message from a connected user
   connection.on('message', function(message) { 
	
      var data; 
      //accepting only JSON messages 
      try {
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      } 
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
			
         case "login": 
            console.log("User logged", data.name); 
				
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            } 
				
            break; 
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name];
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;  
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;  
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               });
            } 
				
            break;  
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
				
            break;  
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break; 
      }  
   });  
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
      delete users[connection.name]; 
		
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName);
            var conn = users[connection.otherName]; 
            conn.otherName = null;  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               });
            }  
         } 
      } 
   });  
	
   connection.send("Hello world"); 
	
});  

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Vậy là công việc đã xong và máy chủ báo hiệu của chúng tôi đã sẵn sàng. Hãy nhớ rằng làm những việc không theo thứ tự khi tạo kết nối WebRTC có thể gây ra sự cố.

Tóm lược

Trong chương này, chúng tôi đã xây dựng một máy chủ báo hiệu đơn giản và dễ hiểu. Chúng tôi đã đi qua quy trình báo hiệu, đăng ký người dùng và cơ chế đề nghị / trả lời. Chúng tôi cũng triển khai gửi ứng viên giữa những người dùng.

Web đang phát triển rất nhanh và nó luôn cải tiến. Các tiêu chuẩn mới được tạo ra mỗi ngày. Các trình duyệt cho phép cài đặt các bản cập nhật mà người dùng không bao giờ biết được, vì vậy bạn nên cập nhật những gì đang diễn ra trong thế giới Web và WebRTC. Dưới đây là tổng quan về những gì điều này là cho đến ngày hôm nay.

Hỗ trợ trình duyệt

Mọi trình duyệt không có tất cả các tính năng WebRTC giống nhau cùng một lúc. Các trình duyệt khác nhau có thể đi trước đường cong, điều này làm cho một số tính năng của WebRTC hoạt động trên một trình duyệt chứ không phải trình duyệt khác. Hỗ trợ hiện tại cho WebRTC trong trình duyệt được hiển thị trong hình sau.

Bạn có thể kiểm tra trạng thái hỗ trợ WebRTC cập nhật tại http://caniuse.com/#feat=rtcpeerconnection.

Chrome, Firefox và Opera

Các phiên bản mới nhất của Chrome, Firefox và Opera trên các hệ điều hành PC chính thống như Mac OS X, Windows và Linux, tất cả đều hỗ trợ WebRTC ngay lập tức. Và quan trọng nhất, các kỹ sư từ nhóm nhà phát triển Chrome và Firefox đã làm việc cùng nhau để khắc phục sự cố để hai trình duyệt này có thể giao tiếp với nhau dễ dàng.

HĐH Android

Trên hệ điều hành Android, các ứng dụng WebRTC cho Chrome và Firefox sẽ hoạt động hiệu quả. Chúng có thể hoạt động với các trình duyệt khác sau phiên bản Android Ice Cream Sandwich (4.0). Điều này là do sự chia sẻ mã giữa phiên bản máy tính để bàn và thiết bị di động.

táo

Apple vẫn chưa đưa ra bất kỳ thông báo nào về kế hoạch hỗ trợ WebRTC trong Safari trên OS X. Một trong những giải pháp khả thi cho hệ điều hành ứng dụng iOS gốc lai là nhúng mã WebRTC trực tiếp vào ứng dụng và tải ứng dụng này vào WebView.

trình duyệt web IE

Microsoft không hỗ trợ WebRTC trên máy tính để bàn. Nhưng họ đã chính thức xác nhận rằng họ sẽ triển khai ORTC (Object Realtime Communications) trong các phiên bản tương lai của IE (Edge). Họ không có kế hoạch hỗ trợ WebRTC 1.0. Họ gắn nhãn ORTC của họ là WebRTC 1.1, mặc dù nó chỉ là một cải tiến cộng đồng và không phải là tiêu chuẩn chính thức. Gần đây, họ đã thêm hỗ trợ ORTC vào phiên bản Microsoft Edge mới nhất. Bạn có thể tìm hiểu thêm tạihttps://blogs.windows.com/msedgedev/2015/09/18/ortc-api-is-now-available-in-microsoftedge/.

Tóm lược

Lưu ý rằng WebRTC là một tập hợp các API và giao thức, không phải là một API duy nhất. Sự hỗ trợ cho từng cái này đang phát triển trên các trình duyệt và hệ điều hành khác nhau ở một cấp độ khác nhau. Một cách tuyệt vời để kiểm tra mức hỗ trợ mới nhất là thông quahttp://canisue.com.Nó theo dõi việc áp dụng các API hiện đại trên nhiều trình duyệt. Bạn cũng có thể tìm thấy thông tin mới nhất về hỗ trợ trình duyệt cũng như các bản trình diễn WebRTC tạihttp://www.webrtc.org, được hỗ trợ bởi Mozilla, Google và Opera.

Trong thế giới di động, hỗ trợ WebRTC không cùng cấp độ với máy tính để bàn. Thiết bị di động có cách riêng của chúng, vì vậy WebRTC cũng là một cái gì đó khác biệt trên nền tảng di động.

Khi phát triển ứng dụng WebRTC cho máy tính để bàn, chúng tôi cân nhắc sử dụng Chrome, Firefox hoặc Opera. Tất cả chúng đều hỗ trợ WebRTC. Nói chung, bạn chỉ cần một trình duyệt và không cần bận tâm về phần cứng của máy tính để bàn.

Trong thế giới di động, ngày nay có ba chế độ khả thi cho WebRTC -

  • Ứng dụng gốc
  • Ứng dụng trình duyệt
  • Trình duyệt gốc

Android

Vào năm 2013, trình duyệt web Firefox dành cho Android đã được giới thiệu với hỗ trợ WebRTC. Giờ đây, bạn có thể gọi điện video trên thiết bị Android bằng trình duyệt di động Firefox.

Nó có ba thành phần WebRTC chính:

  • PeerConnection - cho phép cuộc gọi giữa các trình duyệt

  • getUserMedia - cung cấp quyền truy cập vào máy ảnh và micrô

  • DataChannels - cung cấp truyền dữ liệu ngang hàng

Google Chrome dành cho Android cũng cung cấp hỗ trợ WebRTC. Như bạn đã nhận thấy, các tính năng thú vị nhất thường xuất hiện lần đầu tiên trong Chrome.

Trong năm ngoái, trình duyệt di động Opera đã xuất hiện với hỗ trợ WebRTC. Vì vậy, đối với Android, bạn có Chrome, Firefox và Opera. Các trình duyệt khác không hỗ trợ WebRTC.

iOS

Rất tiếc, WebRTC hiện không được hỗ trợ trên iOS. Mặc dù WebRTC hoạt động tốt trên Mac khi sử dụng Firefox, Opera hoặc Chrome nhưng nó không được hỗ trợ trên iOS.

Ngày nay, ứng dụng WebRTC của bạn sẽ không hoạt động trên các thiết bị di động của Apple. Nhưng có một trình duyệt - Bowser. Đây là một trình duyệt web được phát triển bởi Ericsson và nó hỗ trợ WebRTC ngay lập tức. Bạn có thể kiểm tra trang chủ của nó tạihttp://www.openwebrtc.org/bowser/.

Ngày nay, đây là cách thân thiện duy nhất để hỗ trợ ứng dụng WebRTC của bạn trên iOS. Một cách khác là tự phát triển một ứng dụng gốc.

Điện thoại Windows

Microsoft không hỗ trợ WebRTC trên nền tảng di động. Nhưng họ đã chính thức xác nhận rằng họ sẽ triển khai ORTC (Object Realtime Communications) trong các phiên bản tương lai của IE. Họ không có kế hoạch hỗ trợ WebRTC 1.0. Họ gắn nhãn ORTC của họ là WebRTC 1.1, mặc dù nó chỉ là một cải tiến cộng đồng và không phải là tiêu chuẩn chính thức.

Vì vậy, ngày nay người dùng Window Phone không thể sử dụng các ứng dụng WebRTC và không có cách nào để đánh bại tình trạng này.

Blackberry

Ứng dụng WebRTC cũng không được hỗ trợ trên Blackberry theo bất kỳ cách nào.

Sử dụng trình duyệt gốc WebRTC

Trường hợp thuận tiện và thoải mái nhất cho người dùng sử dụng WebRTC là sử dụng trình duyệt gốc của thiết bị. Trong trường hợp này, thiết bị đã sẵn sàng hoạt động với bất kỳ cấu hình bổ sung nào.

Ngày nay chỉ các thiết bị Android phiên bản 4 trở lên mới cung cấp tính năng này. Apple vẫn không hiển thị bất kỳ hoạt động nào với hỗ trợ WebRTC. Vì vậy người dùng Safari không thể sử dụng các ứng dụng WebRTC. Microsoft cũng không giới thiệu nó trong Windows Phone 8.

Sử dụng WebRTC qua Ứng dụng trình duyệt

Điều này có nghĩa là sử dụng các ứng dụng của bên thứ ba (trình duyệt web không phải gốc) để cung cấp các tính năng WebRTC. Hiện tại, có hai ứng dụng của bên thứ ba như vậy. Bowser, là cách duy nhất để đưa các tính năng WebRTC lên thiết bị iOS và Opera, là một lựa chọn thay thế tốt cho nền tảng Android. Phần còn lại của các trình duyệt di động có sẵn không hỗ trợ WebRTC.

Ứng dụng di động gốc

Như bạn có thể thấy, WebRTC chưa có sự hỗ trợ lớn trong thế giới di động. Vì vậy, một trong những giải pháp khả thi là phát triển một ứng dụng gốc sử dụng API WebRTC. Nhưng nó không phải là sự lựa chọn tốt hơn vì tính năng WebRTC chính là một giải pháp đa nền tảng. Dù sao, trong một số trường hợp, đây là cách duy nhất vì ứng dụng gốc có thể sử dụng các chức năng hoặc tính năng dành riêng cho thiết bị không được trình duyệt HTML5 hỗ trợ.

Hạn chế luồng video cho thiết bị di động và máy tính để bàn

Tham số đầu tiên của API getUserMedia mong đợi một đối tượng gồm các khóa và giá trị cho trình duyệt biết cách xử lý luồng. Bạn có thể kiểm tra toàn bộ các ràng buộc tạihttps://tools.ietf.org/html/draft-alvestrand-constraints-resolution-03. Bạn có thể thiết lập tỷ lệ khung hình video, frameRate và các thông số tùy chọn khác.

Hỗ trợ thiết bị di động là một trong những khó khăn lớn nhất vì thiết bị di động có không gian màn hình hạn chế cùng với tài nguyên hạn chế. Bạn có thể muốn thiết bị di động chỉ ghi được luồng video có độ phân giải 480x320 hoặc nhỏ hơn để tiết kiệm năng lượng và băng thông. Sử dụng chuỗi tác nhân người dùng trong trình duyệt là một cách tốt để kiểm tra xem người dùng có đang sử dụng thiết bị di động hay không. Hãy xem một ví dụ. Tạo tệp index.html -

<!DOCTYPE html> 
<html lang = "en">
 
   <head> 
      <meta charset = "utf-8" /> 
   </head> 
	
   <body> 
      <video autoplay></video> 
      <script src = "client.js"></script> 
   </body>
	
</html>

Sau đó, tạo tệp client.js sau :

//constraints for desktop browser 
var desktopConstraints = { 

   video: { 
      mandatory: { 
         maxWidth:800,
         maxHeight:600   
      }  
   }, 
	
   audio: true 
}; 
 
//constraints for mobile browser 
var mobileConstraints = { 

   video: { 
      mandatory: { 
         maxWidth: 480, 
         maxHeight: 320, 
      } 
   }, 
	
   audio: true 
}
  
//if a user is using a mobile browser 
if(/Android|iPhone|iPad/i.test(navigator.userAgent)) { 
   var constraints = mobileConstraints;   
} else { 
   var constraints = desktopConstraints; 
}
  
function hasUserMedia() { 
   //check if the browser supports the WebRTC 
   return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia); 
}
  
if (hasUserMedia()) {
  
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia;
	
   //enabling video and audio channels 
   navigator.getUserMedia(constraints, function (stream) { 
      var video = document.querySelector('video');
		
      //inserting our stream to the video tag     
      video.src = window.URL.createObjectURL(stream);
		
   }, function (err) {}); 
} else { 
   alert("WebRTC is not supported"); 
}

Chạy máy chủ web bằng lệnh tĩnh và mở trang. Bạn sẽ thấy nó là 800x600. Sau đó, mở trang này trong chế độ xem di động bằng các công cụ chrome và kiểm tra độ phân giải. Nó phải là 480x320.

Ràng buộc là cách dễ nhất để tăng hiệu suất của ứng dụng WebRTC của bạn.

Tóm lược

Trong chương này, chúng ta đã tìm hiểu về các vấn đề có thể xảy ra khi phát triển ứng dụng WebRTC cho thiết bị di động. Chúng tôi đã phát hiện ra những hạn chế khác nhau của việc hỗ trợ API WebRTC trên nền tảng di động. Chúng tôi cũng ra mắt một ứng dụng demo trong đó chúng tôi đặt các ràng buộc khác nhau cho trình duyệt trên máy tính để bàn và thiết bị di động.

Trong chương này, chúng ta sẽ xây dựng một ứng dụng khách cho phép hai người dùng trên các thiết bị riêng biệt giao tiếp bằng cách sử dụng WebRTC. Đơn của chúng tôi sẽ có hai trang. Một để đăng nhập và một để gọi người dùng khác.

Hai trang sẽ là thẻ div . Hầu hết đầu vào được thực hiện thông qua các trình xử lý sự kiện đơn giản.

Máy chủ báo hiệu

Để tạo kết nối WebRTC, khách hàng phải có khả năng truyền thông điệp mà không cần sử dụng kết nối ngang hàng WebRTC. Đây là nơi chúng ta sẽ sử dụng HTML5 WebSockets - kết nối ổ cắm hai chiều giữa hai điểm cuối - máy chủ web và trình duyệt web. Bây giờ chúng ta hãy bắt đầu sử dụng thư viện WebSocket. Tạo tệp server.js và chèn mã sau:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090});
  
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server");
});

Dòng đầu tiên yêu cầu thư viện WebSocket mà chúng tôi đã cài đặt. Sau đó, chúng tôi tạo một máy chủ socket trên cổng 9090. Tiếp theo, chúng tôi lắng nghe sự kiện kết nối . Mã này sẽ được thực thi khi người dùng thực hiện kết nối WebSocket với máy chủ. Sau đó, chúng tôi lắng nghe bất kỳ tin nhắn nào do người dùng gửi. Cuối cùng, chúng tôi gửi phản hồi đến người dùng được kết nối nói rằng “Xin chào từ máy chủ”.

Trong máy chủ báo hiệu của chúng tôi, chúng tôi sẽ sử dụng tên người dùng dựa trên chuỗi cho mỗi kết nối để chúng tôi biết nơi gửi tin nhắn. Hãy thay đổi trình xử lý kết nối của chúng ta một chút -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
});

Bằng cách này, chúng tôi chỉ chấp nhận các tin nhắn JSON. Tiếp theo, chúng ta cần lưu trữ tất cả người dùng được kết nối ở đâu đó. Chúng tôi sẽ sử dụng một đối tượng Javascript đơn giản cho nó. Thay đổi đầu tệp của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

Chúng tôi sẽ thêm một trường loại cho mọi thông báo đến từ máy khách. Ví dụ, nếu người dùng muốn đăng nhập, anh ta sẽ gửi thông báo loại đăng nhập . Hãy định nghĩa nó -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message);
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false 
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            }); 
         } 
			
         break;
			
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break;
   }  
});

Nếu người dùng gửi tin nhắn với kiểu đăng nhập , chúng tôi -

  • Kiểm tra xem có ai đã đăng nhập bằng tên người dùng này không

  • Nếu vậy, hãy nói với người dùng rằng anh ta chưa đăng nhập thành công

  • Nếu không có ai đang sử dụng tên người dùng này, chúng tôi thêm tên người dùng làm khóa cho đối tượng kết nối.

  • Nếu một lệnh không được nhận dạng, chúng tôi sẽ gửi một lỗi.

Đoạn mã sau là một chức năng trợ giúp để gửi tin nhắn đến một kết nối. Thêm nó vào tệp server.js -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Khi người dùng ngắt kết nối, chúng ta nên xóa kết nối của nó. Chúng tôi có thể xóa người dùng khi sự kiện đóng được kích hoạt. Thêm mã sau vào trình xử lý kết nối -

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Sau khi đăng nhập thành công người dùng muốn gọi một người khác. Anh ta nên đưa ra đề nghị cho người dùng khác để đạt được nó. Thêm trình xử lý phiếu mua hàng -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name);
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      //setting that UserA connected with UserB 
      connection.otherName = data.name; 
		
      sendTo(conn, { 
         type: "offer",
         offer: data.offer, 
         name: connection.name 
      }); 
		
   }  
	
   break;

Đầu tiên, chúng tôi nhận được kết nối của người dùng mà chúng tôi đang cố gắng gọi. Nếu nó tồn tại, chúng tôi gửi cho anh ấy chi tiết cung cấp . Chúng tôi cũng thêm otherName vào đối tượng kết nối . Điều này được thực hiện để đơn giản hóa việc tìm kiếm nó sau này.

Trả lời câu trả lời có một mẫu tương tự mà chúng tôi đã sử dụng trong trình xử lý phiếu mua hàng . Máy chủ của chúng tôi chỉ chuyển qua tất cả các tin nhắn dưới dạng câu trả lời cho người dùng khác. Thêm mã sau vào sau trình xử lý phiếu mua hàng -

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name; 
		
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   } 
	
   break;

Phần cuối cùng là xử lý ứng cử viên ICE giữa những người dùng. Chúng tôi sử dụng cùng một kỹ thuật chỉ truyền thông điệp giữa những người dùng. Sự khác biệt chính là các thông báo ứng viên có thể xảy ra nhiều lần cho mỗi người dùng theo bất kỳ thứ tự nào. Thêm trình xử lý ứng viên -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name];
	
   if(conn != null) { 
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   } 
	
   break;

Để cho phép người dùng của chúng tôi ngắt kết nối với người dùng khác, chúng tôi nên triển khai chức năng treo máy. Nó cũng sẽ yêu cầu máy chủ xóa tất cả các tham chiếu của người dùng. Thêm trình xử lý rời -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null; 
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   } 
	
   break;

Điều này cũng sẽ gửi cho người dùng khác sự kiện nghỉ việc để anh ta có thể ngắt kết nối ngang hàng của mình cho phù hợp. Chúng ta cũng nên xử lý trường hợp người dùng làm rớt kết nối của mình khỏi máy chủ báo hiệu. Hãy sửa đổi trình xử lý gần của chúng ta -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;  
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            });
         }
			
      } 
   } 
});

Sau đây là toàn bộ mã của máy chủ báo hiệu của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
	
      var data; 
		
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login
         case "login": 
            console.log("User logged", data.name); 
				
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
					
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            } 
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name);
				
            //if UserB exists then send him offer details 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            }
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break; 
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               }); 
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) {
               sendTo(conn, { 
                  type: "leave" 
              }); 
            }
				
            break;
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break; 
      }
		
   }); 
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
         delete users[connection.name]; 
			
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName); 
            var conn = users[connection.otherName]; 
            conn.otherName = null;
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }
         } 
      }
		
   });  
	
   connection.send("Hello world");  
});
  
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Ứng dụng Khách hàng

Một cách để kiểm tra ứng dụng này là mở hai tab trình duyệt và cố gắng gọi cho nhau.

Trước hết, chúng ta cần cài đặt thư viện bootstrap . Bootstrap là một khung công tác giao diện người dùng để phát triển các ứng dụng web. Bạn có thể tìm hiểu thêm tạihttp://getbootstrap.com/.Tạo một thư mục có tên, chẳng hạn như “videochat”. Đây sẽ là thư mục ứng dụng gốc của chúng tôi. Bên trong thư mục này, hãy tạo một tệp package.json (nó cần thiết để quản lý các phần phụ thuộc npm) và thêm các phần sau:

{ 
   "name": "webrtc-videochat", 
   "version": "0.1.0", 
   "description": "webrtc-videochat", 
   "author": "Author", 
   "license": "BSD-2-Clause" 
}

Sau đó chạy npm install bootstrap . Thao tác này sẽ cài đặt thư viện bootstrap trong thư mục videochat / node_modules .

Bây giờ chúng ta cần tạo một trang HTML cơ bản. Tạo tệp index.html trong thư mục gốc với mã sau:

<html> 
 
   <head> 
      <title>WebRTC Video Demo</title>
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
   </head>
	
   <style>
	
      body { 
         background: #eee; 
         padding: 5% 0; 
      } 
		
      video { 
         background: black; 
         border: 1px solid gray; 
      }
		
      .call-page { 
         position: relative; 
         display: block; 
         margin: 0 auto; 
         width: 500px; 
         height: 500px; 
      } 
		
      #localVideo { 
         width: 150px; 
         height: 150px; 
         position: absolute; 
         top: 15px; 
         right: 15px; 
      }
		
      #remoteVideo { 
         width: 500px; 
         height: 500px; 
      }
		
   </style>
	
   <body>
	
   <div id = "loginPage" class = "container text-center"> 
	
      <div class = "row"> 
         <div class = "col-md-4 col-md-offset-4">
			
            <h2>WebRTC Video Demo. Please sign in</h2> 
            <label for = "usernameInput" class = "sr-only">Login</label> 
            <input type = "email" id = "usernameInput" c
               lass = "form-control formgroup" placeholder = "Login" 
               required = "" autofocus = ""> 
            <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
               Sign in</button>
				
         </div> 
      </div> 
		
   </div>
	
   <div id = "callPage" class = "call-page"> 
      <video id = "localVideo" autoplay></video> 
      <video id = "remoteVideo" autoplay></video>
		
      <div class = "row text-center"> 
         <div class = "col-md-12"> 
            <input id = "callToUsernameInput" type = "text"
               placeholder = "username to call" /> 
            <button id = "callBtn" class = "btn-success btn">Call</button> 
            <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
         </div>	
      </div> 
		
   </div>
	
   <script src = "client.js"></script> 
	
   </body>
	
</html>

Trang này sẽ quen thuộc với bạn. Chúng tôi đã thêm tệp bootstrap css. Chúng tôi cũng đã xác định hai trang. Cuối cùng, chúng tôi đã tạo một số trường văn bản và các nút để lấy thông tin từ người dùng. Bạn sẽ thấy hai phần tử video cho luồng video cục bộ và từ xa. Lưu ý rằng chúng tôi đã thêm một liên kết vào tệp client.js .

Bây giờ chúng ta cần thiết lập kết nối với máy chủ báo hiệu của mình. Tạo tệp client.js trong thư mục gốc với mã sau:

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
  
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data);
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success);
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};

Bây giờ chạy máy chủ báo hiệu của chúng tôi thông qua máy chủ nút . Sau đó, bên trong thư mục gốc, hãy chạy lệnh tĩnh và mở trang bên trong trình duyệt. Bạn sẽ thấy đầu ra bảng điều khiển sau:

Bước tiếp theo là thực hiện đăng nhập người dùng với tên người dùng duy nhất. Chúng tôi chỉ cần gửi một tên người dùng đến máy chủ, sau đó cho chúng tôi biết liệu nó có được lấy hay không. Thêm mã sau vào tệp client.js của bạn -

//****** 
//UI selectors block 
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn');
  
//hide call page 
callPage.style.display = "none"; 
 
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      //display the call page if login is successful 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";  
      //start peer connection 
   } 
};

Đầu tiên, chúng tôi chọn một số tham chiếu đến các phần tử trên trang. Chúng tôi ẩn trang cuộc gọi. Sau đó, chúng tôi thêm một trình nghe sự kiện trên nút đăng nhập. Khi người dùng nhấp vào nó, chúng tôi gửi tên người dùng của anh ta đến máy chủ. Cuối cùng, chúng tôi triển khai lệnh gọi lại handleLogin. Nếu đăng nhập thành công, chúng tôi hiển thị trang cuộc gọi và bắt đầu thiết lập kết nối ngang hàng.

Để bắt đầu kết nối ngang hàng, chúng ta cần -

  • Nhận luồng từ máy ảnh web.
  • Tạo đối tượng RTCPeerConnection.

Thêm mã sau vào “Khối bộ chọn giao diện người dùng” -

var localVideo = document.querySelector('#localVideo'); 
var remoteVideo = document.querySelector('#remoteVideo');
 
var yourConn; 
var stream;

Sửa đổi hàm handleLogin -

function handleLogin(success) { 

   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		  
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local video stream 
      navigator.webkitGetUserMedia({ video: true, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local video stream on the page 
         localVideo.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration);
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteVideo.src = window.URL.createObjectURL(e.stream); 
         };
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) {
			
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
                  candidate: event.candidate 
               }); 
            } 
				
         };
			
      }, function (error) { 
         console.log(error); 
      }); 
   } 
};

Bây giờ nếu bạn chạy mã, trang sẽ cho phép bạn đăng nhập và hiển thị luồng video cục bộ của bạn trên trang.

Bây giờ chúng tôi đã sẵn sàng để bắt đầu cuộc gọi. Đầu tiên, chúng tôi gửi một đề nghị cho một người dùng khác. Sau khi người dùng nhận được đề nghị, anh ta sẽ tạo câu trả lời và bắt đầu giao dịch các ứng viên ICE. Thêm mã sau vào tệp client.js -

//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) {
	
      connectedUser = callToUsername;
		
      // create an offer
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      });  
   } 
});
  
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
};
  
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer));
}; 
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};

Chúng tôi thêm một trình xử lý nhấp chuột vào nút Gọi, nút này sẽ bắt đầu một phiếu mua hàng. Sau đó, chúng tôi thực hiện một số trình xử lý được mong đợi bởi trình xử lý onmessage . Chúng sẽ được xử lý không đồng bộ cho đến khi cả hai người dùng đã kết nối.

Bước cuối cùng là thực hiện tính năng gác máy. Thao tác này sẽ ngừng truyền dữ liệu và yêu cầu người dùng khác đóng cuộc gọi. Thêm mã sau -

//hang up 
hangUpBtn.addEventListener("click", function () { 

   send({ 
      type: "leave" 
   });
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   remoteVideo.src = null; 
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null; 
};

Khi người dùng nhấp vào nút Treo lên -

  • Nó sẽ gửi một thông báo "để lại" cho người dùng khác
  • Nó sẽ đóng RTCPeerConnection và phá hủy kết nối cục bộ

Bây giờ hãy chạy mã. Bạn sẽ có thể đăng nhập vào máy chủ bằng hai tab trình duyệt. Sau đó, bạn có thể gọi tab và kết thúc cuộc gọi.

Sau đây là toàn bộ tệp client.js -

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
  
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data); 
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   }
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};
  
//****** 
//UI selectors block 
//******
 
var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn');
  
var localVideo = document.querySelector('#localVideo'); 
var remoteVideo = document.querySelector('#remoteVideo'); 

var yourConn; 
var stream;
  
callPage.style.display = "none";

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value;
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   }
	
});
  
function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local video stream 
      navigator.webkitGetUserMedia({ video: true, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local video stream on the page 
         localVideo.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration); 
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteVideo.src = window.URL.createObjectURL(e.stream); 
         };
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) { 
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
                  candidate: event.candidate 
               }); 
            } 
         };  
			
      }, function (error) { 
         console.log(error); 
      }); 
		
   } 
};
  
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value;
	
   if (callToUsername.length > 0) { 
	
      connectedUser = callToUsername;
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
      }, function (error) { 
         alert("Error when creating an offer"); 
      });
		
   } 
});
  
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
};
  
//when we got an answer from a remote user
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
  
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
   
//hang up 
hangUpBtn.addEventListener("click", function () { 

   send({ 
      type: "leave" 
   });  
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   remoteVideo.src = null; 
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null; 
};

Tóm lược

Bản trình diễn này cung cấp cơ sở các tính năng mà mọi ứng dụng WebRTC cần. Để cải thiện bản trình diễn này, bạn có thể thêm nhận dạng người dùng thông qua các nền tảng như Facebook hoặc Google, xử lý đầu vào của người dùng đối với dữ liệu không hợp lệ. Ngoài ra, kết nối WebRTC có thể không thành công vì một số lý do như không hỗ trợ công nghệ hoặc không thể vượt qua tường lửa. Rất nhiều công việc đã được thực hiện để làm cho bất kỳ ứng dụng WebRTC nào hoạt động ổn định.

Trong chương này, chúng ta sẽ xây dựng một ứng dụng khách cho phép hai người dùng trên các thiết bị riêng biệt giao tiếp bằng cách sử dụng các luồng âm thanh WebRTC. Đơn của chúng tôi sẽ có hai trang. Một để đăng nhập và một để thực hiện cuộc gọi âm thanh cho người dùng khác.

Hai trang sẽ là thẻ div . Hầu hết đầu vào được thực hiện thông qua các trình xử lý sự kiện đơn giản.

Máy chủ báo hiệu

Để tạo kết nối WebRTC, khách hàng phải có khả năng truyền thông điệp mà không cần sử dụng kết nối ngang hàng WebRTC. Đây là nơi chúng ta sẽ sử dụng HTML5 WebSockets - kết nối ổ cắm hai chiều giữa hai điểm cuối - máy chủ web và trình duyệt web. Bây giờ chúng ta hãy bắt đầu sử dụng thư viện WebSocket. Tạo tệp server.js và chèn mã sau:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090});
  
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

Dòng đầu tiên yêu cầu thư viện WebSocket mà chúng tôi đã cài đặt. Sau đó, chúng tôi tạo một máy chủ socket trên cổng 9090. Tiếp theo, chúng tôi lắng nghe sự kiện kết nối . Mã này sẽ được thực thi khi người dùng thực hiện kết nối WebSocket với máy chủ. Sau đó, chúng tôi lắng nghe bất kỳ tin nhắn nào do người dùng gửi. Cuối cùng, chúng tôi gửi phản hồi đến người dùng được kết nối nói rằng “Xin chào từ máy chủ”.

Trong máy chủ báo hiệu của chúng tôi, chúng tôi sẽ sử dụng tên người dùng dựa trên chuỗi cho mỗi kết nối để chúng tôi biết nơi gửi tin nhắn. Hãy thay đổi trình xử lý kết nối của chúng ta một chút -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON");
      data = {}; 
   } 
});

Bằng cách này, chúng tôi chỉ chấp nhận các tin nhắn JSON. Tiếp theo, chúng ta cần lưu trữ tất cả người dùng được kết nối ở đâu đó. Chúng tôi sẽ sử dụng một đối tượng Javascript đơn giản cho nó. Thay đổi đầu tệp của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

Chúng tôi sẽ thêm một trường loại cho mọi thông báo đến từ máy khách. Ví dụ, nếu người dùng muốn đăng nhập, anh ta sẽ gửi thông báo loại đăng nhập . Hãy định nghĩa nó -

connection.on('message', function(message) {
  
   var data; 
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login",
               success: false 
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            }); 
         } 
			
         break;
			
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break; 
   }  
});

Nếu người dùng gửi tin nhắn với kiểu đăng nhập , chúng tôi -

  • Kiểm tra xem có ai đã đăng nhập bằng tên người dùng này chưa.
  • Nếu vậy, hãy nói với người dùng rằng anh ta chưa đăng nhập thành công.
  • Nếu không có ai đang sử dụng tên người dùng này, chúng tôi thêm tên người dùng làm khóa cho đối tượng kết nối.
  • Nếu một lệnh không được nhận dạng, chúng tôi sẽ gửi một lỗi.

Đoạn mã sau là một chức năng trợ giúp để gửi tin nhắn đến một kết nối. Thêm nó vào tệp server.js -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Khi người dùng ngắt kết nối, chúng ta nên xóa kết nối của nó. Chúng tôi có thể xóa người dùng khi sự kiện đóng được kích hoạt. Thêm mã sau vào trình xử lý kết nối−

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Sau khi đăng nhập thành công người dùng muốn gọi một người khác. Anh ta nên đưa ra đề nghị cho người dùng khác để đạt được nó. Thêm trình xử lý phiếu mua hàng -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      //setting that UserA connected with UserB 
      connection.otherName = data.name; 
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      });
   }		
	
   break;

Đầu tiên, chúng tôi nhận được kết nối của người dùng mà chúng tôi đang cố gắng gọi. Nếu nó tồn tại, chúng tôi gửi cho anh ấy chi tiết cung cấp . Chúng tôi cũng thêm otherName vào đối tượng kết nối . Điều này được thực hiện để đơn giản hóa việc tìm kiếm nó sau này.

Trả lời câu trả lời có một mẫu tương tự mà chúng tôi đã sử dụng trong trình xử lý phiếu mua hàng . Máy chủ của chúng tôi chỉ chuyển qua tất cả các tin nhắn dưới dạng câu trả lời cho người dùng khác. Thêm mã sau vào sau trình xử lý phiếu mua hàng -

case "answer": 
   console.log("Sending answer to: ", data.name); 
   //for ex. UserB answers UserA
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name;
		
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   } 
	
   break;

Phần cuối cùng là xử lý ứng cử viên ICE giữa những người dùng. Chúng tôi sử dụng cùng một kỹ thuật chỉ truyền thông điệp giữa những người dùng. Sự khác biệt chính là các thông báo ứng viên có thể xảy ra nhiều lần cho mỗi người dùng theo bất kỳ thứ tự nào. Thêm trình xử lý ứng viên -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name];
	
   if(conn != null) { 
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   } 
	
   break;

Để cho phép người dùng của chúng tôi ngắt kết nối với người dùng khác, chúng tôi nên triển khai chức năng treo máy. Nó cũng sẽ yêu cầu máy chủ xóa tất cả các tham chiếu của người dùng. Thêm trình xử lý rời -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null; 
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, {
         type: "leave" 
      }); 
   }  
	
   break;

Điều này cũng sẽ gửi cho người dùng khác sự kiện nghỉ việc để anh ta có thể ngắt kết nối ngang hàng của mình cho phù hợp. Chúng ta cũng nên xử lý trường hợp người dùng làm rớt kết nối của mình khỏi máy chủ báo hiệu. Hãy sửa đổi trình xử lý gần của chúng ta -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }
			
      } 
   } 
});

Sau đây là toàn bộ mã của máy chủ báo hiệu của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
	
      var data;
		
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login": 
            console.log("User logged", data.name); 
				
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name;
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            } 
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               });
            } 
				
            break;
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               }); 
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
				
            break;
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            });
				
            break; 
      }  
   });
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
         delete users[connection.name]; 
			
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName); 
            var conn = users[connection.otherName]; 
            conn.otherName = null;  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
              }); 
            }  
         } 
      } 
   });  
	
   connection.send("Hello world"); 
}); 
 
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Ứng dụng Khách hàng

Một cách để kiểm tra ứng dụng này là mở hai tab trình duyệt và cố gắng thực hiện cuộc gọi âm thanh với nhau.

Trước hết, chúng ta cần cài đặt thư viện bootstrap . Bootstrap là một khung công tác giao diện người dùng để phát triển các ứng dụng web. Bạn có thể tìm hiểu thêm tạihttp://getbootstrap.com/.Tạo một thư mục có tên, chẳng hạn như “audiochat”. Đây sẽ là thư mục ứng dụng gốc của chúng tôi. Bên trong thư mục này, hãy tạo một tệp package.json (nó cần thiết để quản lý các phụ thuộc npm) và thêm các thông tin sau:

{ 
   "name": "webrtc-audiochat", 
   "version": "0.1.0", 
   "description": "webrtc-audiochat", 
   "author": "Author", 
   "license": "BSD-2-Clause" 
}

Sau đó chạy npm install bootstrap . Thao tác này sẽ cài đặt thư viện bootstrap trong thư mục audiochat / node_modules .

Bây giờ chúng ta cần tạo một trang HTML cơ bản. Tạo tệp index.html trong thư mục gốc với mã sau:

<html>
 
   <head> 
      <title>WebRTC Voice Demo</title> 
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/> 
   </head>
 
   <style> 
      body { 
         background: #eee; 
         padding: 5% 0; 
      } 
   </style>
	
   <body> 
      <div id = "loginPage" class = "container text-center"> 
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4">
				
               <h2>WebRTC Voice Demo. Please sign in</h2>
				
               <label for = "usernameInput" class = "sr-only">Login</label> 
               <input type = "email" id = "usernameInput" 
                  class = "form-control formgroup"
                  placeholder = "Login" required = "" autofocus = ""> 
               <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
                  Sign in</button> 
            </div> 
         </div> 
			
      </div>
		
      <div id = "callPage" class = "call-page">
		
         <div class = "row"> 
			
            <div class = "col-md-6 text-right"> 
               Local audio: <audio id = "localAudio" 
               controls autoplay></audio> 
            </div>
				
            <div class = "col-md-6 text-left"> 
               Remote audio: <audio id = "remoteAudio" 
                  controls autoplay></audio> 
            </div> 
				
         </div> 
			
         <div class = "row text-center"> 
            <div class = "col-md-12"> 
               <input id = "callToUsernameInput" 
                  type = "text" placeholder = "username to call" /> 
               <button id = "callBtn" class = "btn-success btn">Call</button> 
               <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
            </div> 
         </div>
			
      </div> 
		
      <script src = "client.js"></script> 
		
   </body>
	
</html>

Trang này sẽ quen thuộc với bạn. Chúng tôi đã thêm tệp bootstrap css. Chúng tôi cũng đã xác định hai trang. Cuối cùng, chúng tôi đã tạo một số trường văn bản và các nút để lấy thông tin từ người dùng. Bạn sẽ thấy hai yếu tố âm thanh cho luồng âm thanh cục bộ và từ xa. Lưu ý rằng chúng tôi đã thêm một liên kết vào tệp client.js .

Bây giờ chúng ta cần thiết lập kết nối với máy chủ báo hiệu của mình. Tạo tệp client.js trong thư mục gốc với mã sau:

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
}; 
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);  
   var data = JSON.parse(msg.data);  
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break;
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};

Bây giờ chạy máy chủ báo hiệu của chúng tôi thông qua máy chủ nút . Sau đó, bên trong thư mục gốc, hãy chạy lệnh tĩnh và mở trang bên trong trình duyệt. Bạn sẽ thấy đầu ra bảng điều khiển sau:

Bước tiếp theo là thực hiện đăng nhập người dùng với tên người dùng duy nhất. Chúng tôi chỉ cần gửi một tên người dùng đến máy chủ, sau đó cho chúng tôi biết liệu nó có được lấy hay không. Thêm mã sau vào tệp client.js của bạn -

//****** 
//UI selectors block 
//******

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn');
 
var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
 
var hangUpBtn = document.querySelector('#hangUpBtn');
  
callPage.style.display = "none";
  
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value;
	
   if (name.length > 0) { 
      send({
         type: "login", 
         name: name 
      }); 
   } 
	
}); 
 
function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //**********************
		         
   } 
	
};

Đầu tiên, chúng tôi chọn một số tham chiếu đến các phần tử trên trang. Chúng tôi ẩn trang cuộc gọi. Sau đó, chúng tôi thêm một trình nghe sự kiện trên nút đăng nhập. Khi người dùng nhấp vào nó, chúng tôi gửi tên người dùng của anh ta đến máy chủ. Cuối cùng, chúng tôi triển khai lệnh gọi lại handleLogin. Nếu đăng nhập thành công, chúng tôi hiển thị trang cuộc gọi và bắt đầu thiết lập kết nối ngang hàng.

Để bắt đầu kết nối ngang hàng, chúng ta cần -

  • Nhận luồng âm thanh từ micrô
  • Tạo đối tượng RTCPeerConnection

Thêm mã sau vào “Khối bộ chọn giao diện người dùng” -

var localAudio = document.querySelector('#localAudio'); 
var remoteAudio = document.querySelector('#remoteAudio'); 

var yourConn; 
var stream;

Sửa đổi hàm handleLogin -

function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local audio stream 
      navigator.webkitGetUserMedia({ video: false, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local audio stream on the page
         localAudio.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration); 
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteAudio.src = window.URL.createObjectURL(e.stream); 
         }; 
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) { 
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
               }); 
            } 
         };  
			
      }, function (error) { 
         console.log(error); 
      }); 
		
   } 
};

Bây giờ nếu bạn chạy mã, trang sẽ cho phép bạn đăng nhập và hiển thị luồng âm thanh cục bộ của bạn trên trang.

Bây giờ chúng tôi đã sẵn sàng để bắt đầu cuộc gọi. Đầu tiên, chúng tôi gửi một đề nghị cho một người dùng khác. Sau khi người dùng nhận được đề nghị, anh ta sẽ tạo câu trả lời và bắt đầu giao dịch các ứng viên ICE. Thêm mã sau vào tệp client.js -

//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) { 
      connectedUser = callToUsername; 
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      }); 
   } 
	
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer",
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
	
};
 
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};

Chúng tôi thêm một trình xử lý nhấp chuột vào nút Gọi, nút này sẽ bắt đầu một phiếu mua hàng. Sau đó, chúng tôi thực hiện một số trình xử lý được mong đợi bởi trình xử lý onmessage . Chúng sẽ được xử lý không đồng bộ cho đến khi cả hai người dùng đã kết nối.

Bước cuối cùng là thực hiện tính năng gác máy. Thao tác này sẽ ngừng truyền dữ liệu và yêu cầu người dùng khác đóng cuộc gọi. Thêm mã sau -

//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   });  
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   remoteAudio.src = null;
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null;
};

Khi người dùng nhấp vào nút Treo lên -

  • Nó sẽ gửi một thông báo "để lại" cho người dùng khác
  • Nó sẽ đóng RTCPeerConnection và phá hủy kết nối cục bộ

Bây giờ hãy chạy mã. Bạn sẽ có thể đăng nhập vào máy chủ bằng hai tab trình duyệt. Sau đó, bạn có thể thực hiện cuộc gọi thoại tới tab và kết thúc cuộc gọi.

Sau đây là toàn bộ tệp client.js -

//our username 
var name; 
var connectedUser;
 
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
 
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data); 
   var data = JSON.parse(msg.data); 
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
}; 

conn.onerror = function (err) { 
   console.log("Got error", err); 
};
 
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};
 
//****** 
//UI selectors block 
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn');

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
var localAudio = document.querySelector('#localAudio'); 
var remoteAudio = document.querySelector('#remoteAudio'); 

var yourConn; 
var stream; 

callPage.style.display = "none";
 
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local audio stream 
      navigator.webkitGetUserMedia({ video: false, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local audio stream on the page 
         localAudio.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration); 
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteAudio.src = window.URL.createObjectURL(e.stream); 
         }; 
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) { 
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
                  candidate: event.candidate 
               }); 
            } 
         }; 
			
      }, function (error) { 
         console.log(error); 
      }); 
		
   } 
};
 
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) { 
      connectedUser = callToUsername; 
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
      }, function (error) { 
         alert("Error when creating an offer"); 
      }); 
   } 
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      });
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
	
};
 
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
 
//hang up
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
});
 
function handleLeave() { 
   connectedUser = null; 
   remoteAudio.src = null; 
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null; 
};

Trong chương này, chúng ta sẽ xây dựng một ứng dụng khách cho phép hai người dùng trên các thiết bị riêng biệt gửi tin nhắn cho nhau bằng cách sử dụng WebRTC. Đơn của chúng tôi sẽ có hai trang. Một để đăng nhập và một để gửi tin nhắn cho người dùng khác.

Hai trang sẽ là thẻ div . Hầu hết đầu vào được thực hiện thông qua các trình xử lý sự kiện đơn giản.

Máy chủ báo hiệu

Để tạo kết nối WebRTC, khách hàng phải có khả năng truyền thông điệp mà không cần sử dụng kết nối ngang hàng WebRTC. Đây là nơi chúng ta sẽ sử dụng HTML5 WebSockets - kết nối ổ cắm hai chiều giữa hai điểm cuối - máy chủ web và trình duyệt web. Bây giờ chúng ta hãy bắt đầu sử dụng thư viện WebSocket. Tạo tệp server.js và chèn mã sau:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090});
  
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

Dòng đầu tiên yêu cầu thư viện WebSocket mà chúng tôi đã cài đặt. Sau đó, chúng tôi tạo một máy chủ socket trên cổng 9090. Tiếp theo, chúng tôi lắng nghe sự kiện kết nối . Mã này sẽ được thực thi khi người dùng thực hiện kết nối WebSocket với máy chủ. Sau đó, chúng tôi lắng nghe bất kỳ tin nhắn nào do người dùng gửi. Cuối cùng, chúng tôi gửi phản hồi đến người dùng được kết nối nói rằng “Xin chào từ máy chủ”.

Trong máy chủ báo hiệu của chúng tôi, chúng tôi sẽ sử dụng tên người dùng dựa trên chuỗi cho mỗi kết nối để chúng tôi biết nơi gửi tin nhắn. Hãy thay đổi trình xử lý kết nối của chúng ta một chút -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON");
      data = {}; 
   } 
});

Bằng cách này, chúng tôi chỉ chấp nhận các tin nhắn JSON. Tiếp theo, chúng ta cần lưu trữ tất cả người dùng được kết nối ở đâu đó. Chúng tôi sẽ sử dụng một đối tượng Javascript đơn giản cho nó. Thay đổi đầu tệp của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

Chúng tôi sẽ thêm một trường loại cho mọi thông báo đến từ máy khách. Ví dụ, nếu người dùng muốn đăng nhập, anh ta sẽ gửi thông báo loại đăng nhập . Hãy định nghĩa nó -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   }
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            }); 
         } 
			
         break;
			
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break;
   } 
});

Nếu người dùng gửi tin nhắn với kiểu đăng nhập , chúng tôi -

  • Kiểm tra xem có ai đã đăng nhập bằng tên người dùng này chưa.
  • Nếu vậy, hãy nói với người dùng rằng anh ta chưa đăng nhập thành công.
  • Nếu không có ai đang sử dụng tên người dùng này, chúng tôi thêm tên người dùng làm khóa cho đối tượng kết nối.
  • Nếu một lệnh không được nhận dạng, chúng tôi sẽ gửi một lỗi.

Đoạn mã sau là một chức năng trợ giúp để gửi tin nhắn đến một kết nối. Thêm nó vào tệp server.js -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Khi người dùng ngắt kết nối, chúng ta nên xóa kết nối của nó. Chúng tôi có thể xóa người dùng khi sự kiện đóng được kích hoạt. Thêm mã sau vào trình xử lý kết nối -

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Sau khi đăng nhập thành công người dùng muốn gọi một người khác. Anh ta nên đưa ra đề nghị cho người dùng khác để đạt được nó. Thêm trình xử lý phiếu mua hàng -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null){ 
   //setting that UserA connected with UserB 
   connection.otherName = data.name; 
	
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      }); 
     
   break;

Đầu tiên, chúng tôi nhận được kết nối của người dùng mà chúng tôi đang cố gắng gọi. Nếu nó tồn tại, chúng tôi gửi cho anh ấy chi tiết cung cấp . Chúng tôi cũng thêm otherName vào đối tượng kết nối . Điều này được thực hiện để đơn giản hóa việc tìm kiếm nó sau này.

Trả lời câu trả lời có một mẫu tương tự mà chúng tôi đã sử dụng trong trình xử lý phiếu mua hàng . Máy chủ của chúng tôi chỉ chuyển qua tất cả các tin nhắn dưới dạng câu trả lời cho người dùng khác. Thêm mã sau vào sau trình xử lý phiếu mua hàng -

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name;
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   } 
	
   break;

Phần cuối cùng là xử lý ứng cử viên ICE giữa những người dùng. Chúng tôi sử dụng cùng một kỹ thuật chỉ truyền thông điệp giữa những người dùng. Sự khác biệt chính là các thông báo ứng viên có thể xảy ra nhiều lần cho mỗi người dùng theo bất kỳ thứ tự nào. Thêm trình xử lý ứng viên -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name];
	
   if(conn != null) { 
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   } 
	
   break;

Để cho phép người dùng của chúng tôi ngắt kết nối với người dùng khác, chúng tôi nên triển khai chức năng treo máy. Nó cũng sẽ yêu cầu máy chủ xóa tất cả các tham chiếu của người dùng. Thêm trình xử lý rời -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null;
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   }
	
   break;

Điều này cũng sẽ gửi cho người dùng khác sự kiện nghỉ việc để anh ta có thể ngắt kết nối ngang hàng của mình cho phù hợp. Chúng ta cũng nên xử lý trường hợp người dùng làm rớt kết nối của mình khỏi máy chủ báo hiệu. Hãy sửa đổi trình xử lý gần của chúng ta -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;  
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }  
      } 
   } 
});

Sau đây là toàn bộ mã của máy chủ báo hiệu của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	 
   //when server gets a message from a connected user 
   connection.on('message', function(message) {
	 
      var data; 
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		  
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login": 
            console.log("User logged", data.name); 
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
					
                  sendTo(connection, { 
                     type: "login", 
                     success: true 
                  }); 
            }
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;
				
         case "candidate": 
            console.log("Sending candidate to:",data.name);
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               }); 
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave"
               });
            }  
				
            break;
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break;
				
      }  
   });
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
         delete users[connection.name]; 
			
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName); 
            var conn = users[connection.otherName]; 
            conn.otherName = null;
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
         } 
      } 
   });
	
   connection.send("Hello world");
	
});
  
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Ứng dụng Khách hàng

Một cách để kiểm tra ứng dụng này là mở hai tab trình duyệt và cố gắng gửi tin nhắn cho nhau.

Trước hết, chúng ta cần cài đặt thư viện bootstrap . Bootstrap là một khung công tác giao diện người dùng để phát triển các ứng dụng web. Bạn có thể tìm hiểu thêm tạihttp://getbootstrap.com/.Tạo một thư mục có tên, chẳng hạn như “textchat”. Đây sẽ là thư mục ứng dụng gốc của chúng tôi. Bên trong thư mục này, hãy tạo một tệp package.json (nó cần thiết để quản lý các phụ thuộc npm) và thêm các thông tin sau:

{ 
   "name": "webrtc-textochat", 
   "version": "0.1.0", 
   "description": "webrtc-textchat", 
   "author": "Author", 
   "license": "BSD-2-Clause" 
}

Sau đó chạy npm install bootstrap . Thao tác này sẽ cài đặt thư viện bootstrap trong thư mục textchat / node_modules .

Bây giờ chúng ta cần tạo một trang HTML cơ bản. Tạo tệp index.html trong thư mục gốc với mã sau:

<html> 

   <head> 
      <title>WebRTC Text Demo</title> 
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/> 
   </head>
 
   <style> 
      body { 
         background: #eee; 
         padding: 5% 0; 
      } 
   </style>
	
   <body> 
      <div id = "loginPage" class = "container text-center"> 
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4"> 
               <h2>WebRTC Text Demo. Please sign in</h2> 
               <label for = "usernameInput" class = "sr-only">Login</label> 
               <input type = "email" id = "usernameInput" 
                  class = "form-control formgroup" placeholder = "Login" 
                  required = "" autofocus = "">
               <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
                  Sign in</button> 
            </div> 
         </div> 
			
      </div>
		
      <div id = "callPage" class = "call-page container">
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4 text-center"> 
               <div class = "panel panel-primary"> 
                  <div class = "panel-heading">Text chat</div> 
                  <div id = "chatarea" class = "panel-body text-left"></div> 
               </div> 
            </div> 
         </div>
			
         <div class = "row text-center form-group"> 
            <div class = "col-md-12"> 
               <input id = "callToUsernameInput" type = "text" 
                  placeholder = "username to call" /> 
               <button id = "callBtn" class = "btn-success btn">Call</button> 
               <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
            </div> 
         </div> 
			
         <div class = "row text-center"> 
            <div class = "col-md-12"> 
               <input id = "msgInput" type = "text" placeholder = "message" /> 
               <button id = "sendMsgBtn" class = "btn-success btn">Send</button> 
            </div> 
         </div>
			
      </div> 
		
      <script src = "client.js"></script> 
		
   </body>
 
</html>

Trang này sẽ quen thuộc với bạn. Chúng tôi đã thêm tệp bootstrap css. Chúng tôi cũng đã xác định hai trang. Cuối cùng, chúng tôi đã tạo một số trường văn bản và các nút để lấy thông tin từ người dùng. Trên trang “trò chuyện”, bạn sẽ thấy thẻ div với ID “chatarea”, nơi tất cả các tin nhắn của chúng tôi sẽ được hiển thị. Lưu ý rằng chúng tôi đã thêm một liên kết vào tệp client.js .

Bây giờ chúng ta cần thiết lập kết nối với máy chủ báo hiệu của mình. Tạo tệp client.js trong thư mục gốc với mã sau:

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
}; 
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data);
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate":
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};

Bây giờ chạy máy chủ báo hiệu của chúng tôi thông qua máy chủ nút . Sau đó, bên trong thư mục gốc, hãy chạy lệnh tĩnh và mở trang bên trong trình duyệt. Bạn sẽ thấy đầu ra bảng điều khiển sau:

Bước tiếp theo là thực hiện đăng nhập người dùng với tên người dùng duy nhất. Chúng tôi chỉ cần gửi một tên người dùng đến máy chủ, sau đó cho chúng tôi biết liệu nó có được lấy hay không. Thêm mã sau vào tệp client.js của bạn -

//****** 
//UI selectors block
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput'); 
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
callPage.style.display = "none"; 

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value;
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
   } 
	
};

Đầu tiên, chúng tôi chọn một số tham chiếu đến các phần tử trên trang. Chúng tôi ẩn trang cuộc gọi. Sau đó, chúng tôi thêm một trình nghe sự kiện trên nút đăng nhập. Khi người dùng nhấp vào nó, chúng tôi gửi tên người dùng của anh ta đến máy chủ. Cuối cùng, chúng tôi triển khai lệnh gọi lại handleLogin. Nếu đăng nhập thành công, chúng tôi sẽ hiển thị trang cuộc gọi, thiết lập kết nối ngang hàng và tạo kênh dữ liệu.

Để bắt đầu kết nối ngang hàng với kênh dữ liệu, chúng ta cần -

  • Tạo đối tượng RTCPeerConnection
  • Tạo kênh dữ liệu bên trong đối tượng RTCPeerConnection của chúng tôi

Thêm mã sau vào “Khối bộ chọn giao diện người dùng” -

var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 
var chatArea = document.querySelector('#chatarea'); 

var yourConn; 
var dataChannel;

Sửa đổi hàm handleLogin -

function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //**********************
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]});
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      };
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      };
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      };
		
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };  
   } 
};

Nếu đăng nhập thành công, ứng dụng sẽ tạo đối tượng RTCPeerConnection và thiết lập trình xử lý onicecandidate để gửi tất cả các icecandidate đã tìm thấy đến đồng đẳng khác. Nó cũng tạo ra một dataChannel. Lưu ý rằng khi tạo đối tượng RTCPeerConnection, đối số thứ hai trong phương thức khởi tạo tùy chọn: [{RtpDataChannels: true}] là bắt buộc nếu bạn đang sử dụng Chrome hoặc Opera. Bước tiếp theo là tạo một phiếu mua hàng cho đồng nghiệp khác. Sau khi người dùng nhận được đề nghị, anh ta sẽ tạo câu trả lời và bắt đầu giao dịch các ứng viên ICE. Thêm mã sau vào tệp client.js -

//initiating a call
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value;
	
   if (callToUsername.length > 0) {
	
      connectedUser = callToUsername;
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
		
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      });  
   } 
});
  
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   });
};
  
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
  
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};

Chúng tôi thêm một trình xử lý nhấp chuột vào nút Gọi, nút này sẽ bắt đầu một phiếu mua hàng. Sau đó, chúng tôi thực hiện một số trình xử lý được mong đợi bởi trình xử lý onmessage . Chúng sẽ được xử lý không đồng bộ cho đến khi cả hai người dùng đã kết nối.

Bước tiếp theo là triển khai tính năng gác máy. Thao tác này sẽ ngừng truyền dữ liệu và yêu cầu người dùng khác đóng kênh dữ liệu. Thêm mã sau -

//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
}); 
 
function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
};

Khi người dùng nhấp vào nút Treo lên -

  • Nó sẽ gửi một thông báo "để lại" cho người dùng khác.
  • Nó sẽ đóng RTCPeerConnection và cũng như kênh dữ liệu.

Bước cuối cùng là gửi tin nhắn đến một người ngang hàng khác. Thêm trình xử lý “nhấp chuột” vào nút “gửi tin nhắn” -

//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});

Bây giờ hãy chạy mã. Bạn sẽ có thể đăng nhập vào máy chủ bằng hai tab trình duyệt. Sau đó, bạn có thể thiết lập kết nối ngang hàng với người dùng khác và gửi tin nhắn cho họ cũng như đóng kênh dữ liệu bằng cách nhấp vào nút "Treo máy".

Sau đây là toàn bộ tệp client.js -

//our username 
var name; 
var connectedUser; 

//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090'); 

conn.onopen = function () { 
   console.log("Connected to the signaling server");
};
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data); 
   var data = JSON.parse(msg.data); 
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
}; 

conn.onerror = function (err) { 
   console.log("Got error", err); 
}; 

//alias for sending JSON encoded messages 
function send(message) { 

   //attach the other peer username to our messages
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};
 
//****** 
//UI selectors block 
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 

var chatArea = document.querySelector('#chatarea'); 
var yourConn; 
var dataChannel; 
callPage.style.display = "none"; 

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) {
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]}); 
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      }; 
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      }; 
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      }; 
		
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };
		
   } 
};
 
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) { 
      connectedUser = callToUsername; 
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
         yourConn.setLocalDescription(offer); 
      }, function (error) { 
         alert("Error when creating an offer"); 
      }); 
   } 
	
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
      send({ 
         type: "answer", 
         answer: answer 
      }); 
   }, function (error) { 
      alert("Error when creating an answer"); 
   });
	
};
 
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
 
//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
}); 

function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
};
 
//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});

Trong chương này, chúng tôi sẽ thêm các tính năng bảo mật vào máy chủ báo hiệu mà chúng tôi đã tạo trong chương “Báo hiệu WebRTC”. Sẽ có hai cải tiến -

  • Xác thực người dùng bằng cơ sở dữ liệu Redis
  • Bật kết nối ổ cắm an toàn

Trước tiên, bạn nên cài đặt Redis.

  • Tải xuống bản phát hành ổn định mới nhất tại http://redis.io/download(3.05 trong trường hợp của tôi)

  • Giải nén nó

  • Bên trong thư mục đã tải xuống, chạy sudo thực hiện cài đặt

  • Sau khi cài đặt xong, hãy chạy thực hiện kiểm tra để kiểm tra xem mọi thứ có hoạt động chính xác hay không.

Redis có hai lệnh thực thi -

  • redis-cli - giao diện dòng lệnh cho Redis (phần khách hàng)

  • redis-server - Kho dữ liệu Redis

Để chạy máy chủ Redis, hãy nhập redis-server trong bảng điều khiển đầu cuối. Bạn sẽ thấy những điều sau:

Bây giờ, hãy mở một cửa sổ đầu cuối mới và chạy redis-cli để mở một ứng dụng khách.

Về cơ bản, Redis là một cơ sở dữ liệu khóa-giá trị. Để tạo khóa có giá trị chuỗi, bạn nên sử dụng lệnh SET. Để đọc giá trị khóa, bạn nên sử dụng lệnh GET. Hãy thêm hai người dùng và mật khẩu cho họ. Các khóa sẽ là tên người dùng và giá trị của các khóa này sẽ là mật khẩu tương ứng.

Bây giờ chúng ta nên sửa đổi máy chủ báo hiệu của mình để thêm xác thực người dùng. Thêm mã sau vào đầu tệp server.js -

//require the redis library in Node.js 
var redis = require("redis");
 
//creating the redis client object 
var redisClient = redis.createClient();

Trong đoạn mã trên, chúng tôi yêu cầu thư viện Redis cho Node.js và tạo một ứng dụng khách redis cho máy chủ của chúng tôi.

Để thêm xác thực, hãy sửa đổi trình xử lý thông báo trên đối tượng kết nối -

//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
	
      var data; 
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		
      //check whether a user is authenticated 
      if(data.type != "login") { 
		
         //if user is not authenticated 
         if(!connection.isAuth) { 
            sendTo(connection, { 
               type: "error", 
               message: "You are not authenticated" 
            }); 
            return; 
         } 
      } 
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login": 
            console.log("User logged:", data.name); 
            //get password for this username from redis database 
				
            redisClient.get(data.name, function(err, reply) {  
               //check if password matches with the one stored in redis 
               var loginSuccess = reply === data.password;
				
               //if anyone is logged in with this username or incorrect password 
                  then refuse 
               if(users[data.name] || !loginSuccess) { 
                  sendTo(connection, { 
                     type: "login", 
                     success: false 
                  }); 
               } else { 
                  //save user connection on the server 
                  users[data.name] = connection; 
                  connection.name = data.name;
                  connection.isAuth = true; 
						
                  sendTo(connection, { 
                     type: "login", 
                     success: true 
                  }); 
               }  
            }); 
				
            break;
      }
   });
	
}		
		
//... 
//*****other handlers*******

Trong đoạn mã trên, nếu người dùng cố gắng đăng nhập, chúng tôi nhận được từ Redis mật khẩu của họ, hãy kiểm tra xem mật khẩu đó có khớp với mật khẩu được lưu trữ hay không và nếu thành công, chúng tôi lưu trữ tên người dùng của họ trên máy chủ. Chúng tôi cũng thêm cờ isAuth vào kết nối để kiểm tra xem người dùng có được xác thực hay không. Lưu ý mã này -

//check whether a user is authenticated 
if(data.type != "login") { 

   //if user is not authenticated 
   if(!connection.isAuth) { 
      sendTo(connection, { 
         type: "error", 
         message: "You are not authenticated" 
      });
		
      return; 
   } 
}

Nếu người dùng chưa được xác thực cố gắng gửi phiếu mua hàng hoặc rời khỏi kết nối, chúng tôi chỉ gửi lại lỗi.

Bước tiếp theo là kích hoạt kết nối ổ cắm an toàn. Nó rất được khuyến khích cho các ứng dụng WebRTC. PKI (Cơ sở hạ tầng khóa công khai) là chữ ký số từ CA (Cơ quan cấp chứng chỉ). Sau đó, người dùng kiểm tra xem khóa riêng tư được sử dụng để ký chứng chỉ có khớp với khóa công khai của chứng chỉ CA không. Vì mục đích phát triển. chúng tôi sẽ sử dụng chứng chỉ bảo mật tự ký.

Chúng tôi sẽ sử dụng openssl. Nó là một công cụ mã nguồn mở thực hiện các giao thức SSL (Lớp cổng bảo mật) và TLS (Bảo mật lớp truyền tải). Nó thường được cài đặt theo mặc định trên hệ thống Unix. Chạy phiên bản openssl -a để kiểm tra xem nó đã được cài đặt chưa.

Để tạo khóa chứng chỉ bảo mật công khai và riêng tư, bạn nên làm theo các bước dưới đây:

  • Generate a temporary server password key

openssl genrsa -des3 -passout pass:x -out server.pass.key 2048

  • Generate a server private key

openssl rsa -passin pass:12345 -in server.pass.key -out server.key

  • Generate a signing request. You will be asked additional questions about your company. Just hit the “Enter” button all the time.

openssl req -new -key server.key -out server.csr

  • Generate the certificate

openssl x509 -req -days 1095 -in server.csr -signkey server.key -out server.crt

Bây giờ bạn có hai tệp, chứng chỉ (server.crt) và khóa riêng tư (server.key). Sao chép chúng vào thư mục gốc của máy chủ báo hiệu.

Để kích hoạt kết nối ổ cắm an toàn, hãy sửa đổi máy chủ báo hiệu của chúng tôi.

//require file system module 
var fs = require('fs'); 
var httpServ = require('https');
  
//https://github.com/visionmedia/superagent/issues/205 
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
  
//out secure server will bind to the port 9090 
var cfg = { 
   port: 9090, 
   ssl_key: 'server.key', 
   ssl_cert: 'server.crt' 
};
  
//in case of http request just send back "OK" 
var processRequest = function(req, res) { 
   res.writeHead(200); 
   res.end("OK"); 
};
  
//create our server with SSL enabled 
var app = httpServ.createServer({ 
   key: fs.readFileSync(cfg.ssl_key), 
   cert: fs.readFileSync(cfg.ssl_cert) 
}, processRequest).listen(cfg.port);
	
//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({server: app}); 

//all connected to the server users 
var users = {};
  
//require the redis library in Node.js
var redis = require("redis"); 

//creating the redis client object 
var redisClient = redis.createClient(); 
 
//when a user connects to our sever 
wss.on('connection', function(connection){ 
//...other code

Trong đoạn mã trên, chúng tôi yêu cầu thư viện fs đọc khóa cá nhân và chứng chỉ, tạo đối tượng cfg với cổng ràng buộc và đường dẫn cho khóa cá nhân và chứng chỉ. Sau đó, chúng tôi tạo một máy chủ HTTPS với các khóa của chúng tôi cùng với máy chủ WebSocket trên cổng 9090.

Bây giờ mở https://localhost:9090trong Opera. Bạn sẽ thấy những điều sau:

Nhấp vào nút “vẫn tiếp tục”. Bạn sẽ thấy thông báo “OK”.

Để kiểm tra máy chủ báo hiệu an toàn của chúng tôi, chúng tôi sẽ sửa đổi ứng dụng trò chuyện mà chúng tôi đã tạo trong hướng dẫn “WebRTC Text Demo”. Chúng tôi chỉ cần thêm một trường mật khẩu. Sau đây là toàn bộ tệp index.html -

<html>
  
   <head> 
      <title>WebRTC Text Demo</title>  
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>  
   </head> 

   <style>  
      body { 
         background: #eee; 
         padding: 5% 0; 
      }  
   </style>
	
   <body>  
      <div id = "loginPage" class = "container text-center"> 
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4">  
               <h2>WebRTC Text Demo. Please sign in</h2> 
               <label for = "usernameInput" class = "sr-only">Login</label> 
               <input type = "email" id = "usernameInput" 
                  class = "form-control formgroup" placeholder = "Login" 
                  required = "" autofocus = ""> 
               <input type = "text" id = "passwordInput" 
                  class = "form-control form-group" placeholder = "Password"
                  required = "" autofocus = ""> 
               <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock"
                  >Sign in</button>  
            </div> 
         </div> 
			
      </div> 
		
      <div id = "callPage" class = "call-page container">
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4 text-center"> 
               <div class = "panel panel-primary"> 
                  <div class = "panel-heading">Text chat</div> 
                  <div id = "chatarea" class = "panel-body text-left"></div> 
               </div> 
            </div> 
         </div>
			
         <div class = "row text-center form-group"> 
            <div class = "col-md-12"> 
               <input id = "callToUsernameInput" type = "text" 
                  placeholder = "username to call" /> 
               <button id = "callBtn" class = "btn-success btn">Call</button> 
               <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
            </div> 
         </div>
			
         <div class = "row text-center"> 
            <div class = "col-md-12"> 
               <input id = "msgInput" type = "text" placeholder = "message" /> 
               <button id = "sendMsgBtn" class = "btn-success btn">Send</button> 
            </div> 
         </div>
			
      </div>  
		
      <script src = "client.js"></script> 
  
   </body> 
	
</html>

Chúng ta cũng cần kích hoạt kết nối socket an toàn trong tệp client.js thông qua dòng này var conn = new WebSocket ('wss: // localhost: 9090'); . Lưu ý giao thức wss . Sau đó, trình xử lý nút đăng nhập phải sửa đổi để gửi mật khẩu cùng với tên người dùng -

loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
   var pwd = passwordInput.value;
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name, 
         password: pwd 
      }); 
   } 
	
});

Sau đây là toàn bộ tệp client.js -

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('wss://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
  
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data);
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer":
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};  

//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
}; 
 
//****** 
//UI selectors block 
//******

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var passwordInput = document.querySelector('#passwordInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 
var hangUpBtn = document.querySelector('#hangUpBtn');
  
var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 
var chatArea = document.querySelector('#chatarea'); 

var yourConn; 
var dataChannel;
  
callPage.style.display = "none";
  
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
   var pwd = passwordInput.value;  
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name, 
         password: pwd 
      }); 
   } 
	
}); 
 
function handleLogin(success) { 
   if (success === false) {
      alert("Ooops...incorrect username or password"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]}); 
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      };
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      };
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      };  
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };  
   } 
	
};
  
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value;
	
   if (callToUsername.length > 0) {
	
      connectedUser = callToUsername;
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      });  
   } 
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
	
};
  
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
  
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
   
//hang up 
hangUpBtn.addEventListener("click", function () { 

   send({ 
      type: "leave"
   }); 
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
}; 
 
//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});

Bây giờ chạy máy chủ báo hiệu an toàn của chúng tôi thông qua máy chủ nút . Chạy nút tĩnh bên trong thư mục demo trò chuyện đã sửa đổi. Mởlocalhost:8080trong hai tab trình duyệt. Cố gắng đăng nhập. Hãy nhớ chỉ “user1” với “password1” và “user2” với “password2” mới được phép đăng nhập. Sau đó, thiết lập RTCPeerConnection (gọi người dùng khác) và cố gắng gửi tin nhắn.

Sau đây là toàn bộ mã của máy chủ báo hiệu an toàn của chúng tôi -

//require file system module 
var fs = require('fs'); 
var httpServ = require('https');

//https://github.com/visionmedia/superagent/issues/205 
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
  
//out secure server will bind to the port 9090 
var cfg = { 
   port: 9090, 
   ssl_key: 'server.key', 
   ssl_cert: 'server.crt' 
};
  
//in case of http request just send back "OK" 
var processRequest = function(req, res){ 
   res.writeHead(200); 
   res.end("OK"); 
};
  
//create our server with SSL enabled 
var app = httpServ.createServer({ 
   key: fs.readFileSync(cfg.ssl_key), 
   cert: fs.readFileSync(cfg.ssl_cert) 
}, processRequest).listen(cfg.port);
  
//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({server: app}); 

//all connected to the server users 
var users = {};
  
//require the redis library in Node.js 
var redis = require("redis"); 

//creating the redis client object 
var redisClient = redis.createClient();

//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) {  
	
      var data; 
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      } 
		
      //check whether a user is authenticated 
      if(data.type != "login") { 
         //if user is not authenticated 
         if(!connection.isAuth) { 
            sendTo(connection, { 
               type: "error", 
               message: "You are not authenticated" 
            }); 
				
            return; 
         } 
      }
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login":
            console.log("User logged:", data.name); 
            //get password for this username from redis database 
            redisClient.get(data.name, function(err, reply) {
			  
               //check if password matches with the one stored in redis 
               var loginSuccess = reply === data.password;
				  
               //if anyone is logged in with this username or incorrect password 
                  then refuse 
               if(users[data.name] || !loginSuccess) { 
                  sendTo(connection, { 
                     type: "login", 
                     success: false 
                  }); 
               } else { 
                  //save user connection on the server 
                  users[data.name] = connection; 
                  connection.name = data.name; 
                  connection.isAuth = true; 
						
                  sendTo(connection, { 
                     type: "login", 
                     success: true 
                  }); 
               }  
            }); 
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name];
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name;
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name;
					
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               });
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
				
            break;
				
         connection.on("close", function() {
			
            if(connection.name) { 
               delete users[connection.name]; 
					
               if(connection.otherName) { 
                  console.log("Disconnecting from ", connection.otherName); 
                  var conn = users[connection.otherName]; 
                  conn.otherName = null;  
						
                  if(conn != null) { 
                     sendTo(conn, { 
                        type: "leave" 
                    }); 
                  } 
						
               } 
            } 
         });
			
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command no found: " + data.type 
            }); 
				
            break; 
      }  
   });
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
      if(connection.name) { 
         delete users[connection.name]; 
      } 
   });
	
   connection.send("Hello from server"); 
});
  
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Tóm lược

Trong chương này, chúng tôi đã thêm xác thực người dùng vào máy chủ báo hiệu của chúng tôi. Chúng tôi cũng đã học cách tạo chứng chỉ SSL tự ký và sử dụng chúng trong phạm vi ứng dụng WebRTC.