การสื่อสารแบบเรียลไทม์ด้วย WebRTC

1. บทนำ

WebRTC เป็นโปรเจ็กต์โอเพนซอร์สเพื่อเปิดใช้การสื่อสารแบบเรียลไทม์ของเสียง วิดีโอ และข้อมูลในเว็บและแอปที่มาพร้อมเครื่อง

WebRTC มี JavaScript API หลายรายการ โปรดคลิกลิงก์เพื่อดูการสาธิต

  • getUserMedia(): บันทึกเสียงและวิดีโอ
  • MediaRecorder: บันทึกเสียงและวิดีโอ
  • RTCPeerConnection: สตรีมเสียงและวิดีโอระหว่างผู้ใช้
  • RTCDataChannel: สตรีมข้อมูลระหว่างผู้ใช้

ฉันจะใช้ WebRTC ได้ที่ใด

ใน Firefox, Opera และใน Chrome บนเดสก์ท็อปและ Android WebRTC ยังมีให้บริการสำหรับแอปที่มาพร้อมเครื่องบน iOS และ Android ด้วย

การส่งสัญญาณคืออะไร

WebRTC ใช้ RTCPeerConnection เพื่อสื่อสารสตรีมมิงข้อมูลระหว่างเบราว์เซอร์ แต่ก็ต้องการกลไกในการประสานงานการสื่อสารและส่งข้อความควบคุม ซึ่งเป็นกระบวนการที่เรียกว่าการส่งสัญญาณ WebRTC ไม่ได้ระบุวิธีการและโปรโตคอลการส่งสัญญาณ ใน Codelab นี้ คุณจะใช้ Socket.IO สำหรับการรับส่งข้อความ แต่มีทางเลือกต่างๆ มากมาย

STUN และ TURN คืออะไร

WebRTC ได้รับการออกแบบมาให้ทำงานได้แบบเพียร์ทูเพียร์ เพื่อให้ผู้ใช้เชื่อมต่อได้โดยเส้นทางโดยตรงที่สุดเท่าที่จะเป็นไปได้ อย่างไรก็ตาม WebRTC สร้างมาเพื่อรับมือกับเครือข่ายในโลกแห่งความเป็นจริง แอปพลิเคชันไคลเอ็นต์ต้องข้ามผ่านเกตเวย์ NAT และไฟร์วอลล์ และระบบต้องใช้เครือข่ายเพียร์ทูเพียร์ในกรณีที่การเชื่อมต่อโดยตรงล้มเหลว ในกระบวนการนี้ WebRTC API จะใช้เซิร์ฟเวอร์ STUN เพื่อรับที่อยู่ IP ของคอมพิวเตอร์ของคุณ และ "TURN" เซิร์ฟเวอร์ให้ทำงานเป็นรีเลย์เซิร์ฟเวอร์ในกรณีที่การสื่อสารระหว่างเครื่องล้มเหลว (WebRTC ในชีวิตจริงจะอธิบายรายละเอียดเพิ่มเติม)

WebRTC ปลอดภัยไหม

คอมโพเนนต์ WebRTC ทั้งหมดจำเป็นต้องมีการเข้ารหัสและใช้ JavaScript API ได้จากต้นทางที่ปลอดภัยเท่านั้น (HTTPS หรือ localhost) กลไกการส่งสัญญาณไม่ได้กำหนดโดยมาตรฐาน WebRTC คุณจึงขึ้นอยู่กับที่จะใช้โปรโตคอลที่ปลอดภัย

2. ภาพรวม

สร้างแอปเพื่อรับวิดีโอและถ่ายสแนปชอตด้วยเว็บแคม และแชร์การเชื่อมต่อแบบเพียร์ทูเพียร์ผ่าน WebRTC ระหว่างที่ดำเนินการ คุณจะได้เรียนรู้วิธีใช้ WebRTC API หลัก และตั้งค่าเซิร์ฟเวอร์การรับส่งข้อความโดยใช้ Node.js

สิ่งที่คุณจะได้เรียนรู้

  • รับวิดีโอจากเว็บแคมของคุณ
  • สตรีมวิดีโอด้วย RTCPeerConnection
  • สตรีมข้อมูลด้วย RTCDataChannel
  • ตั้งค่าบริการส่งสัญญาณเพื่อแลกเปลี่ยนข้อความ
  • รวมการเชื่อมต่อและการส่งสัญญาณแบบเพียร์
  • ถ่ายรูปและแชร์ผ่านช่องข้อมูล

สิ่งที่ต้องมี

  • Chrome 47 ขึ้นไป
  • เว็บเซิร์ฟเวอร์สำหรับ Chrome หรือใช้เว็บเซิร์ฟเวอร์ของคุณเอง
  • โค้ดตัวอย่าง
  • เครื่องมือแก้ไขข้อความ
  • ความรู้พื้นฐานเกี่ยวกับ HTML, CSS และ JavaScript

3. รับโค้ดตัวอย่าง

ดาวน์โหลดโค้ด

หากคุณคุ้นเคยกับ git คุณสามารถดาวน์โหลดโค้ดสำหรับ Codelab นี้ได้จาก GitHub โดยการโคลนโค้ด โดยทำดังนี้

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

หรือคลิกปุ่มต่อไปนี้เพื่อดาวน์โหลดไฟล์ ZIP ของโค้ด:

เปิดไฟล์ ZIP ที่ดาวน์โหลดมา การดำเนินการนี้จะแยกโฟลเดอร์โปรเจ็กต์ (adaptive-web-media) ที่มี 1 โฟลเดอร์สำหรับแต่ละขั้นตอนของ Codelab นี้ พร้อมทรัพยากรทั้งหมดที่จะต้องใช้

คุณจะต้องเขียนโค้ดทั้งหมดในไดเรกทอรีที่ชื่อ work

โฟลเดอร์ step-nn มีเวอร์ชันที่เสร็จแล้วสำหรับแต่ละขั้นตอนของ Codelab นี้ โค้ดเหล่านี้มีไว้เพื่ออ้างอิง

ติดตั้งและยืนยันเว็บเซิร์ฟเวอร์

แม้ว่าคุณจะใช้เว็บเซิร์ฟเวอร์ของคุณเองได้อย่างอิสระ แต่ Codelab นี้ได้รับการออกแบบมาให้ทำงานกับเว็บเซิร์ฟเวอร์ของ Chrome ได้ดี หากยังไม่ได้ติดตั้งแอปดังกล่าว คุณสามารถติดตั้งได้จาก Chrome เว็บสโตร์

