YouTubeの字幕をカスタマイズする

# 注意

YouTube APIの公式ドキュメントに載っていない設定値を利用します。
いつ使えなくなるかわかりません。

# tl;dr

1. http://video.google.com/timedtext?type=list&v={videoId} で字幕リスト取得
2. http://video.google.com/timedtext?lang={lang}&name={name}&v={videoId} で字幕情報取得
3. YouTube APIにプログレスイベントがないので `requestAnimationFrame` と `player.getCurrentTime()`で時間を取得
4. 2と3で字幕を表示
5. 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`を取得してからになる。面倒くさい。

```js
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が返る。

（ちなみにこのサーバへのリクエストはどのドメインからも許可されているので、クロスドメイン制限はない。）

```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`を取得して、字幕情報本体をリクエストする

```js
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)
}
```

```xml
<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`から字幕に関する設定を取得できた。

```js
player.getOptions() // => ["iv-module", "captions"]
player.getOptions('captions') // => ["reload", "fontSize", "track", "tracklist", "translationLanguages", "displaySettings", "sampleSubtitle"]
```

いろいろ試したところ、非表示のために設定できるのは以下のとおりで、フォントカラーを透明にしたり、フォントサイズをゼロにしても無効だった。

背景と文字の不透明度を `0` にすることで、なんとか非表示にできた。

```js

const player = new YT.Player('iframeのid', {
	events: {
		onApiChange: () => {
			player.setOption('captions', 'displaySettings', {
				backgroundOpacity: 0,
				textOpacity: 0,
			})
		}
	}
})

```

# 参考

- https://developers.google.com/youtube/js_api_reference?hl=ja#Events
- http://so-zou.jp/web-app/tech/web-api/google/youtube/player-api/iframe.htm#no17