Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Support for EME 5 July 2016 Spec and FairPlay #1

Merged
merged 17 commits into from
Aug 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,162 @@ Supports Encrypted Media Extensions for playback of encrypted content in Video.j

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Using

By default, videojs-contrib-eme is not able to decrypt any audio/video. In order to
decrypt audio/video, a user must pass in methods that are specific to a source and its
combination of key system and codec. These are provided via either videojs-contrib-eme's
plugin options, or source options.

### FairPlay

For FairPlay, only `keySystems` is used from the options passed into videojs-contrib-eme,
or provided as part of the source object.

The required methods to provide are:
* `getCertificate`
* `getContentId`
* `getLicense`
or, if you are using the default FairPlay methods, the only required parameters are:
* `certificateUri`
* `licenseUri`

Below is an example of videojs-contrib-eme options when only using FairPlay:

```javascript
{
keySystems: {
"com.apple.fps.1_0": {
getCertificate: (options, callback) => {
// request certificate
// if err, callback(err)
// if success, callback(null, certificate)
},
getContentId: (initData) => {
// return content ID
},
getLicense: (options, callback) => {
let { contentId, webKitKeyMessage } = options;

// request key using options
// if err, callback(err)
// if success, callback(null, key) as arraybuffer
}
}
}
}
```

Below is an example of videojs-contrib-eme options when only using FairPlay, and using
the default FairPlay methods:

```javascript
{
keySystems: {
"com.apple.fps.1_0": {
certificateUri: "<CERTIFICATE URI>",
licenseUri: "<LICENSE URI>"
}
}
}
```

The default methods are defined as follows:
* getCertificate - GET certificateUri with response type of arraybuffer
* getContentId - gets the hostname from the initData URI
* getLicense - POST licenseUri with response type of arraybuffer, header of
'Content-type': 'application/octet-stream', and body of webKitKeyMessage

### Other DRM Systems

For DRM systems that use the W3C EME specification as of 5 July 2016, only `keySystems`
is required.

`getLicense` is the only required `keySystems` method. `getCertificate` is also supported
if your source needs to retrieve a certificate.

The `audioContentType` and `videoContentType` properties for non-FairPlay sources are
used to determine if the system supports that codec, and to create an appropriate
`keySystemAccess` object. If left out, it is possible that the system will create a
`keySystemAccess` object for the given key system, but will not be able to play the
source due to the browser's inability to use that codec.

Below is an example of videojs-contrib-eme options when only using one of these DRM
systems:

```javascript
{
keySystems: {
"org.w3.clearkey": {
videoContentType: 'audio/webm; codecs="vorbis"',
audioContentType: 'video/webm; codecs="vp9"',
getCertificate: (options, callback) => {
// request certificate
// if err, callback(err)
// if success, callback(null, certificate)
},
getLicense: (options, callback) => {
let keyMessage = options.keyMessage;

// request license using mediaKeyMessage
// if err, callback(err)
// if success, callback(null, license)
}
}
}
}
```

### Source Options

Since each source may have a different set of properties and methods, it is best to use
source options instead of plugin options when specifying key systems. To do that, simply
pass the same options as you would as part of the plugin options, but instead pass them
as part of the source object when specifying `player.src(sourceObject)`.

For example:

```javascript
player.src({
// normal src and type options
src: '<URL>',
type: 'video/webm',
// eme options
keySystems: {
'org.w3.clearkey': {
videoContentType: 'audio/webm; codecs="vorbis"',
audioContentType: 'video/webm; codecs="vp9"',
getCertificate: (options, callback) => {
// request certificate
// if err, callback(err)
// if success, callback(null, certificate)
},
getLicense: (options, callback) => {
let keyMessage = options.keyMessage;

// request license using mediaKeyMessage
// if err, callback(err)
// if success, callback(null, license)
}
}
}
});
```

### Passing methods seems complicated

