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

live stream can switch from clear content to encrypted content #1094 #1217

Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Rostislav Hejduk <[email protected]>
Sam Dutton <[email protected]>
Sanborn Hilland <[email protected]>
Sandra Lokshina <[email protected]>
Semih Gokceoglu <[email protected]>
Seth Madison <[email protected]>
Theodore Abshire <[email protected]>
Thomas Stephens <[email protected]>
Expand Down
51 changes: 51 additions & 0 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ shaka.media.DrmEngine.prototype.getDrmInfo = function() {
shaka.media.DrmEngine.prototype.prepareMediaKeyConfigs_ =
function(manifest, offline, configsByKeySystem, keySystemsInOrder) {
var clearKeyDrmInfo = this.configureClearKey_();
var configDrmInfos = this.getDrmInfosByConfig_(manifest);

manifest.periods.forEach(function(period) {
period.variants.forEach(function(variant) {
Expand All @@ -474,6 +475,14 @@ shaka.media.DrmEngine.prototype.prepareMediaKeyConfigs_ =
variant.drmInfos = [clearKeyDrmInfo];
}

// If initial manifest contains unencrypted content,
// drm configuration overrides DrmInfo so drmEngine can be activated.
// Thus, player can play encrypted content if live stream switches from
// unencrypted content to encrypted content during live stream.
if (!!configDrmInfos) {
Copy link
Contributor Author

@SemihGk SemihGk Jan 6, 2018

Choose a reason for hiding this comment

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

Only if manifest contains clear content, configDrmInfos won't be null. The drawback is that adds drmInfos to each variant although those do not break a playback. I considered to update configsByKeySystem directly instead of modifying manifest, but this way would cause a duplicated code issue. Also, keys are changed for chromecast in this code block. So, I preferred more simple way to add this fix.

variant.drmInfos = configDrmInfos;
Copy link
Contributor Author

@SemihGk SemihGk Jan 6, 2018

Choose a reason for hiding this comment

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

This drmInfos is going to be generated with default drmInfos. When stream switches to encrypted content, encrypted content might have a different configured drmInfos. At this situation, I do not know if there is any risk that having different drm info settings may break the playback. As for my tests in our live streams, I could not reproduce any issue.

}

variant.drmInfos.forEach(function(drmInfo) {
this.fillInDrmInfoDefaults_(drmInfo);

Expand Down Expand Up @@ -774,6 +783,48 @@ shaka.media.DrmEngine.prototype.configureClearKey_ = function() {
};


/**
* Returns the DrmInfo that is generated by drm configation.
* It activates DrmEngine if drm configs have keySystems.
* @param {!shakaExtern.Manifest} manifest
* @return {(Array.<{object}>|null)}
Copy link
Member

Choose a reason for hiding this comment

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

This should be: * @return {Array.<shakaExtern.DrmInfo>}

I think <{object}> is bad syntax. I think the {} in that context look like a record type. And you don't need the alternation with null (|null) because Arrays are nullable by default. (To make it non-nullable, add a ! to the front.) Finally, the more specific type shakaExtern.DrmInfo should be used, because Object is too loose.

* @private
*/
shaka.media.DrmEngine.prototype.getDrmInfosByConfig_ =
function(manifest) {
Copy link
Member

Choose a reason for hiding this comment

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

style nit: It looks like this should fit on the previous line

var config = this.config_;
if (!config) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@joeyparrish suggested me a way that adding a propery to drm config may be a better way to understand this expectation. I did not add this property in this PR just in case you may be interested the "reinitialization drm" solution. If you do not, I will update this PR with a new config property that allows this method.

Copy link
Member

Choose a reason for hiding this comment

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

You shouldn't need to deal with a null config_, since that should only be true before init() and after destroy().

return null;
}

var serverKeys = Object.keys(config.servers || {});
Copy link
Member

Choose a reason for hiding this comment

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

You don't need to worry about null config.servers, either, since Player maintains a complete config object at all times.


if (!serverKeys.length) {
return null;
}

var isEncryptedContent = manifest.periods.every(function(period) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think every() is what we want here. If any period has encrypted variants, the content is encrypted. This should be some().

period.variants.every(function(variant) {
Copy link
Member

Choose a reason for hiding this comment

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

Same here. I believe this should be some(), as well. Also, you forgot a return here, so the outermost every() function has no return value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for reviewing, @joeyparrish . Last min I reversed this behavior from unencrypted to encrypted. I should have noticed this mistake. Apologies. On the other hand, it seems @TheModMaker 's suggestion more simple than mine, but it throws an error. I was going to trace that error, but I was interrupted by something else and I could not spend enough time. Do you think it is not necessary to chase his solution? Thanks again.

Copy link
Member

Choose a reason for hiding this comment

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

It is simpler, but I think we should keep ignoreDrmInfo separate. It serves a different purpose, and it isn't on by default. What you are changing here should, in my opinion, be default behavior to match developer expectations.

return !!variant.drmInfos.length;
Copy link
Member

Choose a reason for hiding this comment

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

No need to do !!, since length is a number which will implicitly convert to boolean.

});
});

// Only creates for clear contents
Copy link
Member

Choose a reason for hiding this comment

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

This comment seems a little bit vague. How about this one:

// We only want to create phony DrmInfos when none are provided by the manifest.

if (isEncryptedContent) {
return null;
}

return serverKeys.map(function(keySystem) {
return {
// ignoring other properties
Copy link
Member

Choose a reason for hiding this comment

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

With the recommended change to return Array.<shakaExtern.DrmInfo>, the compiler will require you to provide all fields here. Use the same values returned from the bottom of configureClearKey_().

// since fillInDrmInfoDefaults_ fills default values
keySystem: keySystem,
licenseServerUri: config.servers[keySystem]
};
});
};


/**
* Creates a DrmInfo object describing the settings used to initialize the
* engine.
Expand Down
25 changes: 24 additions & 1 deletion test/media/drm_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,18 +374,37 @@ describe('DrmEngine', function() {
}).then(done);
});

it('makes no queries for clear content', function(done) {
it('makes no queries for clear content if no key config', function(done) {
requestMediaKeySystemAccessSpy.and.callFake(
fakeRequestMediaKeySystemAccess.bind(null, []));
manifest.periods[0].variants[0].drmInfos = [];
config.servers = {};
config.advanced = {};

drmEngine.configure(config);
drmEngine.init(manifest, /* offline */ false).then(function() {
expect(drmEngine.initialized()).toBe(true);
expect(drmEngine.keySystem()).toBe('');
expect(requestMediaKeySystemAccessSpy.calls.count()).toBe(0);
}).catch(fail).then(done);
});

it('makes queries for clear content if key is configured', function(done) {
Copy link
Member

Choose a reason for hiding this comment

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

Awesome! Thank you for writing tests!

requestMediaKeySystemAccessSpy.and.callFake(
fakeRequestMediaKeySystemAccess.bind(null, ['drm.abc']));
manifest.periods[0].variants[0].drmInfos = [];
config.servers = {
'drm.abc': 'http://abc.drm/license'
};

drmEngine.configure(config);
drmEngine.init(manifest, /* offline */ false).then(function() {
expect(drmEngine.initialized()).toBe(true);
expect(drmEngine.keySystem()).toBe('drm.abc');
expect(requestMediaKeySystemAccessSpy.calls.count()).toBe(1);
}).then(done);
});

it('uses advanced config to override DrmInfo fields', function(done) {
// Leave only one drmInfo
manifest = new shaka.test.ManifestGenerator()
Expand Down Expand Up @@ -508,6 +527,8 @@ describe('DrmEngine', function() {
requestMediaKeySystemAccessSpy.and.callFake(
fakeRequestMediaKeySystemAccess.bind(null, []));
manifest.periods[0].variants[0].drmInfos = [];
config.servers = {};
config.advanced = {};

initAndAttach().then(function() {
expect(mockVideo.setMediaKeys).not.toHaveBeenCalled();
Expand Down Expand Up @@ -775,6 +796,8 @@ describe('DrmEngine', function() {

it('dispatches an error if manifest says unencrypted', function(done) {
manifest.periods[0].variants[0].drmInfos = [];
config.servers = {};
config.advanced = {};

onErrorSpy.and.stub();

Expand Down