hls.js と videojs-contrib-hls で Timed Metadata を同期テキストとして扱う

Timed Metadata を取り出したい

HLS では MPEG-2 Transport Stream の中に、Timed Metadata として ID3 形式のデータを混ぜ込むことをサポートしています。HLS を扱う各ライブラリにおけるメタデータの取り出し方メモ。

videojs-contrib-hls

例によって v0.17 です。videojs では TextTrackTextTrackCueList を摸したオブジェクトが実装されているので、それらからデータを順次取得できます。

現在の master にも player.textTracks() が実装されていたので、恐らく似たような雰囲気のままと思われます。

cuechange イベントを listen するだけで OK

cuechange イベントがくるたびに textTrack.activeCues[0] を取り出せば、その瞬間のセグメントに含まれていた Timed Metadata を取得できます。

const textTrack = player.textTracks()[0]
textTrack.addEventListener('cuechange',() => {
  if (!textTrack.activeCues[0]) {
    return;
  }
  console.log('cuechange', textTrack.activeCues[0].text);
});

hls.js

こちらは v0.5 です。hls.js はデータを受信するたびに Timed Metadata のパースはしてくれますが、これだけだと動画の再生状態と同期してメタデータを処理することができません。

パース時のイベントは hls.on(Hls.Events.FRAG_PARSING_METADATA, (e) => {}) で得られます。videojs と同じように TextTrack の cuechange イベントでタイミングを合わせて処理するために一手間加えます。

自力で VTTCue を作成して TextTrack に加えれば OK

video.addTextTrack('metadata') として hls.js に attach する Video 要素に TextTrack を追加します。これに FRAG_PARSING_METADATA のタイミングでキューを作成して追加すれば OK。あとは同じように TextTrack の cuechange を listen すれば再生タイミングに合わせてメタデータを処理できます。

ブラウザネイティブな仕組みを使っているので、クロスブラウザの罠が潜んでいる可能性アリ

const video = document.createElement('video');
const track = video.addTextTrack('metadata');
const hls = new Hls();

hls.on(Hls.Events.FRAG_PARSING_METADATA, (event, data) => {
  data.samples.map(sample => {
    const cue = new (window.VTTCue || window.TextTrackCue)(-1, -1, '');
    cue.text = String.fromCharCode.apply(null, [].slice.call(sample.data, 22));;
    cue.pauseOnExit = true;
    cue.startTime = sample.pts;
    cue.endTime = Number.MAX_VALUE;
    track.addCue(cue);
  });
});

track.addEventListener('cuechange', function() {
  const current = this.activeCues[this.activeCues.length - 1];

  let cue, i = 0;
  while ((cue = this.activeCues[i++])) {
    if (cue === current) continue;
    cue.endTime = current.startTime;
  }

  console.log('cuechange', current.text);
});

startTime と endTime が適切に設定されたキューを入れれば、activeCues の中身はひとつになるはずですが、上記の例では横着して PTS を startTime として代入しているだけです。

最新の有効なキューは activeCues の末尾に追加されるので、それを cuechange の際に現在のキューとして扱います。これを特定したのち、activeCues 内に残る他のキューの endTime に現在のキューの startTime を代入することで、入れ替わりのごとく既存キューを処分しています。(これで endTime の値をみて勝手に消えていきます)

参考