加密媒體擴充功能 (EME) 提供 API,可讓網頁應用程式與內容保護系統互動,以便播放加密的音訊和影片。
EME 的設計目的是讓相同的應用程式和加密檔案可在任何瀏覽器中使用,不受底層保護系統的影響。前者是透過標準化 API 與流程開發而成,後者可以透過「通用加密」的概念達成。
EME 是 HTMLMediaElement
規格的擴充功能,因此名稱為 EME。由於 EME 是「擴充功能」,因此瀏覽器是否支援 EME 並非必要:如果瀏覽器不支援加密媒體,就無法播放加密媒體,但 HTML 規格並未要求必須使用 EME。根據 EME 規格:
EME 實作項目會使用下列外部元件:
- 金鑰系統:內容保護 (DRM) 機制。除了 Clear Key 之外,EME 本身並未定義 Key Systems。詳情請參閱下文。
- 內容解密模組 (CDM):用戶端軟體或硬體機制,可播放加密的媒體。與 Key Systems 一樣,EME 不會定義任何 CDM,而是提供介面,讓應用程式與可用的 CDM 互動。
- 授權 (金鑰) 伺服器:與 CDM 互動,提供用於解密媒體的金鑰。應用程式負責與授權伺服器協商。
- 包裝服務:對媒體進行編碼和加密,以便發布/消耗。
請注意,使用 EME 的應用程式會與授權伺服器互動,以取得啟用解密的金鑰,但使用者身分和驗證不屬於 EME。系統會在 (選擇性) 驗證使用者後擷取金鑰以啟用媒體播放功能。Netflix 等服務必須在其網路應用程式中驗證使用者:當使用者登入應用程式時,應用程式會判斷使用者的身分和權限。
EME 的運作方式為何?
以下是 EME 元件的互動方式 (對應於以下程式碼範例):
- 網路應用程式嘗試播放含有一或多個加密串流的音訊或影片。
- 瀏覽器會辨識媒體是否已加密 (請參閱下方方塊瞭解發生方式),並觸發
encrypted
事件,其中包含從媒體取得的加密中繼資料 (initData
)。 - 應用程式會處理
encrypted
事件:- 如果尚未有任何
MediaKeys
物件與媒體元素建立關聯,請先使用navigator.requestMediaKeySystemAccess()
查看可用的 Key System,然後為可用的 Key System 透過MediaKeySystemAccess
物件建立MediaKeys
物件。請注意,MediaKeys 物件的初始化作業應在第一個encrypted
事件之前執行。應用程式會獨立取得授權伺服器網址,不受可用金鑰系統的影響。MediaKeys
物件代表可用於解密音訊或視訊元素媒體的所有金鑰。它代表 CDM 例項,並提供 CDM 存取權,特別是用於建立用於從授權伺服器取得金鑰的金鑰工作階段。 - 建立
MediaKeys
物件後,請將其指派給媒體元素:setMediaKeys()
會將MediaKeys
物件與 HTMLMediaElement 建立關聯,以便在解碼期間使用其鍵。
- 如果尚未有任何
- 應用程式會在
MediaKeys
上呼叫createSession()
,藉此建立MediaKeySession
。這會建立MediaKeySession
,代表授權和其金鑰的使用期限。 - 應用程式會在
MediaKeySession
上呼叫generateRequest()
,將在encrypted
處理常式中取得的媒體資料傳遞至 CDM,藉此產生授權要求。 - CDM 會觸發
message
事件:要求從授權伺服器取得金鑰。 MediaKeySession
物件接收message
事件,而應用程式會傳送訊息至授權伺服器 (例如透過 XHR)。- 應用程式會接收授權伺服器的回應,並使用
MediaKeySession
的update()
方法將資料傳遞至 CDM。 - CDM 會使用授權中的金鑰解密媒體。您可以使用與媒體元素相關聯的
MediaKey
中任何工作階段中的有效金鑰。CDM 將存取依金鑰 ID 建立索引的金鑰和政策。 - 媒體播放程序會繼續。
呼…
請注意,CDM 和授權伺服器之間可能存在數則訊息,且瀏覽器和應用程式無法解讀這個程序中的所有通訊:訊息僅可由 CDM 和授權伺服器解讀,雖然應用程式層可以看到 CDM 傳送的訊息類型。授權要求包含 CDM 有效性 (和信任關係) 的證明,以及在產生的授權中加密內容金鑰時要使用的金鑰。
但 CDM 實際上是做什麼的?
EME 實作本身並不會提供將媒體解密的方法,而是提供一個 API,讓網頁應用程式與內容解密模組互動。
EME 規格並未定義 CDM 實際執行的操作,CDM 可能會處理媒體的解碼 (解壓縮) 和解密作業。以下是 CDM 功能適用的幾個可能選項 (取最為最穩健的做法):
- 僅解密,啟用使用一般媒體管道的播放功能,例如透過
<video>
元素。 - 解密和解碼,將影片影格傳遞至瀏覽器進行轉譯。
- 解密和解碼,直接在硬體 (例如 GPU) 中轉譯。
您可以透過多種方式讓 CDM 可供網頁應用程式使用:
- 將 CDM 與瀏覽器一起封裝。
- 分別發布 CDM。
- 在作業系統中建構 CDM。
- 在韌體中加入 CDM。
- 在硬體中嵌入 CDM。
EME 規格並未定義 CDM 的提供方式,但在所有情況下,瀏覽器都負責審查及提供 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')
);
}
一般加密
有了通用加密解決方案,內容供應商就能針對每個容器/編解碼器加密及封裝內容,並搭配各種 Key System、CDM 和用戶端使用,也就是任何支援通用加密的 CDM。舉例來說,使用 Playready 封裝的影片可透過瀏覽器播放,並使用 Widevine CDM 從 Widevine 授權伺服器取得金鑰。
這與舊版解決方案不同,後者只能搭配完整的垂直堆疊運作,包括單一用戶端 (通常也包含應用程式執行階段)。
通用加密 (CENC) 是一種 ISO 標準,定義了 ISO BMFF 的保護機制;WebM 也採用類似的概念。
清除金鑰
雖然 EME 未定義 DRM 功能,但根據我們的規格,目前所有支援 EME 的瀏覽器都必須實作 Clear Key。使用這個系統,您可以使用金鑰加密媒體,然後只要提供該金鑰即可播放。Clear Key 可內建於瀏覽器:不需要使用個別的解密模組。
雖然不可能用於多種商業內容,但 Clear Key 可以在所有支援 EME 的瀏覽器上完全互通。這也非常適合測試 EME 實作項目,以及使用 EME 的應用程式,無須向授權伺服器要求內容金鑰。如需簡單的 Clear Key 範例,請前往 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 操作說明進行。另外也提供商業服務 (至少需符合 ISO BMFF/MP4) 和其他解決方案的開發中。
相關技術 #1
媒體來源擴充功能 (MSE)
HTMLMediaElement 是簡單美的生物。
我們只需提供 src 網址,就可以載入、解碼及播放媒體:
<video src='foo.webm'></video>
Media Source API 是 HTMLMediaElement 的擴充功能,可讓 JavaScript 從影片的「區塊」建立串流,藉此更精細地控管媒體來源。進而啟用自適應串流和時間轉移等技術。
MSE 對 EME 的重要性為何?由於除了散佈受保護的內容外,商業內容供應商也必須能夠根據網路條件和其他需求調整內容傳遞作業。舉例來說,Netflix 會根據網路狀況變化動態調整串流位元率。EME 可搭配 MSE 實作提供的媒體串流播放作業,就像使用 src
屬性提供的媒體一樣。
如何分割及播放以不同位元率編碼的媒體?請參閱下方的「DASH」一節。
您可以在 simpl.info/mse 中查看 MSE 的運作情形。在本例中,我們使用 File API 將 WebM 影片分割成五個片段。在正式版應用程式中,系統會透過 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 文章。
相關技術 #2
透過 HTTP 使用動態自動調整串流 (DASH)
無論是多裝置、多平台還是行動裝置,無論如何,使用者經常是在變動連線條件下體驗網頁。在多裝置環境中,動態智慧調整功能對於因應頻寬限制和變化情形至關重要。
DASH (又稱 MPEG-DASH) 旨在提供最佳的媒體內容傳遞服務,無論是串流還是下載,都能在網路不穩定的情況下正常運作。其他幾種技術也提供類似功能,例如 Apple 的 HTTP 即時串流 (HLS) 和 Microsoft 的 Smooth Streaming,但 DASH 是唯一採用開放標準,透過 HTTP 提供可自動調整位元率串流的技術。YouTube 等網站已開始使用 DASH。
這與 EME 和 MSE 有何關聯?以 MSE 為基礎的 DASH 實作項目可以使用現有的 HTTP 基礎架構,剖析資訊清單、以適當的位元率下載影片片段,並在影片元件需要時提供這些片段。
換句話說,DASH 可讓商業內容供應者針對受保護內容進行適應性串流。
DASH 會執行容器中所說的內容:
- 動態:可因應變動條件做出回應。
- 自動調整:自動調整為提供適當的音訊或影片位元率。
- 串流:可供串流播放及下載。
- HTTP:可透過 HTTP 的優點提供內容,且不受傳統串流伺服器的缺點影響。
BBC 已開始使用 DASH 提供測試串流:
摘要:
- 媒體會以不同的位元率編碼。
- 不同的位元率檔案會透過 HTTP 伺服器提供。
- 用戶端網頁應用程式會選擇要透過 DASH 擷取及播放的位元率。
在影片分割程序中,系統會以程式輔助方式建構 XML 資訊清單,也就是媒體呈現說明 (MPD)。這會說明適應組合和表示法,並提供持續時間和網址。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 支援交由使用 MSE 的 JavaScript 程式庫 (例如 dash.js) 負責。在 JavaScript 中實作 DASH 可讓適應演算法進行調整,而無須更新瀏覽器。使用 MSE 還可嘗試使用其他資訊清單格式和提交機制,而無須變更瀏覽器。Google 的 Shaka Player 實作了支援 EME 的 DASH 用戶端。
Mozilla Developer Network 提供操作說明,說明如何使用 WebM 工具和 FFmpeg 區隔影片並建立 MPD。
結論
使用網路提供付費影片和音訊的情況正在以驚人的速度成長。無論是平板電腦、遊戲主機、連網電視或機上盒,每部新裝置似乎都能透過 HTTP 串流播放主要內容供應者的媒體內容。目前超過 85% 的行動裝置和電腦版瀏覽器支援 <video>
和 <audio>
,而 Cisco 估計,到了 2017 年,影片將佔全球消費者網際網路流量的 80% 到 90%。在這種情況下,瀏覽器支援的受保護內容發布功能可能會越來越重要,因為瀏覽器供應商縮減支援大多數媒體外掛程式所依賴的 API。
延伸閱讀
規格和標準
- EME 規格:最新的編輯器草稿<
- 通用加密 (CENC)
- 媒體來源擴充功能
- DASH 標準 (是的,就是 PDF)
- 關於 DASH 標準
文章
- DTG 網路研討會 (部分已淘汰)
- 什麼是 EME?,作者:Henri Sivonen
- HTML5 Rocks 媒體來源擴充功能文章
- MPEG-DASH 測試串流:BBC 研發部門網誌文章