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

1. ข้อควรทราบก่อนที่จะเริ่มต้น

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

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

  • ความรู้พื้นฐานเกี่ยวกับ HTML, CSS และ JavaScript

สิ่งที่คุณจะสร้าง

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

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

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

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

  1. หากคุณคุ้นเคยกับ Git ให้เรียกใช้คําสั่งนี้เพื่อโคลนโค้ดสําหรับ Codelab นี้จาก GitHub ดังนี้
git clone https://github.com/googlecodelabs/webrtc-web

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

  1. เปิดไฟล์ ZIP ที่ดาวน์โหลดเพื่อคลายโฟลเดอร์โฟลเดอร์ชื่อ webrtc-web-master ซึ่งมีโฟลเดอร์เดียวสําหรับแต่ละขั้นตอนของ Codelab นี้และทรัพยากรทั้งหมดที่คุณต้องการ

โค้ดทั้งหมดจะทําในไดเรกทอรีชื่อ work

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

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

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

  1. หากคุณไม่มีเว็บเซิร์ฟเวอร์สําหรับ Chrome ให้คลิกลิงก์นี้เพื่อติดตั้งจาก Chrome เว็บสโตร์:

D0a4649b4920cf3.png

  1. คลิกเพิ่มใน Chrome ซึ่งจะติดตั้งเว็บเซิร์ฟเวอร์สําหรับ Chrome และเปิดแอป Google โดยอัตโนมัติในแท็บใหม่
  2. คลิกเว็บเซิร์ฟเวอร์ดังนี้

27fce4494f641883.png

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

a300381a486b9e22.png

  1. คลิกเลือกโฟลเดอร์
  2. เลือกโฟลเดอร์ work ที่คุณสร้าง

ในส่วน URL ของเว็บเซิร์ฟเวอร์ คุณจะเห็น URL ที่คุณสามารถดูงานที่อยู่ระหว่างดําเนินการได้

Chrome

  1. ในส่วนตัวเลือก (อาจต้องรีสตาร์ท) ให้เลือกช่องทําเครื่องหมายแสดง index.html โดยอัตโนมัติ
  2. สลับเว็บเซิร์ฟเวอร์: เริ่ม 2 ครั้งเพื่อหยุดและรีสตาร์ทเซิร์ฟเวอร์

f23cafb3993dfac1.png

  1. คลิก URL ใต้ URL ของเว็บเซิร์ฟเวอร์เพื่อดูงานของคุณในเว็บเบราว์เซอร์

คุณควรจะเห็นหน้าลักษณะนี้ ซึ่งสอดคล้องกับ work/index.html:

18a705cb6ccc5181.png

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

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

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

เพิ่มขีดกลาง HTML

คัดลอกโค้ดนี้และวางลงในไฟล์ index.html ในไดเรกทอรี work เพื่อเพิ่มองค์ประกอบ video และ script

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <video autoplay playsinline></video>

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

</body>

</html>

บีบ JavaScript เล็กน้อย

คัดลอกโค้ดนี้และวางลงในไฟล์ main.js ในโฟลเดอร์ js:

'use strict';

// In this codelab, you  only stream video, not audio (video: true).
const mediaStreamConstraints = {
  video: true,
};

// The video element where the stream is displayed
const localVideo = document.querySelector('video');

// The local stream that's displayed on the video
let localStream;

// Handle success and add the MediaStream to the video element
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

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

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

ลองใช้

เปิดไฟล์ index.html ในเบราว์เซอร์ คุณควรเห็นไฟล์ลักษณะนี้ แต่มียอดดูจากเว็บแคม

9297048e43ed0f3d.png

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

หลังการเรียกใช้ getUserMedia() เบราว์เซอร์จะขอสิทธิ์เข้าถึงกล้องหากเป็นคําขอแรกสําหรับการเข้าถึงกล้องสําหรับต้นทางปัจจุบัน

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

เคล็ดลับ

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

ตรวจสอบว่าองค์ประกอบวิดีโอไม่ทําให้คอนเทนเนอร์ล้น Codelab นี้เพิ่ม width และ max-width เพื่อกําหนดขนาดที่ต้องการและขนาดสูงสุดสําหรับวิดีโอ เบราว์เซอร์จะคํานวณความสูงโดยอัตโนมัติ

video {
  max-width: 100%;
  width: 320px;
}

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

ขั้นตอนนี้มีทั้งหมดอยู่ในโฟลเดอร์ step-2

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

ในไฟล์ index.html ให้แทนที่องค์ประกอบ video รายการเดียวด้วยองค์ประกอบ video และ button จํานวน 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 (ในแอปจริง องค์ประกอบ video รายการหนึ่งจะแสดงสตรีมในเครื่อง และอีกรายการจะแสดงสตรีมระยะไกล)

