צילום אודיו ווידאו ב-HTML5

צילום אודיו/וידאו הוא ה "גביע הקדוש" של פיתוח האינטרנט כבר זמן רב. במשך שנים רבות נאלצנו להסתמך על יישומי פלאגין לדפדפן (Flash או Silverlight) כדי לבצע את המשימה. בוא!

HTML5 מציל את המצב. יכול להיות שזה לא ברור, אבל העלייה בשימוש ב-HTML5 הביאה לעלייה חדה בגישה לחומרה של המכשירים. דוגמאות מצוינות לכך הן מיקום גיאוגרפי (GPS),‏ Orientation API (מד מהירות),‏ WebGL (מעבד גרפי) ו-Web Audio API (חומרה של אודיו). התכונות האלה חזקות מאוד, ומציגות ממשקי API ברמה גבוהה של JavaScript שמבוססים על יכולות החומרה הבסיסיות של המערכת.

במדריך הזה נסביר על ממשק API חדש, GetUserMedia, שמאפשר לאפליקציות אינטרנט לגשת למצלמה ולמיקרופון של המשתמש.

הדרך ל-getUserMedia()‎

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

בשנים האחרונות התפתחו כמה וריאנטים של ממשקי Media Capture API. הרבה אנשים הבינו את הצורך לגשת למכשירים מקומיים באינטרנט, אבל זה הוביל את כולם לנסות להכין מפרט חדש. המצב הפך למבולגן כל כך עד ש-W3C החליטה סוף סוף להקים קבוצת עבודה. מה המטרה היחידה שלהם? להבין את הטירוף! קבוצת העבודה בנושא מדיניות ממשקי API למכשירים (DAP) קיבלה את המשימה לאחד את המגוון הרחב של ההצעות וליצור סטנדרטים לגביו.

אנסה לסכם את מה שקרה בשנת 2011…

סבב 1: HTML Media Capture

HTML Media Capture הייתה הניסיון הראשון של DAP לסטנדרטיזציה של צילום מדיה באינטרנט. הוא פועל על ידי עומס יתר על <input type="file"> והוספת ערכים חדשים לפרמטר accept.

אם רוצים לאפשר למשתמשים לצלם תמונת סטילס של עצמם באמצעות מצלמת האינטרנט, אפשר לעשות זאת באמצעות capture=camera:

<input type="file" accept="image/*;capture=camera">

התהליך של הקלטת סרטון או אודיו דומה:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

נחמד, לא? במיוחד אהבתי את העובדה שאפשר לעשות שימוש חוזר בקלט של קובץ. מבחינה סמנטית, זה הגיוני מאוד. הבעיה בממשק ה-API הזה היא היעדר היכולת ליצור אפקטים בזמן אמת (למשל, עיבוד נתונים של מצלמת אינטרנט בשידור חי ל-<canvas> והחלה של מסנני WebGL). התכונה HTML Media Capture מאפשרת רק לצלם קובץ מדיה או לצלם צילום מסך בזמן נתון.

תמיכה:

  • דפדפן Android 3.0 – אחת מהטמעות הראשונות. בסרטון הזה אפשר לראות איך זה עובד.
  • Chrome ל-Android (0.16)
  • Firefox Mobile 10.0
  • iOS6 Safari ו-Chrome (תמיכה חלקית)

סבב 2: אלמנט המכשיר

הרבה אנשים חשבו ש-HTML Media Capture מגביל מדי, ולכן הופיעה מפרט חדש שתומך בכל סוג של מכשיר (עתידי). לא מפתיע שהעיצוב כלל רכיב חדש, הרכיב <device>, שהפך לקודם של getUserMedia().

דפדפן Opera היה בין הדפדפנים הראשונים שיצר הטמעות ראשוניות של צילום וידאו על סמך הרכיב <device>. זמן קצר לאחר מכן (באותו יום ליתר דיוק), ב-WhatWG החליטו לבטל את התג <device> לטובת חלופה חדשה, הפעם ממשק API ל-JavaScript שנקרא navigator.getUserMedia(). שבוע לאחר מכן, Opera פרסמה גרסאות build חדשות שכללו תמיכה במפרט המעודכן של getUserMedia(). בהמשך השנה, Microsoft הצטרפה לחגיגה ופרסמה Lab ל-IE9 שתומך במפרט החדש.

כך <device> היה נראה:

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

תמיכה:

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

נושמים עמוק. הדברים האלה מתקדמים מהר!

סיבוב 3: WebRTC

בסופו של דבר, הרכיב <device> נכחד.

המהירות שבה מצאנו ממשק API מתאים לצילום התגברה הודות למאמץ הגדול יותר של WebRTC (Web Real Time Communications). קבוצת העבודה של W3C בנושא WebRTC אחראית על המפרט הזה. יש הטמעות ב-Google, ב-Opera, ב-Mozilla ובעוד כמה ספקים.

השדה getUserMedia() קשור ל-WebRTC כי הוא השער לקבוצת ממשקי ה-API הזו. הוא מספק את האמצעים לגשת לשידור המקומי של המצלמה או המיקרופון של המשתמש.

