今回はYouTubeの埋め込みプレイヤーをJSで操作する方法はないかと調べた結果を紹介します。
動画の再生位置に文字起こしが追従して、行をクリックするとそこへシークする UI をつくりました。動いているものは ワラケル で見られます(AI がお笑い動画を分析して記事にするという独特なサイトです。全編AI臭しかしないので苦手な方はご注意ください)。
公式には IFrame Player API があることを後々知りましたが、外部コードに依存しない、クッキーが必要ないなどのメリットがあると思います。
postMessage で通信
?enablejsapi=1 つきの埋め込みは親ウィンドウと postMessage で通信していて、公式スクリプトもこれを使っています。観測するとこうなっていました。
const iframe = document.getElementById('ytp');
const send = (msg) =>
iframe.contentWindow.postMessage(
JSON.stringify({ id: 'ytp', channel: 'widget', ...msg }), '*');
let info = {};
addEventListener('message', (e) => {
if (!/(^|\.)youtube(-nocookie)?\.com$/.test(new URL(e.origin).hostname)) return;
const d = JSON.parse(e.data);
// initialDelivery = info 全体、infoDelivery = 差分パッチが毎秒数回
if (d.event === 'initialDelivery' || d.event === 'infoDelivery') Object.assign(info, d.info);
});
send({ event: 'listening' }); // handshake
info.currentTime; // 現在時刻
send({ event: 'command', func: 'seekTo', args: [60] }); // シーク
getter は「最後に届いた info を読むだけ」です。
堅牢性を高めました
ソースコードを読んでいただければわかりますが、シンプルなコードです。ですが、ハンドシェイク失敗への対応や、iframeがリロードされた際の対応など入れています。
ライブラリ化しました
embedwire — npm i embedwire でインストールできます、ゼロ依存・ESM・MITです。
<iframe id="ytp" src="https://www.youtube-nocookie.com/embed/VIDEO_ID?enablejsapi=1"></iframe>
<script type="module">
import { connect } from 'embedwire';
const player = connect(document.getElementById('ytp'));
player.on('ready', () => console.log(player.getDuration(), player.getVideoData().title));
player.on('timeUpdate', ({ t }) => console.log(t));
player.on('stall', () => console.warn('再生中のはずが tick が止まった'));
player.seekTo(60);
</script>
ワラケル にあるような文字起こし追従を embedwire/follow として同梱してあります。ワラケルと合わせてぜひご覧ください。
AIによるレビュー(注意事項)
この通信は公式に文書化されていません。観測して実装したもので、YouTube 側が変えれば壊れます(そのときは公式スクリプトを使っているページも同じ層で一緒に壊れます)。公式スクリプトがサポートされた道です。 これは nocookie を保ちたい・CSP を締めたい・第三者リクエストを減らしたい、というトレードオフを受け入れる場合のためのものです。YouTube とは無関係で、提携も承認もされていません。