6ddeb4aee53c0f0e.png

หลังจากติดตั้งแอปเว็บเซิร์ฟเวอร์สำหรับ Chrome ให้คลิกทางลัดของแอป Chrome จากแถบบุ๊กมาร์ก หน้าแท็บใหม่ หรือจากเครื่องเรียกใช้งานแอป

1d2b4aa977ab7e24.png

คลิกไอคอนเว็บเซิร์ฟเวอร์:

27fce4494f641883.png

ถัดไป คุณจะเห็นกล่องโต้ตอบนี้ ซึ่งให้คุณกำหนดค่าเว็บเซิร์ฟเวอร์ภายใน

Screen Shot 18-02-2016 เวลา 11.48.14 AM.png

คลิกปุ่มเลือกโฟลเดอร์ แล้วเลือกโฟลเดอร์งานที่คุณเพิ่งสร้าง ซึ่งจะทำให้คุณดูงานที่อยู่ระหว่างดำเนินการใน Chrome ได้ผ่าน URL ที่ไฮไลต์ไว้ในกล่องโต้ตอบของเว็บเซิร์ฟเวอร์ในส่วน URL ของเว็บเซิร์ฟเวอร์

ในส่วนตัวเลือก ให้เลือกช่องข้างแสดงindex.htmlโดยอัตโนมัติดังที่แสดงด้านล่าง

Screen Shot 18-02-2016 เวลา 11.56.30 น.

จากนั้นหยุดและรีสตาร์ทเซิร์ฟเวอร์โดยเลื่อนปุ่มสลับที่มีป้ายกำกับว่าเว็บเซิร์ฟเวอร์: STARTED ไปทางซ้ายแล้วกลับมาไปทางขวา

Screen Shot 18-02-2016 เวลา 12.22.18 PM.png

จากนั้นไปที่เว็บไซต์ที่ทำงานในเว็บเบราว์เซอร์โดยคลิก URL ของเว็บเซิร์ฟเวอร์ที่ไฮไลต์ คุณควรจะเห็นหน้าเว็บที่มีลักษณะเช่นนี้ ซึ่งสอดคล้องกับ work/index.html:

18a705cb6ccc5181.png

เห็นได้ชัดว่าแอปนี้ยังไม่ได้ทำอะไรที่น่าสนใจเลย เพราะในขณะนี้เราใช้หลักการง่ายๆ เพียงเล็กน้อยเท่านั้นเพื่อให้แน่ใจว่าเว็บเซิร์ฟเวอร์ของคุณจะทำงานอย่างถูกต้อง คุณจะเพิ่มฟังก์ชันและฟีเจอร์เลย์เอาต์ในขั้นตอนต่อๆ ไป

4. สตรีมวิดีโอจากเว็บแคมของคุณ

สิ่งที่คุณจะได้เรียนรู้

ในขั้นตอนนี้ คุณจะพบกับวิธีการ:

  • รับสตรีมวิดีโอจากเว็บแคม
  • จัดการการเล่นสตรีม
  • ใช้ CSS และ SVG เพื่อปรับแต่งวิดีโอ

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-01

ยัติภาค HTML...

เพิ่มเอลิเมนต์ video และเอลิเมนต์ script ลงใน index.html ในไดเรกทอรี work ดังนี้

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

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

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <video autoplay playsinline></video>

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

</body>

</html>

...และ JavaScript เล็กน้อย

เพิ่มโค้ดต่อไปนี้ลงใน main.js ในโฟลเดอร์ js ของคุณ

'use strict';

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

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

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

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

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

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

ลองเลย

เปิด index.html ในเบราว์เซอร์และคุณควรเห็นบางอย่างแบบนี้ (แน่นอนว่าแสดงภาพจากเว็บแคมของคุณ)

9297048e43ed0f3d.png

วิธีการทำงาน

หลังจากโทรหา getUserMedia() เบราว์เซอร์จะขอสิทธิ์จากผู้ใช้เพื่อเข้าถึงกล้อง (หากนี่เป็นครั้งแรกที่มีการขอสิทธิ์เข้าถึงกล้องสำหรับต้นทางปัจจุบัน) หากสำเร็จ ระบบจะแสดงผล MediaStream ซึ่งองค์ประกอบสื่อสามารถใช้ผ่านแอตทริบิวต์ srcObject ได้ดังนี้

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


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

อาร์กิวเมนต์ constraints ช่วยให้คุณระบุสื่อที่ต้องการรับได้ ในตัวอย่างนี้คือวิดีโอเท่านั้น เนื่องจากมีการปิดใช้เสียงโดยค่าเริ่มต้น

const mediaStreamConstraints = {
  video: true,
};

คุณสามารถใช้ข้อจำกัดสำหรับข้อกำหนดเพิ่มเติม เช่น ความละเอียดของวิดีโอ

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

ข้อกำหนดของ MediaTrackConstraints จะแสดงประเภทข้อจำกัดที่เป็นไปได้ทั้งหมด แม้ว่าบางเบราว์เซอร์จะไม่รองรับตัวเลือกบางรายการก็ตาม หากกล้องที่เลือกไม่รองรับความละเอียดที่ขอ getUserMedia() จะถูกปฏิเสธโดยมี OverconstrainedError และผู้ใช้จะไม่ได้รับข้อความแจ้งเพื่อให้สิทธิ์เข้าถึงกล้อง

หาก getUserMedia() สำเร็จ ระบบจะตั้งค่าสตรีมวิดีโอจากเว็บแคมเป็นแหล่งที่มาขององค์ประกอบวิดีโอ

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

คะแนนโบนัส

  • ออบเจ็กต์ localStream ที่ส่งไปยัง getUserMedia() อยู่ในขอบเขตทั้งหมด คุณจึงตรวจสอบออบเจ็กต์ได้จากคอนโซลของเบราว์เซอร์ โดยเปิดคอนโซล พิมพ์ stream แล้วกด Return (หากต้องการดูคอนโซลใน Chrome ให้กด Ctrl-Shift-J หรือ Command-Option-J หากใช้ Mac)
  • localStream.getVideoTracks() จะส่งคืนอะไร
  • โปรดลองโทรหา localStream.getVideoTracks()[0].stop()
  • ดูที่ออบเจ็กต์ข้อจำกัด: จะเกิดอะไรขึ้นเมื่อคุณเปลี่ยนเป็น {audio: true, video: true}
  • องค์ประกอบวิดีโอมีขนาดเท่าใด แล้วคุณจะดูขนาดปกติของวิดีโอจาก JavaScript แทนที่จะเป็นขนาดการแสดงผลได้อย่างไร ใช้เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เพื่อตรวจสอบ
  • ลองเพิ่มตัวกรอง CSS ลงในองค์ประกอบวิดีโอ เช่น
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • ลองเพิ่มฟิลเตอร์ SVG เช่น
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