If you're wondering why there are so many methods to implement, and why the options can't
simply have URLs (except for the default FairPlay case), you're asking good questions.

We wanted to provide as much flexibility as possible. This means that if your server has
a different structure, you use a different format for FairPlay content IDs, or you want
to test something in the browser without making a request, we can support that, since you
control the methods.

In the future we may provide other default implementations and allow passing through the
minimum amount of details possible. If you have any suggestions on how we should go about
this, we'd love to hear your ideas!

## Getting Started

1. Clone this repository!
Expand Down
67 changes: 67 additions & 0 deletions index-player-options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>videojs-contrib-eme Demo</title>
<link href="/node_modules/video.js/dist/video-js.css" rel="stylesheet">
</head>
<body>
<video id="videojs-contrib-eme-player" class="video-js vjs-default-skin" controls>
<source src="/moose_encrypted.webm" type='video/webm'>
</video>
<ul>
<li><a href="/test/">Run unit tests in browser.</a></li>

</ul>
<script src="/node_modules/video.js/dist/video.js"></script>
<script src="/dist/videojs-contrib-eme.js"></script>
<script>
(function(window, videojs) {
// Example derived from http://www.html5rocks.com/en/tutorials/eme/basics/#clear-key

const KEY = new Uint8Array([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
]);

// Convert Uint8Array into base64 using base64url alphabet, without padding.
let toBase64 = (u8arr) => {
return btoa(String.fromCharCode.apply(null, u8arr)).
replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}

let player = window.player = videojs('videojs-contrib-eme-player', {
plugins: {
eme: {
keySystems: {
'org.w3.clearkey': {
getLicense: (options, callback) => {
let keyMessage = options.keyMessage;
// Parse the clearkey license request.
let request = JSON.parse(new TextDecoder().decode(keyMessage));
// 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);

let keyObj = {
kty: 'oct',
alg: 'A128KW',
kid: request.kids[0],
k: toBase64(KEY)
};

console.log('Key object:', keyObj);

callback(null, new TextEncoder().encode(JSON.stringify({
keys: [keyObj]
})));
}
}
}
}
}
});
}(window, window.videojs));
</script>
</body>
</html>
66 changes: 66 additions & 0 deletions index-source-options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>videojs-contrib-eme Demo</title>
<link href="/node_modules/video.js/dist/video-js.css" rel="stylesheet">
</head>
<body>
<video id="videojs-contrib-eme-player" class="video-js vjs-default-skin" controls data-setup='{"plugins": {"eme": {}}}'>
</video>
<ul>
<li><a href="/test/">Run unit tests in browser.</a></li>

</ul>
<script src="/node_modules/video.js/dist/video.js"></script>
<script src="/dist/videojs-contrib-eme.js"></script>
<script>
(function(window, videojs) {
// Example derived from http://www.html5rocks.com/en/tutorials/eme/basics/#clear-key

const KEY = new Uint8Array([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
]);

let player = window.player = videojs('videojs-contrib-eme-player');

// Convert Uint8Array into base64 using base64url alphabet, without padding.
let toBase64 = (u8arr) => {
return btoa(String.fromCharCode.apply(null, u8arr)).
replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}

player.src({
src: '/moose_encrypted.webm',
type: 'video/webm',
keySystems: {
'org.w3.clearkey': {
getLicense: (options, callback) => {
let keyMessage = options.keyMessage;
// Parse the clearkey license request.
let request = JSON.parse(new TextDecoder().decode(keyMessage));
// 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);

let keyObj = {
kty: 'oct',
alg: 'A128KW',
kid: request.kids[0],
k: toBase64(KEY)
};

console.log('Key object:', keyObj);

callback(null, new TextEncoder().encode(JSON.stringify({
keys: [keyObj]
})));
}
}
}
});
}(window, window.videojs));
</script>
</body>
</html>
26 changes: 0 additions & 26 deletions index.html

This file was deleted.

Binary file added moose_encrypted.webm
Binary file not shown.
Loading