הפעלת תקשורת בזמן אמת באמצעות WebRTC

1. לפני שמתחילים

שיעור Lab זה מלמד איך לבנות אפליקציה כדי להשיג סרטונים ולצלם תמונות באמצעות מצלמת האינטרנט שלכם, ולשתף אותם מקצה לקצה עם WebRTC. תלמדו גם איך להשתמש בממשקי ה-API הבסיסיים של WebRTC ולהגדיר שרת הודעות באמצעות Node.js.

דרישות מוקדמות

  • ידע בסיסי ב-HTML, CSS ו-JavaScript

מה תפתחו

  • מקבלים סרטונים ממצלמת האינטרנט.
  • שידור הווידאו באמצעות RTCPeerConnection.
  • שידור הנתונים עם RTCDataChannel.
  • מגדירים שירות אותות לצורך העברת הודעות.
  • שילוב של חיבור בין אותות ואות.
  • מצלמים תמונה ומשתמשים בערוץ נתונים כדי לשתף אותה.

מה תצטרך להכין

2. קבלת הקוד לדוגמה

להורדת הקוד

  1. אם אתם מכירים את Git, בצעו את הפקודה הבאה כדי לשכפל את הקוד של מעבדת הקוד הזו מ-GitHub:
git clone https://github.com/googlecodelabs/webrtc-web

לחלופין, ניתן ללחוץ על הקישור הזה כדי להוריד קובץ ZIP של הקוד:

  1. יש לפתוח את קובץ ה-ZIP שהוּרדו, כדי לפתוח את תיקיית הפרויקט בשם webrtc-web-master. התיקייה הזו מכילה תיקייה אחת לכל שלב ב-codelab זה ואת כל המשאבים הדרושים לך.

כל הקוד שלך נמצא בספרייה שנקראת work.

step-nn התיקיות מכילות גרסה סופית לכל שלב במעבדה הזו. הם זמינים לעיון.

התקנה ואימות של שרת אינטרנט

אמנם יש לך אפשרות להשתמש בשרת אינטרנט משלך, אך קוד Lab זה תוכנן לפעול היטב עם שרת אינטרנט ל-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. כדי להפסיק ולהפעיל מחדש את השרת, מפעילים פעמיים את שרת האינטרנט: התחיל.

f23cafb3993dfac1.png

  1. לוחצים על כתובת ה-URL בקטע כתובות URL של שרת אינטרנט כדי לראות את העבודה שלכם בדפדפן האינטרנט.

אמור להופיע דף שנראה כך: work/index.html

18a705cb6sandbox5181.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 Developer Tools כדי לבדוק זאת.
  • מוסיפים מסנני CSS לרכיב הווידאו, כך:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • מוסיפים מסנני SVG, כמו זאת:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

טיפים

שיטה מומלצת

מוודאים שהרכיב של הסרטון לא חורג מהמאגר שלו. Lablab זה הוסיף את width ואת max-width כדי להגדיר גודל מועדף וגודל מקסימלי לסרטון. הדפדפן מחשב את הגובה באופן אוטומטי.

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

4. שידור וידאו באמצעות RTCPeerConnection API

גרסה מלאה של השלב הזה נמצאת בתיקייה step-2.

הוספת רכיבים לסרטונים ולחצנים

בקובץ index.html, יש להחליף את הרכיב video היחיד בשני רכיבי video ו-button:

<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>

רכיב וידאו אחד מציג את השידור מ-getUserMedia() והשני מציג את אותו סרטון שהועבר דרך RTCPeerconnection. (באפליקציה של העולם האמיתי, רכיב אחד של video יציג את השידור המקומי והשני יציג את השידור המרוחק).

הוספת תופסן המתאם

יש להעתיק את רכיב הסקריפט הזה ולהדביק אותו מעל רכיב הסקריפט עבור 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 משתמש ב-API של RTCPeerConnection כדי להגדיר חיבור לסטרימינג של סרטונים בין לקוחות WebRTC, הידועים בשם 'עמיתים'.

בדוגמה הזו, שני האובייקטים מסוג RTCPeerConnection מופיעים באותו דף: pc1 ו-pc2.

הגדרת השיחה בין אפליקציות להשוואה דרך WebRTC כוללת שלוש משימות:

  1. יש ליצור RTCPeerConnection לכל סיום שיחה, ולהוסיף בכל אחד מהשידורים החיים את מקור השיחה getUserMedia().
  2. קבלה ושיתוף של פרטי רשת.

נקודות קצה (endpoint) של חיבור פוטנציאלי נקראות מועמדי ICE.

  1. אפשר לקבל ולשתף תיאורים מקומיים ומרוחקים.

