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

מבוא

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

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

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

הדרך אל getUserMedia()

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

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

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

סבב 1: צילום מדיה ב-HTML

הכלי HTML Media Recording היה הראשון ב-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> לטובת משתמש אחר, הפעם ממשק API של JavaScript שנקרא navigator.getUserMedia() שבוע לאחר מכן, Opera הוציאה גרסאות build חדשות שכוללות תמיכה במפרט המעודכן של getUserMedia(). בהמשך השנה, Microsoft הצטרפה למסיבה על ידי שחרור של Lab for 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). המפרט הזה נמצא בפיקוח של קבוצת העבודה WebRTC W3C. ל-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;
}