Организация хостинга зашифрованного видеоконтента с помощью HTML5

Не так давно на хабре обсуждалась новая инициатива от W3С — Encrypted Media Extensions или просто EME. Попробуем же разобраться на практике, что нового и интересного нам предлагают.

Итак, задача: предположим, что есть куча интересного видеоматериала, которую хочется выложить в интернет, но не для всех, а только для хороших людей (для друзей, знакомых,… или просто за денежку). Так вот, инициатива Encrypted Media Extensions как раз и предлагает добавить весь необходимый API для клиентской части в стандарт HTML. Помимо этого в предложении от W3C рассматриваются и другие вопросы, такие как злополучный DRM, но мы их пока отложим, остановившись исключительно на клиентской части организации нашего гипотетического сервиса. Обязательное условие — поддержка браузером спецификации EME, а значит, поддерживается и базовая ключевая система Clear Key.

Итак, есть тег video и файл foo.webm, видео и аудио в котором зашифровано с помощью алгоритма AES (такая возможность поддерживается в контейнере WebM, и авторы EME воспользовались этим):

<video src="foo.webm" autoplay></video>

В данной ситуации EME-браузер при попытке воспроизвести foo.webm обнаружит, что контент зашифрован, остановится и будет ждать, когда ему предоставят ключ для дешифровки. Вообще, основной вариант использования (use case) состоит в том, что информация о том, как зашифрован контент, описывается прямо в контейнере; воспользуемся именно таким вариантом. Кроме того, что браузер остановится, он еще и возбудит событие needkey на самом элементе HTMLMediaElement, а приложению следует его обработать и, в конечном итоге, предоставить ключ:

<video src="foo.webm" autoplay onneedkey="handleKeyNeeded(event)"></video>

Функция handleKeyNeeded() будет не очень сложной — она получает идентификатор ключа, который нужен браузеру, затем делает XHR-запрос на сервер и отдает ответ=ключ браузеру обратно (или не отдает, если того требует ситуация — это уже бизнес-логика). Мы немного упростим — пусть ключ уже у нас есть; для схемы Clear Key+AES, которую мы используем, это будет массив Uint8Array из 16 байт; тогда функция выглядит так:

function handleKeyNeeded(event) {   var video = event.target;   var initData = event.initData;    //var message = initData;   //var xmlhttp = new XMLHttpRequest();   //xmlhttp.open("POST", "http://.../getkey", false);   //xmlhttp.send(message);   //var key = new Uint8Array(xmlhttp.response);   var key = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,                             0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);    if (!video.keys)     video.keys = video.MediaKeys("org.w3.clearkey");   if (!video.keys)     throw "Could not create MediaKeys";    var keySession = video.keys.createSession(mimeType, initData);   if (!keySession)     throw "Could not create key session";   keySession.update(key); } 

Основные действующие лица здесь:

  • event.initData: идентификатор, соответствующий ему ключ нужен браузеру;
  • key: собственно ключ для дешифровки;
  • keySession.update(key): этим вызовом браузер получает тот самый ключ; большего от страницы и не требуется.

Те, кому интересен остальной код функции handleKeyNeeded() (классы MediaKeys, MediaKeySession и т.д.), могут воспользоваться свежим переводом будущей спецификации EME, выполненной с помощью сервиса Catbo.

Таким образом, если бы у нас был браузер с поддержкой EME (текущей версии 2), то на странице encrypted_media_player_v2.html мы бы обнаружили на видео черного медведя, прохаживающегося по клетке (образец зашифрованного видео bear-320x240-av-enc_av.webm взят из кода Chromium). На момент написания этой статьи такого браузера, с высокой долей вероятности, нет. Однако, если чуть доработать код страницы, то медведя все-таки можно будет увидеть, воспользовавшись браузерами Google Chrome или Chromium версии 26 или выше (пока поддержка обнаружена только в них): encrypted_media_player.html.

Далее мы рассмотрим, какие доработки пришлось сделать, чтобы наш пример заработал. Во-первых, сейчас в Chrome/Chromium поддерживается 1-я версия будущей спецификации EME, от которой уже отказались авторы EME в пользу версии 2, сделав последнюю объектно-ориентированной. Однако в Chrome/Chromium включена пока только первая версия EME; кроме того, во многих местах надо навесить префикс webkit; из-за этого функция handleKeyNeeded() изменится следующим образом:

function handleKeyNeeded(event) {   ...    var keySystem = "webkit-org.w3.clearkey";   video.webkitGenerateKeyRequest(keySystem, event.initData);   // добавление ключа   video.webkitAddKey(keySystem, key, event.initData, null); } 

Во-вторых, если сейчас напрямую устанавливать атрибут src тега video, то события needkey мы не дождемся. Дело в том, что в этом случае видео воспроизводится стоковым функционалом ffmpeg, который задействован в браузере, а тот не обращает внимания на всякие флаги зашифрованности в контейнере (далее идет немного технических подробностей о реализации медиа-стека именно в Chrome/Chromium). А вот если воспользоваться другим способом предоставления контента проигрывателю, то получим нужный нам результат (возбуждение события needkey). Этот другой способ имеет название Media Source Extensions; его суть заключается в том, что коду JavaScript позволяется генерировать медиапоток для последующего воспроизведения. На деле это означает, что в браузерах от Google появилась альтернативная реализация demuxer-ов, таких как WebM и mp4; вот их-то и научили возбуждению необходимого нам события needkey. Вот соответствующий код загрузки видео с медведем в наш тег video:

function load() {   var video = document.getElementById("video");    var mediaFile = "bear-320x240-av-enc_av.webm";   //video.src = mediaFile;   var sourceOpened = false;   function onSourceOpen(e) {     if (sourceOpened)       return;     sourceOpened = true;      var mediaType = 'video/webm; codecs="vorbis, vp8"';     var srcBuffer = mediaSource.addSourceBuffer(mediaType);     var xhr = new XMLHttpRequest();     xhr.open('GET', mediaFile);     xhr.responseType = 'arraybuffer';     xhr.addEventListener('load', function(e) {       srcBuffer.append(new Uint8Array(e.target.response));       mediaSource.endOfStream();     });     xhr.send();   }   var mediaSource = new WebKitMediaSource();   mediaSource.addEventListener('webkitsourceopen', onSourceOpen);   video.src = window.URL.createObjectURL(mediaSource);    ... } 

Конечно же, этот код менее универсален, чем простое присваивание

video.src = "bear-320x240-av-enc_av.webm"; 

; более того, будущая спецификация EME не требует подобных ухищрений (но пока добиться работоспособности возможно только таким путем). Ждем дальнейшего развития событий.


За рамками данной статьи остался вопрос о том, как зашифровывать видео. Эта задача контейнеро-специфична; к примеру, WebM такой возможностью обладает, но это неверно для некоторых других контейнеров, и т.д..

Ссылки на тему EME:
Google, Microsoft и Netflix хотят добавить DRM в HTML5
Перевод спецификации Encrypted Media Extensions / Зашифрованный медиаконтент в HTML5

ссылка на оригинал статьи http://habrahabr.ru/post/176115/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *