🎬
React App で Vimeo の動画を再生し、プレイヤーで発生するイベントを拾う
問題
React App で Vimeo の動画を再生するために、 @u-wave/react-vimeo を使う。
単に動画を再生するだけなら、コンポーネントに Vimeo の動画IDを渡せばいい。
<Vimeo
video={videoId}
/>
ただ実サービスの運用では、動画再生やコントロールUIの操作等で発生するイベントに応じて何かしたいことも多い。
(ユーザーの操作ログを取ったり、動画が終了したら何か通知を出したり……)
@u-wave/react-vimeo の内部で使用されているVimeo公式の @vimeo/player では様々なイベントリスナを設定できるが、 @u-wave/react-vimeo 経由だとそれらを設定できない。
(onPlay
、 onVolumeChange
等のpropsはドキュメントには存在するが、実際にはイベントハンドラを渡しても実行してくれない)
<Vimeo
video={videoId}
onPlay={() => console.log('play!!!')} // 実行されない
/>
解決策
プレイヤーのインスタンスを受け取るstateを用意し、そのインスタンスに対してイベントリスナを設定すればよかった。
import Vimeo from '@u-wave/react-vimeo';
import { useEffect, useState } from 'react';
import Player from '@vimeo/player';
function VimeoPlayer() {
const [player, setPlayer] = useState<Player>();
const onPlay = () => console.log('play!!!');
const onEnded = () => console.log('end!!!')
const onVolumeChange = () => console.log('volume changed!!!');
useEffect(() => {
if (player) {
player.on('play', onPlay);
player.on('ended', onEnded);
player.on('volumechange', onVolumeChange);
}
return () => {
if (player) {
player.off('play', onPlay);
player.off('ended', onEnded);
player.off('volumechange', onVolumeChange);
}
};
}, [player]);
return (
<Vimeo
onReady={setPlayer}
video={videoId}
/>
);
}
プレイヤーのマウント時に実行される onReady
イベントだけは動作するようなので、 state の setter を渡してインスタンスを受け取る。
インスタンスを受け取ったら、それに対して .on()
で直接イベントハンドラを設定する。
useEffect
のお約束として、クリーンアップ関数で .off()
するのも忘れずに。
これで想定通り、各イベントに応じて任意の処理を実行できるようになった。
リファクタリング
イベントハンドラの処理が増えるにつれてコードの見通しは悪くなっていくので、イベントに関する処理はhooks化しておく。
useVimeoPlayerEvents.ts
import Player, { TimeEvent, VolumeChangeEvent } from '@vimeo/player';
import { useEffect, useState } from 'react';
export function useVimeoPlayerEvents() {
const [player, setPlayer] = useState<Player>();
const onPlay = (event: TimeEvent) => {
console.log('play', event);
};
const onEnded = (event: TimeEvent) => {
console.log('ended', event);
};
const onVolumeChange = (event: VolumeChangeEvent) => {
console.log('volumechange', event);
};
useEffect(() => {
if (player) {
player.on('play', onPlay);
player.on('ended', onEnded);
player.on('volumechange', onVolumeChange);
}
return () => {
if (player) {
player.off('play', onPlay);
player.off('ended', onEnded);
player.off('volumechange', onVolumeChange);
}
};
}, [player]);
return {
setPlayer
};
}
VimeoPlayer.tsx
import Vimeo from '@u-wave/react-vimeo';
import { useVimeoPlayerEvents } from 'path/to/hooks';
function VimeoPlayer() {
const { setPlayer } = useVimeoPlayerEvents();
return (
<Vimeo
onReady={setPlayer}
video={videoId}
/>
);
}
Discussion