สิ่งที่คุณได้เรียนรู้

ในขั้นตอนนี้ คุณได้เรียนรู้วิธีต่อไปนี้

  • รับวิดีโอจากเว็บแคมของคุณ
  • กำหนดข้อจำกัดสื่อ
  • องค์ประกอบวิดีโอเลือนราง

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-01

เคล็ดลับ

  • อย่าลืมแอตทริบิวต์ autoplay ในองค์ประกอบ video หากไม่มีข้อมูลดังกล่าว คุณจะเห็นเพียงเฟรมเดียวเท่านั้น
  • มีตัวเลือกอีกมากมายสำหรับข้อจำกัด getUserMedia() ดูการสาธิตได้ที่ webrtc.github.io/samples/src/content/peerconnection/constraints คุณจะเห็นว่ามีตัวอย่าง WebRTC ที่น่าสนใจมากมายในเว็บไซต์นั้น

แนวทางปฏิบัติแนะนำ

  • ตรวจสอบว่าองค์ประกอบวิดีโอไม่ล้นออกจากคอนเทนเนอร์ เราได้เพิ่ม width และ max-width เพื่อกำหนดขนาดที่ต้องการและขนาดสูงสุดสำหรับวิดีโอ เบราว์เซอร์จะคำนวณความสูงโดยอัตโนมัติ:
video {
  max-width: 100%;
  width: 320px;
}

รายการถัดไป

คุณมีวิดีโอแล้ว แต่จะสตรีมได้อย่างไร ดูข้อมูลในขั้นตอนถัดไป

5. สตรีมวิดีโอด้วย RTCPeerConnection

สิ่งที่คุณจะได้เรียนรู้

ในขั้นตอนนี้ คุณจะพบกับวิธีการ:

  • ลดความแตกต่างของเบราว์เซอร์ด้วย WebRTC shim, adapter.js
  • ใช้ RTCPeerConnection API เพื่อสตรีมวิดีโอ
  • ควบคุมการจับภาพและการสตรีมสื่อ

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้จะอยู่ในโฟลเดอร์step-2

RTCPeerConnection คืออะไร

RTCPeerConnection คือ API สำหรับการเรียก WebRTC เพื่อสตรีมวิดีโอและเสียง ตลอดจนแลกเปลี่ยนข้อมูล

ตัวอย่างนี้สร้างการเชื่อมต่อระหว่างออบเจ็กต์ RTCPeerConnection 2 รายการ (หรือที่เรียกว่าแอปเทียบเท่า) ในหน้าเดียวกัน

ใช้งานได้จริงไม่มากนัก แต่ช่วยให้เข้าใจวิธีการทำงานของ RTCPeerConnection

เพิ่มองค์ประกอบวิดีโอและปุ่มควบคุม

ใน index.html ให้แทนที่องค์ประกอบวิดีโอรายการเดียวด้วยองค์ประกอบวิดีโอ 2 รายการและปุ่ม 3 ปุ่ม ดังนี้

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


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

องค์ประกอบวิดีโอ 1 รายการจะแสดงสตรีมจาก getUserMedia() และอีกองค์ประกอบหนึ่งจะแสดงวิดีโอเดียวกันที่สตรีมผ่าน RTCPeerconnection (ในการใช้งานจริง องค์ประกอบวิดีโอหนึ่งจะแสดงสตรีมในเครื่องและอีกองค์ประกอบหนึ่งเป็นสตรีมระยะไกล)

เพิ่มอะแดปเตอร์ shim

เพิ่มลิงก์ไปยัง adapter.js เวอร์ชันปัจจุบันเหนือลิงก์ไปยัง main.js:

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

ตอนนี้ Index.html ควรมีลักษณะดังนี้

<!DOCTYPE html>
<html>

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

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

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

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

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

ติดตั้งโค้ด RTCPeerConnection

แทนที่ main.js ด้วยเวอร์ชันในโฟลเดอร์ step-02

โทรออก

เปิด index.html คลิกปุ่มเริ่มเพื่อรับวิดีโอจากเว็บแคม แล้วคลิกโทรเพื่อเชื่อมต่อกับเพียร์ คุณควรจะเห็นวิดีโอเดียวกัน (จากเว็บแคม) ในองค์ประกอบของวิดีโอทั้งสอง ดูคอนโซลของเบราว์เซอร์เพื่อดูการบันทึก WebRTC

วิธีการทำงาน

ขั้นตอนนี้ทำหลายอย่างได้...

WebRTC ใช้ RTCPeerConnection API เพื่อตั้งค่าการเชื่อมต่อเพื่อสตรีมวิดีโอระหว่างไคลเอ็นต์ WebRTC ซึ่งเรียกว่า peers

ในตัวอย่างนี้ ออบเจ็กต์ RTCPeerConnection 2 รายการอยู่ในหน้าเดียวกัน คือ pc1 และ pc2 ไม่ค่อยมีประโยชน์ แต่เหมาะสำหรับการสาธิตการทำงานของ API

การตั้งค่าการเรียกใช้ระหว่างแอปเทียบเท่า WebRTC เกี่ยวข้องกับงาน 3 อย่างดังนี้

  • สร้าง RTCPeerConnection สำหรับปลายทางแต่ละรายการ และเพิ่มสตรีมในเครื่องจาก getUserMedia() ในตอนท้าย
  • รับและแชร์ข้อมูลเครือข่าย: ปลายทางการเชื่อมต่อที่เป็นไปได้เรียกว่าตัวเลือก ICE
  • รับและแชร์คำอธิบายในเครื่องและระยะไกล: ข้อมูลเมตาเกี่ยวกับสื่อในเครื่องในรูปแบบ SDP

ลองจินตนาการว่าอรวรรณและบัญชาต้องการใช้ RTCPeerConnection เพื่อตั้งค่าวิดีโอแชท

