🤔

動画における端末間のデザインを統一した字幕を付ける方法

2024/11/10に公開

背景

動画を<video>要素で再生している状況において、動画上に字幕を付けたい場合はWEBVTTを用いる方法が簡単に実装ができるためオススメである。

WEBVTTについてはこちらの記事を参照していただきたい。

ざっくり説明すると、字幕ファイルであるVTTファイルを<track>要素で読み込むことにより、動画上に字幕が表示されるようになる。

字幕をただ表示させたいだけならば、これで実装完了となる。
しかし、この方法では1つ問題がある。
それは端末間のデザインが異なるという問題である。

以下は1枚目がAndroidでの字幕の見え方、2枚目がiOSでの字幕の見え方である。

このように環境によって、字幕の見え方が変わってしまう問題が存在する。
これは端末側でcss等の見た目に関する設定を強制的に上書きしてしまうために発生する。
システムに組み込むことを考えた場合、デザインのずれは非常に気になる部分である。

そこで、視聴環境の違いによるデザインのずれがなくなる字幕の実装を考えた。

他社実装を調査

世界的に有名な動画配信プラットフォームであるYouTubeの字幕の実装方法を調査した。

結果から言うと以下のようになる。

  • WEBVTTは利用していない
    • 字幕の情報をJSON形式で取得
  • <video>要素のtimeupdateイベントで字幕の表示を更新
  • 字幕はHTMLで構築

意外なことに、WEBVTTは利用していなかった。
POSTリクエストで字幕の情報をJSON形式で取得し、再生中の動画の<video>要素のtimeupdateイベントの発火によって、字幕の表示を更新するという実装であった。

この実装は気になるところが3点ある。

  • timeupdateイベントは高頻度でイベント発火するため、パフォーマンスに影響しないか?
  • timeupdateイベントはシステムの負荷によって発火のタイミングが変わるため、字幕の更新が遅れないか?
  • JSON形式でデータを持っているため、字幕の表示に関する実装は自前で構築する必要があるのではないか?

標準的な機能ですでに字幕ファイルの読み込みと描画まで可能であるため、可能な限り車輪の再発明は避けたいところである。
そこで、別の方法を検討した。

検討

<track>要素を調査すると、cuechangeイベントなるものを発見した。

MDNから引用したものを以下に記載する。

キューの変更の検出

配下にある TextTrack は track プロパティで示され、現在表示されているキューが変更される度に cuechange イベントを受信します。これは、このトラックがメディア要素に関連付けられていない場合でも発生します。
このトラックがメディア要素に関連付けられている場合、 <track> を <audio> または <video> 要素の子として使用すると、 cuechange イベントは HTMLTrackElement にも送信されます。

<track>: 埋め込みテキストトラック要素 - HTML: ハイパーテキストマークアップ言語 | MDN

キューというのは字幕に関する情報の1つの塊である。
つまり、現在表示中である字幕情報がcuechangeイベントを使えば知ることができるのである。

さらにcuechangeイベントについて詳しく調査すると、
以下のようなコードで現在表示中の字幕に関する情報が取得できるらしい。

let textTrackElem = document.getElementById("texttrack");

textTrackElem.addEventListener("cuechange", (event) => {
let cues = event.target.track.activeCues;
});

HTMLTrackElement: cuechange イベント - Web API | MDN

そして、activeCuesVTTCueオブジェクトのリストを返す。
そのため、activeCues[0].textのようにすることで、現在表示中の字幕テキストを知ることが可能である。
VTTCue - Web API | MDN

cuechangeイベントを用いて、イベント発火時にHTMLで構築した字幕の表示を更新するという実装にすることで比較的簡単に実現できそうである。

ロジック

まず、動画と字幕を紐づけるために<video>要素の子要素に<track>要素を配置する。
これで、この動画と字幕は紐づいたため、動画の再生状況に応じて字幕が表示/更新されるようになる。

<video src="...">
    <track id="track" src="..." label="captions" kind="caption" srclang="ja" />
</video>

<track>要素による字幕の描画は不要であるため、非表示にする必要がある。
modehiddenにすることで非表示にすることができる。
今回はcuechangeイベントを発火させたいため、見た目だけ非表示にする必要がある。
もし、disabledに設定するとcuechangeイベントが発火しないので注意。

const trackElem = document.getElementById("track");
trackElem.mode = "hidden";

字幕の表示が更新された場合はHTMLで構築した字幕を更新する処理が必要である。
まずは、字幕用の要素を作っておく。
※cssで字幕のスタイルを調整し、動画上に表示されるように調整する。

<div id="caption"></div>

次に、cuechangeイベント発火時に字幕の内容を更新する処理を実装する。
この処理によって、字幕の表示が切り替わった際にHTMLで構築した字幕の内容も更新されるようになる。

trackElem.addEventListener("cuechange", (event) => {
    const captionElem = document.getElementById("caption");
    const cues = event.target.track.activeCues;

    captionElem.textContent = "";
    for (const cue of cues || []) {
        captionElem.textContent += cue.text;
    }
});

この実装により、字幕のデザインはcssに依存するようになるため、環境に左右されることなく字幕のデザインを統一することが可能となる。

まとめ

WEBVTTを利用し、比較的簡単に環境間の字幕デザインの差異を解消する方法を紹介した。
WEBVTTを利用しているため、字幕ファイルをパースしたり、動画と同期を取るような面倒な実装を行う必要はない。
また、cssによって字幕のデザインを変更することが可能であるため、視聴環境におけるデザインの統一はもちろんのこと、cssを用いて多種多様なデザインにすることも可能である。
cuechangeイベントという字幕の表示が切り替わった際に発火するイベントを利用しているため、timeupdateイベントを利用するよりもイベント発火は基本的に少ないため、パフォーマンス面も問題ないといえる。

Discussion