LoginSignup
1
4

More than 1 year has passed since last update.

React で video.js を使ってみた

Posted at

React で video.js を使ってみたときの作業メモです。バージョンは以下の通りです。

"video.js": "^7.11.8",
"videojs-errors": "^4.5.0",
"@types/video.js": "^7.3.27",
"@types/videojs-errors": "^4.5.0"

ひな形

Video.js and ReactJS integration でひな形をつくります。

コントロールバーのカスタマイズ

コントロールバーの各種ボタン(再生ボタンや全画面表示ボタン)の表示/非表示を実現する素朴な方法です。
まずはそれぞれの名前を調べます。player.controlBar.children() で一覧表示すると便利です。

console.log("player.controlBar.children()::", player.controlBar.children());

独自のボタンも作れます。

import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
const Button = videojs.getComponent("Button");

// Bootstrap Icons あたりから取得しました
const iconSVG = `<div style="cursor: pointer"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-square" viewBox="0 0 16 16">
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
</svg></div>`;

interface MyButtonOption extends VideoJsPlayerOptions {
    onClick: () => void;
}

export default class MyButton extends Button {
    constructor(player: VideoJsPlayer, options: MyButtonOption) {
        super(player, options);
        this.controlText("My Button");
        this.el().innerHTML = iconSVG;
    }

    handleClick() {
        const player = this.player();
        player.pause(); // など、このボタンでやりたいことを書く。
    }

    createEl(): HTMLButtonElement {
        return super.createEl("button", {
            className: "vjs-custom",
        });
    }
}

独自のボタンは、videojs.registerComponent で登録する必要があります。

  React.useEffect(() => {
    // make sure Video.js player is only initialized once
    if (!playerRef.current) {
      const videoElement = videoRef.current;
      if (!videoElement) return;

      videojs.registerComponent("Myutton", MyButton);  // これです

その後、デフォルトのコントロールバーのコンポーネントをいったん削除し、表示したい順で追加します。

const pictureInPictureToggle = player.controlBar.getChild("PictureInPictureToggle");
const fullscreenToggle = player.controlBar.getChild("FullscreenToggle");
const currentTimeDisplay = player.controlBar.getChild("CurrentTimeDisplay");
const volumePanel = player.controlBar.getChild("VolumePanel");
const durationDisplay = player.controlBar.getChild("DurationDisplay");
const progressControl = player.controlBar.getChild("ProgressControl");
const remainingTimeDisplay = player.controlBar.getChild("RemainingTimeDisplay");
player.controlBar.removeChild(volumePanel);
player.controlBar.removeChild(durationDisplay);
player.controlBar.removeChild(progressControl);
player.controlBar.removeChild(pictureInPictureToggle);
player.controlBar.removeChild(fullscreenToggle);
player.controlBar.removeChild(currentTimeDisplay);
player.controlBar.removeChild(remainingTimeDisplay);
player.controlBar.addChild("MyButton");
player.controlBar.addChild("VolumePanel");
player.controlBar.addChild("DurationDisplay");
player.controlBar.addChild("ProgressControl");
player.controlBar.addChild("CurrentTimeDisplay");
player.controlBar.addChild("FullscreenToggle");

もっと高度なカスタマイズは、公式ドキュメント や、個人ブログ How to merge Video.js buttons into one が参考になるはずです。

TypeError: Cannot read properties of null (reading 'currentTime') 対策

CRA で試しているときは問題なかったのですが、大きめのアプリに組み込むと TypeError: Cannot read properties of null (reading 'currentTime') というエラーが発生し、悩まされました。
後処理に player.controlBar.progressControl.seekBar.dispose() を追加することで、解消しました。

useEffect(() => {
    const player = playerRef.current;

    return () => {
        if (player) {
            player.dispose();
            player.controlBar.progressControl.seekBar.dispose(); // TypeError: Cannot read properties of null (reading 'currentTime') 対策
            playerRef.current = null;
        }
    };
}, [playerRef]);

異常系の実装

video.js の MediaError だけでは、補足できないエラーがあります。videojs-errors というプラグインが便利でした。videojs() の第3引数のコールバック内で設定を行う必要があります。

const player: AppPlayer = (playerRef.current = videojs(
    videoElement,
    options,
    () => {
        player.errors(); // videojs-errors の有効化
        player.errors.timeout(10 * 1000); // タイムアウトの設定を追加
        onReady && onReady(player);
    }
));

ただ、VideoJsPlayer の @types/videojs-errors による拡張がうまくいっていないようでした。player.errors() の errors() が生えません。以下のような残念な対応で乗り切りました。

// import * as errors from "videojs-errors";

interface AppPlayer extends VideoJsPlayer {
    //errors: typeof errors; // これをワークアラウンドとしたかったが、errors() がない。
    errors: any;
}

処理自体は player の error イベントのリスナに書いてゆきます。

player.on("error", (e) => {
    console.log("player.error()::", player.error());
});

タイムアウトについては、他に player.techretryplaylist のリスナとして自前実装する案もありましたが、採用しませんでした。

player.on("loadedmetadata", () => {
    let retries = 0;
    player
        .tech({ IWillNotUseThisInPlugins: true })
        .on("retryplaylist", (e) => {
            retries++;
            if (retries >= 5) {
                e.stopImmediatePropagation();
                player.dispose()
            }
        });
});

開発環境構築とデータの準備

フリー動画。

このへんを参考に手元のライブストリーミング環境をつくりました。

次のようなデータもみつけました。

ご参考。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4