อย่างแรก Alice และ Bob แลกเปลี่ยนข้อมูลเครือข่าย นิพจน์ "การค้นหาผู้สมัคร" หมายถึงกระบวนการค้นหาอินเทอร์เฟซและพอร์ตของเครือข่ายโดยใช้เฟรมเวิร์ก ICE

  1. อรวรรณสร้างออบเจ็กต์ RTCPeerConnection ด้วยเครื่องจัดการ onicecandidate (addEventListener('icecandidate')) โค้ดนี้สอดคล้องกับโค้ดต่อไปนี้จาก main.js
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. อรวรรณโทรหา getUserMedia() และเพิ่มสตรีมที่ส่งไปยัง
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
  1. ระบบจะเรียกใช้ตัวแฮนเดิล onicecandidate จากขั้นตอนที่ 1 เมื่อตัวเลือกเครือข่ายพร้อมใช้งาน
  2. อลิซส่งข้อมูลผู้สมัครแบบต่อเนื่องให้กับบ็อบ ในแอปพลิเคชันจริง กระบวนการนี้ (หรือที่เรียกว่าการส่งสัญญาณ) จะเกิดขึ้นผ่านบริการรับส่งข้อความ ซึ่งคุณจะได้ทราบวิธีดำเนินการในขั้นตอนถัดไป แน่นอนว่าในขั้นตอนนี้ ออบเจ็กต์ RTCPeerConnection ทั้ง 2 รายการอยู่ในหน้าเดียวกันและสื่อสารได้โดยตรงโดยไม่ต้องใช้การรับส่งข้อความภายนอก
  3. เมื่อบัญชาได้รับข้อความจากผู้สมัครชื่ออลิซ เขาจึงโทรหา addIceCandidate() เพื่อเพิ่มผู้สมัครคนดังกล่าวไปยังคำอธิบายของเพื่อนระยะไกล:
function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

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

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

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

เพื่อนร่วมงาน WebRTC ต้องค้นหาและแลกเปลี่ยนข้อมูลสื่อเสียงและวิดีโอทั้งในเครื่องและระยะไกล เช่น ความละเอียดและความสามารถของตัวแปลงรหัส การส่งสัญญาณเพื่อแลกเปลี่ยนข้อมูลการกำหนดค่าสื่อจะดำเนินการโดยการแลกเปลี่ยน BLOB ของข้อมูลเมตาที่เรียกว่าข้อเสนอและคำตอบ โดยใช้รูปแบบโปรโตคอลคำอธิบายเซสชันที่เรียกว่า SDP ดังนี้

  1. Alice เรียกใช้เมธอด RTCPeerConnection createOffer() คำมั่นสัญญาที่ส่งกลับมาจะให้ RTCSessionDescription: คำอธิบายเซสชันในเครื่องของ Alice:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. หากสำเร็จ อรวรรณจะกำหนดคำอธิบายในเครื่องโดยใช้ setLocalDescription() แล้วส่งคำอธิบายเซสชันนี้ให้บ็อบผ่านช่องส่งสัญญาณ
  2. อานนท์เขียนคำอธิบายที่อลิสาส่งให้เขาเป็นคำอธิบายระยะไกลโดยใช้ setRemoteDescription()
  3. บัญชาเรียกใช้เมธอด createAnswer() RTCPeerConnection และส่งคำอธิบายระยะไกลที่ได้รับจาก Alice เพื่อให้ระบบสร้างเซสชันในเครื่องที่เข้ากันได้กับของเธอ คำมั่นสัญญาของ createAnswer() จะส่งต่อ RTCSessionDescription: บ๊อบกำหนดให้เป็นคำอธิบายในเครื่องและส่งไปยัง Alice
  4. เมื่ออลิซฟังคำอธิบายเซสชันของบัญชา เธอกำหนดให้คำอธิบายดังกล่าวเป็นคำอธิบายระยะไกลด้วย setRemoteDescription()
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

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

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

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

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

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

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

คะแนนโบนัส

  1. โปรดดูที่ chrome://webrtc-internals การดำเนินการนี้จะแสดงสถิติ WebRTC และข้อมูลการแก้ไขข้อบกพร่อง (ดูรายการ URL ทั้งหมดของ Chrome ได้ที่ chrome://about)
  2. จัดรูปแบบหน้าเว็บด้วย CSS ดังนี้
  • นำวิดีโอไปวางเคียงข้างกัน
  • ทำให้ปุ่มมีความกว้างเท่ากันโดยใช้ข้อความใหญ่ขึ้น
  • ตรวจสอบว่าเลย์เอาต์ใช้งานได้บนอุปกรณ์เคลื่อนที่
  1. จากคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ให้ดูที่ localStream, localPeerConnection และ remotePeerConnection
  2. จากคอนโซล ให้ดูที่ localPeerConnectionpc1.localDescription รูปแบบ SDP มีลักษณะอย่างไร

สิ่งที่คุณได้เรียนรู้

ในขั้นตอนนี้ คุณได้เรียนรู้วิธีต่อไปนี้

  • ลดความแตกต่างของเบราว์เซอร์ด้วย WebRTC shim, adapter.js
  • ใช้ RTCPeerConnection API เพื่อสตรีมวิดีโอ
  • ควบคุมการจับภาพและการสตรีมสื่อ
  • แชร์ข้อมูลสื่อและข้อมูลเครือข่ายระหว่างแอปเทียบเท่าเพื่อเปิดใช้การเรียกใช้ WebRTC

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้จะอยู่ในโฟลเดอร์step-2

เคล็ดลับ

  • ขั้นตอนนี้มีอะไรให้เรียนรู้มากมาย หากต้องการดูแหล่งข้อมูลอื่นๆ ที่อธิบาย RTCPeerConnection โดยละเอียด โปรดดูที่ webrtc.org หน้านี้มีคำแนะนำสำหรับเฟรมเวิร์ก JavaScript หากคุณต้องการใช้ WebRTC แต่ไม่ต้องการจัดการกับ API
  • ดูข้อมูลเพิ่มเติมเกี่ยวกับอะแดปเตอร์ shim จากที่เก็บ GitHub ของ adapter.js
  • อยากทราบว่าแอปวิดีโอแชทที่ดีที่สุดในโลกมีลักษณะเป็นอย่างไร ลองดู AppRTC ซึ่งเป็นแอป Canonical ของโปรเจ็กต์ WebRTC สำหรับการเรียก WebRTC: แอป รหัส เวลาตั้งค่าการโทรน้อยกว่า 500 มิลลิวินาที

แนวทางปฏิบัติแนะนำ

  • เพื่อให้ใช้งานโค้ดได้อย่างต่อเนื่องในอนาคต ให้ใช้ API ใหม่ที่อิงตาม Promise และเปิดใช้ความเข้ากันได้กับเบราว์เซอร์ที่ไม่รองรับ API ดังกล่าวโดยใช้ adapter.js

