웹 PIP + Canvas로 나만의 PIP 창 띄우기

조영도(Young-do Cho)·2021년 12월 5일
3

들어가기 앞서

여기서 이 글에서 나온 코드 전체를 확인할 수 있습니다.

PIP(Picture-in-Picture)를 아시나요?

PIP는 2016년 macOS Sierra 출시와 함께 Safari 브라우저에서 처음 등장했습니다. PIP 기능을 통해 비디오를 항상 최상단에 있는 작은 창에서 볼 수 있습니다.

웹에선 PIP 모드를 마우스 클릭으로 활성화 할 수 있습니다. 아래 사진처럼 원하는 영상에 마우스 우클릭하고 난 뒤 보이는 컨텍스트 메뉴에서 PIP 모드라는 것을 클릭하면 원하는 영상을 PIP로 볼 수 있습니다.

Web PIP API

PIP API를 활용하면 우클릭하지 않고도 PIP를 활성화할 수 있습니다. mdn에서 제공한 예시를 참고했습니다. 아래 예시는 Chrome 환경에서 동작합니다.

우선 PIP가 가능한지 여부를 document의 pictureInPictureEnabled 속성이 존재하는지 확인해야합니다. 존재하지 않다면 PIP 기능을 사용할 수 없는 환경입니다.

if ("pictureInPictureEnabled" in document) {
	...
}

PIP 기능을 제공하는 환경이라면, 아래의 스크립트로 PIP 창을 띄울 수 있습니다.

// <video id="video" ...> 가 존재한다고 가정
document.querySelector("#video").requestPictureInPicture()

그리고 특정 버튼을 클릭하면 PIP가 활성화 될 수 있게 만들 수 있습니다.

// <button id="pipButton" ...> 가 존재한다고 가정
const video = document.querySelector("#video")
const pipButton = document.querySelector("#pipButton")

pipButton.addEventListener("click", () => {
	if (document.pictureInPictureElement) {
		document.exitPictureInPicture();
	} else {
		video.requestPictureInPicture();
	}
})

해당 비디오가 PIP 모드에 들어갔는지 이벤트 감지를 통해 알 수 있습니다.

video.addEventListener("enterpictureinpicture", () => {
	pipButton.textContent = "Exit Picture-in-Picture mode";
});

video.addEventListener("leavepictureinpicture", () => {
	pipButton.textContent = "Enter Picture-in-Picture mode";
});

나만의 PIP 창 만들기

위에서 확인한 것처럼 PIP API는 비디오 엘리먼트의 메서드로 제공되고 있습니다. 즉, 비디오 엘리먼트는 모두 PIP 모드를 사용할 수 있습니다. 캔버스의 CaptureStream 메서드를 사용해서 캔버스에 그려지고 있는 것을 PIP로 확인할 수도 있겠죠!

이 정보를 바탕으로 Canvas API를 통해 아날로그 시계를 만들고, PIP 모드를 통해 항상 확인할 수 있도록 해봅시다. 일단 캔버스에 아날로그 시계를 그려봅시다. 코드는 w3schools의 예시를 사용합니다.

// <canvas id="canvas" width="400" height="400" ... > 가 존재한다고 가정
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
...
setInterval(drawClock, 1000);

function drawClock() {
  drawFace(ctx, radius);
  drawNumbers(ctx, radius);
  drawTime(ctx, radius);
}
...

시계가 잘 그려졌나요? 이제 PIP 모드로 보기위해 CaptureStream 메서드를 통해 기존에 있던 video에 srcObject로 할당합시다.

video.srcObject = canvas.captureStream();


(왼쪽은 캔버스, 오른쪽은 비디오 엘리먼트)

이제 기존의 버튼을 클릭하면 PIP 창에서 캔버스에 그려지고 있는 아날로그 시계를 확인할 수 있습니다.

앞서 소개한 원리로 PIP 창으로 다양한 정보를 표시할 수 있지 않을까요? 하지만 몇 가지 이슈를 알아둘 필요가 있습니다.

이슈 1. User Gesture

PIP 모드를 활성화하고 다른 탭으로 이동하거나 최소화해도 PIP 창은 사라지지 않습니다. 이 이점을 살려서, 사용자가 해당 탭을 벗어나는 경우 (= 즉, document.visibilityState 가 hidden이 되는 경우) 바로 PIP 창이 띄워지도록 만들 수 있을까요?

안됩니다. 다음과 같은 에러 메시지를 만나면서 PIP모드가 활성화되지 않습니다.

즉, PIP 모드 활성화는 유저 제스쳐에 의해서만 가능합니다. 해당 탭을 벗어나거나 돌아오는 행위는 포함되지 않는거죠. 유저 제스쳐가 어떤건지 궁금하시다면 이 링크에서 확인할 수 있습니다.

이슈 2. Safari

사파리에서도 구현하고자 시도했으나, 크롬과 달리 캔버스의 변경사항이 비디오에 제대로 반영되질 않습니다. 원인은 아직 파악하지 못했고, 지금 이야기할 수 있는건 이 예시는 크롬에서 잘 동작한다는 것입니다. 원인을 파악하면 이 부분을 업데이트할 예정입니다.

이슈 3. SetInterval

1초보다 짧은 단위로 캔버스를 그리는 경우, 탭이 inactive 상태가 되면 제대로 동작하지 않을 겁니다. requestAnimationFrame을 사용한 경우도 마찬가지입니다. 이를 대체할 방법은 WebWorker를 사용하여 해결할 수 있고, 아래는 웹워커를 사용한 예시입니다.

const _timeout = 160;
const _workerScript = `
let started = false;
let timer;

self.onmessage = function(e) {
  const { type } = e.data;

  switch (type) {
    case "start":
      if (started) return;
      timer = setInterval(() => self.postMessage("schedule"), ${_timeout});
      started = true;
      break;
    case "stop":
      started = false;
      clearInterval(timer);
  };
};
`;
const _objectUrl = URL.createObjectURL(new Blob([_workerScript], { type: 'text/javascript' }));
const _scheduler = new Worker(_objectUrl);
let _callback: () => void;

const scheduler = {
  setCallback(callback) {
    _callback = callback;
  },
  start() {
    _scheduler.onmessage = () => _callback();
    _scheduler.postMessage({ type: 'start' });
  },
  stop() {
    _scheduler.postMessage({ type: 'stop' });
  },
};

...

// setInterval(drawClock, 160) 대신
scheduler.setCallback(drawClock)
scheduler.start()

결론

PIP 모드는 사용자가 다른 일을 하면서 비디오를 시청하고 싶을 때 유용하게 사용됩니다. 제가 참여한 카카오워크 화상회의에서는 PIP 창을 발표시에 다른 참여자들의 반응을 파악할 수 있는 창으로 활용하고 있습니다. 이처럼 비디오에 한정하지 않고 다양한 정보를 PIP 창 안에서 제공할 수 있습니다. 적절하게 사용한다면 사용자에게 더 나은 편의성을 제공할 수 있다고 생각하며, PIP 모드를 통해 기능을 제공하고자 하는 다른 개발자분들에게 이 글이 도움이 되었으면 합니다.

참고 링크

profile
개념 정리 + 호기심 해결용 블로그

0개의 댓글