จับเสียงและวิดีโอในรูปแบบ HTML5

การบันทึกเสียง/วิดีโอเป็น "สิ่งที่ต้องมี" ของการพัฒนาเว็บมาเป็นเวลานาน เป็นเวลาหลายปีที่เราต้องใช้ปลั๊กอินเบราว์เซอร์ (Flash หรือ Silverlight) เพื่อทำงาน เข้าร่วมเลย

HTML5 จะช่วยแก้ปัญหานี้ได้ เรื่องนี้อาจไม่ชัดเจน แต่การเพิ่มขึ้นของ HTML5 ส่งผลให้การเข้าถึงฮาร์ดแวร์ของอุปกรณ์เพิ่มขึ้นอย่างมาก ตัวอย่างที่ชัดเจน ได้แก่ ตำแหน่งทางภูมิศาสตร์ (GPS), Orientation API (เครื่องวัดความเร่ง), WebGL (GPU) และ Web Audio API (ฮาร์ดแวร์เสียง) ฟีเจอร์เหล่านี้มีประสิทธิภาพสูงมาก โดยแสดง JavaScript API ระดับสูงที่ทำงานร่วมกับความสามารถของฮาร์ดแวร์พื้นฐานของระบบ

บทแนะนํานี้จะแนะนำ API ใหม่อย่าง GetUserMedia ซึ่งช่วยให้เว็บแอปเข้าถึงกล้องและไมโครโฟนของผู้ใช้ได้

เส้นทางสู่ getUserMedia()

หากคุณไม่ทราบประวัติความเป็นมา การสร้าง getUserMedia() API นั้นน่าสนใจทีเดียว

"Media Capture API" หลายรูปแบบได้พัฒนาขึ้นในช่วง 2-3 ปีที่ผ่านมา ผู้คนจำนวนมากตระหนักถึงความจำเป็นในการเข้าถึงอุปกรณ์แบบเนทีฟบนเว็บ แต่นั่นทำให้ทุกคนต้องร่วมกันจัดทำข้อกำหนดใหม่ ทุกอย่างยุ่งเหยิงจน W3C ตัดสินใจจัดตั้งกลุ่มทำงานในที่สุด วัตถุประสงค์เดียวของบุคคลนั้น มาทำความเข้าใจความบ้าคลั่งนี้กัน คณะทำงานด้านนโยบาย Device API (DAP) ได้รับมอบหมายให้รวบรวมและกำหนดมาตรฐานให้กับข้อเสนอจำนวนมาก

ฉันจะพยายามสรุปสิ่งที่เกิดขึ้นในปี 2011 นะ...

รอบที่ 1: การจับภาพสื่อ HTML

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 อนุญาตให้คุณบันทึกไฟล์สื่อหรือถ่ายสแนปชอตได้ทันเวลาเท่านั้น

การสนับสนุน:

รอบที่ 2: องค์ประกอบอุปกรณ์

หลายคนคิดว่า HTML Media Capture จํากัดเกินไป จึงมีการเปิดตัวข้อกําหนดใหม่ซึ่งรองรับอุปกรณ์ทุกประเภท (ในอนาคต) ไม่น่าแปลกใจเลยที่การออกแบบนี้ต้องใช้องค์ประกอบใหม่อย่างองค์ประกอบ <device> ซึ่งกลายเป็นรุ่นก่อนหน้าของ getUserMedia()

Opera เป็นหนึ่งในเบราว์เซอร์แรกๆ ที่เริ่มใช้งานครั้งแรกการจับภาพวิดีโอโดยอิงตามองค์ประกอบ <device> หลังจากนั้นไม่นาน (ในวันเดียวกัน) WhatWG ตัดสินใจยกเลิกการใช้แท็ก <device> เพื่อหันมาใช้แท็กที่กำลังมาแรงอีกรายการหนึ่ง ซึ่งคราวนี้เป็น JavaScript API ที่ชื่อว่า navigator.getUserMedia() 1 สัปดาห์ต่อมา Opera ได้เปิดตัวบิลด์ใหม่ๆ ที่รองรับสเปค 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 น้อยลง 1 รายการ :) <device> มีประโยชน์ 2 อย่าง แต่ 1.) เป็นความหมาย และ 2) มันขยายขีดความสามารถได้ง่ายเพื่อรองรับมากกว่าแค่อุปกรณ์เสียง/วิดีโอ

หายใจเข้าลึกๆ ข้อมูลนี้เปลี่ยนแปลงอย่างรวดเร็ว

รอบที่ 3: WebRTC

องค์ประกอบ <device> ในที่สุดก็สูญพันธุ์ไป

ความเร็วในการค้นหา 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>

โอเค เกิดอะไรขึ้น การจับภาพสื่อเป็นตัวอย่างที่สมบูรณ์แบบของการทำงานร่วมกันของ HTML5 API ใหม่ ซึ่งทำงานร่วมกับ <audio> และ <video> ซึ่งเป็นเพื่อนร่วมทาง HTML5 ของเรา โปรดสังเกตว่าเราไม่ได้ตั้งค่าแอตทริบิวต์ src หรือรวมองค์ประกอบ <source> ในองค์ประกอบ <video> เราจะตั้งค่า srcObject เป็นออบเจ็กต์ LocalMediaStream ที่แสดงถึงเว็บแคมแทนการส่ง URL ไปยังไฟล์สื่อของวิดีโอ

และบอก <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;
}