รายการถัดไป

ขั้นตอนนี้แสดงวิธีใช้ WebRTC เพื่อสตรีมวิดีโอระหว่างแอปเทียบเท่า แต่ Codelab นี้เกี่ยวข้องกับอินเทอร์เน็ตเช่นกัน

ในขั้นตอนถัดไป คุณสามารถดูวิธีสตรีมข้อมูลที่กำหนดเองโดยใช้ RTCDataChannel

6. ใช้ RTCDataChannel เพื่อแลกเปลี่ยนข้อมูล

สิ่งที่คุณจะได้เรียนรู้

  • วิธีแลกเปลี่ยนข้อมูลระหว่างปลายทาง WebRTC (เพียร์)

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-03

อัปเดต HTML ของคุณ

สำหรับขั้นตอนนี้ คุณจะใช้ช่องทางข้อมูล WebRTC เพื่อส่งข้อความระหว่างองค์ประกอบ textarea 2 รายการในหน้าเดียวกัน ฟีเจอร์นี้ไม่ได้มีประโยชน์มากนัก แต่สาธิตวิธีใช้ WebRTC เพื่อแชร์ข้อมูล รวมถึงสตรีมวิดีโอ

นำองค์ประกอบวิดีโอและปุ่มออกจาก index.html แล้วแทนที่ด้วย HTML ต่อไปนี้

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

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

พื้นที่ข้อความหนึ่งมีไว้เพื่อป้อนข้อความ อีกพื้นที่จะแสดงข้อความขณะที่สตรีมระหว่างแอปเทียบเท่า

index.html ควรมีลักษณะดังนี้

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

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

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

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

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

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

</body>

</html>

อัปเดต JavaScript ของคุณ

แทนที่ main.js ด้วยเนื้อหาของ step-03/js/main.js

ลองสตรีมข้อมูลระหว่างแอปเทียบเท่าโดยเปิด index.html กดเริ่มเพื่อตั้งค่าการเชื่อมต่อแบบเพียร์ ป้อนข้อความใน textarea ทางด้านซ้าย จากนั้นคลิกส่งเพื่อโอนข้อความโดยใช้ช่องข้อมูล WebRTC

วิธีการทำงาน

โค้ดนี้ใช้ RTCPeerConnection และ RTCDataChannel เพื่อเปิดใช้การแลกเปลี่ยนข้อความ

โค้ดส่วนใหญ่ในขั้นตอนนี้จะเหมือนกับตัวอย่าง RTCPeerConnection

ฟังก์ชัน sendData() และ createConnection() มีโค้ดใหม่ส่วนใหญ่:

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

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

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

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

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

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

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

ไวยากรณ์ของ RTCDataChannel จงใจกับ WebSocket เมื่อมีเมธอด send() และเหตุการณ์ message

สังเกตการใช้ dataConstraint คุณสามารถกำหนดค่าช่องทางข้อมูลเพื่อเปิดใช้การแชร์ข้อมูลประเภทต่างๆ เช่น ให้ความสำคัญกับการแสดงโฆษณาที่เชื่อถือได้มากกว่าประสิทธิภาพ คุณสามารถดูข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือกต่างๆ ได้ที่เครือข่ายนักพัฒนาซอฟต์แวร์ของ Mozilla

คะแนนโบนัส

  1. ด้วย SCTP โปรโตคอลที่ช่องทางข้อมูล WebRTC ใช้ รวมถึงการนำส่งข้อมูลตามลำดับที่เชื่อถือได้จะเปิดอยู่โดยค่าเริ่มต้น เมื่อใดที่ RTCDataChannel จำเป็นต้องส่งมอบข้อมูลที่เชื่อถือได้ และเมื่อใดที่ประสิทธิภาพอาจมีความสำคัญมากกว่า แม้ว่าจะทำให้ข้อมูลบางส่วนหายไปก็ตาม
  2. ใช้ CSS เพื่อปรับปรุงการออกแบบหน้าเว็บ และเพิ่มแอตทริบิวต์ตัวยึดตำแหน่งไปยัง "dataChannelReceive" พื้นที่ข้อความ
  3. ทดสอบหน้าเว็บบนอุปกรณ์เคลื่อนที่

สิ่งที่คุณได้เรียนรู้

ในขั้นตอนนี้ คุณได้เรียนรู้วิธีต่อไปนี้

  • สร้างการเชื่อมต่อระหว่างแอปเทียบเท่า WebRTC สองเครื่อง
  • แลกเปลี่ยนข้อมูลข้อความระหว่างแอปเทียบเท่า

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-03

ดูข้อมูลเพิ่มเติม

รายการถัดไป

คุณได้เรียนรู้วิธีแลกเปลี่ยนข้อมูลระหว่างแอปเทียบเท่าในหน้าเดียวกันแล้ว แต่จะแลกเปลี่ยนระหว่างคอมพิวเตอร์เครื่องอื่นได้อย่างไร ขั้นแรก คุณต้องตั้งค่าช่องทางการส่งสัญญาณเพื่อแลกเปลี่ยนข้อความข้อมูลเมตา ดูวิธีการในขั้นตอนถัดไป

7. ตั้งค่าบริการส่งสัญญาณเพื่อแลกเปลี่ยนข้อความ

สิ่งที่คุณจะได้เรียนรู้

ในขั้นตอนนี้ คุณจะได้ดูวิธีต่อไปนี้

  • ใช้ npm เพื่อติดตั้งทรัพยากร Dependency ของโปรเจ็กต์ตามที่ระบุไว้ใน package.json
  • เรียกใช้เซิร์ฟเวอร์ Node.js และใช้ Node-static เพื่อแสดงไฟล์แบบคงที่
  • ตั้งค่าบริการรับส่งข้อความบน Node.js โดยใช้ Socket.IO
  • ใช้เพื่อสร้าง "ห้อง" และแลกเปลี่ยนข้อความกัน

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้จะอยู่ในโฟลเดอร์ step-04

แนวคิด

ไคลเอ็นต์ WebRTC (แอปเทียบเท่า) ต้องแลกเปลี่ยนข้อมูลเมตาเพื่อตั้งค่าและรักษาการเรียก WebRTC ต่อไปนี้

  • ข้อมูลผู้สมัคร (เครือข่าย)
  • ข้อความ Offer และ answer ที่ให้ข้อมูลเกี่ยวกับสื่อ เช่น ความละเอียดและตัวแปลงรหัส

