Video.jsでビデオコンテンツを自分好みにカスタマイズしてみる

Video.jsが便利だったので備忘録ついでに書き残しておきます。

# 目標
:::note info
#### プレイヤーの各種設定
Video.jsのデフォルト設定から一部変更を行う
:::
:::note info
#### ±10秒スキップ機能の追加
Video.jsのデフォルト機能にはスキップ機能は無いので、処理を追加して拡張実装を行う
:::
:::note info
#### コントローラの常時表示
CSSでコントローラ部分のレイアウトを変更する
:::
:::note info
#### チャプター機能の追加
JavaScriptでチャプターごとにジャンプできるように実装する
:::

完成はこんな感じ
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1711764/b6c1a245-78dc-a436-66b6-648b783faf5c.png)


# Video.jsとは
Video.jsは、htmlにおける動画コンテンツの実装やスタイリングを簡単に行えるJavaScript製のプラグインです。
jsとcssのCDNも提供しているので、簡単に導入することができます。
ドキュメントの日本語化や日本語の記事は少ないので、情報リソースは基本英語となりますが、カスタマイズに関しては簡単に行うことができるのが特徴です。
[公式サイト&ドキュメント](https://videojs.com/)

# Video.jsの長所
- **ブラウザ依存によるUIの差異や操作性の統合**
⇒Chrome、Edge、Safari、Firefoxなど、幅広いブラウザに対応しています（IEの一部バージョンでは完全には対応できていないようです）。本来のVideoタグを使用すると、ブラウザにより機能やUIが変化しますが、Video.jsではそのような差分を統合して提供することができます。

- **カスタマイズ性、拡張性の充実**
⇒デフォルトでの実装でも最低限の機能を提供してくれますが、UIや機能を独自に定義して実装することも可能です。

- **豊富なプラグインとスキン**
⇒有志により作られたプラグインを導入することで、モダンなスキンや便利な機能を簡単に導入することができます。一覧は[こちら](https://videojs.com/plugins/)

# 実装

### Video.jsの導入
公式サイトよりダウンロードもしくは、CDNをscriptタグに定義することで利用可能です。
公式サイト：https://videojs.com/

**CDN**
JS：`<script src="https://vjs.zencdn.net/7.20.2/video.min.js"></script>`
CSS：`<link href="https://vjs.zencdn.net/7.20.2/video-js.css" rel="stylesheet" />`

### videoタグを定義
デフォルトだとこんな感じです。
```index.html
<video class="video-js" data-setup='{}'>
    <source src="movie.mp4" type="video/mp4">
</video>
```
例えばclassに`vjs-big-play-centered`を追加すれば中央に大きな再生ボタンが表示させることができます。詳しくは公式ドキュメントを参照してください。
私は最終的にこのようにしました。
```index.html
<video class="video-js vjs-big-play-centered" data-setup='{}' controls playsinline id="video">
    <source src="movie.mp4" type="video/mp4">
</video>
```

### 各種設定
videojsクラスからオブジェクトを生成し、各設定が行えるようになります。
今回は、
- videoタグを親要素と同じ大きさに設定
- 再生速度の倍率を変更
- コントローラの表示
- 表示の日本語化

などを行います。
なお、通常のvideoタグはwidth="100%"で親要素いっぱいに大きさを調整できますが、video.jsでは無効となるため、fluid: trueを追加する必要があります。

```script.js
const player = videojs('video', {
    autoplay: false, // 自動再生を無効
    fluid: true, // 動画コンテンツを親要素いっぱいに広げる
    loop: false, // 繰り返し再生無効
    controls: true, // コントローラ表示
    preload: 'auto', // videoタグがロードされた瞬間に動画をダウンロード
    playbackRates: [0.5, 1, 1.3, 1.5], // 再生速度の倍率
    languages: {
        ja: {
            'Play': '再生',
            'Pause': '停止',
            'Play Video': '再生',
            'Mute': '消音',
            'Playback Rate': '再生速度',
            'Picture-in-Picture': 'ピクチャインピクチャ',
            'Fullscreen': '全画面表示',
            'Non-Fullscreen': '通常表示'
        }
    }, // 日本語の言語対応
    language: 'ja' // 言語を日本語に設定
});
```

### ±10秒スキップ機能の追加
次に、スキップ機能を追加していきます。デフォルトのコントローラにはスキップ機能は無いので、アイコンの追加と処理の追加を行う必要があります。
videojsクラスから生成した動画プレイヤーのオブジェクトにgetChild('ControlBar')というメソッドで、コントローラに様々なカスタマイズを施すことができます。
今回はこのように実装しました。

```script.js
// コントローラに10秒戻しボタンと10秒送りボタンを追加
const rewindButton = player.getChild('ControlBar').addChild('button');
const forwardButton = player.getChild('ControlBar').addChild('button');
rewindButton.controlText('10秒戻し');
forwardButton.controlText('10秒送り');

// アイコンを設定
player.getChild('ControlBar')
    .el()
    .insertBefore(
        rewindButton.el(),
        player.getChild('ControlBar').getChild('pictureInPictureToggle').el()
    )
    .innerHTML = `<img src='rewind.png' width=20 />`;

player.getChild('ControlBar')
    .el()
    .insertBefore(
        forwardButton.el(),
        player.getChild('ControlBar').getChild('pictureInPictureToggle').el()
    )
    .innerHTML = `<img src='forward.png' width=20 />`;

// スキップ処理を追加
rewindButton.el().addEventListener('click', () => {
    video.currentTime -= 10;
});
forwardButton.el().addEventListener('click', () => {
    video.currentTime += 10;
});
```


### コントローラの常時表示
通常、コントローラは動画コンテンツにカーソルを合わせたときに表示されるようになっています。
これを常時表示したい場合は、videoタグに`data-setup='{ "inactivityTimeout": 0 }'`という属性を追加することで実現はできますが、これでは動画の下の部分がコントローラにより常に隠れてしまいます。
これは、コントローラを下にずらして表示させることで解決できますが、全画面表示時にはコントローラが消えてしまうので、「通常時はコントローラを下に、全画面表示時はスタイルを元に戻す」という処理が必要となります。
そこで今回は、JSで動的にスタイルを変えるようにしました。

```script.js
// 通常再生時（コントローラを下にずらす）
const setNormalStyle = el => {
    el.style.setProperty('visibility', 'visible', 'important');
    el.style.setProperty('opacity', '1', 'important');
    el.style.setProperty('bottom', '-3em', 'important');
    el.style.setProperty('background-color', 'rgba(7, 20, 30, 1)', 'important');
}

// 全画面表示再生時（スタイルを元に戻す）
const setFullscreenStyle = el => {
    el.style.setProperty('visibility', '', 'important');
    el.style.setProperty('opacity', '', 'important');
    el.style.setProperty('bottom', '', 'important');
    el.style.setProperty('background-color', '', 'important');
}

// コントローラの要素を取得
const controlBar = player.getChild('ControlBar').el();

// 画面表示切替イベントが起きた際の処理を記述
videojs('video').on('fullscreenchange', () => {
    if(player.isFullscreen()) {
        setFullscreenStyle(video);
        setFullscreenStyle(controlBar);
    } else {
        setNormalStyle(video);
        setNormalStyle(controlBar);
    }
});

// 画面ロード時は通常再生時のスタイルを読込
setNormalStyle(video);
setNormalStyle(controlBar);
```
画面表示切替のイベントは`fullscreenchange`で発火できます。
また、`isFullscreen()`は全画面表示なら`true`を、そうでなければ`false`を返します。

なお、全画面表示を使わないという場合は以下のようにCSSで指定するだけでOKです。
```style.css
.video-js .vjs-control-bar {
  visibility: visible !important;
  opacity: 1 !important;
  bottom: -3em !important;
  background-color: rgba(7, 20, 30, 1) !important;
}
```


### チャプター機能の追加
こちらはJavaScriptから簡単に実装しました。
currentTimeは秒数で指定する必要があるので、分秒単位表記との変換とかは別で実装する必要がありますね。
```index.html
<table class="table">
    <tr>
        <td><a href="javascript:void(0)" onclick="moveTime(0)">00:00</a></td>
        <td>サンプルサンプルサンプル</td>
    </tr>
    <tr>
        <td><a href="javascript:void(0)" onclick="moveTime(5)">00:05</a></td>
        <td>サンプルサンプルサンプル</td>
    </tr>
    <tr>
        <td><a href="javascript:void(0)" onclick="moveTime(10)">00:10</a></td>
        <td>サンプルサンプルサンプル</td>
    </tr>
    <tr>
        <td><a href="javascript:void(0)" onclick="moveTime(30)">00:15</a></td>
        <td>サンプルサンプルサンプル</td>
    </tr><tr>
        <td><a href="javascript:void(0)" onclick="moveTime(80)">01:20</a></td>
        <td>サンプルサンプルサンプル</td>
    </tr>
</table>
```
```script.js
const moveTime = (time) => {
    video.currentTime = time;
}
```

以上で完成です。

# 所感
今回初めてVideo.jsを使用してみましたが、拡張性が強く非常に使いやすかったです。
一方で、ドキュメントに用例などが無く説明不足な点や日本語資料が少ない点が少し気になりました。
普通はYoutubeなどの動画プラットフォームを埋め込むことが多いので、このように動画プレイヤー自体を作成することは少ないと思いますが、もしレイアウトが気に入らない、欲しい機能が無いなどがあれば、かなり有用な一助となるかと思います。