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

feat: add option to cache encrpytion keys in the player #446

Merged
merged 7 commits into from
Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from 6 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Video.js Compatibility: 6.0, 7.0
- [Source](#source)
- [List](#list)
- [withCredentials](#withcredentials)
- [handleManifestRedirects](#handlemanifestredirects)
- [useCueTags](#usecuetags)
- [overrideNative](#overridenative)
- [blacklistDuration](#blacklistduration)
Expand All @@ -51,6 +52,7 @@ Video.js Compatibility: 6.0, 7.0
- [allowSeeksWithinUnsafeLiveWindow](#allowseekswithinunsafelivewindow)
- [customTagParsers](#customtagparsers)
- [customTagMappers](#customtagmappers)
- [cacheEncryptionKeys](#cacheencryptionkeys)
- [Runtime Properties](#runtime-properties)
- [hls.playlists.master](#hlsplaylistsmaster)
- [hls.playlists.media](#hlsplaylistsmedia)
Expand Down Expand Up @@ -418,6 +420,14 @@ With `customTagParsers` you can pass an array of custom m3u8 tag parser objects.

Similar to `customTagParsers`, with `customTagMappers` you can pass an array of custom m3u8 tag mapper objects. See https://github.com/videojs/m3u8-parser#custom-parsers

##### cacheEncryptionKeys
* Type: `boolean`
* can be used as a source option
* can be used as an initialization option

This option forces the player to cache AES-128 encryption keys internally instead of requesting the key alongside every segment request.
This option defaults to `false`.

### Runtime Properties
Runtime properties are attached to the tech object when HLS is in
use. You can get a reference to the HLS source handler like this:
Expand Down
15 changes: 4 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/bin-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ export const initSegmentId = function(initSegment) {
].join(',');
};

/**
* Returns a unique string identifier for a media segment key.
*/
export const segmentKeyId = function(key) {
return key.resolvedUri;
};

/**
* utils to help dump binary data to the console
*/
Expand Down
6 changes: 4 additions & 2 deletions src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export class MasterPlaylistController extends videojs.EventTarget {
blacklistDuration,
enableLowInitialPlaylist,
sourceType,
seekTo
seekTo,
cacheEncryptionKeys
} = options;

if (!url) {
Expand Down Expand Up @@ -125,7 +126,8 @@ export class MasterPlaylistController extends videojs.EventTarget {
syncController: this.syncController_,
decrypter: this.decrypter_,
sourceType: this.sourceType_,
inbandTextTracks: this.inbandTextTracks_
inbandTextTracks: this.inbandTextTracks_,
cacheEncryptionKeys
};

this.masterPlaylistLoader_ = this.sourceType_ === 'dash' ?
Expand Down
8 changes: 5 additions & 3 deletions src/media-segment-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,16 +285,18 @@ const decryptSegment = (decrypter, segment, doneFn) => {

decrypter.addEventListener('message', decryptionHandler);

const keyBytes = segment.key.bytes.slice();

// this is an encrypted segment
// incrementally decrypt the segment
decrypter.postMessage(createTransferableMessage({
source: segment.requestId,
encrypted: segment.encryptedBytes,
key: segment.key.bytes,
key: keyBytes,
iv: segment.key.iv
}), [
segment.encryptedBytes.buffer,
segment.key.bytes.buffer
keyBytes.buffer
]);
};

Expand Down Expand Up @@ -432,7 +434,7 @@ export const mediaSegmentRequest = (xhr,
const finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn);

// optionally, request the decryption key
if (segment.key) {
if (segment.key && !segment.key.bytes) {
const keyRequestOptions = videojs.mergeOptions(xhrOptions, {
uri: segment.key.resolvedUri,
responseType: 'arraybuffer'
Expand Down
56 changes: 51 additions & 5 deletions src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import SourceUpdater from './source-updater';
import Config from './config';
import window from 'global/window';
import { removeCuesFromTrack } from './mse/remove-cues-from-track';
import { initSegmentId } from './bin-utils';
import { initSegmentId, segmentKeyId } from './bin-utils';
import { mediaSegmentRequest, REQUEST_ERRORS } from './media-segment-request';
import { TIME_FUDGE_FACTOR, timeUntilRebuffer as timeUntilRebuffer_ } from './ranges';
import { minRebufferMaxBandwidthSelector } from './playlist-selectors';
Expand Down Expand Up @@ -183,6 +183,11 @@ export default class SegmentLoader extends videojs.EventTarget {
// Fragmented mp4 playback
this.activeInitSegmentId_ = null;
this.initSegments_ = {};

// HLSe playback
this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
this.keyCache_ = {};

// Fmp4 CaptionParser
this.captionParser_ = new CaptionParser();

Expand Down Expand Up @@ -342,6 +347,8 @@ export default class SegmentLoader extends videojs.EventTarget {
const id = initSegmentId(map);
let storedMap = this.initSegments_[id];

// TODO: We should use the HTTP Expires header to invalidate our cache per
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, this accidentally ended up in the initSegment method instead of segmentKey

if (set && !storedMap && map.bytes) {
this.initSegments_[id] = storedMap = {
resolvedUri: map.resolvedUri,
Expand All @@ -355,6 +362,42 @@ export default class SegmentLoader extends videojs.EventTarget {
return storedMap || map;
}

/**
* Gets and sets key for the provided key
*
* @param {Object} key
* The key object representing the key to get or set
* @param {Boolean=} set
* If true, the key for the provided key should be saved
* @return {Object}
* Key object for desired key
*/
segmentKey(key, set = false) {
if (!key) {
return null;
}

const id = segmentKeyId(key);
let storedKey = this.keyCache_[id];

if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
this.keyCache_[id] = storedKey = {
resolvedUri: key.resolvedUri,
bytes: key.bytes
};
}

const result = {
resolvedUri: (storedKey || key).resolvedUri
};

if (storedKey) {
result.bytes = storedKey.bytes;
}

return result;
}

/**
* Returns true if all configuration required for loading is present, otherwise false.
*
Expand Down Expand Up @@ -1048,10 +1091,8 @@ export default class SegmentLoader extends videojs.EventTarget {
0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence
]);

simpleSegment.key = {
resolvedUri: segment.key.resolvedUri,
iv
};
simpleSegment.key = this.segmentKey(segment.key);
simpleSegment.key.iv = iv;
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
}

if (segment.map) {
Expand Down Expand Up @@ -1136,6 +1177,11 @@ export default class SegmentLoader extends videojs.EventTarget {
simpleSegment.map = this.initSegment(simpleSegment.map, true);
}

// if this request included a segment key, save that data in the cache
if (simpleSegment.key) {
this.segmentKey(simpleSegment.key, true);
}

this.processSegmentResponse_(simpleSegment);
}

Expand Down
4 changes: 3 additions & 1 deletion src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ class HlsHandler extends Component {
this.options_.useBandwidthFromLocalStorage || false;
this.options_.customTagParsers = this.options_.customTagParsers || [];
this.options_.customTagMappers = this.options_.customTagMappers || [];
this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;

if (typeof this.options_.blacklistDuration !== 'number') {
this.options_.blacklistDuration = 5 * 60;
Expand Down Expand Up @@ -443,7 +444,8 @@ class HlsHandler extends Component {
'smoothQualityChange',
'customTagParsers',
'customTagMappers',
'handleManifestRedirects'
'handleManifestRedirects',
'cacheEncryptionKeys'
].forEach((option) => {
if (typeof this.source_[option] !== 'undefined') {
this.options_[option] = this.source_[option];
Expand Down