Encrypted Media Extensions の概要

暗号化されたメディア拡張機能(EME)は、ウェブ アプリケーションがコンテンツ保護システムとやり取りして、暗号化された音声と動画の再生を可能にする API を提供します。

EME は、基盤となる保護システムに関係なく、どのブラウザでも同じアプリと暗号化されたファイルを使用できるように設計されています。前者は標準化された API とフローによって可能になり、後者は共通暗号化のコンセプトによって可能になります。

EME は HTMLMediaElement 仕様の拡張機能であるため、この名前が付けられています。「拡張機能」とは、ブラウザでの EME のサポートがオプションであることを意味します。ブラウザが暗号化されたメディアをサポートしていない場合、暗号化されたメディアを再生することはできませんが、HTML 仕様に準拠するために EME は必要ありません。EME 仕様:

EME 実装では、次の外部コンポーネントが使用されます。

  • 鍵システム: コンテンツ保護(DRM)メカニズム。EME では、クリアキーを除き、鍵システム自体は定義されていません(詳しくは後述)。
  • Content Decryption Module(CDM): 暗号化されたメディアの再生を可能にするクライアントサイドのソフトウェアまたはハードウェア メカニズム。Key Systems と同様に、EME は CDM を定義しませんが、利用可能な CDM とやり取りするためのインターフェースを提供します。
  • ライセンス(鍵)サーバー: CDM と連携して、メディアを復号するための鍵を提供します。ライセンス サーバーとのネゴシエーションはアプリケーションの責任です。
  • パッケージング サービス: 配信 / 使用のためにメディアをエンコードして暗号化します。

EME を使用するアプリケーションは、ライセンス サーバーと通信して復号用の鍵を取得しますが、ユーザーの ID と認証は EME には含まれません。メディアの再生を可能にするキーの取得は、(必要に応じて)ユーザーの認証後に行われます。Netflix などのサービスは、ウェブ アプリケーション内でユーザーを認証する必要があります。ユーザーがアプリケーションにログインすると、アプリケーションはユーザーの ID と権限を判断します。

EME の仕組み

EME のコンポーネントがどのように相互作用するかを、次のコード例に対応して示します。

  1. ウェブ アプリケーションが、1 つ以上の暗号化されたストリームを含む音声または動画の再生を試行している。
  2. ブラウザはメディアが暗号化されていることを認識し(方法については下のボックスを参照)、暗号化についてメディアから取得したメタデータ(initData)とともに encrypted イベントを発生させます。
  3. アプリが encrypted イベントを処理します。
    1. メディア要素に MediaKeys オブジェクトが関連付けられていない場合は、まず navigator.requestMediaKeySystemAccess() を使用して使用可能なキーシステムを選択し、使用可能なキーシステムを確認してから、MediaKeySystemAccess オブジェクトを介して使用可能なキーシステムの MediaKeys オブジェクトを作成します。MediaKeys オブジェクトの初期化は、最初の encrypted イベントの前に行う必要があります。ライセンス サーバー URL の取得は、使用可能なキーシステムの選択とは別にアプリによって行われます。MediaKeys オブジェクトは、音声要素または動画要素のメディアの復号に使用できるすべての鍵を表します。これは CDM インスタンスを表し、CDM へのアクセスを提供します。特に、ライセンス サーバーから鍵を取得するために使用される鍵セッションの作成に使用されます。
    2. MediaKeys オブジェクトを作成したら、メディア要素に割り当てます。setMediaKeys() は、MediaKeys オブジェクトを HTMLMediaElement に関連付け、再生中(デコード中)にその鍵を使用できるようにします。
  4. アプリは MediaKeys に対して createSession() を呼び出して MediaKeySession を作成します。これにより、ライセンスとそのキーの有効期間を表す MediaKeySession が作成されます。
  5. アプリは、encrypted ハンドラで取得したメディアデータを CDM に渡し、MediaKeySessiongenerateRequest() を呼び出して、ライセンス リクエストを生成します。
  6. CDM は、message イベント(ライセンス サーバーからキーを取得するためのリクエスト)を発行します。
  7. MediaKeySession オブジェクトは message イベントを受信し、アプリがライセンス サーバーに(XHR などを介して)メッセージを送信します。
  8. アプリはライセンス サーバーからレスポンスを受信し、MediaKeySessionupdate() メソッドを使用してデータを CDM に渡します。
  9. CDM は、ライセンス内の鍵を使用してメディアを復号します。有効なキーは、メディア要素に関連付けられた MediaKey 内の任意のセッションから使用できます。CDM は、Key ID でインデックスされた鍵とポリシーにアクセスします。
  10. メディアの再生が再開されます。

ふう…

