Skip to content

Commit

Permalink
feat: drm (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Ziv committed Aug 28, 2017
1 parent e36809b commit a844b81
Show file tree
Hide file tree
Showing 9 changed files with 680 additions and 340 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@
"karma-webpack": "^2.0.2",
"mocha": "^3.2.0",
"mocha-cli": "^1.0.1",
"playkit-js": "https://github.com/kaltura/playkit-js.git#v0.3.0",
"pre-push": "^0.1.1",
"playkit-js": "https://github.com/kaltura/playkit-js.git#master",
"shaka-player": "latest",
"sinon": "^2.0.0",
"sinon-chai": "^2.8.0",
Expand Down
53 changes: 52 additions & 1 deletion src/dash-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import shaka from 'shaka-player';
import {registerMediaSourceAdapter, BaseMediaSourceAdapter} from 'playkit-js'
import {Track, VideoTrack, AudioTrack, TextTrack} from 'playkit-js'
import {Utils} from 'playkit-js'
import Widevine from './drm/widevine'
import PlayReady from './drm/playready'

/**
* Adapter of shaka lib for dash content
Expand Down Expand Up @@ -30,6 +32,20 @@ export default class DashAdapter extends BaseMediaSourceAdapter {
* @private
*/
static _dashMimeType = 'application/dash+xml';
/**
* The DRM protocols implementations for dash adapter.
* @type {Array<Function>}
* @private
* @static
*/
static _drmProtocols: Array<Function> = [Widevine, PlayReady];
/**
* The DRM protocol for the current playback.
* @type {?Function}
* @private
* @static
*/
static _drmProtocol: ?Function = null;
/**
* The shaka player instance
* @member {any} _shaka
Expand Down Expand Up @@ -74,6 +90,25 @@ export default class DashAdapter extends BaseMediaSourceAdapter {
return canPlayType;
}

/**
* Checks if dash adapter can play a given drm data.
* @param {Array<Object>} drmData - The drm data to check.
* @returns {boolean} - Whether the dash adapter can play a specific drm data.
* @static
*/
static canPlayDrm(drmData: Array<Object>): boolean {
let canPlayDrm = false;
for (let drmProtocol of DashAdapter._drmProtocols) {
if (drmProtocol.canPlayDrm(drmData)) {
DashAdapter._drmProtocol = drmProtocol;
canPlayDrm = true;
break;
}
}
DashAdapter._logger.debug('canPlayDrm result is ' + canPlayDrm.toString(), drmData);
return canPlayDrm;
}

/**
* Checks if the dash adapter is supported
* @function isSupported
Expand All @@ -97,11 +132,23 @@ export default class DashAdapter extends BaseMediaSourceAdapter {
DashAdapter._logger.debug('Creating adapter. Shaka version: ' + shaka.Player.version);
super(videoElement, source, config);
this._shaka = new shaka.Player(videoElement);
this._shaka.configure(config);
this._maybeSetDrmConfig();
this._shaka.configure(this._config);
this._shaka.setTextTrackVisibility(true);
this._addBindings();
}

/**
* Configure drm for shaka player.
* @private
* @returns {void}
*/
_maybeSetDrmConfig(): void {
if (DashAdapter._drmProtocol && this._sourceObj && this._sourceObj.drmData) {
DashAdapter._drmProtocol.setDrmPlayback(this._config, this._sourceObj.drmData);
}
}

/**
* Add the required bindings to shaka.
* @function _addBindings
Expand Down Expand Up @@ -161,6 +208,10 @@ export default class DashAdapter extends BaseMediaSourceAdapter {
this._sourceObj = null;
this._removeBindings();
this._shaka.destroy();
if (DashAdapter._drmProtocol) {
DashAdapter._drmProtocol.destroy();
DashAdapter._drmProtocol = null;
}
}

/**
Expand Down
39 changes: 39 additions & 0 deletions src/drm/playready.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @flow
import {BaseDrmProtocol} from 'playkit-js'

const DrmSupport = BaseDrmProtocol.DrmSupport;
const DrmScheme = BaseDrmProtocol.DrmScheme;

export default class PlayReady extends BaseDrmProtocol {
static _logger = BaseDrmProtocol.getLogger('PlayReady');

/**
* PlayReady playback supports in case 2 conditions are met:
* 1. The environment supports PlayReady playback.
* 2. The drm data of the source object contains entry with PlayReady scheme.
* @param {Array<Object>} drmData - The drm data to check.
* @return {boolean} - Whether FairPlay can be play on the current environment.
*/
static canPlayDrm(drmData: Array<Object>): boolean {
PlayReady._logger.debug("Can play DRM scheme of: " + DrmScheme.PLAYREADY);
return DrmSupport.isProtocolSupported(DrmScheme.PLAYREADY, drmData);
}

/**
* Sets the PlayReady playback.
* @param {Object} config - The shaka player config.
* @param {Array<Object>} drmData - The drm data.
* @returns {void}
*/
static setDrmPlayback(config: Object, drmData: Array<Object>): void {
PlayReady._logger.debug("Sets drm playback");
let prDrmEntry = drmData.find((drmEntry) => drmEntry.scheme === DrmScheme.PLAYREADY);
if (prDrmEntry) {
config.drm = {
servers: {
[DrmScheme.PLAYREADY]: prDrmEntry.licenseUrl
}
};
}
}
}
49 changes: 49 additions & 0 deletions src/drm/widevine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// @flow
import {BaseDrmProtocol, Env} from 'playkit-js'

