我目前正在研究加密和在浏览器中播放加密视频的主题。在使用Widevine时,我已经与castlabs的DRMToday和Shaka Player取得了一些成功。
现在我正在尝试使用ClearKey在没有外部服务的情况下加密视频并在Chrome中播放(使用可以处理的任何js播放器)。
我确实设法使用MP4Box加密单个mp4文件(并且mse-eme用于创建加密配置),但我不知道如何在浏览器中播放它。 HTML5的视频甚至没有触发“加密”事件。加密本身工作正常 - 我能够使用相同的工具使用正确的密钥对其进行解密。
我尝试用这个加密文件创建一个DASH并在Shaka Player中播放。我使用MP4Box创建了清单。我不得不手动将缺少的xmlns添加到此文件中(xmlns:cenc =“urn:mpeg:cenc:2013”),以便DOMParser正确解析它。我不知道如何处理许可证。
我发现很少有播放编码的webm文件的例子(包括Shaka Player的演示页面)。如何加密webm文件?我确实找到了https://github.com/webmproject/webm-tools,但似乎需要构建整个Chromium才能工作。
还有其他可以加密webm文件的工具吗?
答案 0 :(得分:2)
这是我测试ClearKey DRM播放的文件集。
mp4box(gpac)drm.xml规范文件,您可以在init.mp4段内生成一个或多个PSSH表。
<?xml version="1.0" encoding="UTF-8" ?>
<GPACDRM type="CENC AES-CTR">
<!--
kid=0x43215678123412341234123412341234
key=0x12341234123412341234123412341234
iv=0x22ee7d4745d3a26a
-->
<!-- CENC -->
<DRMInfo type="pssh" version="1">
<BS ID128="1077efecc0b24d02ace33c1e52e2fb4b"/>
<BS bits="32" value="1"/>
<BS ID128="43215678123412341234123412341234"/>
</DRMInfo>
<CrypTrack trackID="1" IsEncrypted="1" IV_size="8" first_IV="0x22ee7d4745d3a26a" saiSavedBox="senc">
<key KID="0x43215678123412341234123412341234" value="0x12341234123412341234123412341234"/>
</CrypTrack>
</GPACDRM>
命令行加密视频+音频和拆分细分。
MP4Box.exe -crypt gpacdrm.xml temp-v1.mp4 -out ./drm/temp-v1.mp4
MP4Box.exe -crypt gpacdrm.xml temp-a1.mp4 -out ./drm/temp-a1.mp4
MP4Box.exe -dash 6000 -frag 6000 -mem-frags -rap -profile dashavc264:live -profile-ext urn:hbbtv:dash:profile:isoff-live:2012 -min-buffer 3000 -bs-switching no -sample-groups-traf -single-traf -subsegs-per-sidx 1 -segment-name $RepresentationID$_$Number$$Init=i$ -segment-timeline -out manifest.mpd temp-v1.mp4#trackID=1:id=v1:period=p0 temp-a1.mp4#trackID=1:id=a1:period=p0
用于ClearKey播放的ShakaPlayer独立演示,使用Chrome或Firefox。我在互联网上找到了这个源代码,所以不管是谁做的。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/2.1.0/shaka-player.compiled.js"></script>
<title>MPEG-DASH Player Test</title>
<script>
var manifestUrl = 'https://my.server.com/drm/manifest_clearkey.mpd';
var laUrl = 'https://my.server.com/drm/laurl_ck.php';
function initApp() {
// Install built-in polyfills to patch browser incompatibilities.
shaka.polyfill.installAll();
// Check to see if the browser supports the basic APIs Shaka needs.
if (shaka.Player.isBrowserSupported()) {
// Everything looks good!
initPlayer();
} else {
// This browser does not have the minimum set of APIs we need.
console.error('Browser not supported!');
}
}
function initPlayer() {
// Create a Player instance.
var video = document.getElementById('video');
var player = new shaka.Player(video);
// Configue
player.configure({
drm: {
servers: {
'org.w3.clearkey': laUrl
},
clearKeys: {
//'kid': 'key'
}
}
});
// Attach player to the window to make it easy to access in the JS console.
window.player = player;
// Listen for error events.
player.addEventListener('error', onErrorEvent);
// Try to load a manifest.
// This is an asynchronous process.
player.load(manifestUrl).then(function () {
// This runs if the asynchronous load is successful.
console.log('The video has now been loaded!');
}).catch(onError); // onError is executed if the asynchronous load fails.
}
function onErrorEvent(event) {
// Extract the shaka.util.Error object from the event.
onError(event.detail);
}
function onError(error) {
console.error('Error code', error.code, 'object', error);
alert("ErrorCode="+error.code);
}
document.addEventListener('DOMContentLoaded', initApp);
</script>
</head>
<body>
<video id="video" autoplay controls></video>
</body>
</html>
ClearKey DRM“许可证服务器php脚本”,播放器发送一个json文档,此脚本返回KID = KEY配对。
<?php
header( "Expires: Mon, 20 Dec 1998 01:00:00 GMT" );
header( "Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT" );
header( "Cache-Control: no-cache, must-revalidate" );
header( "Pragma: no-cache" );
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: origin,range,accept,accept-encoding,referer,content-type, SOAPAction,X-AxDRM-Message');
header('Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST');
header('Access-Control-Expose-Headers: server,range,content-range,content-length,content-type');
// write content-type header after OPTIONS check
// ClearKey DRM server
// some players may submit OPTIONS request(zero-length) before POST drm.xml submit
if ($_SERVER['REQUEST_METHOD']=="OPTIONS") {
header("Content-Length: 0");
header("Expires: -1");
return;
}
//header('Content-Type: text/plain; charset=utf-8');
header('Content-Type: application/json; charset=utf-8');
// Request may have one or more KIDs(base64), read first KID from the request for now.
// KID base64 is without trailing "=" padding chars.
// Request : {"kids":["QyFWeBI0EjQSNBI0EjQSNA"],"type":"temporary"}
// Response: {"keys": [{"k": "EjQSNBI0EjQSNBI0EjQSNA", "kty": "oct", "kid": "QyFWeBI0EjQSNBI0EjQSNA" }], "type": "temporary"}
$req = file_get_contents('php://input'); // read POST bodypart
$json= json_decode($req);
$kidb= $json->{"kids"}[0]; // base64 format
$kid = bin2hex(base64_decode($kidb, true)); // hex format
// KID=KEY lookup table, find KEY and base64(trim trailing "==" chars)
// "EjQSNBI0EjQSNBI0EjQSNA==" -> "EjQSNBI0EjQSNBI0EjQSNA"
$keys = array(
"43215678123412341234123412341234" => "12341234123412341234123412341234",
"43215678123412341234123412341235" => "12341234123412341234123412341235",
"43215678123412341234123412341236" => "12341234123412341234123412341236",
"43215678123412341234123412341237" => "12341234123412341234123412341237",
"43215678123412341234123412341238" => "12341234123412341234123412341238"
);
$key = base64_encode(hex2bin($keys[$kid]));
$key = str_replace("=", "", $key);
$data = "{\"keys\": [{\"k\": \$key, \"kty\": \"oct\", \"kid\": \$kid }], \"type\": \"temporary\"}";
$data = str_replace("\$key", "\"".$key."\"", $data);
$data = str_replace("\$kid", "\"".$kidb."\"", $data);
echo $data;
?>
使用CENC和ClearKey contentprotection元素清单。
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" xmlns:mas="urn:marlin:mas:1-0:services:schemas:mpd" xmlns:mspr="urn:microsoft:playready" maxSegmentDuration="PT0H0M6.000S" mediaPresentationDuration="PT0H1M30.000S" minBufferTime="PT3.000S" profiles="urn:mpeg:dash:profile:isoff-live:2011,http://dashif.org/guidelines/dash264,urn:hbbtv:dash:profile:isoff-live:2012" type="static">
<Period duration="PT0H1M30.000S" id="p0">
<AdaptationSet lang="und" maxFrameRate="25" maxHeight="360" maxWidth="640" par="16:9" segmentAlignment="true" startWithSAP="1">
<ContentProtection cenc:default_KID="43215678-1234-1234-1234-123412341234" schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAFDIVZ4EjQSNBI0EjQSNBI0AAAAAA==</cenc:pssh>
</ContentProtection>
<SegmentTemplate initialization="$RepresentationID$_i.mp4" media="$RepresentationID$_$Number$.m4s" startNumber="1" timescale="1000">
<SegmentTimeline>
<S d="6000" r="14" t="0"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="491773" codecs="avc1.4D4028" frameRate="25" height="360" id="v1" mimeType="video/mp4" sar="1:1" width="640">
</Representation>
</AdaptationSet>
<AdaptationSet lang="und" segmentAlignment="true" startWithSAP="1">
<ContentProtection cenc:default_KID="43215678-1234-1234-1234-123412341234" schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAFDIVZ4EjQSNBI0EjQSNBI0AAAAAA==</cenc:pssh>
</ContentProtection>
<SegmentTemplate initialization="$RepresentationID$_i.mp4" media="$RepresentationID$_$Number$.m4s" startNumber="1" timescale="1000">
<SegmentTimeline>
<S d="5973" t="0"/>
<S d="5995" r="1"/>
<S d="5994"/>
<S d="5995" r="1"/>
<S d="5994"/>
<S d="5995"/>
<S d="5994"/>
<S d="5995" r="2"/>
<S d="5994"/>
<S d="5995" r="1"/>
<S d="101"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="48000" bandwidth="133119" codecs="mp4a.40.2" id="a1" mimeType="audio/mp4">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>
清单cenc:pssh 包含KID值,如果只使用一个密钥,则很容易生成。请参阅pssh元素值的base64tohexdump。
00 00 00 34 70 73 73 68 01 00 00 00
10 77 EF EC C0 B2 4D 02 AC E3 3C 1E 52 E2 FB 4B
00 00 00 01
43 21 56 78 12 34 12 34 12 34 12 34 12 34 12 34
00 00 00 00
答案 1 :(得分:1)
您可以使用Azure媒体服务在不同的流协议(HLS,平滑流和MPEG-DASH)中使用AES清除密钥动态加密多比特率MP4。您不必自己构建加密器。我们还有一个播放器可以在所有浏览器中播放AES加密内容 - 例如在现代浏览器中使用DASH的AES,在Safari中使用HLS的AES和在旧浏览器中使用Flash平滑流式传输的AES。
您可以在此处查看示例:http://amsplayer.azurewebsites.net/azuremediaplayer.html。并选择与AES相关的样本流。您可以按照本教程https://azure.microsoft.com/en-us/documentation/articles/media-services-protect-with-aes128/配置视频的AES加密。
答案 2 :(得分:0)
要具体回答“ HTML5的视频甚至都不会触发其上的“加密”事件。 ”-在2019年,Chrome不会触发经过加密的事件,除非MSE与视频配合使用时,Firefox会显示一条错误消息,指示EME在没有MSE的情况下无法使用。 因此,要播放加密的视频,您必须使用媒体源扩展。
这一点在文档中并不明显,但在此处突出显示 https://github.com/cpearce/eme-in-non-fragmented-mp4
使用正确加密的媒体,您可以在MSE中将单个MP4文件添加为“段”并以chrome格式播放(例如,使用此https://github.com/cpearce/mse-eme的修改版)(我的测试仅是clearkey,使用的是bento4到第一个片段,然后加密单个MP4文件-不要将片段与段混淆。 这不是非常有效,因为我假设在开始播放之前就已下载了整个文件(并将其保存在浏览器的内存中),即,这与直接html5视频元素播放不同,浏览器使用范围请求并管理文件下载和内存使用情况