Skip to content

Commit

Permalink
fix(HLS): Fix SAMPLE-AES playback (#6402)
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad committed Apr 11, 2024
1 parent 73117f0 commit af88a32
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 11 deletions.
9 changes: 9 additions & 0 deletions demo/common/assets.js
Expand Up @@ -384,6 +384,15 @@ shakaAssets.testAssets = [
.addFeature(shakaAssets.Feature.SURROUND)
.addFeature(shakaAssets.Feature.OFFLINE)
.addLicenseServer('com.widevine.alpha', 'https://cwip-shaka-proxy.appspot.com/no_auth'),
new ShakaDemoAssetInfo(
/* name= */ 'Angel One (HLS, MP4, SAMPLE-AES-CTR, multi-key)',
/* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/angel_one.png',
/* manifestUri= */ 'https://storage.googleapis.com/shaka-demo-assets/angel-one-sample-aes-ctr-multiple-key/manifest.m3u8',
/* source= */ shakaAssets.Source.SHAKA)
.addKeySystem(shakaAssets.KeySystem.CLEAR_KEY)
.addFeature(shakaAssets.Feature.HLS)
.addFeature(shakaAssets.Feature.MP4)
.addFeature(shakaAssets.Feature.OFFLINE),
new ShakaDemoAssetInfo(
/* name= */ 'Sintel (HLS, TS, AES-128 key rotation)',
/* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/sintel.png',
Expand Down
27 changes: 17 additions & 10 deletions lib/hls/hls_parser.js
Expand Up @@ -4293,16 +4293,23 @@ shaka.hls.HlsParser = class {
const keyUris = shaka.hls.Utils.constructSegmentUris(
getUris(), drmTag.getRequiredAttrValue('URI'), variables);

const keyMapKey = keyUris.sort().join('');
if (!this.identityKeyMap_.has(keyMapKey)) {
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
const request = shaka.net.NetworkingEngine.makeRequest(
keyUris, this.config_.retryParameters);
const keyResponse = this.makeNetworkRequest_(request, requestType);
this.identityKeyMap_.set(keyMapKey, keyResponse);
}
const keyResponse = await this.identityKeyMap_.get(keyMapKey);
const key = shaka.util.Uint8ArrayUtils.toHex(keyResponse.data);
let key;
if (keyUris[0].startsWith('data:text/plain;base64,')) {
key = shaka.util.Uint8ArrayUtils.toHex(
shaka.util.Uint8ArrayUtils.fromBase64(
keyUris[0].split('data:text/plain;base64,').pop()));
} else {
const keyMapKey = keyUris.sort().join('');
if (!this.identityKeyMap_.has(keyMapKey)) {
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
const request = shaka.net.NetworkingEngine.makeRequest(
keyUris, this.config_.retryParameters);
const keyResponse = this.makeNetworkRequest_(request, requestType);
this.identityKeyMap_.set(keyMapKey, keyResponse);
}
const keyResponse = await this.identityKeyMap_.get(keyMapKey);
key = shaka.util.Uint8ArrayUtils.toHex(keyResponse.data);
}

// NOTE: The ClearKey CDM requires a key-id to key mapping. HLS doesn't
// provide a key ID anywhere. So although we could use the 'URI' attribute
Expand Down
25 changes: 24 additions & 1 deletion lib/media/drm_engine.js
Expand Up @@ -2314,9 +2314,17 @@ shaka.media.DrmEngine = class {
shaka.util.BufferUtils.equal(a.initData, b.initData);
};

const clearkeyDataStart = 'data:application/json;base64,';
const clearKeyLicenseServers = [];

for (const drmInfo of drmInfos) {
// Build an array of unique license servers.
if (!licenseServers.includes(drmInfo.licenseServerUri)) {
if (drmInfo.keySystem == 'org.w3.clearkey' &&
drmInfo.licenseServerUri.startsWith(clearkeyDataStart)) {
if (!clearKeyLicenseServers.includes(drmInfo.licenseServerUri)) {
clearKeyLicenseServers.push(drmInfo.licenseServerUri);
}
} else if (!licenseServers.includes(drmInfo.licenseServerUri)) {
licenseServers.push(drmInfo.licenseServerUri);
}

Expand Down Expand Up @@ -2353,6 +2361,21 @@ shaka.media.DrmEngine = class {
}
}
}

if (clearKeyLicenseServers.length == 1) {
licenseServers.push(clearKeyLicenseServers[0]);
} else if (clearKeyLicenseServers.length > 0) {
const keys = [];
for (const clearKeyLicenseServer of clearKeyLicenseServers) {
const license = window.atob(
clearKeyLicenseServer.split(clearkeyDataStart).pop());
const jwkSet = /** @type {{keys: !Array}} */(JSON.parse(license));
keys.push(...jwkSet.keys);
}
const newJwkSet = {keys: keys};
const newLicense = JSON.stringify(newJwkSet);
licenseServers.push(clearkeyDataStart + window.btoa(newLicense));
}
}

/**
Expand Down
45 changes: 45 additions & 0 deletions test/hls/hls_parser_unit.js
Expand Up @@ -3732,6 +3732,51 @@ describe('HlsParser', () => {
expect(newDrmInfoSpy).toHaveBeenCalled();
});

it('constructs DrmInfo for ClearKey with raw key', async () => {
const master = [
'#EXTM3U\n',
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
'RESOLUTION=960x540,FRAME-RATE=60\n',
'video\n',
].join('');

const media = [
'#EXTM3U\n',
'#EXT-X-TARGETDURATION:6\n',
'#EXT-X-PLAYLIST-TYPE:VOD\n',
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
'KEYFORMAT="identity",',
'URI="data:text/plain;base64,Pj6hFgt5iFZtfBLN6oq8Eg==",\n',
'#EXT-X-MAP:URI="init.mp4"\n',
'#EXTINF:5,\n',
'main.mp4',
].join('');

const initDataBase64 = 'eyJraWRzIjpbIkFBQUFBQUFBQUFBQUFBQUFBQUFBQUEiXX0=';
const keyId = '00000000000000000000000000000000';

const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
manifest.addPartialVariant((variant) => {
variant.addPartialStream(ContentType.VIDEO, (stream) => {
stream.encrypted = true;
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
drmInfo.licenseServerUri = 'data:application/json;base64,eyJrZXl' +
'zIjpbeyJrdHkiOiJvY3QiLCJraWQiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFB' +
'IiwiayI6IlBqNmhGZ3Q1aUZadGZCTE42b3E4RWcifV19';
drmInfo.keyIds.add(keyId);
drmInfo.addKeyIdsData(initDataBase64);
});
});
});
manifest.sequenceMode = sequenceMode;
manifest.type = shaka.media.ManifestParser.HLS;
});

await testHlsParser(master, media, manifest);
expect(newDrmInfoSpy).toHaveBeenCalled();
});

describe('constructs DrmInfo with EXT-X-SESSION-KEY', () => {
it('for Widevine', async () => {
const initDataBase64 =
Expand Down

0 comments on commit af88a32

Please sign in to comment.