📺

Reactで動画プレイヤーを作成するならreact-playerがおすすめ!

2024/10/06に公開

こんにちは、Web エンジニアのまさぴょんです!
今回は、React で動画プレイヤーを作成するなら react-player が簡単に実装できて、おすすめなのでご紹介していきます 🐱

React 動画ライブラリ比較結果 (2024 年 9 月時点)

React の動画プレイヤー・ライブラリで調べて出てきたものを比較してみました 👀
候補は「React 動画ライブラリ」で調べてでてきた次の 5 つです 🙏

  1. react-player
  2. mux-player-react
  3. react-video-js-player
  4. video-react
  5. video.js

https://npmtrends.com/@mux-elements/mux-player-react-vs-react-player-vs-react-video-js-player-vs-video-react-vs-video.js

結論:react-player がよさそう

判断基準は、次のとおりです 👀✨

  1. 要件を満たす機能を備えていること。
  2. React 対応のライブラリであること。
  3. 正式リリースがされており、安定版であること。
  4. 定期的にメンテナンスがされていること。
  5. npm trends や GitHub Star などを参照して、使用率や人気がある状態であることが望ましい。

react-player とは?

react-player は、React アプリケーションで動画や音声などのメディアを再生するためのオープンソースの React コンポーネントです。
YouTube、Vimeo、Mux、Twitch、SoundCloud、Facebook など、さまざまなプラットフォームからのコンテンツをサポートしています。
また、シンプルな API と豊富なプロパティを提供しており、メディアプレーヤーのカスタマイズや制御が簡単です 💪🥺🔥
npm や GitHub は、次のとおりです 👀✨

npm: react-player

https://www.npmjs.com/package/react-player

GitHub: react-player

公式 Doc がない代わりに、GitHub の README.md にて設定できる Option に関する説明が掲載されています 👀✨

https://github.com/CookPete/react-player

React Player デモ環境

react-player のデモで、いろいろな動画タイプや、パラメーターを試すことができます 🙏

https://cookpete.github.io/react-player/

react-player で動画プレイヤーを作成する

それでは、実際に react-player で動画プレイヤーを作成していきます。

react-player をインストール

bash
npm install react-player

yarn add react-player

シンプルな Video Player を実装してみる

試しに、シンプルな Video Player を実装してみると、
これぐらいのCode量で、簡単な Video Playerが実装できます👀✨

SimpleVideoPlayer.tsx
import ReactPlayer from "react-player";

interface SimpleVideoPlayerProps {
  videoUrl: string;
  isLoop?: boolean;
  isAutoPlay?: boolean;
  isControls?: boolean;
}

export const SimpleVideoPlayer = ({
  videoUrl,
  isLoop,
  isAutoPlay,
  isControls,
}: SimpleVideoPlayerProps) => {
  return (
    <ReactPlayer
      url={videoUrl}
      width={"100%"}
      height={"100%"}
      playing={isLoop} // 自動再生
      loop={isAutoPlay} // ループ再生
      controls={isControls} // 動画の操作が可能かどうか
    />
  );
};

React Player の Option について

react-player の README には、渡せる Props や Callback 系などの設定値の一覧がまとまっているので、これを見れば何ができるのかは、把握できます🙆‍♂️

また、Youtube, Vimeo, Mux などの各動画配信サービスの設定プロパティにも対応しているのも、Goodなポイントです🙌

Props

主な Props は、次のとおりです👀✨

