Media Source Extensionsとは何か?
Media Source ExtensionsはW3Cが定めたHTMLMediaElement(video/audio)を操作するためのJavaScript APIのことを指します。
このAPIを利用することで、アダプティブストリーミング(通信状況に合わせて画質を調整しながら配信する方式)といったことがブラウザで実現できるようになります。
この記事では、Media Source Extensionsの具体的なAPIの役割を見ていこうと思います。
参考リンク
- https://developers.google.com/web/fundamentals/media/mse/basics
- https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API
概要
登場人物は、HTMLMediaElement, MediaSource, Fetch or XMLHttpRequest, SourceBuffer, ArrayBufferの5つに絞っていきます。
関係としては、HTMLMediaElementとJavaScriptの橋渡しをするのがMediaSourceという感じです。
HTMLMediaElement(DOM) <- MediaSource -> JavaScript
メディアデータはFetch or XMLHttpRequestを使ってArrayBuffer型として取得します。
取得したメデイアデータをSourceBufferを経由してMediaSourceに渡すという流れです。
それぞれをもう少し細かく見ていきたいと思います。
MediaSource
const mediaSource = new MediaSource();
このようにMediaSourceは引数なしのコンストラクタでMediaSourceオブジェクトを生成します。
MediaSourceオブジェクトにはいくつかのプロパティとメソッドが生えているのでそれぞれ見ていきます。
MediaSource.readyState
MediaSource.readyStateは、MediaSourceオブジェクトの状態を返す読み取り専用のプロパティです。
MediaSource.readyStateが返す状態としては、次の3つのいずれかになります。
- closed ... メディア要素が紐付いていない状態
- open ... メディア要素が紐付いていて、SourceBufferを受け取れる状態
- 後述のURL.createObjectURLを使って、HTMLMediaElementと紐付けるとこの状態になる
- ended ... メディア要素が紐付いているが、ストリームが終了している状態
- 後述のMediaSource.endOfStreamを呼び出した場合にこの状態になる
そのためMediaSourceオブジェクト生成直後は、"closed"の文字列を返すようになっています。
const mediaSource = new MediaSource();
console.log(mediaSource.readyState); // => "closed"
MediaSource.addSourceBuffer()
MediaSource.addSourceBufferは、mimeTypeを引数に渡すことでSourceBufferオブジェクトを生成して、そのオブジェクトをMediaSourceに追加するAPIです。
returnは生成したSourceBufferオブジェクトになります。
注意として、MediaSource.readyStateがopenになっていないと例外が発生します。
Uncaught DOMException: Failed to execute 'addSourceBuffer' on 'MediaSource': The MediaSource's readyState is not 'open'.
MediaSourceはsourceBuffersとしてSourceBufferオブジェクトの配列を持っていて、そこに追加されるような挙動になります。
const mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
const sourceBuffer = mediaSource.addSourceBuffer(mime);
MediaSource.endOfStream()
MediaSource.endOfStreamは、ストリームが終了したことをMediaSourceに通知するAPIです。
returnはなく引数にエラーを渡すことで、異常な終了な場合に通知することができます。
MediaSource.addSourceBufferと同様にopenな状態でないMediaSourceに対して呼び出すと例外が発生します。
mediaSource.endOfStream();
MediaSource#sourceopenイベント
MediaSourceの状態がopenになったときに発火するイベントです。
MediaSourceのaddEventListenerメソッドでリスナーを登録することができます。
mediaSource.addEventListener('sourceopen', () => {
console.log(mediaSource.readyState); // => "open"
});
URL
HTMLMediaElementとMediaSourceを紐付けるにはURLオブジェクトを使用します。
より正確にはBlob URLオブジェクトを使ってバイナリのやりとりをします。
Media Source Extensionsを使っている適当な動画サイトのvideoタグを見ると、blob:~
で始まっているURLを目にすることができます。
MediaSourceと紐付いたURLオブジェクトを生成するには、createObjectURLを利用します。
URL.createObjectURL()
URL.createObjectURLはFileオブジェクト、Blobオブジェクト、MediaSourceオブジェクトのいずれかを引数にとりオブジェクトのURLを生成するメソッドです。
以下のようにして、MediaSourceオブジェクトとHTMLMediaElementを紐付けられます。
const url = URL.createObjectURL(mediaSource);
// document.querySelector('video')なりで取得したvideoElementのsrc属性を書き換える
videoElement.src = url;
URL.revokeObjectURL()
URL.revokeObjectURLを呼び出すことで、URLを然るべきタイミングで破棄するようになります。
(参照がなくなるとGCで回収される)
sourceopenした段階で、URLはvideoElementに紐付いているので、revokeObjectURLしてもすぐに破棄されることはないようです。
const url = URL.createObjectURL(mediaSource);
// sourceopenした段階でrevokeして問題ない
mediaSource.addEventListener('sourceopen', () => {
URL.revokeObjectURL(url);
});
SourceBuffer
HTMLMediaElementとMediaSourceはBlob URLによって紐付けることができました。
では、MediaSourceにメディアデータを流すにはどうしたらよいのでしょうか?
MediaSource.addSourceBufferをさきほどMediaSourceのメソッドとして取り上げましたが、それによって生成されたSourceBufferを使って、実際のメディアデータを渡します。
SourceBuffer.appendBuffer()
SourceBuffer.appendBufferはArrayBufferViewあるいはArrayBufferを引数に渡すことでSourceBufferにメディアデータを渡すことができるAPIです。
var sourceBuffer = mediaSource.addSourceBuffer(mime);
// fetchまたはXMLHttpRequestでArrayBufferを取得し、arrayBufferに代入しておく
sourceBuffer.appendBuffer(arrayBuffer);
このようにしてHTMLMediaElementにメディアデータを渡すことができました。
まとめ
Media Source Extensionsの基本的なところは、少し理解できました。
実際に動画の配信となるとこれ以上に複雑なことが必要になると思いますが、これらのメソッドをベースに考えていくと良さそうに思います。
DRMの学習のために次はEncrypted Media Extensionsを学習する予定です。