מטא-נתונים על מדיה מקומית הם בפורמט פרוטוקול תיאור של סשן (SDP).

נניח שאליס ובוב רוצים להשתמש ב-RTCPeerConnection כדי ליצור וידאו צ'אט.

ראשית, אליס ובוב מחליפים פרטי רשת. הביטוי איתור מועמדים מתייחס לתהליך של חיפוש ממשקים ויציאות של רשתות באמצעות המסגרת ICE.

  1. דנה יוצרת אובייקט RTCPeerConnection עם handler של 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. ה-handler של onicecandidate, שמופיע בשלב הראשון, יופעל כשהמועמדים לרשת יהיו זמינים.
  2. דפנה שולחת נתוני מועמד לסידורים.

באפליקציה, התהליך הזה, המכונה אותות, מתבצע באמצעות שירות העברת הודעות. נלמד אותך איך לעשות זאת בשלב מאוחר יותר. כמובן שבשלב הזה שני האובייקטים של RTCPeerConnection נמצאים באותו דף והם יכולים לתקשר ישירות ללא צורך בהודעה חיצונית.

  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 צריכים גם לגלות ולהחליף את פרטי המדיה האודיו והווידאו המקומית והמרוחקת, כמו רזולוציה ויכולות קודק. סימון לחילופי מידע על תצורת מדיה ממשיך עם חילופי ה-blobs, הידועים כהצעה ותשובה, באמצעות הפורמט SDP.

  1. אליס מפעילה את השיטה RTCPeerConnection createOffer().

ההבטחה שהוחזרה כוללת תיאור של הסשן המקומי ב-RTCSessionDescription&lex:

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 Developer Console, בודקים את localStream, localPeerConnection ואת remotePeerConnection.
  6. במסוף, מחפשים את localPeerConnectionpc1.localDescription.

איך נראה פורמט SDP?

טיפים

  • מידע נוסף על המתאם מתאם.js זמין במאגר adpter.js ב-GitHub.
  • מומלץ לקרוא את AppRTC ואת הקוד שלה, האפליקציה הקנונית של WebRTC לקריאות ל-WebRTC. זמן הגדרת השיחה קצר מ-500 אלפיות השנייה.

שיטה מומלצת

כדי להגן על הקוד שלכם בעתיד, יש להשתמש בממשקי ה-API החדשים המבוססים על Promise ולהפעיל תאימות עם דפדפנים שלא תומכים בהם ב-adapter.js.

5. שימוש בערוץ נתונים להמרת נתונים

גרסה מלאה של השלב הזה נמצאת בתיקייה step-03.

עדכון ה-HTML

בשלב זה, משתמשים בערוצי נתונים של WebRTC כדי לשלוח טקסט בין שני רכיבי textarea באותו דף. זה לא מועיל במיוחד, אבל הוא מראה איך 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 כדי להפעיל שליחה של הודעות טקסט.

חלק גדול מהקוד בשלב זה זהה לקוד לדוגמה של 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 כדי לשפר את פריסת הדף ולהוסיף מאפיין placeholder ל-dataChannelReceive textarea.
  3. בודקים את הדף בנייד.

למידע נוסף

6. הגדרה של שירות אותות לצורך העברת הודעות

למדת איך להחליף נתונים בין עמיתים באותו דף, אבל איך עושים את זה בין מכונות שונות? תחילה, עליכם להגדיר ערוץ אותות כדי להחליף הודעות מטא-נתונים.

גרסה מלאה של השלב הזה נמצאת בתיקייה step-04.

על היישום

WebRTC משתמש ב-JavaScript בצד הלקוח, אבל לצורך שימוש בעולם האמיתי נדרש גם שרת אותות (העברת הודעות) ושרתי STUN ו-turn. מידע נוסף זמין כאן.

בשלב זה, אתם בונים שרת אותות פשוט של Node.js באמצעות המודול Socket.IO Node.js וספריית JavaScript להעברת הודעות.

בדוגמה הזו, השרת (אפליקציית Node.js) מוטמע ב-index.js והלקוח שפועל בו (אפליקציית האינטרנט) מוטמע ב-index.html.

לאפליקציית Node.js בשלב הזה יש שתי משימות.

תחילה, הוא משמש כממסר הודעות:

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

