Skip to content

Commit

Permalink
Add DRM checking capabilities for YouTube (#3)
Browse files Browse the repository at this point in the history
- added the checkDrmById to the player and checkDrm to the main file
- added a generic object pool to handle resource management (players here)
  • Loading branch information
Jonathan Raoult authored Apr 28, 2017
1 parent 39dc471 commit cf83a11
Show file tree
Hide file tree
Showing 15 changed files with 596 additions and 222 deletions.
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ gulp.task('serve', ['watch'], function() {

browserSync({
open: false,
ghostMode: false,
server: {
baseDir: baseDirs.concat(['node_modules/jasmine-core'])
}
Expand Down
6 changes: 5 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ module.exports = function(config) {
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
reporters: ['spec'],

specReporter: {
suppressPassed: true, // do not print information about passed tests
suppressSkipped: true, // do not print information about skipped tests
},

// web server port
port: 9876,
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"name": "mixtube-playback",
"version": "1.0.1",
"main": "./src/main/playback",
"main": "./src/main/index",
"dependencies": {
"lodash": "^4.17.4",
"p-finally": "^1.0.0",
"shifty": "^1.5.1"
},
"devDependencies": {
Expand All @@ -21,6 +22,7 @@
"karma-chrome-launcher": "^2.0.0",
"karma-firefox-launcher": "^1.0.1",
"karma-jasmine": "^1.1.0",
"karma-spec-reporter": "0.0.31",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.4.0"
},
Expand Down
61 changes: 61 additions & 0 deletions src/main/drmChecker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

var playersPool = require('./playersPool'),
playerFactory = require('./playerFactory'),
defaults = require('lodash/defaults'),
pFinally = require('p-finally');

/**
* @typedef {Object} drmCheckerConfig
* @property {PlayersPool} playersPool
*/

/**
*
* @param {drmCheckerConfig} config
* @returns {DrmChecker}
*/
function drmChecker(config) {

var _config = defaults({}, config, {
max: 4
}),

_playersPool = playersPool({
playerFactory: playerFactory({
elementProducer: _config.elementProducer,
// debug settings make non sense for DRM check
debug: {
mediaDuration: -1,
mediaQuality: 'default'
}
}),
max: _config.max
});

/**
* @param {Entry} entry
* @returns {Promise.<DrmCheckReport>}
*/
function checkDrm(entry) {
return _playersPool.getPlayer(entry.provider)
.then(function(player) {
return pFinally(player.checkDrmById(entry.id),
function checkDrmFinally() {
_playersPool.releasePlayer(player);
});
});
}

/**
* @typedef DrmChecker
* @name DrmChecker
*/
var DrmChecker = {
checkDrm: checkDrm
};

return Object.freeze(DrmChecker);
}

module.exports = drmChecker;
44 changes: 24 additions & 20 deletions src/main/playback.js → src/main/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var sequencer = require('./sequencer'),
playbackSlot = require('./playbackSlot'),
playersPool = require('./playersPool'),
playerFactory = require('./playerFactory'),
drmChecker = require('./drmChecker'),
defaults = require('lodash/defaults'),
noop = require('lodash/noop');

Expand All @@ -18,13 +19,13 @@ var sequencer = require('./sequencer'),
*/

/**
* @typedef {Object} playbackDebug
* @typedef {Object} engineDebug
* @property {number} mediaDuration the forced duration of the medias in seconds
* @property {number} mediaQuality the forced quality for the medias (supported values: low, default)
*/

/**
* @typedef {Object} playbackConfig
* @typedef {Object} engineConfig
* @property {function: Element} elementProducer
* @property {function(*): Video} videoProducer
* @property {function(*): *} nextEntryProducer
Expand All @@ -35,41 +36,44 @@ var sequencer = require('./sequencer'),
* @property {?function(Entry, boolean)} loadingChanged called each time an entry started or stopped to load
* following an user action (does not include preloading)
* @property {?function(Entry, ?Error)} loadFailed
* @property {?playbackDebug} debug
* @property {?engineDebug} debug
*/

var debugDefaults = {
mediaDuration: -1,
mediaQuality: 'default'
};

/**
* Creates the "Playback" facade.
*
* Transition is triggered at "transitionDuration" before the end of the media and the "comingNext: is notified
* 2 times "transitionDuration" before the end.
*
* @param {playbackConfig} config
* @returns {Playback}
* @param {engineConfig} config
* @returns {Engine}
*/
function playback(config) {
function engine(config) {

/** @type {playbackConfig} */
/** @type {engineConfig} */
var _config = defaults({}, config, {
comingNext: noop,
stateChanged: noop,
playingChanged: noop,
loadingChanged: noop,
loadFailed: noop,
debug: {
mediaDuration: -1,
mediaQuality: 'default'
}
debug: debugDefaults
}),

_playersPool = playersPool({
_playersPoolMain = playersPool({
playerFactory: playerFactory({
elementProducer: _config.elementProducer,
debug: {
duration: _config.debug.mediaDuration,
quality: _config.debug.mediaQuality
}
})
}),
max: Infinity
});

/**
Expand All @@ -95,7 +99,7 @@ function playback(config) {
callback: slotProducerCfg.ending
}
},
playersPool: _playersPool
playersPool: _playersPoolMain
});
}

Expand All @@ -110,20 +114,20 @@ function playback(config) {
});

/**
* @typedef Playback
* @name Playback
* @typedef Engine
* @name Engine
*/
var Playback = {
var Engine = {
play: _sequencer.play,
pause: _sequencer.pause,
skip: _sequencer.skip,
stop: _sequencer.stop,
checkNextEntry: _sequencer.checkNextEntry
};

return Object.freeze(Playback);
return Object.freeze(Engine);
}

playback.States = sequencer.States;
engine.States = sequencer.States;

module.exports = playback;
module.exports = engine;
9 changes: 9 additions & 0 deletions src/main/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

var engine = require('./engine'),
drmChecker = require('./drmChecker');

module.exports = {
engine: engine,
drmChecker: drmChecker
};
152 changes: 152 additions & 0 deletions src/main/objectsPool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use strict';

/**
* @typedef {Object} ObjectsPoolBucket
* @property {*} payload
* @property {boolean} free
*/

/**
* @typedef {Object} objectsPoolConfig
* @property {function():*|Promise.<*>} factory
* @property {number} max
*/

function pathBucket(bucket) {
return bucket.payload;
}

function predicateBucketFree(bucket) {
return bucket.free;
}

/**
* @param {objectsPoolConfig} config
* @returns {ObjectsPool}
*/
function objectsPool(config) {
var factory = config.factory,
max = config.max;

var buckets = [];
var waiters = [];

function canGrow() {
return buckets.length < max;
}

/**
* @returns {ObjectsPoolBucket}
*/
function findBucketFree() {
return buckets.find(predicateBucketFree);
}

/**
* @param {*} payload
* @returns {ObjectsPoolBucket}
*/
function findBucketForByPayload(payload) {
return buckets.find(function(bucket) {
return bucket.payload === payload;
});
}

function removeBucket(bucket) {
buckets.splice(buckets.findIndex(function(bucket_) {
return bucket_ === bucket;
}), 1);
}

/**
* @returns {Promise.<ObjectsPoolBucket>}
*/
function allocateBucket() {
// allocate the bucket straight but make it not available
var bucket = {payload: undefined, free: false};
buckets.push(bucket);

return Promise
.resolve(factory())
.then(function(payload) {
bucket.payload = payload;
bucket.free = true;
return bucket;
}).catch(function(error) {
// in case of error we remove the bucket
removeBucket(bucket);
throw error;
});
}

/**
* @returns {Promise.<function(*)>}
*/
function enqueueWaiterForBucket() {
return new Promise(function(resolve) {
waiters.push(resolve);
});
}

/**
* @returns {Promise.<ObjectsPoolBucket>}
*/
function ensureBucket() {
var bucket = findBucketFree();

if (!bucket) {
// cache miss
if (!canGrow()) {
return enqueueWaiterForBucket()
.then(markBucketOccupied);
}

return allocateBucket()
.then(markBucketOccupied);
}

return Promise.resolve(markBucketOccupied(bucket));
}

/**
* @param {ObjectsPoolBucket} bucket
* @returns {ObjectsPoolBucket}
*/
function markBucketOccupied(bucket) {
bucket.free = false;
return bucket;
}

function acquire() {
return ensureBucket().then(pathBucket);
}

function release(payload) {
var bucket = findBucketForByPayload(payload);

if (bucket === undefined) {
throw new Error('Can not release a foreign object from a pool');
}

var waiter = waiters.shift();
if (waiter !== undefined) {
// recycle straight if someone is waiting
waiter(bucket);
} else {
bucket.free = true;
}
}

/**
* @typedef ObjectsPool
* @name ObjectsPool
*/
var ObjectsPool = {
acquire: acquire,
release: release
};

return Object.freeze(ObjectsPool);
}

module.exports = objectsPool;
Loading

0 comments on commit cf83a11

Please sign in to comment.