เพิ่ม Shim ของอะแดปเตอร์.js

คัดลอกองค์ประกอบสคริปต์นี้ แล้ววางไว้เหนือองค์ประกอบสคริปต์สําหรับ main.js:

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

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

<!DOCTYPE html>
<html>

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

<body>
  <h1>Real-time 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

โทรออก

  1. เปิดไฟล์ index.html
  2. คลิกเริ่มเพื่อรับวิดีโอจากเว็บแคม
  3. คลิกโทรเพื่อเชื่อมต่อกับเพื่อน

คุณควรเห็นวิดีโอเดียวกันจากเว็บแคมของคุณในองค์ประกอบ video ทั้งคู่

  1. ดูคอนโซลเบราว์เซอร์เพื่อดูการบันทึก WebRTC

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

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

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

ในตัวอย่างนี้ ออบเจ็กต์ RTCPeerConnection จํานวน 2 รายการอยู่ในหน้าเดียวกัน ได้แก่ pc1 และ pc2

การตั้งค่าการโทรระหว่างกลุ่ม WebRTC เกี่ยวข้องกับงาน 3 อย่างดังนี้

  1. สร้าง RTCPeerConnection สําหรับการโทรแต่ละสายและเพิ่มสตรีมในพื้นที่จาก getUserMedia() ในแต่ละสาย
  2. รับและแชร์ข้อมูลเครือข่าย

ปลายทางการเชื่อมต่อที่เป็นไปได้ที่รู้จักกันในชื่อผู้สมัคร ICE

  1. รับและแชร์คําอธิบายในพื้นที่และระยะไกล

