キューポイントをプログラムで実装する
概要
キューポイントは動画に設定できます。動画再生中、各キューポイントに到達するとイベントが発火します。
次の動画を再生すると、プレーヤーがプリロール、ミッドロール(5秒、10秒、12秒)、ポストロールのタイミングでキューポイント情報を表示する様子を確認できます。
******** キューポイント情報 ********
****** キューポイント情報終了 ******
この動画を再生すると、4つのキューポイントの情報がプレーヤーの下に表示されます。
主要な概念
Brightcove プレーヤーでキューポイントを効果的に利用するためには、いくつか理解すべき概念があります。このセクションではそれらについて説明します。Brightcove プレーヤーでキューポイントを効果的に利用するためには、次の概念を理解する必要があります。
Video Cloud カタログのキューポイント
最初に理解すべき概念は用語に関するものです。Brightcoveプレーヤーでは、キューポイントは HTML 標準に従ってテキストトラック要素として保存されます。つまり、Video Cloud 動画が使用される場合、「Video Cloud 形式」のキューポイントはテキストトラックに変換されます。
これらの「Video Cloud 形式」のキューポイントは、Video Cloud カタログから読み込まれるため「カタログ」キューポイントと呼ぶこともできます(カタログについて詳しくは Player Catalog を参照してください)。変換時には、タイプやキューポイントの時間など、一部の情報がテキストトラックに変換されます。
カタログ キューポイントの構造
次に理解すべき概念は、カタログ キューポイントと HTML 標準の構造に大きな違いがあるという点です。HTML キューポイントは継続時間を持つことができます。つまり、各キューポイントには開始時と終了時の 2 つのイベントが発火します。
変換プロセスでは、カタログ キューポイントはすべて開始時間と終了時間が同じキューポイントに変換されます。つまり、各カタログ キューポイントでは 2 つのイベントが発生するため、コード側で考慮する必要があります。
activecues 配列
HTML キューポイントを扱う際に理解すべきもう 1 つの HTML キューポイントを扱う際に理解すべき重要な概念が activeCues 配列です。すべてのキューポイントは配列として定義されますが、activeCues 配列には「アクティブ」なキューポイント、つまり再生時間がそのキューポイントの開始時間と終了時間の間にあるものが格納されます。
カタログ キューポイントを使用する場合、開始時間と終了時間が同じであるため、アクティブになるのはその 1 秒間だけです。また、カタログ キューポイントは重ならないため、複数のキューポイントが同時にアクティブになる可能性はほとんどありません。
キューポイントのタイプ
このドキュメントでは、キューポイントに type プロパティが割り当てられている例を紹介します。これらのタイプ値は、Studio の UI でキューポイントを作成する際に設定されます。キューポイントには 2 種類ありますが、いずれも type に文字列値を設定するものです。この値は、キューポイント情報を処理するカスタム JavaScript を使用する場合に役立つものです。タイプは以下のとおりです:
- Ad -
typeプロパティにADを設定 - Code -
typeプロパティにCODEを設定
Video Cloud キューポイント
このセクションでは、カタログ キューポイントの設定方法と、カタログ キューポイントのイベント ディスパッチを受け取る方法について説明します。
Video Cloud キューポイントの設定
Video Cloud キューポイントは、Video Cloud Studio を使用して動画に関連付けることができます。そのほかの方法については、次のドキュメントで詳しく説明されています: Media モジュールでのキューポイントの操作。
Video Cloud キューポイントの処理 - プレーヤーに静的にバインドされた動画
このセクションでは、動画がプレーヤーに静的にバインドされている場合、つまり動画が Studio または Player Management API を使用してプレーヤーに読み込まれている場合に、カタログ キューポイントを処理する方法を説明します。
キューポイントが読み込まれる前に処理を始めてしまう競合状態(レース コンディション)を防ぐために、キューポイントを扱う前に loadedmetadata イベントがディスパッチされるのを待つ必要があります。適切なテキストトラックを取得したら、oncuechange イベントを利用してキューポイント イベントのディスパッチを監視します。
以下のコードは、キューポイントを監視し、キューポイントから取得したデータを表示する方法を示しています。この例では、動画はプレーヤーに静的にバインドされています。
- 11行目: 動的に生成した HTML を挿入する場所として、p要素を作成します。
- 18行目、30行目:
one()メソッドを使用して、loadedmetadataイベントのリスナーを 1回だけ登録します。イベント ハンドラーはここで無名関数として定義されています。 - 19行目:
textTracks()メソッドを使用して TextTracks 配列を取得し、キューポイントを保持している 0 番目の要素を変数ttに代入します。実装によっては、キューポイントが別の配列要素に存在する可能性があります。詳細は後述の 正しいトラックを見つける セクションを参照してください。 - 20行目、28行目:
oncuechangeイベントがディスパッチされたときのイベント ハンドラーを設定します。 - 21行目: 最初の(配列の 0 番目の要素の)キューポイント ディスパッチを取得していることを確認します。この条件がない場合、各キューポイントに対して 2 回処理が実行されます。継続時間が重なり合うキューポイントを使用している場合は、この条件を変更する必要があります。
- 22〜26行目: キューポイントから取得した情報を使用して HTML を動的に生成し、HTML ページに挿入します。
- 29行目: 動画を再生します。
<video-js id="myPlayerID"
data-account="1507807800001"
data-player="zN3V18ZPEu"
data-embed="default"
controls=""
data-video-id="1507781667001"
data-playlist-id=""
data-application-id=""
width="960" height="540"></video-js>
<p id="insertionPoint"></p>
<script src="https://players.brightcove.net/1507807800001/zN3V18ZPEu_default/index.min.js"></script>
<script>
videojs.getPlayer('myPlayerID').ready(function() {
var player = this;
player.one("loadedmetadata", function () {
var tt = player.textTracks()[0];
tt.oncuechange = function () {
if (tt.activeCues[0] !== undefined) {
var dynamicHTML = "id: " + tt.activeCues[0].id + ", ";
dynamicHTML += "text: " + tt.activeCues[0].text + ", ";
dynamicHTML += "startTime: " + tt.activeCues[0].startTime + ", ";
dynamicHTML += "endTime: " + tt.activeCues[0].endTime;
document.getElementById("insertionPoint").innerHTML += dynamicHTML + "<br/>";
}
}
player.play();
});
});
</script>
Video Cloud キューポイントの処理 - プレーヤーに動画を動的に読み込む場合
このセクションでは、catalog.getVideo() と catalog.load() メソッドを使用して動画をプレーヤーに動的に読み込む場合に、カタログ キューポイントを処理する方法を説明します。
プレーヤー カタログを使用して動画を取得・読み込む場合、静的にバインドされた動画と比べてキューポイントの処理は少し簡単になります。loadedmetadata イベントを使用する必要がないためです。
- 11行目: 動的に生成したHTMLを挿入する場所として、p要素を作成します。
- 17行目、31行目:
catalog.getVideo()メソッドを使用して動画を取得します。コールバック関数はここで無名関数として定義されています。 - 19行目:
catalog.load()メソッドを使用して動画をプレーヤーに読み込みます。 - 21行目:
textTracks()メソッドを使用して TextTracks 配列を取得し、キューポイントを保持している 0 番目の要素を変数ttに代入します。実装によっては、キューポイントが別の配列要素に存在する可能性があります。詳細は後述の 正しいトラックを見つける セクションを参照してください。 - 22〜30行目:
oncuechangeイベントがディスパッチされたときのイベント ハンドラーを設定します。 - 23行目: 最初の(配列の 0 番目の要素の)キューポイントが定義されていることを確認します。この条件がない場合、各キューポイントに対して 2 回処理が実行され、2 回目は
activecues配列内に要素が定義されていない状態(2 回目のキューチェンジはキューポイント終了時のため)で実行されます。継続時間が重なり合うキューポイントを使用している場合は、この条件を変更する必要があります。 - 24〜28行目: キューポイントから取得した情報を使用して HTML を動的に生成し、HTML ページに挿入します。
- 32行目: 動画を再生します。
<video-js id="myPlayerID"
data-account="1507807800001"
data-player="zN3V18ZPEu"
data-embed="default"
controls=""
data-video-id=""
data-playlist-id=""
data-application-id=""
width="960" height="540"></video-js>
<p id="insertionPoint"></p>
<script src="https://players.brightcove.net/1507807800001/zN3V18ZPEu_default/index.min.js"></script>
<script>
videojs.getPlayer('myPlayerID').ready(function() {
var player = this;
player.catalog.getVideo('1507781667001', function (error, video) {
//deal with error
player.catalog.load(video);
player.one("loadedmetadata", function () {
var tt = player.textTracks()[0];
tt.oncuechange = function () {
if (tt.activeCues[0] !== undefined) {
var dynamicHTML = "id: " + tt.activeCues[0].id + ", ";
dynamicHTML += "text: " + tt.activeCues[0].text + ", ";
dynamicHTML += "startTime: " + tt.activeCues[0].startTime + ", ";
dynamicHTML += "endTime: " + tt.activeCues[0].endTime;
document.getElementById("insertionPoint").innerHTML += dynamicHTML + "<br/>";
}
}
});
player.play();
});
});
</script>
すべての Video Cloud キューポイント情報を取得する
既にお気づきかもしれませんが、すべての Video Cloud キューポイント情報が activecues 配列から直接取得できるわけではありません。これは、mediainfo プロパティから必要な情報を取得することで簡単に解決できます。
このソリューションの基本的なアプローチは次のとおりです:
mediainfoプロパティが設定されるよう、loadstartイベントが発生するのを待ちます。mediainfoプロパティからcue_points配列を取得し、変数に代入します。この変数には、Video Cloud キューポイントの完全な情報が含まれます。- キューポイント イベント発生時に、
timeプロパティの値に基づいて対応するキューポイント データを取得します。これは、配列内のオブジェクトから特定のプロパティ値に一致するオブジェクトを取得するヘルパー関数を使って実行します。 - 取得したキューポイント データを使用します。
次の画像は、キューポイント配列全体(左上)、単一キューポイントのデータコレクション(右上)、およびその単一キューポイント データコレクション内の 1 つのプロパティ(右下)を示しています。

