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

เกริ่นนำ

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

HTML5 ช่วยคุณได้ แม้ว่า 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 เป็นวิธีแรกของ 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 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> รวมอยู่ในนั้นด้วย ไม่ต้องกังวลไป 1 API :) <device> มีข้อดี 2 ประการดังนี้ 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>

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

สำหรับการกำหนดค่าเพิ่มเติม โปรดดู 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;
}