ข้อมูลเมตาเกี่ยวกับสื่อท้องถิ่นอยู่ในรูปแบบ Session Description Protocol (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 จากขั้นตอนแรกเมื่อผู้สมัครเครือข่ายพร้อมให้ใช้งาน
  2. ขวัญใจส่งข้อมูลผู้สมัครตามลําดับให้กับบัญชา

ในแอปจริง กระบวนการนี้ (เรียกว่าการส่งสัญญาณ) จะเกิดขึ้นผ่านบริการข้อความ ดูวิธีการได้ในภายหลัง แน่นอนว่าในขั้นตอนนี้ ออบเจ็กต์ RTCPeerConnection ทั้ง 2 รายการอยู่ในหน้าเดียวกัน และสื่อสารได้โดยตรงโดยไม่ต้องมีการรับส่งข้อความภายนอก

  1. เมื่อบัญชาได้รับข้อความจากผู้สมัครรับเลือกตั้งจากขวัญใจ เขาโทรหา 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 จะต้องค้นพบและแลกเปลี่ยนข้อมูลสื่อเสียงและวิดีโอในพื้นที่และระยะไกล เช่น ความละเอียดและตัวแปลงรหัส การส่งสัญญาณเพื่อแลกเปลี่ยนข้อมูลการกําหนดค่าสื่อจะดําเนินการแลกเปลี่ยนข้อมูลข้อมูลเมตาต่างๆ หรือที่เรียกว่าข้อเสนอและคําตอบ โดยใช้รูปแบบ SDP

  1. ขวัญใจใช้เมธอด RTCPeerConnection createOffer()

คําสัญญาที่ส่งคืนให้คําอธิบายเซสชันในพื้นที่ของ RTCSessionDescription - อลิซ

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. หากสําเร็จ อลิซตั้งค่าคําอธิบายในพื้นที่โดยใช้ setLocalDescription() แล้วส่งคําอธิบายเซสชันให้บัญชาผ่านช่องทางส่งสัญญาณ
  2. อานนท์กําหนดคําอธิบายที่อลิซส่งเป็นคําอธิบายระยะไกลด้วย setRemoteDescription()
  3. บัญชาเรียกใช้เมธอด createAnswer() ของ RTCPeerConnection และส่งคําอธิบายระยะไกลที่อลิซได้รับจากอลิซ เพื่อให้เซสชันในเครื่องสร้างขึ้นเพื่อเข้ากันได้กับกําหนดการของอลิซ
  4. คําสัญญา createAnswer() จะผ่าน RTCSessionDescription ซึ่งบัญชากําหนดให้เป็นคําอธิบายในพื้นที่และส่งไปยังอลิซ
  5. เมื่ออลิซได้รับคําอธิบายเซสชันของบัญชาแล้ว เธอจะกําหนดเป็นคําอธิบายระยะไกลด้วย 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. ไปที่ chrome://webrtc-internals

หน้านี้ให้สถิติ WebRTC และข้อมูลการแก้ไขข้อบกพร่อง (ดูรายการ URL ของ Chrome ได้ที่ chrome://about)

  1. จัดรูปแบบหน้าด้วย CSS
  2. วางวิดีโอไว้ข้างกัน
  3. ทําให้ปุ่มมีความกว้างเท่ากันด้วยข้อความขนาดใหญ่ขึ้น
  4. ตรวจสอบว่าเลย์เอาต์ทํางานบนอุปกรณ์เคลื่อนที่
  5. จากคอนโซลเครื่องมือสําหรับนักพัฒนาซอฟต์แวร์ Chrome ให้ดู localStream, localPeerConnection และ remotePeerConnection
  6. ดู localPeerConnectionpc1.localDescription จากคอนโซล

รูปแบบ SDP มีลักษณะอย่างไร

เคล็ดลับ

  • ดูข้อมูลเพิ่มเติมเกี่ยวกับอะแดปเตอร์.ads.js ได้ที่ adapter.js ที่เก็บของ GitHub
  • ดู AppRTC และโค้ดของแอปซึ่งเป็นแอป Canonical ของ WebRTC สําหรับการเรียก WebRTC เวลาตั้งค่าการโทรน้อยกว่า 500 มิลลิวินาที

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

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

5. ใช้ช่องทางข้อมูลเพื่อแลกเปลี่ยนข้อมูล

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

อัปเดต HTML

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

นําองค์ประกอบ video และ button ออกจาก 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>

textarea รายการหนึ่งมีไว้สําหรับป้อนข้อความ ส่วนอีกรายการมีไว้สําหรับแสดงข้อความระหว่างสตรีมแอปเทียบเท่า

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

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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

  1. แทนที่ main.js ด้วยเนื้อหาของ step-03/js/main.js
  1. ลองสตรีมมิงระหว่างแอปเทียบเท่า:
  2. เปิด index.html
  3. คลิกเริ่มต้นเพื่อตั้งค่าการเชื่อมต่อเพียร์
  4. ป้อนข้อความใน textarea ทางด้านซ้าย
  5. คลิกส่งเพื่อโอนข้อความโดยใช้ช่องทางข้อมูล WebRTC

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

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

โค้ดส่วนใหญ่ในขั้นตอนนี้เหมือนกับในตัวอย่าง 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 คุณสามารถกําหนดค่าช่องทางข้อมูลเพื่อให้แชร์ข้อมูลประเภทต่างๆ ได้ เช่น ให้ความสําคัญกับการแสดงโฆษณาที่เชื่อถือได้มากกว่าประสิทธิภาพ

คะแนนโบนัส

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

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

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

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

ขั้นตอนนี้มีทั้งหมดอยู่ในโฟลเดอร์ step-04

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

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

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

ในตัวอย่างนี้ เซิร์ฟเวอร์ (แอป 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

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

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

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

</html>

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

  1. แทนที่ 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>
  1. สร้างไฟล์ชื่อ package.json ที่ระดับบนสุดของไดเรกทอรี work ที่มีเนื้อหาต่อไปนี้
{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

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

การพึ่งพา

  1. หากต้องการติดตั้งทรัพยากร Dependency เช่น /socket.io/socket.io.js ให้เรียกใช้คําสั่งต่อไปนี้จากเทอร์มินัลบรรทัดคําสั่งในไดเรกทอรี work
npm install

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

ไฟล์ 3ab06b7bcc7664b9.png

คุณจะเห็น npm ติดตั้งทรัพยากร Dependency ที่กําหนดไว้ใน package.json

  1. สร้างไฟล์ใหม่ 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);
        }
      });
    }
  });

});
  1. จากบรรทัดคําสั่ง ให้เรียกใช้คําสั่งต่อไปนี้ในไดเรกทอรี work
node index.js
  1. เปิดเบราว์เซอร์แล้วไปที่ http://localhost:8080

ทุกครั้งที่คุณไปยัง URL นี้ ระบบจะแจ้งให้ป้อนชื่อห้องแชท

หากต้องการเข้าร่วมห้องแชทเดียวกัน ให้ป้อนชื่อห้องเดียวกันทุกครั้ง เช่น foo

  1. เปิดแท็บใหม่เพื่อไปที่ http://localhost:8080 อีกครั้ง และป้อนชื่อห้องเดิมอีกครั้ง
  2. เปิดแท็บใหม่อีกแท็บแล้วไปที่ http://localhost:8080 แล้วป้อนชื่อห้องเดิมอีกครั้ง
  3. ตรวจสอบคอนโซลในแต่ละแท็บ

คุณควรเห็นการบันทึกจาก JavaScript