שנית, הוא מנהל את חדרי הצ'אט בווידאו של 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 מאפשרת לכל היותר שני עמיתים לשתף חדר.

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. ברמה העליונה של ספריית work, יוצרים קובץ בשם package.json עם התוכן הבא:
{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

זהו מניפסט של אפליקציה שמספר ל-Node Package Manager (npm) איזה פרויקט

יחסי תלות להתקנה.

  1. כדי להתקין קשרי תלות, כמו /socket.io/socket.io.js, מריצים את הקטע הבא ממסוף שורת הפקודה שבספרייה שלך ב-work:
npm install

אתם אמורים לראות יומן התקנה שמסתיים בערך כך:

3ab06b7bcc7664b9.png

כפי שאפשר לראות, npm התקין את התלות שהוגדרו ב-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.

מקבלים נקודות בונוס

  • אילו מנגנונים חלופיים לשליחת הודעות יכולים להיות? באילו בעיות נתקלת אם אתה משתמש ב-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

אם בחרת שלא לעקוב אחר קוד שיעור זה מספריית work שלך, ייתכן שיהיה צורך להתקין את תלויות החלון בתיקייה step-05 או בתיקיית העבודה הנוכחית.

  1. מריצים את הפקודה הבאה מספריית העבודה:
npm install
  1. לאחר ההתקנה, אם שרת Node.js לא פועל, מפעילים אותו על ידי הרצת הפקודה הבאה בספרייה work:
node index.js

חשוב לוודא שאתם משתמשים בגרסה של index.js מהשלב הקודם שמטמיע את Socket.IO. מידע נוסף על Node ו-Socket IO זמין בקטע 'הגדרת שירות אותות', שבו אפשר להחליף הודעות.

  1. בדפדפן, נכנסים לכתובת http://localhost:8080.
  2. פותחים כרטיסייה חדשה ועוברים שוב אל http://localhost:8080.

רכיב אחד של video מציג את השידור המקומי מ-getUserMedia() והשני מציג את הסרטון השלט הרחוק שהועבר דרך RTCPeerconnection.

  1. לצפייה ביומן במסוף הדפדפן.

ניקוד של b****נקודות

  • האפליקציה הזו תומכת רק בווידאו צ'אט אחד על אחד. איך תוכלו לשנות את העיצוב כדי לאפשר ליותר מאדם אחד לשתף את אותו חדר הווידאו?
  • בדוגמה, שם החדר foo הועתק בתוך הקוד. מה תהיה הדרך הטובה ביותר להפעיל שמות אחרים של חדרים?
  • איך המשתמשים יוכלו לשתף את שם החדר? אפשר לנסות לבנות חלופה לשיתוף שמות של חדרים.
  • איך אפשר לשנות את האפליקציה?

טיפים

  • בכתובת chrome://webrtc-internals אפשר למצוא נתונים על ניפוי באגים של WebRTC ונתונים על ניפוי באגים.
  • אפשר להשתמש בפותר הבעיות של WebRTC כדי לבדוק את הסביבה המקומית ולבדוק את המצלמה והמיקרופון.
  • אם נתקלתם בבעיות מוזרות במטמון, נסו את הפעולות הבאות:
  1. לוחצים על Control ולוחצים על טעינה מחדש של הדף הזה.
  2. מפעילים את הדפדפן מחדש.
  3. מריצים את npm cache clean משורת הפקודה.

8. צילום תמונה ושיתוף דרך ערוץ נתונים

גרסה מלאה של השלב הזה נמצאת בתיקייה step-06.

איך זה עובד

בעבר למדת איך לשלוח הודעות טקסט באמצעות 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. לוחצים על Snap כדי לקבל תמונת מצב (מסגרת של סרטון) מזרם הווידאו ולהציג אותו ברכיב 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. אם בחרת שלא לעקוב אחר קוד שיעור זה מספריית work שלך, ייתכן שיהיה צורך להתקין את תלויות החלון בתיקייה 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 אם יבוצעו השינויים.

למידע נוסף על Node ו-Socket.IO, יש לעיין בקטע 'הגדרת אותות'

שירות לחילופי הודעות.

  1. אם צריך, לוחצים על אישור כדי לאפשר לאפליקציה להשתמש במצלמת האינטרנט.

האפליקציה יוצרת מזהה חדר באופן אקראי ומוסיפה את המזהה לכתובת ה-URL.

  1. פותחים את כתובת ה-URL מסרגל הכתובות בכרטיסייה או בחלון חדשים בדפדפן.
  2. לוחצים על Snap &AMP; שליחה, ולאחר מכן מעיינים בקטע תמונות נכנסות בכרטיסייה השנייה שבתחתית הדף.

האפליקציה מעבירה תמונות בין כרטיסיות.

אתם אמורים לראות משהו כזה:

911b40f36ba6ba8.png

מקבלים נקודות בונוס

איך משנים את הקוד כדי לאפשר שיתוף של כל סוגי הקבצים?

למידע נוסף

9. מזל טוב

יצרתם אפליקציה לסטרימינג בזמן אמת ולהחלפת נתונים!

מידע נוסף