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

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

HTML5 מציל את המצב. יכול להיות שזה לא ברור, אבל העלייה בשימוש ב-HTML5 הביאה לעלייה חדה בגישה לחומרה של המכשירים. Geolocation (GPS), Orientation API (מד תאוצה), WebGL (GPU) ו-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> עבר בסופו של דבר בדרך של Dodo.

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

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

תמיכה:

getUserMedia() נתמך החל מ-Chrome 21, Opera 18 ו-Firefox 17.

תחילת העבודה

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