กล่าวคือ ต้องมีการแลกเปลี่ยนข้อมูลเมตาก่อนที่จะทำการสตรีมเสียง วิดีโอ หรือข้อมูลแบบเพียร์ทูเพียร์ กระบวนการนี้เรียกว่าการส่งสัญญาณ

ในขั้นตอนก่อนหน้านี้ ออบเจ็กต์ RTCPeerConnection ของผู้ส่งและผู้รับอยู่บนหน้าเดียวกัน ดังนั้น "การส่งสัญญาณ" ก็คือการส่งข้อมูลเมตาระหว่างออบเจ็กต์

ในแอปพลิเคชันในโลกแห่งความเป็นจริง RTCPeerConnections ของผู้ส่งและตัวรับจะทำงานในหน้าเว็บบนอุปกรณ์ที่แตกต่างกัน คุณจึงต้องมีวิธีให้อุปกรณ์สื่อสารข้อมูลเมตา

โดยใช้เซิร์ฟเวอร์ส่งสัญญาณ ซึ่งเป็นเซิร์ฟเวอร์ที่ส่งข้อความระหว่างไคลเอ็นต์ WebRTC (เพียร์) ได้ ข้อความจริงๆ จะเป็นข้อความธรรมดา ซึ่งก็คือออบเจ็กต์ JavaScript แบบสตริง

สิ่งที่ต้องทำก่อน: ติดตั้ง Node.js

หากต้องการเรียกใช้ขั้นตอนถัดไปของ Codelab นี้ (โฟลเดอร์ step-04 ถึง step-06) คุณจะต้องเรียกใช้เซิร์ฟเวอร์บน localhost โดยใช้ Node.js

คุณสามารถดาวน์โหลดและติดตั้ง Node.js ได้จากลิงก์นี้หรือผ่านเครื่องมือจัดการแพ็กเกจที่ต้องการ

เมื่อติดตั้งแล้ว คุณจะสามารถนำเข้าทรัพยากร Dependency ที่จำเป็นสำหรับขั้นตอนถัดไป (เรียกใช้ npm install) รวมถึงเรียกใช้เซิร์ฟเวอร์ localhost ขนาดเล็กเพื่อเรียกใช้ Codelab (เรียกใช้ node index.js) ได้ ระบบจะระบุคำสั่งเหล่านี้ในภายหลัง เมื่อจำเป็นต้องใช้

เกี่ยวกับแอป

WebRTC ใช้ JavaScript API ฝั่งไคลเอ็นต์ แต่สำหรับการใช้งานจริงต้องมีเซิร์ฟเวอร์ส่งสัญญาณ (การรับส่งข้อความ) รวมถึงเซิร์ฟเวอร์ STUN และ TURN ด้วย ดูข้อมูลเพิ่มเติมได้ที่นี่

ในขั้นตอนนี้ คุณจะได้สร้างเซิร์ฟเวอร์ส่งสัญญาณ Node.js แบบง่าย โดยใช้โมดูล Socket.IO Node.js และไลบรารี JavaScript สำหรับการรับส่งข้อความ ประสบการณ์ในการใช้ Node.js และ Socket.IO จะมีประโยชน์ แต่ไม่ได้สำคัญมาก องค์ประกอบการรับส่งข้อความนั้นเรียบง่ายมาก

ในตัวอย่างนี้ เซิร์ฟเวอร์ (แอปพลิเคชัน Node.js) จะติดตั้งใน index.js และไคลเอ็นต์ที่เรียกใช้บนเซิร์ฟเวอร์ (เว็บแอป) จะติดตั้งใน index.html

แอปพลิเคชัน Node.js ในขั้นตอนนี้มี 2 งาน

ประการแรก บริการนี้ทำงานเป็นการส่งต่ออีเมล

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

ประการที่ 2 โปรแกรมนี้จัดการ "ห้องแชท" วิดีโอแชท WebRTC:

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

แอปพลิเคชัน WebRTC แบบง่ายของเราจะอนุญาตให้ผู้เข้าร่วมใช้ห้องร่วมกันได้สูงสุด 2 คน

HTML และ JavaScript

อัปเดต index.html ให้มีลักษณะดังนี้

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

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

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

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

</html>

คุณจะไม่เห็นข้อมูลใดๆ บนหน้าเว็บในขั้นตอนนี้ กล่าวคือ การบันทึกทั้งหมดจะทำในคอนโซลเบราว์เซอร์ (หากต้องการดูคอนโซลใน Chrome ให้กด Ctrl-Shift-J หรือ Command-Option-J หากใช้ Mac)

แทนที่ js/main.js ด้วยข้อมูลต่อไปนี้

'use strict';

var isInitiator;

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

var socket = io.connect();

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

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

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

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

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

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

ตั้งค่า Socket.IO เพื่อให้ทำงานบน Node.js

ในไฟล์ HTML คุณอาจเห็นว่าคุณกำลังใช้ไฟล์ Socket.IO

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

ที่ระดับบนสุดของไดเรกทอรี work ให้สร้างไฟล์ชื่อ package.json ซึ่งมีเนื้อหาต่อไปนี้

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

นี่คือไฟล์ Manifest ของแอปที่แจ้งให้ผู้จัดการแพ็กเกจโหนด (npm) ทราบว่าต้องติดตั้งทรัพยากร Dependency ของโปรเจ็กต์ใด

หากต้องการติดตั้งทรัพยากร Dependency (เช่น /socket.io/socket.io.js) ให้เรียกใช้คำสั่งต่อไปนี้จากเทอร์มินัลบรรทัดคำสั่งในไดเรกทอรี work ของคุณ

npm install

คุณควรเห็นบันทึกการติดตั้งที่สิ้นสุดคล้ายๆ กับนี้

3ab06b7bcc7664b9.png

คุณจะเห็นว่า npm ติดตั้ง Dependencies ที่กำหนดไว้ใน package.json แล้ว

สร้างไฟล์ index.js ใหม่ที่ระดับบนสุดของไดเรกทอรี work (ไม่ใช่ในไดเรกทอรี js) แล้วเพิ่มโค้ดต่อไปนี้

'use strict';

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

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

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

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

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

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

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

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

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

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

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

});

จากเทอร์มินัลบรรทัดคำสั่ง ให้เรียกใช้คำสั่งต่อไปนี้ในไดเรกทอรี work

node index.js

จากเบราว์เซอร์ ให้เปิด localhost:8080

