🤔 굳이 video.js를 사용해야 하나요..? 그냥 video 태그를 써도 되는데..?
물론 html 5 video tag를 사용해도 영상은 정상적으로 플레이 가능하다. 하지만 다음과 같은 상황이라면 video tag 대신 video.js 사용을 고려해볼만 하다
물론 나온지 오래된 라이브러리이기도 해서 단점도 존재한다
ℹ️ 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
ℹ️ 기본 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({ src: url, type: "video/mp4" });
위 처럼 src메서드를 통해 직접적으로 source를 변경 해줄 수 있다. 하지만 이럴 경우 한 영상 라이프사이클 안에서 다른 영상을 재생해야 하는 경우 영상 소스에대한 추적이 어렵고 버그를 발생시킬 수 있다. (예를 들어 컨텐츠 영상 앞에 광고 영상을 재생해야 하는 경우가 있을 수 있다)
다른 방법으로는 영상이 변경 되었을때 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 컴포넌트의 가독성이 보다 좋아졌다고 느껴졌다.
만약 react 개발환경이고 별도의 플러그인이 필요한 상황이 아니라면 react-player가 사실 개발하기에도 훨씬 편하다.
react-player가 편한데 왜 video.js로 개발하셨나요?! 👀