HTML5에서 오디오 및 동영상 캡처

소개

오디오/동영상 캡처는 오랫동안 웹 개발의 성배였습니다. 여러 해 동안 우리는 작업을 완료하기 위해 브라우저 플러그인 (Flash 또는 Silverlight)에 의존해야 했습니다. 자!

HTML5를 사용했습니다. 분명하지 않을 수도 있지만 HTML5의 증가로 인해 기기 하드웨어에 대한 액세스가 급증했습니다. Geolocation (GPS), Orientation API (가속도계), WebGL (GPU), Web Audio API (오디오 하드웨어)가 완벽한 예입니다. 이러한 기능은 엄청나게 강력하여 시스템의 기본 하드웨어 기능 위에 있는 고급 JavaScript API를 노출합니다.

이 튜토리얼에서는 웹 앱이 사용자의 카메라와 마이크에 액세스할 수 있는 새로운 API인 GetUserMedia를 도입합니다.

getUserMedia()

이 API의 역사를 모르시나요? getUserMedia() API에 관해 알아보는 것은 흥미로운 이야기입니다.

지난 몇 년간 'Media Capture API'의 여러 변형이 진화했습니다. 많은 사람들이 웹에서 네이티브 기기에 액세스할 수 있어야 한다는 점을 알고 있었지만, 이 때문에 모든 사람과 어머니가 새로운 사양을 갖추게 되었습니다. 상황이 너무 복잡해져 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 미디어 캡처는 미디어 파일을 녹화하거나 제때에 스냅샷을 캡처하도록 허용합니다.

지원:

  • Android 3.0 브라우저 - 최초 구현 중 하나입니다. 이 동영상에서 실제 사례를 확인해 보세요.
  • Android용 Chrome (0.16)
  • Firefox 모바일 10.0
  • iOS6 Safari 및 Chrome (부분 지원)

2단계: 기기 요소

많은 사람들이 HTML Media Capture가 너무 제한적이라고 생각하여 모든 유형의 (향후) 기기를 지원하는 새로운 사양이 등장했습니다. 당연히 디자인에서 새로운 요소인 <device> 요소를 요구했으며, 이는 getUserMedia()의 전신이 되었습니다.

Opera는 <device> 요소를 기반으로 동영상 캡처의 초기 구현을 만든 최초의 브라우저 중 하나입니다. 얼마 지나지 않아(정확하게는 당일) WhatWG는 <device> 태그를 스크랩하기로 했으며 이번에는 navigator.getUserMedia()이라는 JavaScript API를 사용하기로 했습니다. 1주일 후 Opera는 업데이트된 getUserMedia() 사양 지원이 포함된 새 빌드를 출시했습니다. 그해 말 Microsoft는 새 사양을 지원하는 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를 따라갔습니다.

WebRTC (Web Real Time Communications)라는 더 큰 노력 덕분에 적합한 캡처 API를 더욱 빨리 찾을 수 있게 되었습니다. 이 사양은 W3C WebRTC 실무 그룹에서 감독합니다. Google, Opera, Mozilla 및 몇 가지 기타 업체에서 구현되어 있습니다.

getUserMedia()는 WebRTC와 관련이 있습니다. 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의 완벽한 예입니다. Google의 다른 HTML5 친구인 <audio><video>와 함께 사용할 수 있습니다. src 속성을 설정하지 않거나 <video> 요소에 <source> 요소를 포함하지 않습니다. 동영상에 미디어 파일의 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를 참고하세요.

미디어 소스 선택

MediaDevices 인터페이스의 enumerateDevices() 메서드는 마이크, 카메라, 헤드셋 등 사용 가능한 미디어 입력 및 출력 장치 목록을 요청합니다. 반환된 프로미스는 기기를 설명하는 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;
}