Encrypted Media Extensions (EME) 提供 API,可讓網路應用程式與內容保護系統互動,以便播放已加密的音訊和視訊。
EME 的設計目的是讓相同的應用程式和加密檔案可在任何瀏覽器中使用,不受底層保護系統的影響。前者是透過標準化 API 和流程實現,後者則是透過通用加密概念實現。
EME 是 HTMLMediaElement
規格的擴充功能,因此名稱為 EME。由於 EME 是「擴充功能」,因此瀏覽器是否支援 EME 並非強制規定:如果瀏覽器不支援加密媒體,就無法播放加密媒體,但 HTML 規格並未強制規定必須支援 EME。根據 EME 規格:
EME 實作項目會使用下列外部元件:
- 金鑰系統:內容保護 (DRM) 機制。除了 Clear Key 之外,EME 不會定義 Key System (詳情請參閱下文)。
- 內容解密模組 (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 的瀏覽器中,Clear Key 都能完全互通。這也非常適合測試 EME 實作項目,以及使用 EME 的應用程式,無須向授權伺服器要求內容金鑰。您可以在 simpl.info/ck 中找到簡單的 Clear Key 範例。以下是程式碼的逐步操作說明,與上述步驟類似,但不會與授權伺服器互動。
// 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 取自 .mpd 檔案,用於 YouTube DASH 示範播放器)
根據 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 估計影片將佔全球消費者網路流量的 80% 到 90%。在這種情況下,瀏覽器支援的受保護內容發布功能可能會越來越重要,因為瀏覽器供應商 縮減支援大多數媒體外掛程式所依賴的 API。
延伸閱讀
規格和標準
- EME 規格:最新編輯器草稿<
- 通用加密 (CENC)
- 媒體來源擴充功能
- DASH 標準 (是的,就是 PDF)
- 關於 DASH 標準
文章
- DTG 網路研討會 (部分已淘汰)
- 什麼是 EME?,作者:Henri Sivonen
- HTML5 Rocks 媒體來源擴充功能文章
- MPEG-DASH 測試串流:BBC 研發部門網誌文章