CDM とライセンス サーバー間で複数のメッセージが送信される場合があります。このプロセスでの通信はすべてブラウザとアプリに対して不透明です。メッセージは CDM とライセンス サーバーによってのみ認識されますが、アプリレイヤは CDM が送信するメッセージの種類を確認できます。ライセンス リクエストには、CDM の有効性(および信頼関係)の証明と、生成されるライセンス内のコンテンツ鍵を暗号化する際に使用する鍵が含まれています。

...では、CDM は実際にどのような役割を担っているのでしょうか。

EME の実装自体には、メディアを復号する方法は用意されていません。ウェブ アプリケーションがコンテンツ復号モジュールを操作するための API が用意されているだけです。

CDM が実際に行う処理は EME 仕様で定義されていません。CDM は、復号だけでなくメディアのデコード(解凍)も処理できます。CDM 機能には、堅牢性の低いものから高いものにかけて、いくつかのオプションがあります。

  • 復号のみ。通常のメディア パイプライン(<video> 要素など)を使用して再生を有効にします。
  • 復号とデコード、レンダリング用に動画フレームをブラウザに渡す。
  • 復号とデコード、ハードウェア(GPU など)での直接レンダリング。

CDM をウェブアプリで利用できるようにする方法は複数あります。

  • CDM をブラウザにバンドルする。
  • CDM を個別に配布する。
  • CDM をオペレーティング システムに組み込みます。
  • ファームウェアに CDM を含める。
  • CDM をハードウェアに埋め込む。

CDM を公開する方法は EME 仕様で定義されていませんが、いずれの場合も CDM の審査と公開はブラウザの責任となります。

EME では特定のキー システムが義務付けられていません。現在のパソコン用およびモバイル用ブラウザでは、Chrome が Widevine をサポートし、IE11 が PlayReady をサポートしています。

ライセンス サーバーからキーを取得する

一般的な商用利用では、コンテンツはパッケージング サービスまたはツールを使用して暗号化され、エンコードされます。暗号化されたメディアがオンラインで利用可能になると、ウェブ クライアントはライセンス サーバーから鍵(ライセンス内に含まれる)を取得し、その鍵を使用してコンテンツの復号と再生を可能にできます。

次のコード(仕様の例を基に改変)は、アプリケーションが適切な鍵システムを選択し、ライセンス サーバーから鍵を取得する方法を示しています。

var video = document.querySelector('video');

var config = [{initDataTypes: ['webm'],
  videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}]}];

if (!video.mediaKeys) {
  navigator.requestMediaKeySystemAccess('org.w3.clearkey',
      config).then(
    function(keySystemAccess) {
      var promise = keySystemAccess.createMediaKeys();
      promise.catch(
        console.error.bind(console, 'Unable to create MediaKeys')
      );
      promise.then(
        function(createdMediaKeys) {
          return video.setMediaKeys(createdMediaKeys);
        }
      ).catch(
        console.error.bind(console, 'Unable to set MediaKeys')
      );
      promise.then(
        function(createdMediaKeys) {
          var initData = new Uint8Array([...]);
          var keySession = createdMediaKeys.createSession();
          keySession.addEventListener('message', handleMessage,
              false);
          return keySession.generateRequest('webm', initData);
        }
      ).catch(
        console.error.bind(console,
          'Unable to create or initialize key session')
      );
    }
  );
}

function handleMessage(event) {
  var keySession = event.target;
  var license = new Uint8Array([...]);
  keySession.update(license).catch(
    console.error.bind(console, 'update() failed')
  );
}

共通暗号化

共通暗号化ソリューションを使用すると、コンテンツ プロバイダはコンテナ / コーデックごとに 1 回コンテンツを暗号化してパッケージ化し、さまざまな鍵システム、CDM、クライアント(共通暗号化をサポートする CDM)で使用できます。たとえば、Playready を使用してパッケージ化された動画は、Widevine ライセンス サーバーからキーを取得する Widevine CDM を使用してブラウザで再生できます。

これは、単一のクライアントを含む単一のクライアントを含む完全な垂直スタックでのみ機能する従来のソリューションとは対照的であり、多くの場合、アプリケーション ランタイムもこれに含まれます。

Common Encryption(CENC)は、ISO BMFF の保護スキームを定義する ISO 規格です。同様の概念が WebM にも当てはまります。

クリアキー

EME では DRM 機能は定義されていませんが、現在の仕様では、EME をサポートするすべてのブラウザで Clear Key を実装することが義務付けられています。このシステムを使用すると、鍵でメディアを暗号化して、その鍵を提供するだけでメディアを再生できます。クリアキーはブラウザに組み込めます。別の復号モジュールを使用する必要はありません。