คะแนนโบนัส

  • กลไกการรับส่งข้อความอื่นที่อาจเป็นไปได้มีอะไรบ้าง คุณอาจพบปัญหาใดโดยใช้ Pure ของ WebSocket
  • ปัญหาใดที่อาจเกี่ยวข้องกับการปรับขนาดแอปนี้ ช่วยพัฒนาวิธีทดสอบคําขอห้องในเวลาเดียวกันหรือหลายพันคําขอได้ไหม
  • แอปนี้ใช้ข้อความแจ้ง JavaScript เพื่อรับชื่อห้อง ค้นหาวิธีรับชื่อห้องจาก URL เช่น http://localhost:8080/foo จะตั้งชื่อห้องแชทว่า foo

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

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

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

แทนที่ HTML และ JavaScript

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

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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>
  1. แทนที่ js/main.js ด้วยเนื้อหาของ step-05/js/main.js

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

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

  1. เรียกใช้คําสั่งต่อไปนี้จากไดเรกทอรีการทํางานของคุณ
npm install
  1. เมื่อติดตั้งแล้ว หากเซิร์ฟเวอร์ Node.js ของคุณไม่ทํางาน ให้เรียกใช้คําสั่งต่อไปนี้โดยเรียกใช้คําสั่งต่อไปนี้ในไดเรกทอรี work
node index.js

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

  1. เปิดเบราว์เซอร์แล้วไปที่ http://localhost:8080
  2. เปิดแท็บใหม่และไปที่ http://localhost:8080 อีกครั้ง

องค์ประกอบ video รายการหนึ่งแสดงสตรีมท้องถิ่นจาก getUserMedia() และอีกรายการแสดงวิดีโอระยะไกลที่สตรีมผ่าน RTCPeerconnection

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

คะแนน****เพื่อให้คะแนนเพิ่มขึ้น

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

เคล็ดลับ

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

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

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

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

ก่อนหน้านี้คุณได้เรียนรู้วิธีแลกเปลี่ยน SMS โดยใช้ RTCDataChannel ขั้นตอนนี้จะช่วยให้แชร์ไฟล์ทั้งหมดได้ ในตัวอย่างนี้ รูปภาพจะบันทึกด้วย getUserMedia()

องค์ประกอบหลักของขั้นตอนนี้มีดังนี้

  1. สร้างแชแนลข้อมูล

คุณไม่ควรเพิ่มสตรีมสื่อใดๆ ลงในการเชื่อมต่อเพียร์ในขั้นตอนนี้

  1. บันทึกวิดีโอวิดีโอจากเว็บแคมด้วย 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 the 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 approximately 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 applicable.
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}

ฝั่งที่รับจะแปลงไบต์ของข้อความช่องข้อมูลให้เป็นรูปภาพและแสดงรูปภาพให้กับผู้ใช้ดังนี้

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');
  // The trail is the element that holds 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);
}

รับโค้ด

  1. แทนที่เนื้อหาของโฟลเดอร์ work ด้วยเนื้อหาของ step-06

ไฟล์ index.html ใน work ควรมีลักษณะดังนี้****

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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>
  1. หากไม่ทําตาม Codelab นี้จากไดเรกทอรี work คุณอาจต้องติดตั้งทรัพยากร Dependency สําหรับโฟลเดอร์ step-06 หรือโฟลเดอร์การทํางานปัจจุบัน เพียงเรียกใช้คําสั่งต่อไปนี้จากไดเรกทอรีการทํางานของคุณ
npm install
  1. เมื่อติดตั้งแล้ว หากเซิร์ฟเวอร์ Node.js ของคุณไม่ทํางาน ให้เรียกใช้คําสั่งต่อไปนี้โดยเรียกใช้คําสั่งต่อไปนี้จากไดเรกทอรี work ของคุณ
node index.js
    Make sure that you're using the version of `index.js` that implements Socket.IO and 

อย่าลืมรีสตาร์ทเซิร์ฟเวอร์ Node.js ของคุณหากคุณทําการเปลี่ยนแปลง

ดูข้อมูลเพิ่มเติมเกี่ยวกับโหนดและ Socket.IO ได้ในส่วน "ตั้งค่าการส่งสัญญาณ"

บริการแลกเปลี่ยนข้อความ

  1. หากจําเป็น ให้คลิกอนุญาตเพื่อให้แอปใช้เว็บแคมของคุณ

แอปจะสร้างรหัสห้องแบบสุ่มและเพิ่มรหัสดังกล่าวลงใน URL

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

แอปจะโอนรูปภาพระหว่างแท็บ

คุณควรจะมีลักษณะดังนี้

911b40f36ba6ba8.png

คะแนนโบนัส

คุณสามารถเปลี่ยนโค้ดเพื่อให้สามารถแชร์ไฟล์ประเภทใดได้บ้าง

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

9. ยินดีด้วย

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

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