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 shaka-project#1391

Change-Id: Ieb401a1e4dfbcc958f7a14fa96df546237b0f446
  • Loading branch information
michellezhuogg committed Mar 23, 2021
1 parent ea20df0 commit acfa1a8
Show file tree
Hide file tree
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
Expand Up @@ -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_);
}

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

/**
Expand Down
7 changes: 6 additions & 1 deletion lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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()!');

Expand Down Expand Up @@ -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);
Expand Down
69 changes: 45 additions & 24 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');


/**
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand All @@ -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',
};
Expand Down Expand Up @@ -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);
}
}


Expand Down
32 changes: 0 additions & 32 deletions test/hls/hls_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions test/media/drm_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -149,6 +155,7 @@ describe('DrmEngine', () => {

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

Expand Down Expand Up @@ -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(''));
Expand All @@ -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: '',
Expand Down

0 comments on commit acfa1a8

Please sign in to comment.