注意
YouTube APIの公式ドキュメントに載っていない設定値を利用します。
いつ使えなくなるかわかりません。
tl;dr
- http://video.google.com/timedtext?type=list&v={videoId} で字幕リスト取得
- http://video.google.com/timedtext?lang={lang}&name={name}&v={videoId} で字幕情報取得
- YouTube APIにプログレスイベントがないので
requestAnimationFrame
とplayer.getCurrentTime()
で時間を取得 - 2と3で字幕を表示
- APIの
onApiChange
イベント検知後にplayer.setOption('captions', 'displaySettings', {option})
で既存の字幕を非表示に
最低でも2つXMLを取得する必要がある
字幕本体は http://video.google.com/timedtext?lang={lang}&name={name}&v={videoId} にリクエストすれば取得できるが、各パラメータが完全にマッチしている必要がある。特にname
はわからないので、一度 http://video.google.com/timedtext?type=list&v={videoId} へアクセスし、リストを取得してname
を取得してからになる。面倒くさい。
import superagent from 'superagent'
const YOUTUBE_VIDEO_TIMED_TEXT_API = 'http://video.google.com/timedtext'
const lang = 'ja'
const videoId = 'videoId'
const ccList = []
let ccTrackName;
requestCCList () {
superagent
.get(YOUTUBE_VIDEO_TIMED_TEXT_API)
.accept('json')
.query({
type: 'list',
v: videoId,
})
.end(responseCCList)
}
requestCCList()
から以下のXMLが返る。
(ちなみにこのサーバへのリクエストはどのドメインからも許可されているので、クロスドメイン制限はない。)
<transcript_list docid="xxxxxxxxxxxxxxxxxx">
<track id="0" name="hogehoge" lang_code="en" lang_original="English" lang_translated="English" lang_default="true"/>
<track id="1" name="ほげほげ" lang_code="ja" lang_original="日本語" lang_translated="Japanese"/>
</transcript_list>
受け取ったXMLからname
を取得して、字幕情報本体をリクエストする
responseCCList (err, res) {
if (err) {
throw new Error(err)
}
if (res.text && res.type === 'text/xml' && res.xhr && res.xhr.responseXML) {
const body = res.xhr.responseXML
const trackList = body.querySelectorAll('track')
for (let i = 0, l = trackList.length; i < l; i++) {
const trackElement = trackList[i]
const trackLang = trackElement.getAttribute('lang_code')
if (trackLang === this.lang) {
ccTrackName = trackElement.getAttribute('name')
break
}
}
if (this.ccTrackName != null) {
requestCC()
} else {
throw new Error('CC is not exist in ' + this.lang)
}
}
}
requestCC () {
superagent
.get(YOUTUBE_VIDEO_TIMED_TEXT_API)
.accept('json')
.query({
lang: lang,
name: ccTrackName,
v: videoId,
})
.end(responseCC)
}
<transcript>
<text start="39.2" dur="5">あいうえお</text>
<text start="47.14" dur="4">かきくけこ</text>
<text start="53.65" dur="4">さしすせそ</text>
</transcript>
あとは表示時に処理しやすいように自由に配列などに正規化すればよい。
YouTube APIにプログレスイベントがない
ので requestAnimationFrame
(もしくはsetTimeout
)の再帰の中で player.getCurrentTime()
から時間を取得する。
表示の仕方は要件次第なので割愛。
既存の字幕を非表示にする
これが一番問題だった。公式的には iframe のオプション cc_load_policy
しか設定できないことになっていて、しかも cc_load_policy=1
は強制的に表示、他の値は無効で、デフォルトだとユーザの設定に依存となる。つまり、ユーザが表示させる設定にしていれば非表示にできない。
onApiChange
イベント と player.getOptions
にヒント
公式によると、取得できるオプション(モジュール)は cc
だけということになっていたが、どうも変更になっているようで、captions
から字幕に関する設定を取得できた。
player.getOptions() // => ["iv-module", "captions"]
player.getOptions('captions') // => ["reload", "fontSize", "track", "tracklist", "translationLanguages", "displaySettings", "sampleSubtitle"]
いろいろ試したところ、非表示のために設定できるのは以下のとおりで、フォントカラーを透明にしたり、フォントサイズをゼロにしても無効だった。
背景と文字の不透明度を 0
にすることで、なんとか非表示にできた。
const player = new YT.Player('iframeのid', {
events: {
onApiChange: () => {
player.setOption('captions', 'displaySettings', {
backgroundOpacity: 0,
textOpacity: 0,
})
}
}
})