const DrmSupport = BaseDrmProtocol.DrmSupport;
const DrmScheme = BaseDrmProtocol.DrmScheme;

export default class Widevine extends BaseDrmProtocol {
static _logger = BaseDrmProtocol.getLogger('Widevine');

/**
* Widevine playback supports in case 2 conditions are met:
* 1. The environment supports Widevine playback.
* 2. The drm data of the source object contains entry with Widevine scheme.
* @param {Array<Object>} drmData - The drm data to check.
* @return {boolean} - Whether FairPlay can be play on the current environment.
*/
static canPlayDrm(drmData: Array<Object>): boolean {
Widevine._logger.debug("Can play DRM scheme of: " + DrmScheme.WIDEVINE);
return DrmSupport.isProtocolSupported(DrmScheme.WIDEVINE, drmData);
}

/**
* Sets the Widevine playback.
* @param {Object} config - The shaka player config.
* @param {Array<Object>} drmData - The drm data.
* @returns {void}
*/
static setDrmPlayback(config: Object, drmData: Array<Object>): void {
Widevine._logger.debug("Sets drm playback");
let browser = Env.browser.name;
let wwDrmEntry = drmData.find((drmEntry) => drmEntry.scheme === DrmScheme.WIDEVINE);
if (wwDrmEntry) {
config.drm = {
servers: {
[DrmScheme.WIDEVINE]: wwDrmEntry.licenseUrl
},
advanced: {}
};
if (browser === 'Chrome') {
config.drm.advanced = {
[DrmScheme.WIDEVINE]: {
'videoRobustness': 'SW_SECURE_CRYPTO',
'audioRobustness': 'SW_SECURE_CRYPTO'
}
};
}
}
}
}
36 changes: 36 additions & 0 deletions test/src/dash-adapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import loadPlayer from 'playkit-js'
import DashAdapter from '../../src/dash-adapter';
import * as TestUtils from 'playkit-js/test/src/utils/test-utils'
import {VideoTrack, AudioTrack, TextTrack} from 'playkit-js';
import Widevine from '../../src/drm/widevine'
import PlayReady from '../../src/drm/playready'

const targetId = 'player-placeholder_dash-adapter.spec';

Expand Down Expand Up @@ -59,6 +61,40 @@ describe.skip('DashAdapter [debugging and testing manually]', () => {
});
});

describe('DashAdapter: canPlayDrm', () => {
let sandbox;

beforeEach(() => {
sandbox = sinon.sandbox.create();
});

afterEach(() => {
sandbox.restore();
DashAdapter._drmProtocol = null;
});

it('should return true since widevine can play drm', function () {
sandbox.stub(Widevine, 'canPlayDrm', () => true);
sandbox.stub(PlayReady, 'canPlayDrm', () => false);
DashAdapter.canPlayDrm().should.be.true;
DashAdapter._drmProtocol.should.equal(Widevine);
});

it('should return true since playready can play drm', function () {
sandbox.stub(Widevine, 'canPlayDrm', () => false);
sandbox.stub(PlayReady, 'canPlayDrm', () => true);
DashAdapter.canPlayDrm().should.be.true;
DashAdapter._drmProtocol.should.equal(PlayReady);
});

it('should return false since no drm can be played', function () {
sandbox.stub(Widevine, 'canPlayDrm', () => false);
sandbox.stub(PlayReady, 'canPlayDrm', () => false);
DashAdapter.canPlayDrm().should.be.false;
(DashAdapter._drmProtocol === null).should.be.true;
});
});