ทุกครั้งที่เปิด URL นี้ ระบบจะขอให้คุณป้อนชื่อห้อง หากต้องการเข้าร่วมห้องเดียวกัน ให้เลือกชื่อห้องเดิมทุกครั้ง เช่น "foo"

เปิดหน้าแท็บใหม่ แล้วเปิด localhost:8080 อีกครั้ง เลือกชื่อห้องเดียวกัน

เปิด localhost:8080 ในแท็บหรือหน้าต่างที่ 3 เลือกชื่อห้องเดิมอีกครั้ง

ตรวจสอบคอนโซลในแต่ละแท็บ ซึ่งคุณควรเห็นการบันทึกจาก JavaScript ด้านบน

คะแนนโบนัส

  1. กลไกการสื่อสารแบบอื่นที่เป็นไปได้มีอะไรบ้าง คุณอาจพบปัญหาใดเมื่อใช้ "บริสุทธิ์" WebSocket
  2. ปัญหาใดที่อาจเกี่ยวข้องกับการปรับขนาดแอปพลิเคชันนี้ คุณพัฒนาวิธีทดสอบคำขอห้องพักพร้อมกันหลายพันหรือหลายล้านรายการได้ไหม
  3. แอปนี้ใช้พรอมต์ JavaScript เพื่อรับชื่อห้อง หาวิธีดึงชื่อห้องจาก URL เช่น localhost:8080/foo จะตั้งชื่อห้องว่า foo

สิ่งที่คุณได้เรียนรู้

ในขั้นตอนนี้ คุณได้เรียนรู้วิธี:

  • ใช้ npm เพื่อติดตั้งทรัพยากร Dependency ของโปรเจ็กต์ตามที่ระบุไว้ในแพ็กเกจ.json
  • เรียกใช้เซิร์ฟเวอร์ Node.js ไปยังเซิร์ฟเวอร์ไฟล์แบบคงที่
  • ตั้งค่าบริการรับส่งข้อความบน Node.js โดยใช้ socket.io
  • ใช้เพื่อสร้าง "ห้อง" และแลกเปลี่ยนข้อความกัน

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้จะอยู่ในโฟลเดอร์ step-04

ดูข้อมูลเพิ่มเติม

รายการถัดไป

ดูวิธีใช้การส่งสัญญาณเพื่อให้ผู้ใช้ 2 คนเชื่อมต่อกันได้

8. รวมการเชื่อมต่อและการส่งสัญญาณแบบเพียร์

สิ่งที่คุณจะได้เรียนรู้

ในขั้นตอนนี้ คุณจะพบกับวิธีการ:

  • เรียกใช้บริการส่งสัญญาณ WebRTC โดยใช้ Socket.IO ที่ทำงานบน Node.js
  • ใช้บริการดังกล่าวเพื่อแลกเปลี่ยนข้อมูลเมตา WebRTC ระหว่างแอปเทียบเท่า

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-05

แทนที่ HTML และ JavaScript

แทนที่เนื้อหาของ index.html ด้วยข้อมูลต่อไปนี้

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

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

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

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

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

</html>

แทนที่ js/main.js ด้วยเนื้อหาของ step-05/js/main.js

เรียกใช้เซิร์ฟเวอร์ Node.js

หากคุณไม่ได้ติดตาม Codelab นี้จากไดเรกทอรี work คุณอาจต้องติดตั้งทรัพยากร Dependency สำหรับโฟลเดอร์ step-05 หรือโฟลเดอร์ที่ใช้งานอยู่ในปัจจุบัน เรียกใช้คำสั่งต่อไปนี้จากไดเรกทอรีการทำงานของคุณ

npm install

เมื่อติดตั้งแล้ว หากเซิร์ฟเวอร์ Node.js ไม่ทำงาน ให้เริ่มต้นโดยเรียกใช้คำสั่งต่อไปนี้ในไดเรกทอรี work

node index.js

ตรวจสอบว่าคุณกำลังใช้ index.js เวอร์ชันจากขั้นตอนก่อนหน้าที่ใช้ Socket.IO ดูข้อมูลเพิ่มเติมเกี่ยวกับ Node และ Socket IO ได้ที่ส่วน "ตั้งค่าบริการส่งสัญญาณเพื่อแลกเปลี่ยนข้อความ"

จากเบราว์เซอร์ ให้เปิด localhost:8080

เปิด localhost:8080 อีกครั้งในแท็บหรือหน้าต่างใหม่ องค์ประกอบวิดีโอ 1 รายการจะแสดงสตรีมในเครื่องจาก getUserMedia() และอีกองค์ประกอบหนึ่งจะแสดง "รีโมต" ที่สตรีมผ่าน RTCPeerconnection

ดูการบันทึกในคอนโซลของเบราว์เซอร์

คะแนนโบนัส

  1. แอปพลิเคชันนี้สนับสนุนวิดีโอแชทแบบตัวต่อตัวเท่านั้น คุณจะเปลี่ยนการออกแบบอย่างไรเพื่อให้ผู้ใช้มากกว่า 1 คนแชร์ห้องวิดีโอแชทเดียวกันได้
  2. ตัวอย่างมีชื่อห้องว่า foo ฮาร์ดโค้ด วิธีใดคือวิธีที่ดีที่สุดในการเปิดใช้ชื่อห้องอื่นๆ
  3. ผู้ใช้จะแชร์ชื่อห้องอย่างไร ลองสร้างทางเลือกอื่นในการแชร์ชื่อห้อง
  4. คุณจะเปลี่ยนแอปได้อย่างไร

สิ่งที่คุณได้เรียนรู้

ในขั้นตอนนี้ คุณได้เรียนรู้วิธีต่อไปนี้

  • เรียกใช้บริการส่งสัญญาณ WebRTC โดยใช้ Socket.IO ที่ทำงานบน Node.js
  • ใช้บริการดังกล่าวเพื่อแลกเปลี่ยนข้อมูลเมตา WebRTC ระหว่างแอปเทียบเท่า

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-05

เคล็ดลับ

  • สถิติ WebRTC และข้อมูลการแก้ไขข้อบกพร่องสามารถดูได้จาก chrome://webrtc-internals
  • test.webrtc.org สามารถใช้เพื่อตรวจสอบสภาพแวดล้อมภายในและทดสอบกล้องถ่ายรูปและไมโครโฟน
  • หากคุณประสบปัญหาแปลกๆ ในการแคช ให้ลองทำตามขั้นตอนต่อไปนี้
  • รีเฟรชทั้งหมดโดยกด Ctrl ค้างไว้ แล้วคลิกปุ่มโหลดซ้ำ
  • รีสตาร์ทเบราว์เซอร์
  • เรียกใช้ npm cache clean จากบรรทัดคำสั่ง

