19. Webcam Fun

Junghyun Park·2020년 12월 17일
0

Javascript30

목록 보기
13/30
post-thumbnail

프로젝트 소개

  • webcam에서 수신된 영상데이터를 웹 페이지의 canvas에 렌더링하는 동시에 다양한 effect 구현 연습
  • 사진 촬영(캡쳐)한 이미지를 기기에 저장하는 기능 포함

배운 것들

media(video, audio)의 이벤트

- 발생 순서 : loadedmetadata -> loadeddata -> canplay -> canplaythrough**

loadedmetadata
: 미디어의 메타 데이터가 로드되었을 때를 나타낸다.
: 메타 데이터는 우리가 유용하게 사용할 수 있는 동영상의 재생시간과 같은 것을 의미한다. 미디어가 로드되기 전에, 먼저 메타 데이터를 뽑아와서 활용할 수 있다.
loadeddata
: 미디어의 프레임이 로드되었을 때를 나타낸다.
: 여기서 프레임은 미디어에 대한 전체 프레임이 아닌, 첫 프레임 또는 현재 프레임을 뜻할 수 있다. 즉, 조금이라도 다운로드가 되었을 때이고, 이러한 의미는 재생할 수 있다는 것을 알아차릴 수 있다.
: 하지만 주의해야할 것은 로드된 데이터가 재생에 있어서, 충분하다고 보장하지않는다. 즉, loadeddata 이벤트가 발생한 시점에서 play() 메소드를 호출하면, 재생이 실패할 수도 있다.
: 메타 데이터를 우선 가져오기에, loadedmetadata 이벤트가 발생한 후에, loadeddata 이벤트가 발생한다고 볼 수 있다. 즉, loadeddata 에서도 메타 데이터를 활용할 수 있다는 것이다.
canplay
: 재생을 할 수 있는 정도의 충분한 데이터가 로드되었을 때, 미디어는 재생할 수 있다고 판단하고 이벤트를 호출한다. 즉, canplay 이벤트는 미디어에 대한 재생을 할 수 있다는 것을 나타낸다.
: 하지만 재생을 보장하지만, 버퍼로 인해 중단될 수 있다. 즉, 재생을 보장한다는 것은, 전체 재생이 아닌 현재 시점에서 재생을 할 수 있는지 없는지를 나타낸다.
canplaythrough
: canplay 이벤트와 동일하지만, 차이점은 전체 미디어가 중단없이 재생할 수 있을 때 호출된다. canplay 이벤트가 전체 재생을 보장하지는 못하였다면, canplaythrough 는 중단없이 전체 재생을 보장하는 목적이다. 현재 로드 속도를 유지한다고 가정하고, 중단이 되지 않는다고 판단하면 호출된다. 하지만 이것 또한 가정이기때문에, canplay 이벤트보다는 전체 재생을 보장하겠지만, 확신할 수는 없다. 그리고 다른 면에서 조금 더 생각해보면, canplaythrough 이벤트는 canplay 이벤트가 일어난 후에 호출된다.

출처: https://mygumi.tistory.com/356 [마이구미의 HelloWorld]

사용자 에이전트의 '상태'와 '신원정보'를 나타내며, 스크립트로 해당 정보를 질의할 때와 애플리케이션을 특정 활동에 등록할 때 사용
.geolocation : 장치의 위치 정보에 접근할 수 있는 Geolocation 객체를 반환
.mediaDevice : 이용 가능한 미디어 장치에 대한 정보를 얻는데 사용될 수 있는 MediaDevice Objet에 대한 reference를 반환(MediaDevices.enumerateDevices())하고, 사용자 디바이스에서 media에 대한 지원 제한사항들을 반환(MediaDevices.getSupportedConstraints())하고, media에 대한 접근을 요청(MediaDevices.getUserMedia())

.getUserMedia() method

promise 객체 형식으로, MainStream 객체(비디오 재생을 위한 주소를 포함)를 반환
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia

.drawImage (canvas method)

void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage

.getImageData (canvas method)

canvas위의 pixel 값을 imagedata 객체 형식으로 가져옴 => pixel 단위의 변형을 만들어 내고자 하는 경우 사용
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData

.putImageData (canvas method)

pixel data를 canvas 영역에 뿌려주는 method
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData

.toDataURL (canvas method)

cavas 화면 상의 이미지를 지정된 type(default : jpeg)으로 dataURL 값을 반환 => img src 값으로 할당
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL

a 태그의 'download' attribute

download 속성을 포함하는 a 태그를 클릭하면, 해당 데이터(이미지 등)를 다운로드 받을 수 있음
'download: "x"' => x라는 파일명으로 다운로드 받도록 지정 가능
https://www.w3schools.com/tags/att_a_download.asp

최종 코드

const video = document.querySelector('.player');
const canvas = document.querySelector('.photo');
const ctx = canvas.getContext('2d');
const strip = document.querySelector('.strip');
const snap = document.querySelector('.snap');

function getVideo() {
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: false })
    .then((localMediaStream) => {
      console.log(localMediaStream);

      //  DEPRECIATION :
      //       The following has been depreceated by major browsers as of Chrome and Firefox.
      //       video.src = window.URL.createObjectURL(localMediaStream);
      //       Please refer to these:
      //       Deprecated  - https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
      //       Newer Syntax - https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject

      video.srcObject = localMediaStream;
      video.play();
    })
    .catch((err) => {
      console.error(`OH NO!!!`, err);
    });
}

function paintToCanvas() {
  const width = video.videoWidth;
  const height = video.videoHeight;
  canvas.width = width;
  canvas.height = height;

  return setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);
    // take the pixels out
    let pixels = ctx.getImageData(0, 0, width, height);
    // mess with them

    pixels = rgbSplit(pixels);
    // ctx.globalAlpha = 0.8;

    // put them back
    ctx.putImageData(pixels, 0, 0);
  }, 16);
}

function takePhoto() {
  // played the sound
  snap.currentTime = 0;
  snap.play();

  // take the data out of the canvas
  const data = canvas.toDataURL('image/jpeg');
  const link = document.createElement('a');
  link.href = data;
  link.setAttribute('download', 'handsome');
  link.innerHTML = `<img src="${data}" alt="Handsome Man" />`;
  strip.insertBefore(link, strip.firstChild);
}


function rgbSplit(pixels) {
  for (let i = 0; i < pixels.data.length; i += 4) {
    pixels.data[i - 150] = pixels.data[i + 0]; // RED
    pixels.data[i + 500] = pixels.data[i + 1]; // GREEN
    pixels.data[i - 550] = pixels.data[i + 2]; // Blue
  }
  return pixels;
}


getVideo();

video.addEventListener('canplay', paintToCanvas);

느낀 점/ 기억할 점

  • 기존 MediaStream을 받아오는 내용이 최신 방법으로 변경되었음(최종코드 내 내용참조)
  • 픽셀의 변형 등 세부적인 내용에 대한 이해는 완전히 되지 않았음
profile
21c Carpenter

0개의 댓글