Skip to content

Commit

Permalink
feat(MediaCap): get decodingInfo results before queryMediaKeys
Browse files Browse the repository at this point in the history
Previously, we fill in the variants' drmInfos with the drm
configurations of |clearKeys|, |servers| and |advanced| during
initializing the DrmEngine, before we query the media keys.

Now we need to call |MediaCapabilities.decodingInfo()| to get the
mediaKeySystemAccess of the variants after the DrmEngine is
created and configured, and the drm values are filled in for the
variants, and before |DrmEngine.queryMediaKey_()|.

The steps would be:
0. StreamUtils.setDecodingInfo() should not be called before
   DrmEngine is initialized and configured.
1. Create and configure DrmEngine.
2. Fill in drm values for the variants with configurations.
3. Call StreamUtils.setDecodingInfo() to get the decodingInfo of
   the variants.
4. Use the decodingInfo results to set up the Drm mediaKeys in
   DrmEngine.
5. When StreamUtils.filterManifest() is called, we can reuse the
   decodingInfo results instead of calling decodingInfo again.

Previously we call filterManifest() when parsing the manifest, to
filter out the unsupported streams.
Now decodingInfo can tell us if a variant is supported and its
MediaKeys at once. When initializing the DrmEngine, we get the
decodingInfo results of the variants, and only use the supported
streams to set up the MediaKeys. After that, we filter the manifest
right after DrmEngine is initialized.
Thus, we can skip filterManifest() in manifest parsers.

Using decodingInfo to get media keys will be in the next CL.

Issue #1391

Change-Id: Ieb401a1e4dfbcc958f7a14fa96df546237b0f446
michellezhuogg committed Mar 23, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent ea20df0 commit acfa1a8
Showing 6 changed files with 83 additions and 61 deletions.
3 changes: 0 additions & 3 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
@@ -478,9 +478,6 @@ shaka.dash.DashParser = class {
// after period combining, while we still have a direct reference, so that
// any new streams will appear in the period combiner.
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);

goog.asserts.assert(this.manifest_, 'Manifest should exist by now!');
await this.playerInterface_.filter(this.manifest_);
}

