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

מבוא

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

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

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

הדרך אל getUserMedia()

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

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

אני אנסה לסכם את מה שקרה ב-2011...

סבב 1: לכידת מדיה של HTML

HTML Media Capture (הקלטת מדיה ב-HTML) היה הצעד הראשון ב-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' מאפשרת רק להקליט קובץ מדיה או לצלם תמונת מצב בזמן.

תמיכה:

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

סיבוב 2: רכיב במכשיר

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

Opera היה בין הדפדפנים הראשונים שיצרו הטמעות ראשוניות של צילום וידאו על סמך האלמנט <device>. זמן קצר לאחר מכן (באותו יום, ליתר דיוק), צוות WhatWG החליט למחוק את התג <device> לטובת תג אחר, והפעם ממשק JavaScript API בשם navigator.getUserMedia(). שבוע לאחר מכן, אופרה הוציאה גרסאות 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);

למידע נוסף על הגדרות אישיות, אפשר לעיין בממשק ה-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);
}

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

אבטחה

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