多くの種類の商用コンテンツには使用されませんが、Clear Key は EME をサポートするすべてのブラウザで完全に相互運用できます。また、ライセンス サーバーにコンテンツ キーをリクエストしなくても、EME の実装と EME を使用するアプリをテストするのにも便利です。簡単なクリアキーの例は simpl.info/ck にあります。以下のコードのチュートリアルは、前述の手順とほぼ同じですが、ライセンス サーバーとのやり取りは行いません。

// Define a key: hardcoded in this example
// – this corresponds to the key used for encryption
var KEY = new Uint8Array([
  0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
  0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
]);

var config = [{
  initDataTypes: ['webm'],
  videoCapabilities: [{
    contentType: 'video/webm; codecs="vp8"'
  }]
}];

var video = document.querySelector('video');
video.addEventListener('encrypted', handleEncrypted, false);

navigator.requestMediaKeySystemAccess('org.w3.clearkey', config).then(
  function(keySystemAccess) {
    return keySystemAccess.createMediaKeys();
  }
).then(
  function(createdMediaKeys) {
    return video.setMediaKeys(createdMediaKeys);
  }
).catch(
  function(error) {
    console.error('Failed to set up MediaKeys', error);
  }
);

function handleEncrypted(event) {
  var session = video.mediaKeys.createSession();
  session.addEventListener('message', handleMessage, false);
  session.generateRequest(event.initDataType, event.initData).catch(
    function(error) {
      console.error('Failed to generate a license request', error);
    }
  );
}

function handleMessage(event) {
  // If you had a license server, you would make an asynchronous XMLHttpRequest
  // with event.message as the body.  The response from the server, as a
  // Uint8Array, would then be passed to session.update().
  // Instead, we will generate the license synchronously on the client, using
  // the hard-coded KEY at the top.
  var license = generateLicense(event.message);

  var session = event.target;
  session.update(license).catch(
    function(error) {
      console.error('Failed to update the session', error);
    }
  );
}

// Convert Uint8Array into base64 using base64url alphabet, without padding.
function toBase64(u8arr) {
  return btoa(String.fromCharCode.apply(null, u8arr)).
      replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}

// This takes the place of a license server.
// kids is an array of base64-encoded key IDs
// keys is an array of base64-encoded keys
function generateLicense(message) {
  // Parse the clearkey license request.
  var request = JSON.parse(new TextDecoder().decode(message));
  // We only know one key, so there should only be one key ID.
  // A real license server could easily serve multiple keys.
  console.assert(request.kids.length === 1);

  var keyObj = {
    kty: 'oct',
    alg: 'A128KW',
    kid: request.kids[0],
    k: toBase64(KEY)
  };
  return new TextEncoder().encode(JSON.stringify({
    keys: [keyObj]
  }));
}

このコードをテストするには、再生する暗号化された動画が必要です。Clear Key で使用するために動画を暗号化するには、webm_crypt の手順に沿って WebM で行うことができます。商用サービスも利用可能で(少なくとも ISO BMFF / MP4 の場合)、その他のソリューションも開発中です。

Media Source Extensions(MSE)

HTMLMediaElement は、シンプルな美しさを実現する要素です。

src URL を指定するだけで、メディアを読み込み、デコードして再生できます。

<video src='foo.webm'></video>

Media Source API は HTMLMediaElement の拡張機能であり、JavaScript で動画の「チャンク」から再生用のストリームを作成できるようにすることで、メディアのソースをより詳細に制御できるようにします。これにより、アダプティブ ストリーミングやタイムシフトなどの手法が可能になります。

MSE が EME にとって重要な理由商用コンテンツ プロバイダは、保護されたコンテンツを配信するだけでなく、ネットワークの状態やその他の要件に応じてコンテンツ配信を適応させる必要があります。たとえば、Netflix はネットワークの状態に応じてストリーミング ビットレートを動的に変更します。EME は、src 属性を介して提供されるメディアと同様に、MSE 実装によって提供されるメディア ストリームの再生に対応しています。

異なるビットレートでエンコードされたメディアをチャンク化して再生するにはどうすればよいですか?以下の DASH セクションをご覧ください。

MSE の動作は simpl.info/mse で確認できます。この例では、File API を使用して WebM 動画が 5 つのチャンクに分割されています。本番環境のアプリケーションでは、動画のチャンクは Ajax 経由で取得されます。

まず、SourceBuffer が作成されます。

var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');

次に、appendBuffer() メソッドを使用して各チャンクを追加することで、映画全体が動画要素に「ストリーミング」されます。

reader.onload = function (e) {
  sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
  if (i === NUM_CHUNKS - 1) {
    mediaSource.endOfStream();
  } else {
    if (video.paused) {
      // start playing after first chunk is appended
      video.play();
    }
    readChunk_(++i);
  }
};

MSE について詳しくは、HTML5 Rocks の記事をご覧ください。