Prop Description Default
url 再生するビデオまたは曲のURL。array または MediaStream オブジェクトを指定可能。
playing true または false を設定してメディアの再生や一時停止を行う。 false
loop true または false を設定してメディアをループ再生する。 false
controls ネイティブプレーヤーコントロールを表示するかどうかを設定。Vimeoビデオの場合、コントロールを非表示にするにはビデオ所有者が有効にする必要がある。 false
light true に設定するとビデオのサムネイルのみが表示され、クリックでフルプレーヤーが読み込まれる。プレビュー画像をオーバーライドするには画像URLを渡す。 false
volume プレーヤーの音量を 0 から 1 まで設定。null の場合、全プレーヤーでデフォルトの音量が使用される。 null
muted プレーヤーをミュートにする。volume が設定されている場合のみ機能。 false
playbackRate プレーヤーの再生速度を設定。YouTube、Wistia、およびファイルパスでのみサポートされている。 1
width プレーヤーの幅を設定。 640px
height プレーヤーの高さを設定。 360px
style ルート要素に inline styles を追加。 {}
progressInterval onProgress コールバックの間隔をミリ秒単位で設定。 1000
playsinline 対応している場合に playsinline 属性を適用。 false
pip true または false を設定してピクチャー・イン・ピクチャーモードを有効化または無効化する。ファイルURLで再生している場合のみ、特定のブラウザでサポートされている。 false
stopOnUnmount pip を使用している場合、stopOnUnmount={false} を使用してReactPlayerがアンマウントされてもピクチャー・イン・ピクチャーモードで再生を続けるようにする。 true
fallback 遅延読み込みを使用している場合に使用するフォールバック要素またはコンポーネント。 null
wrapper コンテナ要素として使用する要素またはコンポーネント。 div
playIcon ライトモードで再生アイコンとして使用する要素またはコンポーネント。
previewTabIndex ライトモードで使用するタブインデックスを設定。 0
config 各プレーヤーの設定を上書きするオプションを指定。config propを参照。

https://github.com/CookPete/react-player?tab=readme-ov-file#props

Callback Props

主な Callback Props は、次のとおりです👀✨

Prop Description
onReady メディアがロードされ再生の準備が完了した際に呼び出される。playingtrue に設定されている場合、メディアは即座に再生される。
onStart メディアの再生が開始されたときに呼び出される。
onPlay メディアが再生または一時停止後やバッファリング後に再開されたときに呼び出される。
onProgress playedloaded の進捗状況を割合で提供するコールバック。秒単位で playedSecondsloadedSeconds も提供される。例: { played: 0.12, playedSeconds: 11.3, loaded: 0.34, loadedSeconds: 16.7 }
onDuration メディアの長さ(秒単位)を含むコールバック。
onPause メディアが一時停止されたときに呼び出される。
onBuffer メディアがバッファリングを開始したときに呼び出される。
onBufferEnd メディアのバッファリングが完了したときに呼び出される。ファイル、YouTube、Facebookで動作する。
onSeek seconds パラメータでメディアがシークされたときに呼び出される。
onPlaybackRateChange プレーヤーの再生速度が変更されたときに呼び出される。YouTube、Vimeo、Wistia、およびファイルパスでサポートされている。
onPlaybackQualityChange プレーヤーの再生品質が変更されたときに呼び出される。YouTubeでのみサポートされている(有効にした場合)。
onEnded メディアの再生が終了したときに呼び出される。looptrue に設定されている場合は発火しない。
onError メディアの再生中にエラーが発生したときに呼び出される。
onClickPreview ユーザーが light モードのプレビューをクリックしたときに呼び出される。
onEnablePIP ピクチャー・イン・ピクチャーモードが有効になったときに呼び出される。

https://github.com/CookPete/react-player?tab=readme-ov-file#callback-props

視聴ログを定期的に送信する Youtube Video Player を実装する

続いて、視聴ログを定期的に送信する Youtube Video Player を実装してみます。

実際に実装した画面は、次のような感じです👀✨

YoutubeVideoPlayer.tsx
import { Fragment, useState, useRef, useEffect } from "react";
import ReactPlayer from "react-player";
import type { OnProgressProps } from "react-player/base";

// ------------------------ ここから Next.js SSR 対応のコード ------------------------
// // NOTE: Next.js対応
// import dynamic from "next/dynamic";
// import type ReactPlayer from "react-player";

// // NOTE: Next.js での動的インポート対応
// // ReactPlayer を動的にインポート (SSR 無効化)
// const Player = dynamic(() => import("react-player"), { ssr: false });
// ------------------------ ここまで Next.js SSR 対応のコード ------------------------

interface YoutubeVideoPlayerProps {
  videoKey: string; // youtube video key
  autoPlay?: boolean; // 自動再生フラグ
  originUrl?: string; // ルートURL
  watchLogIntervalSeconds?: number; // 視聴ログ送信間隔(秒)
}

