Help us understand the problem. What is going on with this article?

Media Source Extensionsの基本的なところをまとめた

Media Source Extensionsとは何か?

Media Source ExtensionsはW3Cが定めたHTMLMediaElement(video/audio)を操作するためのJavaScript APIのことを指します。

この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を学習する予定です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした