Итак, задача: предположим, что есть куча интересного видеоматериала, которую хочется выложить в интернет, но не для всех, а только для хороших людей (для друзей, знакомых,… или просто за денежку). Так вот, инициатива 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-запрос на сервер и отдает ответ=ключ браузеру обратно (или не отдает, если того требует ситуация — это уже бизнес-логика). Мы немного упростим — пусть ключ уже у нас есть; для схемы 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/
Добавить комментарий