รายการถัดไป

ดูวิธีถ่ายรูป รับข้อมูลรูปภาพ และแชร์รูปนี้ระหว่างเพื่อนร่วมทางระยะไกล

9. ถ่ายรูปและแชร์ผ่านช่องข้อมูล

สิ่งที่คุณจะได้เรียนรู้

ในขั้นตอนนี้ คุณจะได้เรียนรู้วิธีการต่อไปนี้

  • ถ่ายภาพและรับข้อมูลจากภาพโดยใช้องค์ประกอบ Canvas
  • แลกเปลี่ยนข้อมูลรูปภาพกับผู้ใช้ระยะไกล

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-06

วิธีการทำงาน

ก่อนหน้านี้คุณได้เรียนรู้วิธีแลกเปลี่ยน SMS โดยใช้ RTCDataChannel

ขั้นตอนนี้ช่วยให้แชร์ทั้งไฟล์ได้ ในตัวอย่างนี้คือรูปภาพที่ถ่ายผ่าน getUserMedia()

ส่วนหลักของขั้นตอนนี้มีดังนี้

  1. สร้างช่องทางข้อมูล โปรดทราบว่าคุณไม่ได้เพิ่มสตรีมสื่อไปยังการเชื่อมต่อเพียร์ในขั้นตอนนี้
  2. บันทึกสตรีมวิดีโอจากเว็บแคมของผู้ใช้ด้วย getUserMedia():
var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}
  1. เมื่อผู้ใช้คลิกปุ่มสแนป ก็จะได้สแนปชอต (เฟรมวิดีโอ) จากสตรีมวิดีโอและแสดงในองค์ประกอบ canvas ดังนี้
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}
  1. เมื่อผู้ใช้คลิกปุ่มส่ง ให้แปลงรูปภาพเป็นไบต์และส่งผ่านช่องทางข้อมูลดังนี้
function sendPhoto() {
  // Split data channel message in chunks of this byte length.
  var CHUNK_LEN = 64000;
  var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
    len = img.data.byteLength,
    n = len / CHUNK_LEN | 0;

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

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

  // send the reminder, if any
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}
  1. ฝั่งผู้รับจะแปลงจำนวนไบต์ของข้อความช่องทางข้อมูลกลับไปเป็นรูปภาพ และแสดงรูปภาพต่อผู้ใช้ดังนี้
function receiveDataChromeFactory() {
  var buf, count;

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

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

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

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

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

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

รับโค้ด

แทนที่เนื้อหาของโฟลเดอร์ work ด้วยเนื้อหาของ step-06 ไฟล์ index.html ในงานควรมีลักษณะดังนี้****

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

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

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

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

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

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

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

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

</body>

</html>

หากคุณไม่ได้ติดตาม Codelab นี้จากไดเรกทอรี work คุณอาจต้องติดตั้งทรัพยากร Dependency สำหรับโฟลเดอร์ step-06 หรือโฟลเดอร์ที่ใช้งานอยู่ในปัจจุบัน เพียงเรียกใช้คำสั่งต่อไปนี้จากไดเรกทอรีการทำงานของคุณ:

npm install

เมื่อติดตั้งแล้ว หากเซิร์ฟเวอร์ Node.js ไม่ทำงาน ให้เริ่มต้นโดยเรียกใช้คำสั่งต่อไปนี้จากไดเรกทอรี work

node index.js

ตรวจสอบว่าคุณใช้ index.js เวอร์ชันที่ใช้ Socket.IO และอย่าลืมรีสตาร์ทเซิร์ฟเวอร์ Node.js หากคุณทำการเปลี่ยนแปลง ดูข้อมูลเพิ่มเติมเกี่ยวกับ Node และ Socket IO ได้ที่ส่วน "ตั้งค่าบริการส่งสัญญาณเพื่อแลกเปลี่ยนข้อความ"

หากจำเป็น ให้คลิกปุ่มอนุญาตเพื่ออนุญาตให้แอปใช้เว็บแคม

แอปจะสร้างรหัสห้องแบบสุ่มแล้วเพิ่มรหัสนั้นลงใน URL เปิด URL จากแถบที่อยู่ในแท็บหรือหน้าต่างใหม่ของเบราว์เซอร์

คลิกปุ่ม Snap & ปุ่มส่ง จากนั้นดูที่พื้นที่ขาเข้าในอีกแท็บหนึ่งที่ด้านล่างของหน้า แอปจะโอนรูปภาพระหว่างแท็บ

คุณควรจะเห็นบางสิ่งเช่นนี้:

911b40f36ba6ba8.png

คะแนนโบนัส

  1. คุณจะเปลี่ยนโค้ดเพื่อให้แชร์ไฟล์ได้ทุกประเภทได้อย่างไร

ดูข้อมูลเพิ่มเติม

สิ่งที่คุณได้เรียนรู้

  • วิธีถ่ายภาพและรับข้อมูลจากภาพโดยใช้องค์ประกอบ Canvas
  • วิธีแลกเปลี่ยนข้อมูลดังกล่าวกับผู้ใช้ระยะไกล

เวอร์ชันที่สมบูรณ์ของขั้นตอนนี้อยู่ในโฟลเดอร์ step-06

10. ขอแสดงความยินดี

คุณสร้างแอปเพื่อสตรีมวิดีโอแบบเรียลไทม์และแลกเปลี่ยนข้อมูล

สิ่งที่คุณได้เรียนรู้

คุณได้เรียนรู้วิธีต่อไปนี้ใน Codelab

  • รับวิดีโอจากเว็บแคมของคุณ
  • สตรีมวิดีโอด้วย RTCPeerConnection
  • สตรีมข้อมูลด้วย RTCDataChannel
  • ตั้งค่าบริการส่งสัญญาณเพื่อแลกเปลี่ยนข้อความ
  • รวมการเชื่อมต่อและการส่งสัญญาณแบบเพียร์เข้าด้วยกัน
  • ถ่ายรูปและแชร์ผ่านช่องข้อมูล

ขั้นตอนถัดไป

ดูข้อมูลเพิ่มเติม

  • แหล่งข้อมูลมากมายสำหรับการเริ่มต้นใช้งาน WebRTC มีให้บริการที่ webrtc.org