// TODO: 実際に実装する際は Logic は Custom Hook に切り出す
export const YoutubeVideoPlayer = ({
  videoKey,
  autoPlay,
  originUrl,
  watchLogIntervalSeconds,
}: YoutubeVideoPlayerProps) => {
  // プレーヤーの参照を保持する Ref
  const playerRef = useRef<ReactPlayer>(null);
  // 視聴ログ送信間隔の Timer Ref
  const intervalRef = useRef<NodeJS.Timeout>();
  // 再生中フラグ
  const [isPlaying, setIsPlaying] = useState(false);

  // progress の最新値を保持するための Ref を追加
  const progressRef = useRef<OnProgressProps>({
    played: 0,
    playedSeconds: 0,
    loaded: 0,
    loadedSeconds: 0,
  });

  const handlePlayOn = () => {
    setIsPlaying(true);
  };

  const handlePlayOff = () => {
    setIsPlaying(false);
  };

  // 再生中に定期実行されるコールバック
  const handleProgress = (progress: OnProgressProps) => {
    console.log("onProgress Called", progress);
    // 最新の progress (進捗状況)を Ref に保存, setInterval での参照のため。
    progressRef.current = progress;
  };

  // 視聴ログを送信する関数
  const sendVideoWatchLog = () => {
    console.log("sendVideoWatchLog Called");
    console.log("playerRef", playerRef);

    // プレーヤーが存在する場合のみ処理を実行
    if (playerRef.current) {
      // プレイヤーの再生位置を取得(秒), playedSeconds と同じ値
      const currentTime = playerRef.current?.getCurrentTime();
      console.log(`再生位置: ${currentTime}`);
      console.log("progress", progressRef.current);

      const {
        played, // 動画の再生済み部分を全体の割合で示した値 (0〜1)
        playedSeconds, // 動画の再生済み時間を秒単位で示した値 (秒)
        loaded, // 動画の読み込み(バッファリング)済み部分を全体の割合で示した値 (0〜1)
        loadedSeconds, // 動画の読み込み済み時間を秒単位で示した値 (秒)
      } = progressRef.current;

      console.log(
        "currentTime === playedSeconds",
        currentTime === progressRef.current.playedSeconds
      );
      // 再生済みのパーセンテージを表示
      console.log(`再生位置(%): ${(played * 100).toFixed(2)}%`);
      // 再生済みの時間を表示
      console.log(`再生位置(秒): ${playedSeconds.toFixed(2)}`);

      // 読み込み済みのパーセンテージを表示
      console.log(`読み込み済み(%): ${(loaded * 100).toFixed(2)}%`);
      // 読み込み済みの時間を表示
      console.log(`読み込み時間(秒): ${loadedSeconds.toFixed(2)}`);

      // 送信するデータを作成
      const logData = {
        currentTime, // 再生位置
        played, // 再生済みの割合 (0〜1)
        playedSeconds, // 再生済みの時間 (秒)
        loaded, // 動画の読み込み済み割合 (0〜1)
        loadedSeconds, // 動画の読み込み済み時間 (秒)
        timestamp: new Date().toISOString(),
      };
      // TODO: 視聴ログをサーバーに送信する処理を実装
      console.log("視聴ログを送信:", logData);
    }
  };

  // 再生中なら、視聴ログ送信間隔ごとに視聴ログを送信する
  useEffect(() => {
    if (isPlaying && watchLogIntervalSeconds) {
      const interval = watchLogIntervalSeconds * 1000;

      // 再生中ならタイマーを開始
      intervalRef.current = setInterval(() => {
        // 設定された視聴ログ送信間隔ごとに視聴ログを送信する
        sendVideoWatchLog();
      }, interval);
    } else {
      // 再生中でないならタイマーをクリア
      clearInterval(intervalRef.current);
      intervalRef.current = undefined;
    }
    // クリーンアップ処理
    return () => {
      clearInterval(intervalRef.current);
    };
  }, [isPlaying, watchLogIntervalSeconds]);

  return (
    <Fragment>
      <ReactPlayer
        ref={playerRef}
        url={`https://www.youtube.com/watch?v=${videoKey}`}
        width={"100%"}
        height={"100%"}
        playing={autoPlay} // 自動再生
        loop={false} // ループ再生
        controls={true} // 動画の操作が可能かどうか
        onStart={handlePlayOn} // 再生開始時 Callback Func
        onPlay={handlePlayOn} // メディアが一時停止またはバッファリング後に再生を開始または再開する時 Callback Func
        onPause={handlePlayOff} // 一時停止時 Callback Func
        onEnded={handlePlayOff} // 再生が終了した時 Callback Func
        progressInterval={1000} // 進捗状況を取得する間隔(秒), onProgressの実行間隔, Default: 1000(1秒)
        onProgress={handleProgress} // 再生中に定期実行される Callback Func
        // 各プレーヤーごとの独自設定
        config={{
          /**
           * YouTubeのプレーヤーのパラメータ設定 Docs
           * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5&hl=ja
           */
          youtube: {
            playerVars: {
              autoplay: autoPlay ? 1 : 0, // 自動再生
              playsinline: 1, //  iOS 上の HTML5 プレーヤーで動画をインライン再生する
              origin: originUrl, // ルートURL指定
              rel: 0, // パラメータの値が 0 に設定されている場合、関連動画は表示されません。
            },
          },
        }}
      />
      <p className="flex items-center justify-center mt-5 text-lg">
        再生中: {isPlaying ? "Yes" : "No"}
      </p>
    </Fragment>
  );
};

