שימוש ב-getUserMedia לקבל וידאו ואודיו מהמשתמשים

הסבר מקיף על ה-API שמאפשר לנו להתחבר לקבל אודיו ו-וידאו מהמשתמש בצד לקוח עם ג'אווהסקריפט
דוגמה עם חצים הממחישים ויזואלית את מה שאני כותב. ברגע שהמשתמש מאשר את הבקשה יש לי גישה ל-stream של התקן המדיה ואני יכול לעשות איתו מה שאני רוצה.

אחד מה-API המעניינים יותר בשנים האחרונות בתקן HTML 5 הוא getUserMedia שמאפשר לנו גישה נוחה וקלה באמצעות ג'אווהסקריפט למצלמת הוידאו של המשתמש ולמיקרופון שלו. היום כמעט לכל מחשב יש מצלמת וידאו (כל לפטופ, טלפון או טאבלט) ולאף יותר יש מיקרופון. באמצעות WebRTC API אנחנו יכולים לצלם או להקליט את המשתמש לצרכים שונים. למשל לשיחות וידאו/קול, לקחת צילום של תמונת פרופיל או סרטונים וכל שימוש שהוא. לא צריך נייטיב (נייטיב זה בעצם תוכנה שיושבת על מערכת ההפעלה ללא תיווך דפדפן). במאמר הזה אני אסביר על getUserMedia ואיך משתמשים בו ללכידת אודיו ו-וידאו. נכון להיום,getUserMedia נתמך בפיירפוקס, כרום ואדג'. כרום לא מאפשר שימוש ב-getUserMedia באתרים שאינם https או localhost.

הדוגמאות נכתבו בתקן ES6 ונדרשת הבנה ב-promises, פונקציות חץ וקלאסים.

אז בואו ונתחיל דווקא בדוגמה חיה. הכנסו אל הדף הזה עם דפדפן כרום או פיירפוקס (במובייל או בדסקטופ אבל כדאי עם מכשיר שיש בו מצלמה), אל תשכחו לאשר את הרשאות האודיו והוידאו שמאפשרות את זה. אם אתם גולשים בדסקטופ בלי מובייל, השתמשו ב-QR code הזה על מנת לגשת לדף.

https://internet-israel.com/internet_files/webrtc/another_example/

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

See the Pen WebRTC sample by Ran Bar-Zik (@barzik) on CodePen.

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


    const constraints = {
      audio: true,
      video: true
    };

    // Get user permission,
    navigator.mediaDevices.getUserMedia(constraints).
      then((stream) => {
        this.handleSuccess(stream);
      }).
      catch((error) => {
        this.handleError(error);
      });

ראשית אני מגדיר אובייקט שבו יש את ההרשאות שאני מבקש. את האובייקט הזה אני מעביר דרך האובייקט הגלובלי navigator. באובייקט זה, שכאמור הוא זמין בכל דפדפן, יש את mediaDevices ולו יש את תכונת getUserMedia שאליה אני מעביר את ההרשאות. התכונה הזו מחזירה promise שהוא בעצם מתקיים אך ורק אם המשתמש אישר את הבקשה ורק אם יש ציוד קצה שמתאים. הבקשה מתמלאת ומעבירה לי את ה-stream. משתנה ג'אווהסקריפטי שמייצג את זרם הנתונים מהמצלמה. מה אני יכול לעשות עם זה? בת'כלס כל מה שמתחשק לי. אבל השימוש הראשוני הוא להכניס אותו ישירות לתוך אלמנט וידאו כדי שהמשתמש יוכל לראות את עצמו. איך אני עושה את זה? בפונקציה הראשונה, שמתממשת אם המשתמש אישר את ההרשאות והכל תקין, אני לוקח את ה-stream ומעביר אותו איך שהוא לאלמנט וידאו כ-src. ממש ככה:


  handleSuccess(stream) {
    this.video.srcObject = stream;
  }

this.video הוא אלמנט פשוט שאפשר להשיג אותו עם סלקטור פשוט כמו getElementById או כל אחד אחר. srcObject זהה למדי ל-src. זה הכל. בשניה שעשיתי את זה המשתמש יכול לראות את עצמו באלמנט הוידאו.

דוגמה עם חצים הממחישים ויזואלית את מה שאני כותב. ברגע שהמשתמש מאשר את הבקשה יש לי גישה ל-stream של התקן המדיה ואני יכול לעשות איתו מה שאני רוצה.
דוגמה עם חצים הממחישים ויזואלית את מה שאני כותב. ברגע שהמשתמש מאשר את הבקשה יש לי גישה ל-stream של התקן המדיה ואני יכול לעשות איתו מה שאני רוצה.

אם אתם רוצים לראות, אז הנה הקישור לדוגמה הפשוטה.

https://internet-israel.com/internet_files/webrtc/simple_example/
https://internet-israel.com/internet_files/webrtc/simple_example/

הנה הקוד המלא:


'use strict';
class Recorder {
  constructor() {
    // Defining all globals.
    this.video = document.querySelector('video');
    // Get user permission,
    const constraints = {
      audio: false,
      video: true
    };
    navigator.mediaDevices.getUserMedia(constraints).
      then((stream) => {
        this.handleSuccess(stream);
      }).
      catch((error) => {
        this.handleError(error);
      });
  }


