מידע על Codelab זה
1. לפני שמתחילים
שיעור Lab זה מלמד איך לבנות אפליקציה כדי להשיג סרטונים ולצלם תמונות באמצעות מצלמת האינטרנט שלכם, ולשתף אותם מקצה לקצה עם WebRTC. תלמדו גם איך להשתמש בממשקי ה-API הבסיסיים של WebRTC ולהגדיר שרת הודעות באמצעות Node.js.
דרישות מוקדמות
- ידע בסיסי ב-HTML, CSS ו-JavaScript
מה תפתחו
- מקבלים סרטונים ממצלמת האינטרנט.
- שידור הווידאו באמצעות
RTCPeerConnection
. - שידור הנתונים עם
RTCDataChannel
. - מגדירים שירות אותות לצורך העברת הודעות.
- שילוב של חיבור בין אותות ואות.
- מצלמים תמונה ומשתמשים בערוץ נתונים כדי לשתף אותה.
מה תצטרך להכין
- Chrome מגרסה 47 ואילך
- שרת אינטרנט עבור Chrome או שרת אינטרנט לבחירתכם
- עורך טקסט שתבחרו
- Node.js
2. קבלת הקוד לדוגמה
להורדת הקוד
- אם אתם מכירים את Git, בצעו את הפקודה הבאה כדי לשכפל את הקוד של מעבדת הקוד הזו מ-GitHub:
git clone https://github.com/googlecodelabs/webrtc-web
לחלופין, ניתן ללחוץ על הקישור הזה כדי להוריד קובץ ZIP של הקוד:
- יש לפתוח את קובץ ה-ZIP שהוּרדו, כדי לפתוח את תיקיית הפרויקט בשם
webrtc-web-master
. התיקייה הזו מכילה תיקייה אחת לכל שלב ב-codelab זה ואת כל המשאבים הדרושים לך.
כל הקוד שלך נמצא בספרייה שנקראת work
.
step-nn
התיקיות מכילות גרסה סופית לכל שלב במעבדה הזו. הם זמינים לעיון.
התקנה ואימות של שרת אינטרנט
אמנם יש לך אפשרות להשתמש בשרת אינטרנט משלך, אך קוד Lab זה תוכנן לפעול היטב עם שרת אינטרנט ל-Chrome.
- אם אין לך שרת אינטרנט עבור Chrome, לחץ על הקישור הזה כדי להתקין אותו מחנות האינטרנט של Chrome:
- לוחצים על הוספה ל-Chrome, שמתקינים את שרת האינטרנט של Chrome ופותחים את אפליקציות Google באופן אוטומטי בכרטיסייה חדשה.
- לוחצים על שרת אינטרנט:
תופיע תיבת דו-שיח שמאפשרת לך להגדיר את שרת האינטרנט המקומי שלך:
- לוחצים על בחירת תיקייה.
- בוחרים את התיקייה
work
שיצרתם.
בקטע כתובות URL של שרת אינטרנט, מופיעה כתובת ה-URL שבה ניתן לראות את העבודה שלך בתהליך.
Chrome.
- בקטע אפשרויות (ייתכן שיהיה צורך להפעיל מחדש), מסמנים את התיבה הצגה אוטומטית של index.html.
- כדי להפסיק ולהפעיל מחדש את השרת, מפעילים פעמיים את שרת האינטרנט: התחיל.
- לוחצים על כתובת ה-URL בקטע כתובות URL של שרת אינטרנט כדי לראות את העבודה שלכם בדפדפן האינטרנט.
אמור להופיע דף שנראה כך: work/index.html
כמובן שהאפליקציה הזו עדיין לא עושה שום דבר מעניין. זהו שלד מינימלי בלבד כדי להבטיח ששרת האינטרנט שלכם פועל כראוי. בשלבים הבאים יתווספו תכונות פונקציונליות ופריסה.
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
בדפדפן אמור להופיע בערך כך, אבל כמובן בעזרת התצוגה ממצלמת האינטרנט:
איך זה עובד
אחרי השיחה, האפליקציה 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%);
}
טיפים
- אל תשכח את המאפיין
autoplay
ברכיבvideo
. בלעדיו, תראו רק מסגרת אחת! - יש עוד אפשרויות נוספות במסגרת
getUserMedia()
מגבלות. דוגמאות נוספות זמינות בקטע מגבלות &נתונים סטטיסטיים ומגבלות נוספות & נתונים סטטיסטיים על דוגמאות WebRTC.
שיטה מומלצת
מוודאים שהרכיב של הסרטון לא חורג מהמאגר שלו. 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
.
ביצוע השיחה
- פותחים את הקובץ
index.html
. - לוחצים על התחלה כדי לקבל סרטון ממצלמת האינטרנט.
- לוחצים על שיחה כדי ליצור את החיבור להשוואה
הסרטון הזה אמור להופיע במצלמת האינטרנט בשני הרכיבים של video
.
- הצג את מסוף הדפדפן כדי לראות את הרישום של WebRTC.
איך זה עובד
שלב זה עושה הרבה.
WebRTC משתמש ב-API של RTCPeerConnection
כדי להגדיר חיבור לסטרימינג של סרטונים בין לקוחות WebRTC, הידועים בשם 'עמיתים'.
בדוגמה הזו, שני האובייקטים מסוג RTCPeerConnection
מופיעים באותו דף: pc1
ו-pc2
.
הגדרת השיחה בין אפליקציות להשוואה דרך WebRTC כוללת שלוש משימות:
- יש ליצור
RTCPeerConnection
לכל סיום שיחה, ולהוסיף בכל אחד מהשידורים החיים את מקור השיחהgetUserMedia()
. - קבלה ושיתוף של פרטי רשת.
נקודות קצה (endpoint) של חיבור פוטנציאלי נקראות מועמדי ICE.
- אפשר לקבל ולשתף תיאורים מקומיים ומרוחקים.
מטא-נתונים על מדיה מקומית הם בפורמט פרוטוקול תיאור של סשן (SDP).
נניח שאליס ובוב רוצים להשתמש ב-RTCPeerConnection
כדי ליצור וידאו צ'אט.
ראשית, אליס ובוב מחליפים פרטי רשת. הביטוי איתור מועמדים מתייחס לתהליך של חיפוש ממשקים ויציאות של רשתות באמצעות המסגרת ICE.
- דנה יוצרת אובייקט
RTCPeerConnection
עם handler שלonicecandidate (addEventListener('icecandidate'))
.
מתאים לקוד הבא מ-main.js
:
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
- אליס מתקשרת אל
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.');
- ה-handler של
onicecandidate
, שמופיע בשלב הראשון, יופעל כשהמועמדים לרשת יהיו זמינים. - דפנה שולחת נתוני מועמד לסידורים.
באפליקציה, התהליך הזה, המכונה אותות, מתבצע באמצעות שירות העברת הודעות. נלמד אותך איך לעשות זאת בשלב מאוחר יותר. כמובן שבשלב הזה שני האובייקטים של RTCPeerConnection
נמצאים באותו דף והם יכולים לתקשר ישירות ללא צורך בהודעה חיצונית.
- כשיוסי מקבל הודעה על מועמדותו של דנה, הוא מתקשר אל
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.
- אליס מפעילה את השיטה
RTCPeerConnection
createOffer()
.
ההבטחה שהוחזרה כוללת תיאור של הסשן המקומי ב-RTCSessionDescription
&lex:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
- אם ההקלטה תצליח, אליס תגדיר את התיאור המקומי באמצעות
setLocalDescription()
ואז תשלח את תיאור הסשן הזה לבוב דרך ערוץ האותות שלה. - יוסי מגדיר את התיאור שאוסי שלח לו בתור התיאור המרוחק עם
setRemoteDescription()
. - יוסי מפעיל את השיטה
createAnswer()
בRTCPeerConnection
ומעביר לו את התיאור המרוחק שהוא קיבל מאליס, כך שהמערכת יוצרת סשן מקומי שתואם שלה. - ההבטחה של
createAnswer()
עוברתRTCSessionDescription
, והיא מוגדרת כתיאור המקומי ושולחת את אליס. - כשדנה מקבלת את תיאור הפעילות של יוסי, היא מגדירה אותו כתיאור המרוחק עם
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);
}
מקבלים נקודות בונוס
- עוברים אל chrome://webrtc-internals.
הדף הזה מספק נתונים סטטיסטיים של WebRTC ונתונים על ניפוי באגים. (ניתן למצוא רשימה מלאה של כתובות URL ב-Chrome בכתובת chrome://about.)
- עיצוב הדף באמצעות CSS:
- מציבים את הסרטונים זה לצד זה.
- שינוי הגודל והלחצנים צריכים להיות ברוחב זהה.
- חשוב לוודא שהפריסה פועלת בנייד.
- מהמסוף של Chrome Developer Console, בודקים את
localStream
,localPeerConnection
ואתremotePeerConnection
. - במסוף, מחפשים את
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
- יש להחליף את
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
. אפשר להגדיר ערוצי נתונים כך שיכללו סוגים שונים של שיתוף נתונים, כמו קביעת עדיפות של מסירה אמינה על פני ביצועים.
מקבלים נקודות בונוס
- באמצעות SCTP, הפרוטוקול המשמש את ערוצי הנתונים של WebRTC, מועבר נתונים בצורה ממומנת ומודרנית כברירת מחדל. מתי
RTCDataChannel
יצטרך לספק הצגת נתונים אמינה ומתי הביצועים עשויים להיות חשובים יותר, גם אם המשמעות היא אובדן נתונים מסוימים? - שימוש ב-CSS כדי לשפר את פריסת הדף ולהוסיף מאפיין placeholder ל-
dataChannelReceive
textarea
. - בודקים את הדף בנייד.
למידע נוסף
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
- עדכנו את
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).
- יש להחליף את
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"
}
}
זהו מניפסט של אפליקציה שמספר ל-Node Package Manager (npm
) איזה פרויקט
יחסי תלות להתקנה.
- כדי להתקין קשרי תלות, כמו
/socket.io/socket.io.js
, מריצים את הקטע הבא ממסוף שורת הפקודה שבספרייה שלך ב-work
:
npm install
אתם אמורים לראות יומן התקנה שמסתיים בערך כך:
כפי שאפשר לראות, npm
התקין את התלות שהוגדרו ב-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
- עוברים לדפדפן http://localhost:8080.
בכל פעם שתנווטו לכתובת URL זו, תתבקשו להזין שם חדר.
כדי להצטרף לאותו חדר, יש להזין את אותו שם חדר בכל פעם, למשל foo
.
- פותחים כרטיסייה חדשה בכתובת http://localhost:8080 ומזינים שוב את אותו שם חדר.
- פותחים כרטיסייה חדשה, מנווטים שוב אל http://localhost:8080 ומזינים את אותו שם חדר.
- יש לבדוק את המסוף בכל אחת מהכרטיסיות.
אתם אמורים לראות רישום ביומן מ-JavaScript.
מקבלים נקודות בונוס
- אילו מנגנונים חלופיים לשליחת הודעות יכולים להיות? באילו בעיות נתקלת אם אתה משתמש ב-WebSocket טהור?
- אילו בעיות עשויות להיות כתוצאה מהרחבה של האפליקציה הזו? האם תוכלו לפתח שיטה לבדיקת אלפי בקשות למיליוני משתמשים בו-זמנית?
- האפליקציה הזו משתמשת בבקשה ל-JavaScript כדי לקבל שם חדר. מוסבר איך למצוא את שם החדר מכתובת ה-URL. למשל, http://localhost:8080/foo יספק את שם החדר
foo
.
למידע נוסף
7. שילוב של חיבור בין אותות ואות
גרסה מלאה של השלב הזה נמצאת בתיקייה step-05
.
החלפת HTML ו-JavaScript
- יש להחליף את התוכן של
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>
- יש להחליף את
js/main.js
בתוכן שלstep-05/js/main.js
.
הרצת שרת Node.js
אם בחרת שלא לעקוב אחר קוד שיעור זה מספריית work
שלך, ייתכן שיהיה צורך להתקין את תלויות החלון בתיקייה step-05
או בתיקיית העבודה הנוכחית.
- מריצים את הפקודה הבאה מספריית העבודה:
npm install
- לאחר ההתקנה, אם שרת Node.js לא פועל, מפעילים אותו על ידי הרצת הפקודה הבאה בספרייה
work
:
node index.js
חשוב לוודא שאתם משתמשים בגרסה של index.js
מהשלב הקודם שמטמיע את Socket.IO. מידע נוסף על Node ו-Socket IO זמין בקטע 'הגדרת שירות אותות', שבו אפשר להחליף הודעות.
- בדפדפן, נכנסים לכתובת http://localhost:8080.
- פותחים כרטיסייה חדשה ועוברים שוב אל http://localhost:8080.
רכיב אחד של video
מציג את השידור המקומי מ-getUserMedia()
והשני מציג את הסרטון השלט הרחוק שהועבר דרך RTCPeerconnection
.
- לצפייה ביומן במסוף הדפדפן.
ניקוד של b****נקודות
- האפליקציה הזו תומכת רק בווידאו צ'אט אחד על אחד. איך תוכלו לשנות את העיצוב כדי לאפשר ליותר מאדם אחד לשתף את אותו חדר הווידאו?
- בדוגמה, שם החדר
foo
הועתק בתוך הקוד. מה תהיה הדרך הטובה ביותר להפעיל שמות אחרים של חדרים? - איך המשתמשים יוכלו לשתף את שם החדר? אפשר לנסות לבנות חלופה לשיתוף שמות של חדרים.
- איך אפשר לשנות את האפליקציה?
טיפים
- בכתובת chrome://webrtc-internals אפשר למצוא נתונים על ניפוי באגים של WebRTC ונתונים על ניפוי באגים.
- אפשר להשתמש בפותר הבעיות של WebRTC כדי לבדוק את הסביבה המקומית ולבדוק את המצלמה והמיקרופון.
- אם נתקלתם בבעיות מוזרות במטמון, נסו את הפעולות הבאות:
- לוחצים על
Control
ולוחצים על טעינה מחדש של הדף הזה. - מפעילים את הדפדפן מחדש.
- מריצים את
npm cache clean
משורת הפקודה.
8. צילום תמונה ושיתוף דרך ערוץ נתונים
גרסה מלאה של השלב הזה נמצאת בתיקייה step-06
.
איך זה עובד
בעבר למדת איך לשלוח הודעות טקסט באמצעות RTCDataChannel
. שלב זה יאפשר לך לשתף קבצים שלמים. בדוגמה הזו, התמונות מצולמות באמצעות getUserMedia()
.
החלקים העיקריים של שלב זה הם:
- יוצרים ערוץ נתונים.
בשלב זה לא מוסיפים שידורי מדיה לחיבור האפליקציות להשוואה.
- צילום הווידאו בסטרימינג ממצלמת אינטרנט באמצעות
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);
});
}
- לוחצים על 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);
}
- לוחצים על שליחה כדי להמיר את התמונה לבייטים ולשלוח אותם דרך ערוץ נתונים:
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);
}
קבל את הקוד
- יש להחליף את התוכן של התיקייה
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 & 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>
- אם בחרת שלא לעקוב אחר קוד שיעור זה מספריית
work
שלך, ייתכן שיהיה צורך להתקין את תלויות החלון בתיקייהstep-06
או בתיקיית העבודה הנוכחית. מריצים את הפקודה הבאה מספריית העבודה:
npm install
- לאחר ההתקנה, אם שרת 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, יש לעיין בקטע 'הגדרת אותות'
שירות לחילופי הודעות.
- אם צריך, לוחצים על אישור כדי לאפשר לאפליקציה להשתמש במצלמת האינטרנט.
האפליקציה יוצרת מזהה חדר באופן אקראי ומוסיפה את המזהה לכתובת ה-URL.
- פותחים את כתובת ה-URL מסרגל הכתובות בכרטיסייה או בחלון חדשים בדפדפן.
- לוחצים על Snap & שליחה, ולאחר מכן מעיינים בקטע תמונות נכנסות בכרטיסייה השנייה שבתחתית הדף.
האפליקציה מעבירה תמונות בין כרטיסיות.
אתם אמורים לראות משהו כזה:
מקבלים נקודות בונוס
איך משנים את הקוד כדי לאפשר שיתוף של כל סוגי הקבצים?