再生中なら、視聴ログ送信間隔ごとにsendVideoWatchLog()を実行して、視聴ログを送信するようにしています!

擬似ライブ(時間指定の擬似的・生配信)の Video Player を実装する

擬似的なライブ配信動画の Video Player を作成する場合は、次のような設定になります。

PseudoLiveStreamingVideoPlayer.tsx
import { Fragment, useRef } from "react";
import ReactPlayer from "react-player";

// ------------------------ ここから Next.js SSR 対応のコード ------------------------
// // NOTE: Next.js対応
// import dynamic from "next/dynamic";
// import type ReactPlayer from "react-player";

// // NOTE: Next.js での動的インポート対応
// // ReactPlayer を動的にインポート (SSR 無効化)
// const Player = dynamic(() => import("react-player"), { ssr: false });

// ------------------------ ここまで Next.js SSR 対応のコード ------------------------
interface PseudoLiveStreamingVideoPlayerProps {
  videoUrl: string;
}

// TODO: 実際に実装する際は Logic は Custom Hook に切り出す
// 擬似ライブ動画再生用のコンポーネント
export const PseudoLiveStreamingVideoPlayer = ({
  videoUrl,
}: PseudoLiveStreamingVideoPlayerProps) => {
  // プレーヤーの参照を保持する Ref
  const playerRef = useRef<ReactPlayer | null>(null);

  // 動画を指定した位置から再生するFunc
  const offSetStart = (offSetSeconds: number) => {
    console.log("offSetStart Called");
    if (playerRef && playerRef.current) {
      // 動画の再生位置を変更する (秒指定)
      playerRef.current.seekTo(offSetSeconds, "seconds");
    }
  };

  // プレーヤーの準備完了時 Call
  const onReady = () => {
    console.log("onReady Called");

    // TODO: VideoApi.getOffset() を Call して 動画の再生位置(開始時刻) offset を取得する
    const offSetSeconds = 12;

    offSetStart(offSetSeconds);
  };

  // シーク(再生位置を変更した)時 Call
  const onSeek = () => {
    console.log("onSeek Called");
    console.log("再生位置の変更を検知");
  };

  return (
    <Fragment>
      <ReactPlayer
        ref={playerRef}
        url={videoUrl}
        width={"100%"}
        height={"100%"}
        playing={true} // 自動再生する
        loop={false} // ループ再生はしない
        controls={false} // 動画は操作できない
        onReady={onReady} // プレーヤーの準備完了時 Call
        onSeek={onSeek} // シーク(再生位置を変更した)時 Call
      />
    </Fragment>
  );
};

実装のポイントは、次のとおりです。

  1. onReady()で、Video Player の準備が整ったら、offSetStart()を Call する。
  2. playerRef.current.seekTo()で動画の再生位置を設定して、動画を開始する。

まとめ

react-player は、動画プレイヤーを簡単に実装できオプションも豊富なので、おすすめです。

参考・引用

https://zenn.dev/manase/scraps/2c3feac9abf294

https://www.npmjs.com/package/react-player

https://github.com/CookPete/react-player

Discussion