describe('DashAdapter: canPlayType', () => {

it('should return true for application/dash+xml', () => {
Expand Down
11 changes: 11 additions & 0 deletions test/src/drm/fake-drm-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {BaseDrmProtocol} from 'playkit-js'

export const wwDrmData = [{
licenseUrl: 'LICENSE_URL',
scheme: BaseDrmProtocol.DrmScheme.WIDEVINE
}];

export const prDrmData = [{
licenseUrl: 'LICENSE_URL',
scheme: BaseDrmProtocol.DrmScheme.PLAYREADY
}];
55 changes: 55 additions & 0 deletions test/src/drm/playready.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import PlayReady from '../../../src/drm/playready'
import {BaseDrmProtocol, Env} from 'playkit-js'
import {wwDrmData, prDrmData} from './fake-drm-data'

const DrmSupport = BaseDrmProtocol.DrmSupport;
const DrmScheme = BaseDrmProtocol.DrmScheme;
const BROWSER: string = Env.browser.name;

function isValidEnvForPlayReady() {
return (BROWSER === 'Edge' || BROWSER === 'IE')
&& (DrmSupport._Browsers['Edge']() === DrmScheme.PLAYREADY
|| DrmSupport._Browsers['IE']() === DrmScheme.PLAYREADY);
}

describe('PlayReady', function () {
describe('canPlayDrm', function () {
it('should return true for playready data on valid playready env and false otherwise', function () {
if (isValidEnvForPlayReady()) {
PlayReady.canPlayDrm(prDrmData).should.be.true;
} else {
PlayReady.canPlayDrm(prDrmData).should.be.false;
}
});

it('should return false for non-playready data any case', function () {
PlayReady.canPlayDrm(wwDrmData).should.be.false;
});
});

describe('setDrmPlayback', function () {
let config = {};
let expectedConfig = {
drm: {
servers: {
[DrmScheme.PLAYREADY]: prDrmData[0].licenseUrl
}
}
};

afterEach(function () {
config = {};
});

it('sets the correct shaka drm config for playready data', function () {
PlayReady.setDrmPlayback(config, prDrmData);
config.should.deep.equal(expectedConfig);
});

it('sets nothing for non-playready data', function () {
PlayReady.setDrmPlayback(config, wwDrmData);
config.should.deep.equal({});
});
});
});

63 changes: 63 additions & 0 deletions test/src/drm/widevine.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Widevine from '../../../src/drm/widevine'
import {BaseDrmProtocol, Env} from 'playkit-js'
import {wwDrmData, prDrmData} from './fake-drm-data'

const DrmSupport = BaseDrmProtocol.DrmSupport;
const DrmScheme = BaseDrmProtocol.DrmScheme;
const BROWSER: string = Env.browser.name;

function isValidEnvForWidevine() {
return (BROWSER === 'Chrome' || BROWSER === 'Firefox')
&& (DrmSupport._Browsers['Chrome']() === DrmScheme.WIDEVINE
|| DrmSupport._Browsers['Firefox']() === DrmScheme.WIDEVINE);
}

describe('Widevine', function () {
describe('canPlayDrm', function () {
it('should return true for widevine data on valid widevine env and false otherwise', function () {
if (isValidEnvForWidevine()) {
Widevine.canPlayDrm(wwDrmData).should.be.true;
} else {
Widevine.canPlayDrm(wwDrmData).should.be.false;
}
});

it('should return false for non-widevine data any case', function () {
Widevine.canPlayDrm(prDrmData).should.be.false;
});
});

describe('setDrmPlayback', function () {
let config = {};
let expectedConfig = {
drm: {
servers: {
[DrmScheme.WIDEVINE]: wwDrmData[0].licenseUrl
}
}
};

afterEach(function () {
config = {};
});

it('sets the correct shaka drm config for widevine data', function () {
Widevine.setDrmPlayback(config, wwDrmData);
if (BROWSER === 'Chrome') {
expectedConfig.drm.advanced = {
[DrmScheme.WIDEVINE]: {
'videoRobustness': 'SW_SECURE_CRYPTO',
'audioRobustness': 'SW_SECURE_CRYPTO'
}
};
}
config.should.deep.equal(expectedConfig);
});

it('sets nothing for non-widevine data', function () {
Widevine.setDrmPlayback(config, prDrmData);
config.should.deep.equal({});
});
});
});

Loading

0 comments on commit a844b81

Please sign in to comment.