Skip to content

Commit

Permalink
Detect when key systems don't support a content type.
Browse files Browse the repository at this point in the history
Sometimes a key system will not support the same content types that
MSE will.  We need to use the capabilities returned from EME to
detect what content types are supported.

Closes #342

Change-Id: I356f8ab33fca3a7b036e81f83051de2c3b2d2864
  • Loading branch information
TheModMaker committed Apr 27, 2016
1 parent 1108700 commit 50a43ff
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 19 deletions.
22 changes: 22 additions & 0 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ shaka.media.DrmEngine = function(networkingEngine, onError, onKeyStatus) {
/** @private {string} */
this.keySystem_ = '';

/** @private {!Array.<string>} */
this.supportedTypes_ = [];

/** @private {!Array.<shakaExtern.DrmInfo>} */
this.drmInfos_ = [];

Expand Down Expand Up @@ -122,6 +125,7 @@ shaka.media.DrmEngine.prototype.destroy = function() {
}

this.drmInfos_ = [];
this.supportedTypes_ = [];
this.mediaKeys_ = null;
this.video_ = null;
this.eventManager_ = null;
Expand Down Expand Up @@ -282,6 +286,17 @@ shaka.media.DrmEngine.prototype.keySystem = function() {
};


/**
* Returns an array of the media types supported by the current key system.
* These will be full mime types (e.g. 'video/webm; codecs="vp8"').
*
* @return {!Array.<string>}
*/
shaka.media.DrmEngine.prototype.getSupportedTypes = function() {
return this.supportedTypes_;
};


/**
* @param {!shakaExtern.Manifest} manifest
* @param {boolean} offline True if we are storing or loading offline content.
Expand Down Expand Up @@ -421,6 +436,12 @@ shaka.media.DrmEngine.prototype.queryMediaKeys_ =
p = p.then(function(mediaKeySystemAccess) {
if (this.destroyed_) return Promise.reject();

// Store the capabilities of the key system.
var realConfig = mediaKeySystemAccess.getConfiguration();
var caps =
realConfig.audioCapabilities.concat(realConfig.videoCapabilities);
this.supportedTypes_ = caps.map(function(c) { return c.contentType; });

var originalConfig = configsByKeySystem[mediaKeySystemAccess.keySystem];
this.drmInfos_ = originalConfig.drmInfos;
this.keySystem_ = mediaKeySystemAccess.keySystem;
Expand All @@ -441,6 +462,7 @@ shaka.media.DrmEngine.prototype.queryMediaKeys_ =
// We failed to create MediaKeys. This generally shouldn't happen.
this.keySystem_ = '';
this.drmInfos_ = [];
this.supportedTypes_ = [];
return Promise.reject(new shaka.util.Error(
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.FAILED_TO_CREATE_CDM,
Expand Down
50 changes: 35 additions & 15 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,8 @@ shaka.Player.prototype.applyConfig_ = function(
return (type == 'audio' && audioLangChanged) ||
(type == 'text' && textLangChanged) ||
(activeStreams[type] &&
!activeStreams[type].allowedByApplication);
(!activeStreams[type].allowedByApplication ||
!activeStreams[type].allowedByKeySystem));
});
this.deferredSwitch_(streamsToSwitch, /* clearBuffer */ true);
}
Expand Down Expand Up @@ -1063,6 +1064,19 @@ shaka.Player.prototype.destroyStreaming_ = function() {
shaka.Player.TextTrackId_ = 'Shaka Player TextTrack';


/**
* Determines if the given stream set has any playable streams.
* @param {shakaExtern.StreamSet} streamSet
* @return {boolean}
* @private
*/
shaka.Player.hasPlayableStreams_ = function(streamSet) {
return streamSet.streams.some(function(stream) {
return stream.allowedByApplication && stream.allowedByKeySystem;
});
};


// TODO: consider moving config-parsing to another file.
/**
* @param {!Object} destination
Expand Down Expand Up @@ -1268,8 +1282,10 @@ shaka.Player.prototype.applyRestrictions_ = function(period) {
*/
shaka.Player.prototype.filterPeriod_ = function(period) {
var keySystem = '';
var supportedMimeTypes = null;
if (this.drmEngine_ && this.drmEngine_.initialized()) {
keySystem = this.drmEngine_.keySystem();
supportedMimeTypes = this.drmEngine_.getSupportedTypes();
}

var activeStreams = {};
Expand Down Expand Up @@ -1321,6 +1337,13 @@ shaka.Player.prototype.filterPeriod_ = function(period) {
--j;
continue;
}

if (supportedMimeTypes && stream.keyId &&
supportedMimeTypes.indexOf(fullMimeType) < 0) {
streamSet.streams.splice(j, 1);
--j;
continue;
}
}

if (streamSet.streams.length == 0) {
Expand All @@ -1331,11 +1354,8 @@ shaka.Player.prototype.filterPeriod_ = function(period) {

this.applyRestrictions_(period);

var hasPlayableStreamSets = period.streamSets.some(function(streamSet) {
return streamSet.streams.some(function(stream) {
return stream.allowedByKeySystem && stream.allowedByApplication;
});
});
var hasPlayableStreamSets =
period.streamSets.some(shaka.Player.hasPlayableStreams_);
if (!hasPlayableStreamSets) {
this.onError_(new shaka.util.Error(
shaka.util.Error.Category.MANIFEST,
Expand Down Expand Up @@ -1432,18 +1452,19 @@ shaka.Player.prototype.onSeek_ = function() {
*/
shaka.Player.prototype.chooseStreams_ = function(period) {
var LanguageUtils = shaka.util.LanguageUtils;
var hasPlayableStreams = shaka.Player.hasPlayableStreams_;

// Choose the first stream set listed as the default.
/** @type {!Object.<string, shakaExtern.StreamSet>} */
var streamSetsByType = {};
period.streamSets.forEach(function(set) {
if (set.type in streamSetsByType) return;
if (!hasPlayableStreams(set) || set.type in streamSetsByType) return;
streamSetsByType[set.type] = set;
});

// Then if there are primary stream sets, override the default.
period.streamSets.forEach(function(set) {
if (set.primary)
if (hasPlayableStreams(set) && set.primary)
streamSetsByType[set.type] = set;
});

Expand All @@ -1458,6 +1479,9 @@ shaka.Player.prototype.chooseStreams_ = function(period) {
LanguageUtils.MatchType.EXACT]
.forEach(function(matchType) {
period.streamSets.forEach(function(set) {
if (!hasPlayableStreams(set))
return;

/** @type {string} */
var pref;
if (set.type == 'audio')
Expand All @@ -1477,13 +1501,9 @@ shaka.Player.prototype.chooseStreams_ = function(period) {
}.bind(this));

// If there are no unrestricted streams, issue an error.
var hasPlayableStreams = shaka.util.MapUtils.values(streamSetsByType)
.some(function(streamSet) {
return streamSet.streams.some(function(stream) {
return stream.allowedByApplication && stream.allowedByKeySystem;
});
});
if (!hasPlayableStreams) {
var manifestIsPlayable =
shaka.util.MapUtils.values(streamSetsByType).some(hasPlayableStreams);
if (!manifestIsPlayable) {
this.onError_(new shaka.util.Error(
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.ALL_STREAMS_RESTRICTED));
Expand Down
20 changes: 19 additions & 1 deletion test/drm_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ describe('DrmEngine', function() {
}).catch(fail).then(done);
});

it('detects content type capabilities of key system', function(done) {
requestMediaKeySystemAccessSpy.and.callFake(
fakeRequestMediaKeySystemAccess.bind(null, ['drm.abc']));

drmEngine.init(manifest, /* offline */ false).then(function() {
expect(drmEngine.initialized()).toBe(true);
expect(drmEngine.getSupportedTypes()).toEqual([
'audio/webm', 'video/mp4; codecs="fake"'
]);
}).catch(fail).then(done);
});

it('tries the second key system if the first fails', function(done) {
// Accept drm.def, but not drm.abc.
requestMediaKeySystemAccessSpy.and.callFake(
Expand Down Expand Up @@ -1286,9 +1298,15 @@ describe('DrmEngine', function() {
function createMockMediaKeySystemAccess() {
var mksa = {
keySystem: '',
getConfiguration: function() {},
getConfiguration: jasmine.createSpy('getConfiguration'),
createMediaKeys: jasmine.createSpy('createMediaKeys')
};
mksa.getConfiguration.and.callFake(function() {
return {
audioCapabilities: [{contentType: 'audio/webm'}],
videoCapabilities: [{contentType: 'video/mp4; codecs="fake"'}]
};
});
mksa.createMediaKeys.and.callFake(function() {
return Promise.resolve(mockMediaKeys);
});
Expand Down
8 changes: 8 additions & 0 deletions test/player_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,7 @@ describe('Player', function() {
.addStream(8).size(1500, 200)
.addStream(9).size(900, 900)
.addStream(10).size(100, 100).bandwidth(10)
.addStream(11).bandwidth(200).mime('video/webm')
.build();

var factory = shaka.test.FakeManifestParser.createFactory(manifest);
Expand Down Expand Up @@ -867,6 +868,13 @@ describe('Player', function() {
expectDoesNotInclude(tracks, 4);
});

it('removes if key system does not support codec', function() {
// Should already be removed from filterPeriod_
var tracks = player.getTracks();
expect(tracks.length).toBe(9);
expectDoesNotInclude(tracks, 11);
});

it('removes based on bandwidth', function() {
player.configure(
{restrictions: {minVideoBandwidth: 100, maxVideoBandwidth: 1000}});
Expand Down
10 changes: 7 additions & 3 deletions test/util/simple_fakes.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,18 @@ shaka.test.FakeAbrManager.prototype.chooseStreams = function(
shaka.test.FakeDrmEngine = function() {
var resolve = Promise.resolve.bind(Promise);

var ret = jasmine.createSpyObj(
'FakeDrmEngine',
['destroy', 'configure', 'init', 'attach', 'initialized', 'keySystem']);
var ret = jasmine.createSpyObj('FakeDrmEngine', [
'destroy', 'configure', 'init', 'attach', 'initialized', 'keySystem',
'getSupportedTypes'
]);
ret.destroy.and.callFake(resolve);
ret.init.and.callFake(resolve);
ret.attach.and.callFake(resolve);
ret.initialized.and.returnValue(true);
ret.keySystem.and.returnValue('com.example.fake');
// See shaka.test.ManifestGenerator.protototype.createStream.
ret.getSupportedTypes.and.returnValue(
['video/mp4; codecs="avc1.4d401f"']);
return ret;
};

Expand Down

0 comments on commit 50a43ff

Please sign in to comment.