תמיכה:

התמיכה ב-getUserMedia() קיימת מאז הגרסה 21 של Chrome, הגרסה 18 של Opera והגרסה 17 של Firefox.

תחילת העבודה

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

זיהוי תכונות

זיהוי התכונה הוא בדיקה פשוטה של קיומו של navigator.mediaDevices.getUserMedia:

if (navigator.mediaDevices?.getUserMedia) {
  // Good to go!
} else {
  alert("navigator.mediaDevices.getUserMedia() is not supported");
}

קבלת גישה למכשיר לקליטת נתונים

כדי להשתמש במצלמת האינטרנט או במיקרופון, אנחנו צריכים לבקש הרשאה. הפרמטר הראשון של navigator.mediaDevices.getUserMedia() הוא אובייקט שמציין את הפרטים והדרישות לכל סוג מדיה שרוצים לגשת אליו. לדוגמה, אם רוצים לגשת למצלמת האינטרנט, הפרמטר הראשון צריך להיות {video: true}. כדי להשתמש גם במיקרופון וגם במצלמה, מעבירים את הערך {video: true, audio: true}:

<video autoplay></video>

<script>
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((localMediaStream) => {
      const video = document.querySelector("video");
      video.srcObject = localMediaStream;
    })
    .catch((error) => {
      console.log("Rejected!", error);
    });
</script>

אישור. אז מה קורה כאן? תיעוד מדיה הוא דוגמה מושלמת לאופן שבו ממשקי API חדשים של HTML5 פועלים יחד. הוא פועל בשילוב עם החברים האחרים שלנו ב-HTML5, <audio> ו-<video>. שימו לב שלא מגדירים מאפיין src ולא כוללים רכיבי <source> באלמנט <video>. במקום להזין לסרטון כתובת URL לקובץ מדיה, אנחנו מגדירים את srcObject לאובייקט LocalMediaStream שמייצג את מצלמת האינטרנט.

אני גם אומר ל-<video> לעבור ל-autoplay, אחרת הוא יהיה קפוא בפריים הראשון. הוספת controls פועלת גם היא כצפוי.

הגדרת אילוצים של מדיה (רזולוציה, גובה, רוחב)

אפשר להשתמש בפרמטר הראשון של getUserMedia() גם כדי לציין דרישות (או אילוצים) נוספים על סטרימינג המדיה המוחזר. לדוגמה, במקום לציין רק שאתם רוצים גישה בסיסית לסרטון (למשל {video: true}), תוכלו לדרוש בנוסף שהסטרימינג יהיה באיכות HD:

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

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
  video: { width: { exact:  640} , height: { exact: 360 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);

הגדרות נוספות זמינות ב-constraints API.

בחירת מקור מדיה

השיטה enumerateDevices() בממשק MediaDevices מבקשת רשימה של מכשירי הקלט והפלט של המדיה הזמינים, כמו מיקרופונים, מצלמות, אוזניות וכו'. ה-Promise המוחזר נפתר במערך של אובייקטים מסוג MediaDeviceInfo שמתארים את המכשירים.

בדוגמה הזו, המיקרופון והמצלמה האחרונים שנמצאו נבחרים כמקור של מקור הנתונים של המדיה:

if (!navigator.mediaDevices?.enumerateDevices) {
  console.log("enumerateDevices() not supported.");
} else {
  // List cameras and microphones.
  navigator.mediaDevices
    .enumerateDevices()
    .then((devices) => {
      let audioSource = null;
      let videoSource = null;

      devices.forEach((device) => {
        if (device.kind === "audioinput") {
          audioSource = device.deviceId;
        } else if (device.kind === "videoinput") {
          videoSource = device.deviceId;
        }
      });
      sourceSelected(audioSource, videoSource);
    })
    .catch((err) => {
      console.error(`${err.name}: ${err.message}`);
    });
}

async function sourceSelected(audioSource, videoSource) {
  const constraints = {
    audio: { deviceId: audioSource },
    video: { deviceId: videoSource },
  };
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
}

מומלץ לצפות בהדגמה המדהימה של Sam Dutton, שבה הוא מראה איך לאפשר למשתמשים לבחור את מקור המדיה.

אבטחה

בדפדפנים מוצגת תיבת דו-שיח של הרשאה כשמפעילים את navigator.mediaDevices.getUserMedia(), שמאפשרת למשתמשים להעניק או לדחות גישה למצלמה או למיקרופון. לדוגמה, זוהי תיבת הדו-שיח של ההרשאות ב-Chrome:

תיבת הדו-שיח של ההרשאות ב-Chrome
תיבת הדו-שיח של ההרשאה ב-Chrome

מתן חלופה

למשתמשים שאין להם תמיכה ב-navigator.mediaDevices.getUserMedia(), אפשרות אחת היא לעבור לקובץ וידאו קיים אם ה-API לא נתמך או שהקריאה נכשלת מסיבה כלשהי:

if (!navigator.mediaDevices?.getUserMedia) {
  video.src = "fallbackvideo.webm";
} else {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
}