Dynamic Adaptive Streaming over HTTP(DASH)

マルチデバイス、マルチプラットフォーム、モバイルなど、ウェブは接続状況が変化する環境で使用されることがよくあります。マルチデバイスの世界で帯域幅の制約や変動に対処するには、動的で適応型の配信が不可欠です。

DASH(MPEG-DASH)は、ストリーミングとダウンロードの両方で、不安定な環境でも可能な限り最適なメディア配信を可能にするように設計されています。Apple の HTTP Live Streaming(HLS)や Microsoft の Smooth Streaming など、同様の処理を行う技術もありますが、DASH はオープン標準に基づいた、HTTP によるアダプティブ ビットレート ストリーミングの唯一の方法です。DASH は、YouTube などのサイトですでに使用されています。

これは EME と MSE とどう関係していますか?MSE ベースの DASH 実装では、既存の HTTP インフラストラクチャを使用して、マニフェストを解析し、動画セグメントを適切なビットレートでダウンロードして、空腹時に動画要素にフィードできます。

つまり、DASH を使用すると、商用コンテンツ プロバイダは保護されたコンテンツをアダプティブ ストリーミングできます。

DASH は、その名の通り、次の機能を備えています。

  • 動的: 変化する状況に対応します。
  • アダプティブ: 適切な音声または動画ビットレートを提供するように適応します。
  • ストリーミング: ストリーミングとダウンロードが可能です。
  • HTTP: 従来のストリーミング サーバーの欠点がなく、HTTP の利点を活かしてコンテンツを配信できます。

BBC は、DASH を使用したテスト ストリームの提供を開始しました。

まとめ

  1. メディアが異なるビットレートでエンコードされている。
  2. さまざまなビットレートのファイルは HTTP サーバーから利用できます。
  3. クライアント ウェブアプリは、DASH で取得して再生するビットレートを指定します。

動画セグメンテーション プロセスの一環として、Media Presentation Description(MPD)と呼ばれる XML マニフェストがプログラムで作成されます。これは、再生時間と URL とともに、アダプテーション セットと表現を記述したものです。MPD は次のようになります。

<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" mediaPresentationDuration="PT0H3M1.63S" minBufferTime="PT1.5S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
type="static">
  <Period duration="PT0H3M1.63S" start="PT0S">
    <AdaptationSet>
      <ContentComponent contentType="video" id="1" />
      <Representation bandwidth="4190760" codecs="avc1.640028" height="1080" id="1" mimeType="video/mp4" width="1920">
        <BaseURL>car-20120827-89.mp4</BaseURL>
        <SegmentBase indexRange="674-1149">
          <Initialization range="0-673" />
        </SegmentBase>
      </Representation>
      <Representation bandwidth="2073921" codecs="avc1.4d401f" height="720" id="2" mimeType="video/mp4" width="1280">
        <BaseURL>car-20120827-88.mp4</BaseURL>
        <SegmentBase indexRange="708-1183">
          <Initialization range="0-707" />
        </SegmentBase>
      </Representation>

          </AdaptationSet>
  </Period>
</MPD>

(この XML は、YouTube DASH デモ プレーヤーで使用される .mpd ファイルから取得したものです)。

DASH 仕様では、理論上、MPD ファイルを動画の src として使用できます。ただし、ブラウザ ベンダーはウェブ デベロッパーに柔軟性を持たせるため、代わりに dash.js などの MSE を使用した JavaScript ライブラリまで DASH のサポートを許可することにしました。JavaScript で DASH を実装すると、ブラウザを更新しなくても適応アルゴリズムを進化させることができます。MSE を使用すると、ブラウザを変更することなく、代替のマニフェスト形式や配信メカニズムをテストすることもできます。Google の Shaka Player は、EME をサポートする DASH クライアントを実装しています。

WebM ツールと FFmpeg を使用して動画をセグメント化し、MPD を作成する方法については、Mozilla Developer Network の手順をご覧ください。

まとめ

有料の動画や音声を配信するためにウェブを利用することが急速に増加しています。タブレット、ゲーム機、コネクテッド テレビ、セットトップ ボックスなど、新しいデバイスはすべて、主要なコンテンツ プロバイダから HTTP 経由でメディアをストリーミングできるようです。モバイルとパソコンのブラウザの 85% 以上<video><audio> をサポートしており、Cisco は 2017 年までに動画が世界全体の消費者向けインターネット トラフィックの 80~90% を占めると予測しています。このような状況において、ほとんどのメディア プラグインが依存する API のブラウザ ベンダーによるサポートの縮小が進むにつれ、保護されたコンテンツの配信に対するブラウザのサポートの重要性がますます高まっていくと考えられます。

関連情報

仕様と標準

記事