단순히 IFrame에 src로 넣어서 Youtube 영상을 볼 수도 있지만 IFrame Player API를 사용하면 볼 수 있을 뿐만 아니라 웹에서 Youtube 동영상 플레이어를 쉽게 제어할 수 있다.
우선, IFrame API를 사용하기 위해서 로드를 해야하기 때문에 script를 생성하고 로드하는 유틸함수를 작성해주었다.
여기서 onYouTubeIframeAPIReady함수는 IFrame API 로드가 완료되었을 때 호출되며 이 함수내에서 표시할 플레이어 개체를 만드는 과정이 필요하다.
this.onload를 null로 하는 이유는 IE9에서 작동을 하지 않기 때문에 많이들 저렇게 작성한다.
function load(src: string, cb: (err: Error) => void) {
const firstScript = document.getElementsByTagName('script')[0];
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onerror = function () {
this.onload = null;
this.onerror = null;
cb(new Error(`Failed to load ${this.src}`));
};
if (firstScript) firstScript.parentNode?.insertBefore(script, firstScript);
else {
document.head.appendChild(script);
}
}
function loadIFrameApi() {
return new Promise((resolve, reject) => {
if (typeof window.YT === 'object') {
resolve(window.YT);
return;
}
const protocol = window.location.protocol === 'http:' ? 'http:' : 'https:';
load(`${protocol}///www.youtube.com/iframe_api`, (err) => {
if (err) reject(err);
});
window.onYouTubeIframeAPIReady = function () {
resolve(window.YT);
};
});
}
export default loadIFrameApi;
이제 위에서 작성한 유틸함수를 불러오는 컴포넌트가 필요하며 컴포넌트가 mount되는 시점에 호출하고 호출한 함수에서 플레이어 개체를 만들어주면 된다.
플레이어 개체가 생성될 때 참조하고 있는 div tag가<iframe>
으로 대체되고 플레이어를 제어할 수 있는 매개변수들을 사용할 수 있고 플레이어 개체를 player라는 변수에 담아두면 이 컴포넌트를 호출하는 부모 컴포넌트에서 직접 접근하여 플레이어 개체를 제어 할 수 있게 된다.
this binding을 해주지 않으면 플레이어 개체의 콜백함수들이 this를 찾지 못하기 때문에 콜백으로 호출되는 함수들에 대해서 this를 binding 해주어야 한다.
class Youtube extends Component<Props, State> {
public player: YT.Player | undefined = undefined;
constructor(props: Props) {
super(props);
this.onPlayerReady = this.onPlayerReady.bind(this);
this.onPlayerStateChange = this.onPlayerStateChange.bind(this);
}
componentDidMount() {
this.createYoutubePlayer();
}
componentDidUpdate(prevProps: Props) {
// TODO: update 관련 핸들링
}
componentWillUnmount() {
this.player?.destroy();
}
onPlayerReady() {
const {
customProps: { volume, muted, paused },
} = this.props;
if (typeof volume !== 'undefined') this.setVolume(volume);
if (typeof muted !== 'undefined') this.setMute(muted);
if (typeof paused !== 'undefined') this.setPaused(paused);
}
onPlayerStateChange(event: YT.OnStateChangeEvent) {
const { stateChangeHandler } = this.props;
if (stateChangeHandler) stateChangeHandler(event);
}
setVolume(volume: number) {
this.player?.setVolume(volume * 100);
}
setMute(isMute: boolean) {
if (isMute) {
this.player?.mute();
} else {
this.player?.unMute();
}
}
setPaused(isPaused: boolean) {
if (isPaused) {
this.player?.pauseVideo();
} else {
this.player?.playVideo();
}
}
async createYoutubePlayer() {
const {
width = '100',
height = '100',
autoplay,
customProps: { videoId },
} = this.props;
try {
const YT = await loadIFrameApi();
this.player = new YT.Player('DJ-playlist-player', {
videoId,
height,
width,
playerVars: {
autoplay: autoplay ? 1 : 0,
...PLAYER_DEFAULT_OPTS,
},
events: {
onReady: this.onPlayerReady,
onStateChange: this.onPlayerStateChange,
},
});
} catch (err) {
console.log('Player를 생성할 수 없습니다.');
}
}
render() {
return <div id="DJ-playlist-player" />;
}
}
export default Youtube;
렌더링될 때마다 함수를 다시 생성하고 해결하기 위해 useCallback을 사용하는 등의 추가적인 작업이 필요해 보다 편리하게 작성할 수 있는 클래스 컴포넌트로 작성하게 되었다.
여기까지 작성하고 나면 해당 컴포넌트를 호출했을 때 아래와 같이 자동으로 재생되는 유튜브 영상을 볼 수 있게된다.
하지만 기본적으로 제공하는 플레이어를 사용하지 않고 커스텀 플레이어
를 사용하려고 하기 때문에 변화되는 상태에 대해 조작을 해주어야 한다.
그래서 커스텀 플레이어를 조작하게 될 때 플레이어 개체를 동일하게 해줘야하는 작업이 필요하다.
다음에...
TODO:
1. 커스텀 플레이어 상태 통일화
2. any type 수정