🎬

React App で Vimeo の動画を再生し、プレイヤーで発生するイベントを拾う

2024/10/27に公開

問題

React App で Vimeo の動画を再生するために、 @u-wave/react-vimeo を使う。

単に動画を再生するだけなら、コンポーネントに Vimeo の動画IDを渡せばいい。

<Vimeo
  video={videoId}
/>

ただ実サービスの運用では、動画再生やコントロールUIの操作等で発生するイベントに応じて何かしたいことも多い。
(ユーザーの操作ログを取ったり、動画が終了したら何か通知を出したり……)

@u-wave/react-vimeo の内部で使用されているVimeo公式の @vimeo/player では様々なイベントリスナを設定できるが、 @u-wave/react-vimeo 経由だとそれらを設定できない。
(onPlayonVolumeChange 等の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