/**
1 change: 0 additions & 1 deletion lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
@@ -536,7 +536,6 @@ shaka.hls.HlsParser = class {
minBufferTime: 0,
};
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
await this.playerInterface_.filter(this.manifest_);
}

/**
7 changes: 6 additions & 1 deletion lib/media/drm_engine.js
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ goog.require('shaka.util.MapUtils');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Platform');
goog.require('shaka.util.PublicPromise');
goog.require('shaka.util.StreamUtils');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Timer');
goog.require('shaka.util.Uint8ArrayUtils');
@@ -286,7 +287,7 @@ shaka.media.DrmEngine = class {
* @return {!Promise} Resolved if/when a key system has been chosen.
* @private
*/
init_(variants) {
async init_(variants) {
goog.asserts.assert(this.config_,
'DrmEngine configure() must be called before init()!');

@@ -341,6 +342,10 @@ shaka.media.DrmEngine = class {
}
}

// We should get the decodingInfo results for the variants after we filling
// in the drm infos, and before queryMediaKeys_().
await shaka.util.StreamUtils.getDecodingInfosForVariants(variants);

/** @type {!Map.<string, MediaKeySystemConfiguration>} */
const configsByKeySystem =
this.prepareMediaKeyConfigsForVariants_(variants);
69 changes: 45 additions & 24 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
@@ -8,14 +8,14 @@ goog.provide('shaka.util.StreamUtils');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.media.DrmEngine');
goog.require('shaka.text.TextEngine');
goog.require('shaka.util.Functional');
goog.require('shaka.util.LanguageUtils');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.MultiMap');
goog.require('shaka.util.Platform');
goog.requireType('shaka.media.DrmEngine');


/**
@@ -269,6 +269,12 @@ shaka.util.StreamUtils = class {
* @param {shaka.extern.Manifest} manifest
*/
static async filterManifest(drmEngine, currentVariant, manifest) {
// Once we use decodingInfo() with drmInfo of the variants to get media
// keys, the decodingInfo result can tell us whether the variant's DRM is
// supported by the platform. This way, filterManifestByDrm_() won't be
// needed.
// TODO: remove the first parameter 'drmEngine' and the function
// 'filterManifestByDrm_'.
shaka.util.StreamUtils.filterManifestByDrm_(drmEngine, manifest);
await shaka.util.StreamUtils.filterManifestByMediaCapabilities_(manifest);
shaka.util.StreamUtils.filterManifestByCurrentVariant(
@@ -310,23 +316,7 @@ shaka.util.StreamUtils = class {
goog.asserts.assert(navigator.mediaCapabilities,
'MediaCapabilities should be valid.');

const MediaCapabilities = navigator.mediaCapabilities;
const getVariantDecodingInfo = (async (variant) => {
/** @type {!MediaDecodingConfiguration} */
const decodingConfig =
shaka.util.StreamUtils.prepareDecodingConfiguration_(variant);
const result = await MediaCapabilities.decodingInfo(decodingConfig);
variant.decodingInfos.push(result);
});

const operations = [];
for (const variant of manifest.variants) {
if (!variant.decodingInfos.length) {
operations.push(getVariantDecodingInfo(variant));
}
}
await Promise.all(operations);

await shaka.util.StreamUtils.getDecodingInfosForVariants(manifest.variants);
manifest.variants = manifest.variants.filter((variant) => {
const supported = variant.decodingInfos.some((decodingInfo) => {
return decodingInfo.supported;
@@ -342,18 +332,43 @@ shaka.util.StreamUtils = class {


/**
* Generate a MediaDecodingConfiguration object for the variant, used for
* MediaCapabilities decodingInfo.
* Get the decodingInfo results of the variants via MediaCapabilities.
* This should be called after the DrmEngine is created and configured, and
* before DrmEngine sets the mediaKeys.
*
* @param {!Array.<shaka.extern.Variant>} variants
* @exportDoc
*/
static async getDecodingInfosForVariants(variants) {
const gotDecodingInfo = variants.some((variant) =>
variant.decodingInfos.length);
if (gotDecodingInfo) {
shaka.log.debug('Already got the variants\' decodingInfo.');
return;
}

const operations = [];
for (const variant of variants) {
operations.push(shaka.util.StreamUtils.getDecodingInfos_(variant));
}
await Promise.all(operations);
}


/**
* Generate a MediaDecodingConfiguration object to get the decodingInfo
* results for each variant.
* @param {!shaka.extern.Variant} variant
* @return {!MediaDecodingConfiguration}
* @private
*/
static prepareDecodingConfiguration_(variant) {
static async getDecodingInfos_(variant) {
const mediaCapabilities = navigator.mediaCapabilities;

const audio = variant.audio;
const video = variant.video;
const ContentType = shaka.util.ManifestParserUtils.ContentType;


/** @type {!MediaDecodingConfiguration} */
const mediaDecodingConfig = {
type: 'media-source',
};
@@ -384,7 +399,13 @@ shaka.util.StreamUtils = class {
};
}

return mediaDecodingConfig;
try {
const result = await mediaCapabilities.decodingInfo(mediaDecodingConfig);
variant.decodingInfos.push(result);
} catch (e) {
shaka.log.info('mediaCapabilities.decodingInfo() failed.',
JSON.stringify(mediaDecodingConfig), e);
}
}


32 changes: 0 additions & 32 deletions test/hls/hls_parser_unit.js
Original file line number Diff line number Diff line change
@@ -871,38 +871,6 @@ describe('HlsParser', () => {
await testHlsParser(master, media, manifest);
});

it('should call filter during parsing', async () => {
const master = [
'#EXTM3U\n',
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
'RESOLUTION=960x540,FRAME-RATE=60\n',
'video',
].join('');

const media = [
'#EXTM3U\n',
'#EXT-X-PLAYLIST-TYPE:VOD\n',
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
'#EXTINF:5,\n',
'#EXT-X-BYTERANGE:121090@616\n',
'main.mp4',
].join('');

fakeNetEngine
.setResponseText('test:/master', master)
.setResponseText('test:/audio', media)
.setResponseText('test:/video', media)
.setResponseValue('test:/init.mp4', initSegmentData)
.setResponseValue('test:/main.mp4', segmentData);

/** @type {!jasmine.Spy} */
const filter = jasmine.createSpy('filter');
playerInterface.filter = Util.spyFunc(filter);

await parser.start('test:/master', playerInterface);
expect(filter).toHaveBeenCalledTimes(1);
});

it('fetch the start time for one audio/video stream and reuse for the others',
async () => {
const SEGMENT = shaka.net.NetworkingEngine.RequestType.SEGMENT;
32 changes: 32 additions & 0 deletions test/media/drm_engine_unit.js
Original file line number Diff line number Diff line change
@@ -29,10 +29,13 @@ describe('DrmEngine', () => {
navigator.requestMediaKeySystemAccess;
const originalLogError = shaka.log.error;
const originalBatchTime = shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME;
const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;

/** @type {!jasmine.Spy} */
let requestMediaKeySystemAccessSpy;
/** @type {!jasmine.Spy} */
let decodingInfoSpy;
/** @type {!jasmine.Spy} */
let logErrorSpy;
/** @type {!jasmine.Spy} */
let onErrorSpy;
@@ -74,6 +77,9 @@ describe('DrmEngine', () => {
jasmine.createSpy('requestMediaKeySystemAccess');
navigator.requestMediaKeySystemAccess =
shaka.test.Util.spyFunc(requestMediaKeySystemAccessSpy);
decodingInfoSpy = jasmine.createSpy('decodingInfo');
navigator.mediaCapabilities.decodingInfo =
shaka.test.Util.spyFunc(decodingInfoSpy);

logErrorSpy = jasmine.createSpy('shaka.log.error');
shaka.log.error = shaka.test.Util.spyFunc(logErrorSpy);
@@ -149,6 +155,7 @@ describe('DrmEngine', () => {

navigator.requestMediaKeySystemAccess =
originalRequestMediaKeySystemAccess;
navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
shaka.log.error = originalLogError;
});

@@ -2145,6 +2152,10 @@ describe('DrmEngine', () => {
}

function setRequestMediaKeySystemAccessSpy(acceptableKeySystems) {
// TODO: Setting both the requestMediaKeySystemAccessSpy and decodingInfoSpy
// as a temporary solution. Only decodingInfoSpy is needed once we use
// decodingInfo API to get mediaKeySystemAccess.
setDecodingInfoSpy(acceptableKeySystems);
requestMediaKeySystemAccessSpy.and.callFake((keySystem) => {
if (!acceptableKeySystems.includes(keySystem)) {
return Promise.reject(new Error(''));
@@ -2154,6 +2165,27 @@ describe('DrmEngine', () => {
});
}

function setDecodingInfoSpy(acceptableKeySystems) {
decodingInfoSpy.and.callFake((config) => {
const keySystem = config && config.keySystemConfiguration ?
config.keySystemConfiguration.keySystem : null;
let res;
if (!config.keySystemConfiguration) {
// Unencrypted content, return supported decodingInfo.
res = {supported: true};
} else if (!acceptableKeySystems.includes(keySystem)) {
res = {supported: false};
} else {
mockMediaKeySystemAccess.keySystem = keySystem;
res = {
supported: true,
keySystemAccess: mockMediaKeySystemAccess,
};
}
return Promise.resolve(res);
});
}

function createMockMediaKeySystemAccess() {
const mksa = {
keySystem: '',

0 comments on commit acfa1a8

Please sign in to comment.