video.js 만지기 (with react & typescript)

HappyFrog·2023년 1월 17일
3
post-thumbnail

why video js?

🤔 굳이 video.js를 사용해야 하나요..? 그냥 video 태그를 써도 되는데..?

물론 html 5 video tag를 사용해도 영상은 정상적으로 플레이 가능하다. 하지만 다음과 같은 상황이라면 video tag 대신 video.js 사용을 고려해볼만 하다

  • HLS, MPEG-DASH와 같은 영상 type을 지원해야 하는 경우
  • 브라우저와 상관없이 동일한 플레이어를 제공해야 하는 경우
  • 여러가지 다양한 plugin들을 적용 시켜야 하는 경우
    • 필자의 경우 광고 플러그인을 player에 적용시켰어야 해서 이 부분이 video.js 선택에 영향을 미쳤다

들어가기 앞서 video.js의 단점....

물론 나온지 오래된 라이브러리이기도 해서 단점도 존재한다

  • react로 개발하기 불편.... (이 부분은 react-player가 더 편리하다)
  • video.js의 플러그인의 경우 방대하지만... 그 만큼 방치되어있는 플러그인도 많음
    • typescript 기반으로 개발할시 불편하다 (any script....)

본론

ℹ️ react & typescript 기반으로 작성된 문서입니다

설치

  • npm install video.js or yarn add video.js
  • npm install --save-dev @types/video.js or yarn add -D @types/video.js

react 기반의 기본적인 사용법

ℹ️ 기본 style을 사용하기 위해서는 "video.js/dist/video-js.css"를 import 해야 합니다

	import "video.js/dist/video-js.css";
interface VjsProps {
	options: VideoJsPlayerOptions;
    onReady?: (player: VideoJsPlayer) => void;
}

const CustomVjsComponent = ({ options, onReady }: VjsProps) => {
    // mutableRef 주의
	const videoRef = useRef<HTMLDivElement | null>(null);
    const playerRef = useRef<VideoJsPlayer | null>(null);
  
  	// player 인스턴스 생성
    useEffect(() => {
        // player 인스턴스는 life cycle간 한번 초기화 되야함
    	if (!playerRef.current) {
          const videoElement = document.createElement("video-js");
          // 필요하다면, video.js 관련 class 추가
          videoElement.classList.add("vjs-fill");
          videoRef.appendChild(videoElement);
          
          const player = (playerRef.current = videojs(videoElement, options, () => {
             onReady && onReady(player);
          }));
		  // plugin 설정
        }
    }, [options, videoRef]);
    
   // player clean up
   useEffect(() => {
   	  const player = playerRef.current;
      return () => {
      	if (player && !player.isDisposed()) {
          player.dispose();
          playerRef.current = null;
        }
      }
   }, [playerRef]);
  
   return <div ref={videoRef} />
}
<CustomVjsComponent options={options} onReady={() => {}} />

주의점은 위의 코드의 주석에도 작성하였지만, player 인스턴스는 한번만 생성되어야 한다는 점이다

영상 변경시

react 기반으로 개발한다는 의미는 곧 spa를 기반으로 개발한다는 의미와도 같다 생각한다. 이러한 환경에서 영상 소스가 변경 될 시 options를 변경 시키면, 영상 소스가 변경되지 않아 당황 할 수 있다. 왜냐하면 options를 통해 영상 설정을 하는 부분은 플레이어 초기화 부분(위의 useEffect 부분)이기 때문이다. 따라서 이때 다음과 같은 방법들을 사용할 수 있다.

player 인스턴스의 src 변경을 통해 소스 변경

	player.src({ src: url, type: "video/mp4" });

위 처럼 src메서드를 통해 직접적으로 source를 변경 해줄 수 있다. 하지만 이럴 경우 한 영상 라이프사이클 안에서 다른 영상을 재생해야 하는 경우 영상 소스에대한 추적이 어렵고 버그를 발생시킬 수 있다. (예를 들어 컨텐츠 영상 앞에 광고 영상을 재생해야 하는 경우가 있을 수 있다)

player 인스턴스 초기화

다른 방법으로는 영상이 변경 되었을때 player instance를 dispose 시키고 다시 초기화하는 방법 또한 존재한다. video.js에 pre roll 광고를 붙히는 작업 중 src로 소스를 change 하는 작업이 광고 플러그인 사이에서 문제를 일으켜서 (e.g 광고 영상을 메인 컨텐츠로 재생 하거나 컨텐츠 영상을 광고로 재생하거나) 결국 안전하다고 생각하는 이 방법을 나는 택하였다

const playerCleanUp = (cb?: () => void) => {
	// cb: cleanup간 실행 시킬 callback 함수 (optional)
    const player = playerRef.currennt; // player instance
    if (player && !player.isDisposed()) {
        // player clean up
    	player.dispose();
        playerRef.current = null;
        if (cb) cb();
    }
}

const playerReset = () => {
	playerCleanUp();
    // video.js initialize code
};

🤔 코드 개선 시도 해보기

위의 video.js 코드 만으로 video player 사용은 가능하나, 추후 다른 플러그인들을 적용하고 다른 기능들을 붙혀야 할때 코드가 많이 더러워 짐을 느꼈다 (특히나 가독성이 현저히 감소했다) 그래서 코드 개선 방법을 고민하다 hook pattern을 도입 하여 플레이어의 인스턴스를 관리하는 로직을 분리하기로 결정 해보았다.

// useVideo.tsx
interface VideoProps {
	videoTarget: HTMLElement | null;
    options?: VideoJsPlayerOptions;
    plugins?: string[];
}

const useVideo = ({ options, videoTarget, plugins = [] }: VideoProps) => {
  const playerRef = useRef<VideoJsPlayer | null>(null);
    
  const initializeVideo = () => {
  	// .... 위의 초기화 코드 (useEffect)
  };
  
  const playerReset = () => {
  	// ... 위의 reset 코드
  };
  
  return { player: playerRef.current, playerReset }
}
const Video = React.forwardRef<HTMLDivElement | null, VideoProps>(
  ({ sources, styles, videoOptions }, refs) => {
    const videoRef = useRef<HTMLDivElement | null>(null);
    const [target, setTarget] = useState<HTMLDivElement | null>(
      videoRef.current
    );
    const { player, playerReset } = useVideo({
      videoTarget: target,
      options: { sources, ...videoOptions },
    });
	
    // source 변경시 player reset
    useEffect(() => {
      if (player) playerReset();
    }, [sources]);

    useEffect(() => {
      if (videoRef.current) {
        setTarget(videoRef.current);
        playerReset();
      }
    }, [videoRef]);

    return (
      <div ref={refs}>
        <div ref={videoRef} style={styles} />
      </div>
    );
  }
);

코드가 전반적으로 개선이 되었는가? 라는 것은 장담할 수 없으나 그래도 플레이어의 라이프사이클을 관리하는 로직을 분리하여 추상화 시킴으로써 Video 컴포넌트의 가독성이 보다 좋아졌다고 느껴졌다.

마무리

video.js vs react-player?

만약 react 개발환경이고 별도의 플러그인이 필요한 상황이 아니라면 react-player가 사실 개발하기에도 훨씬 편하다.

소스코드 & storybook demo

https://github.com/syoo970/videojs-customize

스토리북 데모: https://63c605548c76513e84ac879d-cpnrofmfjd.chromatic.com/?path=/story/%EC%8B%9C%EC%9E%91-introduction--page

profile
성장하고 싶은 긍정 개구리

2개의 댓글

comment-user-thumbnail
2024년 7월 25일

react-player가 편한데 왜 video.js로 개발하셨나요?! 👀

1개의 답글