以下のスニペットでは、前述のサンプルから新たに追加/変更されたコードのみを説明します。
- 452〜463行目: すべてのキューポイント配列から単一のキューポイントデータコレクションを抽出するための関数を定義します。この関数には、すべてのキューポイント配列、検索対象のプロパティ名、そして検索する値を引数として渡します。
- 432行目:
loadstartイベントを監視します。動画の読み込みが開始されると、mediainfoプロパティが設定されます。 - 434行目: すべての Video Cloud キューポイント配列を変数に代入します。
- 443行目: キューポイント ディスパッチ イベント ハンドラー内で、特定のキューポイント データコレクションを変数に代入します。ここで、最初の箇条書きで説明した関数が呼び出されます。使用される引数は次のとおりです:
cuePointAra: Video Cloud キューポイントの全コレクション'time': 値を検索する対象プロパティtt.activeCues[0].startTime: 現在のキューポイント ディスパッチ イベント ハンドラーで処理しているキューポイントの開始時刻
- 444〜445行目: デバッグ用の
console.log()メソッド呼び出しです。本番コードでは削除する必要があります。
<script>
videojs.getPlayer('myPlayerID').ready(function() {
var myPlayer = this,
cuePointAra = [],
allCuePointData;
myPlayer.on('loadstart', function () {
//console.log('mediainfo', myPlayer.mediainfo);
cuePointAra = myPlayer.mediainfo.cue_points;
var tt = myPlayer.textTracks()[0];
tt.oncuechange = function () {
if (tt.activeCues[0] !== undefined) {
var dynamicHTML = "id: " + tt.activeCues[0].id + ", ";
dynamicHTML += "text: " + tt.activeCues[0].text + ", ";
dynamicHTML += "startTime: " + tt.activeCues[0].startTime + ", ";
dynamicHTML += "endTime: " + tt.activeCues[0].endTime;
document.getElementById("insertionPoint").innerHTML += dynamicHTML + "<br/>";
allCuePointData = getSubArray(cuePointAra, 'time', tt.activeCues[0].startTime);
console.log('cue point data:', allCuePointData);
console.log('cue point metadata:', allCuePointData[0].metadata);
}
}
myPlayer.play();
myPlayer.muted(true);
});
function getSubArray(targetArray, objProperty, value) {
var i, totalItems = targetArray.length,
objFound = false,
idxArr = [];
for (i = 0; i < totalItems; i++) {
if (targetArray[i][objProperty] === value) {
objFound = true;
idxArr.push(targetArray[i]);
}
}
return idxArr;
};
});
</script>
HTML5 標準キューポイント
HTML5 標準キューポイントは、所定の形式で track 要素として保存されます。入門として有用なコンテンツが、次の HTML5 Rocks チュートリアルにあります: Track 要素の入門ガイド。このセクションでは、WebVTT キューポイント ファイルの形式と、そのキューポイントの処理方法について説明します。
キューポイント向け WebVTT ファイル形式
WebVTT ファイル形式は厳密に定義されています。キューポイント用のファイルは、次のように構成されます。
- ファイルの1行目に文字列 WebVTT を記述
- 空行を1行挿入
- 特定のキューポイントの識別子
- 継続時間を 00:00:00.000 --> 00:00:00.000 の形式で記述
- 継続時間の行から次の空行までの文字列は text 値として格納
- 空行を1行挿入
- 識別子/継続時間/テキスト/空行の形式で複数キューポイントを追加可能
次は有効な WebVTT キューポイント ドキュメントの例で、2つのキューポイントを定義しています。
WEBVTT
Carry
00:00:03.000 --> 00:00:09.000
{
"id": "First cue point",
"title": "Carry the rim",
"description": "Getting ready to mount a tire on the rim."
}
Balance
00:00:10.000 --> 00:00:15.000
{
"id": "Second cue point",
"title": "Balance the tire and rim",
"description": "Spin the mounted tire to check the balance."
}
次の動画を再生すると、上記 WebVTT ファイルのキューポイント処理が確認できます。開始時には HTML が動的に挿入され、終了時には Cue point duration over が表示されます。
******** Cue Point Information ********
****** End Cue Point Information ******
キューポイント処理の概要
HTML5 標準キューポイントを利用する基本手順は次のとおりです。
<track>タグで WebVTT ファイルを読み込むloadedmetadataイベントで適切なテキストトラックを取得oncuechangeイベント ハンドラーを設定activecues配列に要素があれば開始イベントとして処理- 配列に要素がなければ終了イベントとして処理
キューポイント処理コード
- 11行目: track タグで WebVTT を読み込み
- 15行目: 挿入先として
<pre>を生成 - 22行目・50行目:
one()で loadedmetadata を1回だけ監視 - 23行目: WebVTT の text track を変数
ttに格納 - 31行目・49行目: oncuechange を設定
- 35行目・38行目: active cue がなければ終了イベント
- 40〜48行目: JSON を含むキューポイント情報をHTMLに挿入
<video-js id="myPlayerID"
data-account="1752604059001"
data-player="default"
data-embed="default"
controls=""
data-video-id="4607357817001"
width="640" height="360">
<track kind="metadata" label="external-metadata-vtt"
src="https://solutions.brightcove.com/bcls/brightcove-player/cuepoints/cuepoints-2022.vtt" />
</video-js>
<pre id="insertionPoint"></pre>
<script src="https://players.brightcove.net/1752604059001/default_default/index.min.js"></script>
<script>
const player = videojs.getPlayer('myPlayerID');
player.ready(() => {
player.one("loadedmetadata", () => {
const tt = [].find.call(player.textTracks(), ({ label }) => label === 'external-metadata-vtt');
if (!tt) {
return;
}
tt.mode = 'hidden';
tt.oncuechange = () => {
const outputEl = document.getElementById("insertionPoint");
const activeCue = tt.activeCues[0];
if (!activeCue) {
outputEl.innerHTML += `Cue point duration over\n\n`;
return;
}
const { id, text, startTime, endTime } = activeCue;
outputEl.innerHTML += `id: ${id}\ntext: ${text}\nstartTime: ${startTime}, endTime: ${endTime}\n`;
try {
const { title, description } = JSON.parse(text);
outputEl.innerHTML += `${title}: ${description}\n\n`;
} catch (e) {}
};
});
});
</script>
キューポイント処理コード
- 270行目: track タグで WebVTT を読み込む
- 273行目: 動的 HTML 挿入用に段落要素を生成
- 285行目・309行目: loadedmetadata を one() で1回だけ監視
- 286行目: 最後に追加された text track の index を取得
- 287行目: textTracks()[index] を取得
- 289行目・301行目: oncuechange を設定
- 290行目: active cue があれば開始イベント
- 291〜294行目: JSON 解析を含む動的 HTML を生成
- 295〜297行目: キューポイント終了を表示
- 302行目: 動画を再生
なお、addRemoteTextTrack() を使用して WebVTT を追加できますが、読み込み競合によりキューポイントが準備できず、信頼性が低下する可能性があります。ここで示したように <track> タグを使う方法が安全です。
正しいトラックを見つける
このドキュメント内の複数箇所で、プレーヤーに複数のテキストトラックが関連付けられている場合に問題が発生する可能性があります。サンプルコードでは、プレーヤーに 1 つのテキストトラックしか関連付けられていないという前提で、次のコードが使用されています: var tt = myPlayer.textTracks()[0];。配列の0番目を選択しているため、テキストトラックが 1 つだけであることを前提にしています。
テキストトラックはキューポイントだけでなく、他の種類のデータにも使用されます。kind 属性には次の値が含まれる可能性があります:
- subtitles
- captions
- descriptions
- chapters
- metadata
つまり、プレーヤーに複数のテキストトラックが関連付けられている可能性があり、アプリケーション ロジックで正しいトラックを識別する必要があります。以下のコードは、利用可能なテキストトラックをループし、metadata(キューポイント)に該当するテキストトラックが見つかるまで繰り返します:
<script type="text/javascript">
videojs.getPlayer('myPlayerID').ready(function() {
var myPlayer = this,
allTextTacks,
attLength,
tt;
myPlayer.one("loadedmetadata", function () {
allTextTacks = myPlayer.textTracks();
attLength = allTextTacks.length;
for (var i = 0; i < attLength; i++) {
if (allTextTacks[i].kind === 'metadata') {
tt = allTextTacks[i];
break;
};
};
});
});
</script>
その後、これまでのサンプルで紹介したロジックを使い、目的のテキストトラックを格納した tt 変数を用いてキューポイントを処理できます。
プログラムによるキューポイントの追加
キューポイントはプログラムから追加することも可能です。重要となるメソッドは HTML5 の VTTCue インターフェースです。以下の構文を使用してキューポイントを作成できます:
new VTTCue( startTime, endTime, text )
キューポイント処理のロジックは前述の例とほぼ同じであるため、以下では異なる部分のみ詳細に説明します。
-
43行目・44行目: プレーヤーの
addRemoteTextTrack()メソッドを使用してtextTrack要素を追加します。kind属性には metadata を指定し、labelにはこの例では Timed Cue Point を指定しています。 -
45〜48行目: プレーヤーの
addCue()メソッドを使用して2つのキューポイントを作成する前に、10ミリ秒待ちます。キューポイントは HTML5 のVTTCue()コンストラクタを使用して生成されます。 - 22行目:
addtrackイベントを監視します。 - 23行目: プレーヤーに関連付けられているすべてのテキストトラックを取得します。
- 24行目:
attLength変数にテキストトラック数を代入します。 - 25〜30行目: 各テキストトラックをループし、
labelが一致するトラックを見つけたら変数に代入し、ループを終了します。
<video-js id="myPlayerID"
data-video-id="4607746980001"
data-account="1507807800001"
data-player="default"
data-embed="default"
width="640" height="360"
controls=""></video-js>
<script src="https://players.brightcove.net/1507807800001/default_default/index.min.js"></script>
<p id="insertionPoint"></p>
<script type="text/javascript">
videojs.getPlayer("myPlayerID").ready(function () {
var myPlayer = this,
textTrack = [],
allTextTacks,
attLength,
tt;
myPlayer.one("loadedmetadata", function () {
myPlayer.textTracks().addEventListener('addtrack', function () {
allTextTacks = myPlayer.textTracks();
attLength = allTextTacks.length;
for (var i = 0; i < attLength; i++) {
if (allTextTacks[i].label === 'Timed Cue Point') {
tt = allTextTacks[i];
break;
}
}
tt.oncuechange = function () {
if (tt.activeCues[0] !== undefined) {
var dynamicHTML = "id: " + tt.activeCues[0].id + ", ";
dynamicHTML += "text: <strong>" + tt.activeCues[0].text + "</strong>, ";
dynamicHTML += "startTime: <strong>" + tt.activeCues[0].startTime + "</strong>, ";
dynamicHTML += "endTime: <strong>" + tt.activeCues[0].endTime + "</strong>";
document.getElementById("insertionPoint").innerHTML += dynamicHTML + "<br/><br/>";
} else {
document.getElementById("insertionPoint").innerHTML += "Cue point duration over" + "<br/><br/>";
}
}; //end oncuechange
}); // end playing
textTrack = myPlayer.addRemoteTextTrack({kind: 'metadata', label: 'Timed Cue Point', mode: 'hidden'}, false);
textTrack.track.mode = 'hidden';
setTimeout(function(){
textTrack.track.addCue(new window.VTTCue(2, 5, 'cue point 1 text'));
textTrack.track.addCue(new window.VTTCue(10, 15, 'cue point 2 text'));
}, 10);
}); //end on loadedmetadata
}); //end ready
</script>
このコードを実行すると、次のスクリーンショットのような出力が生成されます。なお、id の値は VTTCue() コンストラクタ使用時には自動的には設定されません。
ID3 とメディア キューポイント
メディアに ID3 キューポイントやメディア キューポイントが関連付けられている場合、id3CuePointsTrack() と mediaCuePointsTrack() メソッドを使用して処理できます。例えば、キューポイントの変化を監視するには次のように記述します。
videojs.getPlayer('myPlayerID').ready(function () {
var myPlayer = this;
myPlayer.one("canplay", function () {
myPlayer.id3CuePointsTrack().on('cuechange', function () {
// process cue point here
});
});
});
ID3 の詳細
以下に、ID3 キューポイントを使用する際の補足情報を示します。
- ID3 タグは、ストリーム内にタイムド メタデータを挿入するために使用できます。
- 1 つのセグメント内に複数の ID3 フレームを含めることができます。
- Brightcove プレーヤーは ID3 キューポイントを解析し、前述の
id3CuePointsTrack()メソッドを使用してテキストトラックとして公開します。 canplayイベントを待ってからトラックにアクセスすることが推奨されます。そうしないと、アクセス時にトラックを取得できない可能性があります。- 1 つのタグ内の複数フレームもサポートされています。
既知の問題
- Safari では、終了時間が開始時間と同じ場合、キューポイントはトリガーされません。そのため、Safari への対応が必要な場合は、キューポイントの継続時間を 0 より大きく設定する必要があります。