  /**
   * When user granting success, this function is firing. 
   * @param {*} stream of video
   */
  handleSuccess(stream) {
    this.video.srcObject = stream;
  }
  /**
   * If user is not granting success, this function is firing.
   * @param {*} error 
   */
  handleError(error) {
    console.log('navigator.getUserMedia error: ', error);
  }

}


new Recorder(); // Init class constructor.

תראו כמה זה קל! כל מה שצריך זה לקרוא ל-navigator.mediaDevices.getUserMedia ולהבין מספיק ב-promises כדי לדעת מה עושים עם הפלט. הפלט הןא stream ואפשר לעשות איתו מה שרוצים. במקרה הזה זה פשוט, אבל אני יכול לקחת את ה-stream הזה ולאגור אותו. איך אני עושה את זה? באמצעות שימוש באובייקט mediaRecorder שמאפשר לי להקליט stream. ראשית אני צריך לאתחל אותו. איך? ככה:


let options = { mimeType: 'video/webm;codecs=vp9' };
mediaRecorder = new MediaRecorder(window.stream, options);

אני מעביר לו את ה-stream עצמו. שניתן להציב אותו בפונקציה handleSuccess ב-window.stream על מנת שיהיה גלובלי. אני גם מעביר לו את הקידוד שאני רוצה לשמור בו את המדיה. עכשיו אני יוצר event listener שירוץ בכל פעם שהמאגר של ה-MediaRecorder יתמלא. אני מתחיל את ההקלטה באמצעות mediaRecorder.start(10) כאשר הארגומנט (שבמקרה הזה הוא 10) הוא מספר המילישניות שיש במאגר. על מנת להפסיק להקליט אני צריך ללחוץ על this.mediaRecorder.stop();


mediaRecorder.ondataavailable = this.handleDataAvailable;
    mediaRecorder.start(10); // collect 10ms of data

השלב האחרון הוא הקולבק של ondataavailable. כלומר הפונקציה handleDataAvailable שמופעלת בכל פעם שאני מקבל מידע.


handleDataAvailable(event) {
    if (event.data && event.data.size > 0) {
      this.recordedBlobs.push(event.data);
    }
  }

הפונקציה הזו פשוט שומרת את המידע במערך גלובלי של recordedBlobs. זה הכל. ומכאן? מכאן אני יכול לעשות מה שאני רוצה עם recordedBlobs. אני יכול לשלוח אותו לשרת ואני יכול לשים אותו באלמנט וידאו אחר. כמו בדוגמה הראשונה. הקוד בדוגמה הראשונה זמין גם ב-codepen וגם באתר ואתם יותר ממוזמנים לבחון אותו. כתבתי הערות היכן שצריך. על מנת להמחיש את התהליך, הנה דוגמה:
הסבר באמצעות דיאגרמת זרימה על התהליך שהסברתי בטקסט

ברגע שמבינים ש-stream הוא בעצם נתון כמו כל נתון ושאפשר להשתמש בו או כמקור מידע לאלמנט HTML של וידאו או כמקור מידע ל-MediaRecorder שיכול להקליט את המידע ולשדר אותו לכל מקום אחר (או אלמנט וידאו אחר או לשרת שיכול להעביר אותו לכל מקום אחר). אז זה פשוט.

כיוון שהקלטה של משתמשים היא בעייתית מאוד מבחינה חוקית, הדפדפנים מבצעים כמה פעילויות על מנת להגן על המשתמשים שלהם. הפעילות הראשונה היא לבקש, בפעם הראשונה שהדף עולה, הרשאה ספציפית מהמשתמש לשימוש בהתקני המדיה שלו: מיקרופון ו/או מצלמה (תלוי מה ביקשתם). הבקשה הזו היא חד פעמית. נגעתם? נסעתם. לא צריך לבקש שוב. שכבת ההגנה השניה היא אינדקיציה גרפית על הלשונית. הנה האינדקיציה ששלושת הדפדפנים המרכזיים נותנים בדסקטופ:

התרעה על שימוש בהקלטה (וידאו או אודיו) בדפדפנים שונים

ההגנה האחרונה היא של מערכת ההפעלה שמדליקה נורית קטנה ליד המצלמה במידה והיא מופעלת. אני מאמין שכל המתכנתים שמבקרים באתר הזה ולומדים ממנו ישתמשו בידע הזה באופן אתי ולא ינסו לצלם אותי רוקד בתחתונים ליד המחשב 🙂

פוסטים נוספים שכדאי לקרוא

פיתוח ב-JavaScript

Axios interceptors

תכנון נכון של קריאות AJAX באפליקציה ריאקטית וניהול השגיאות או ההצלחות עם פיצ׳ר נחמד של axios

למפתחי ובוני אתרי אינטרנט

מדריך לשאילתות יעילות ל Chat GPT

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

פתרונות ומאמרים על פיתוח אינטרנט

SSG עם next

אחרי שלמדנו במאמר הקודם מה זה SSR והבנו שלא מדובר בקליע כסף שפותר את כל הבעיות שלנו, נלמד על SSG שיכול להקל על כמה מהבעיות של SSR.

גלילה לראש העמוד