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

บทนำ

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

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

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

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

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

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

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

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

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

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

รอบที่ 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 น้อยลงก็เป็นเรื่องดี :) <device> มีข้อดี 2 อย่าง คือ 1) เป็นแบบ Semantic และ 2) ขยายเพื่อรองรับ อุปกรณ์อื่นๆ นอกเหนือจากอุปกรณ์เสียง/วิดีโอได้ง่าย

พักหายใจสักหน่อย เรื่องนี้เกิดขึ้นเร็วมาก

รอบที่ 3: WebRTC

ในที่สุดองค์ประกอบ <device> ก็หายไป

ความเร็วในการค้นหา Capture API ที่เหมาะสมเพิ่มขึ้นเนื่องจากความพยายามที่มากขึ้นของ WebRTC (Web Real Time Communications) กลุ่มทำงาน WebRTC ของ W3C เป็นผู้ดูแลข้อกำหนดเฉพาะดังกล่าว Google, Opera, Mozilla และอีก 2-3 รายมีการติดตั้งใช้งาน

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 ที่ทำงานร่วมกัน โดยจะทำงานร่วมกับ <audio> และ <video> ซึ่งเป็น HTML5 อื่นๆ ของเรา โปรดสังเกตว่าเราไม่ได้ตั้งค่าแอตทริบิวต์ 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;
}