Skip to content

Commit

Permalink
feat: Add support for WisePlay DRM (#7854)
Browse files Browse the repository at this point in the history
  • Loading branch information
vlazh and avelad authored Jan 9, 2025
1 parent b153a9c commit 7ef0f92
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 31 deletions.
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ HLS features supported:
- MPEG-2 TS support
- WebVTT and TTML
- CEA-608/708 captions
- Encrypted content with PlayReady and Widevine
- Encrypted content with PlayReady, Widevine and WisePlay
- Encrypted content with FairPlay (Safari on macOS and iOS 9+ only)
- AES-128, AES-256 and AES-256-CTR support on browsers with Web Crypto API support
- SAMPLE-AES and SAMPLE-AES-CTR (identity) support on browsers with ClearKey support
Expand Down Expand Up @@ -227,21 +227,22 @@ MSS features **not** supported:

## DRM support matrix

|Browser |Widevine |PlayReady|FairPlay |ClearKey⁶ |
|:------------:|:--------:|:-------:|:-------:|:--------:|
|Chrome¹ |**Y** | - | - |**Y** |
|Firefox² |**Y** | - | - |**Y** |
|Edge³ | - |**Y** | - | - |
|Edge Chromium |**Y** |**Y** | - |**Y** |
|Safari | - | - |**Y** | - |
|Opera |**Y** | - | - |**Y** |
|Chromecast |**Y** |**Y** | - |**Y** |
|Tizen TV |**Y** |**Y** | - |**Y** |
|WebOS⁷ |untested⁷ |untested⁷| - |untested⁷ |
|Hisense⁷ |untested⁷ |untested⁷| - |untested⁷ |
|Xbox One | - |**Y** | - | - |
|Playstation 4⁷| - |untested⁷| - |untested⁷ |
|Playstation 5⁷| - |untested⁷| - |untested⁷ |
|Browser |Widevine |PlayReady|FairPlay |WisePlay |ClearKey⁶ |
|:------------:|:--------:|:-------:|:-------:|:-------:|:--------:|
|Chrome¹ |**Y** | - | - | - |**Y** |
|Firefox² |**Y** | - | - | - |**Y** |
|Edge³ | - |**Y** | - | - | - |
|Edge Chromium |**Y** |**Y** | - | - |**Y** |
|Safari | - | - |**Y** | - | - |
|Opera |**Y** | - | - | - |**Y** |
|Chromecast |**Y** |**Y** | - | - |**Y** |
|Tizen TV |**Y** |**Y** | - | - |**Y** |
|WebOS⁷ |untested⁷ |untested⁷| - | - |untested⁷ |
|Hisense⁷ |untested⁷ |untested⁷| - | - |untested⁷ |
|Xbox One | - |**Y** | - | - | - |
|Playstation 4⁷| - |untested⁷| - | - |untested⁷ |
|Playstation 5⁷| - |untested⁷| - | - |untested⁷ |
|Huawei⁷ | - | - | - |untested⁷|untested⁷ |

Other DRM systems should work out of the box if they are interoperable and
compliant to the EME spec.
Expand All @@ -257,11 +258,11 @@ NOTES:
- ⁷: These are expected to work, but are community-supported and untested by
us.

|Manifest |Widevine |PlayReady|FairPlay |ClearKey |
|:--------:|:--------:|:-------:|:-------:|:--------:|
|DASH |**Y** |**Y** | - |**Y** |
|HLS |**Y** |**Y** |**Y** ¹ | - |
|MSS | - |**Y** | - | - |
|Manifest |Widevine |PlayReady|FairPlay |WisePlay |ClearKey |
|:--------:|:--------:|:-------:|:-------:|:-------:|:--------:|
|DASH |**Y** |**Y** |**Y** |**Y** |**Y** |
|HLS |**Y** |**Y** |**Y** ¹ |**Y** |**Y** |
|MSS | - |**Y** | - | - | - |

NOTES:
- ¹: By default, FairPlay is handled using Apple's native HLS player, when on
Expand Down
1 change: 1 addition & 0 deletions lib/drm/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,7 @@ shaka.drm.DrmEngine = class {
'com.chromecast.playready',
'com.apple.fps.1_0',
'com.apple.fps',
'com.huawei.wiseplay',
];

if (!shaka.drm.DrmUtils.isBrowserSupported()) {
Expand Down
43 changes: 43 additions & 0 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4900,6 +4900,47 @@ shaka.hls.HlsParser = class {
return drmInfo;
}

/**
* @param {!shaka.hls.Tag} drmTag
* @return {?shaka.extern.DrmInfo}
* @private
*/
static wiseplayDrmParser_(drmTag) {
const method = drmTag.getRequiredAttrValue('METHOD');
const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR'];
if (!VALID_METHODS.includes(method)) {
shaka.log.error('WisePlay in HLS is only supported with [',
VALID_METHODS.join(', '), '], not', method);
return null;
}

let encryptionScheme = 'cenc';
if (method == 'SAMPLE-AES') {
encryptionScheme = 'cbcs';
}

const uri = drmTag.getRequiredAttrValue('URI');
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri.split('?')[0]);

// The data encoded in the URI is a PSSH box to be used as init data.
const pssh = shaka.util.BufferUtils.toUint8(parsedData.data);
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
'com.huawei.wiseplay', encryptionScheme, [
{initDataType: 'cenc', initData: pssh},
]);

const keyId = drmTag.getAttributeValue('KEYID');
if (keyId) {
const keyIdLowerCase = keyId.toLowerCase();
// This value should begin with '0x':
goog.asserts.assert(
keyIdLowerCase.startsWith('0x'), 'Incorrect KEYID format!');
// But the output should not contain the '0x':
drmInfo.keyIds = new Set([keyIdLowerCase.substr(2)]);
}
return drmInfo;
}

/**
* See: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-5.1
*
Expand Down Expand Up @@ -5197,6 +5238,8 @@ shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
shaka.hls.HlsParser.widevineDrmParser_,
'com.microsoft.playready':
shaka.hls.HlsParser.playreadyDrmParser_,
'urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c':
shaka.hls.HlsParser.wiseplayDrmParser_,
};


Expand Down
3 changes: 2 additions & 1 deletion lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -1993,6 +1993,7 @@ shaka.offline.Storage.defaultSystemIds_ = new Map()
.set('com.microsoft.playready.software',
'9a04f07998404286ab92e65be0885f95')
.set('com.microsoft.playready.hardware',
'9a04f07998404286ab92e65be0885f95');
'9a04f07998404286ab92e65be0885f95')
.set('com.huawei.wiseplay', '3d5e6d359b9a41e8b843dd3c6e72c42c');

shaka.Player.registerSupportPlugin('offline', shaka.offline.Storage.support);
2 changes: 2 additions & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ shaka.util.PlayerConfiguration = class {
'com.microsoft.playready',
'urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2':
'com.apple.fps',
'urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c':
'com.huawei.wiseplay',
},
manifestPreprocessor:
shaka.util.PlayerConfiguration.defaultManifestPreprocessor,
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"prepublishOnly": "python build/checkversion.py && python build/all.py --force"
},
"dependencies": {
"eme-encryption-scheme-polyfill": "^2.1.6"
"eme-encryption-scheme-polyfill": "^2.2.0"
},
"engines": {
"node": ">=18"
Expand Down
1 change: 1 addition & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ Verimatrix
Vidaa
Vnova
Widevine
wiseplay
Zenterio

# streaming
Expand Down
1 change: 1 addition & 0 deletions test/dash/dash_parser_content_protection_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ describe('DashParser ContentProtection', () => {
buildDrmInfo('com.microsoft.playready', keyIds),
buildDrmInfo('com.microsoft.playready', keyIds),
buildDrmInfo('com.apple.fps', keyIds),
buildDrmInfo('com.huawei.wiseplay', keyIds),
], variantKeyIds);
await testDashParser(source, expected, /* ignoreDrmInfo= */ true);
});
Expand Down
99 changes: 99 additions & 0 deletions test/hls/hls_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3761,6 +3761,54 @@ describe('HlsParser', () => {
expect(newDrmInfoSpy).toHaveBeenCalled();
});

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

const initDataBase64 =
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';

const keyId = 'abc123';

const media = [
'#EXTM3U\n',
'#EXT-X-TARGETDURATION:6\n',
'#EXT-X-PLAYLIST-TYPE:VOD\n',
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
'KEYID=0X' + keyId + ',',
'KEYFORMAT="urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c",',
'URI="data:text/plain;base64,',
initDataBase64, '",\n',
'#EXT-X-MAP:URI="init.mp4"\n',
'#EXTINF:5,\n',
'#EXT-X-BYTERANGE:121090@616\n',
'main.mp4',
].join('');

const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
manifest.addPartialVariant((variant) => {
variant.addPartialStream(ContentType.VIDEO, (stream) => {
stream.encrypted = true;
stream.addDrmInfo('com.huawei.wiseplay', (drmInfo) => {
drmInfo.addCencInitData(initDataBase64);
drmInfo.keyIds.add(keyId);
drmInfo.encryptionScheme = 'cenc';
});
});
});
manifest.sequenceMode = sequenceMode;
manifest.type = shaka.media.ManifestParser.HLS;
});

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

it('constructs DrmInfo for PlayReady', async () => {
const master = [
'#EXTM3U\n',
Expand Down Expand Up @@ -4039,6 +4087,57 @@ describe('HlsParser', () => {
expect(actual).toEqual(manifest);
});

it('for WisePlay', async () => {
const initDataBase64 =
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';

const keyId = 'abc123';

const master = [
'#EXTM3U\n',
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
'RESOLUTION=960x540,FRAME-RATE=30\n',
'video\n',
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
'RESOLUTION=960x540,FRAME-RATE=60\n',
'video2\n',
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
'KEYID=0X' + keyId + ',',
'KEYFORMAT="urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c",',
'URI="data:text/plain;base64,',
initDataBase64, '",\n',
].join('');

const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
manifest.addPartialVariant((variant) => {
variant.addPartialStream(ContentType.VIDEO, (stream) => {
stream.addDrmInfo('com.huawei.wiseplay', (drmInfo) => {
drmInfo.addCencInitData(initDataBase64);
drmInfo.keyIds.add(keyId);
drmInfo.encryptionScheme = 'cenc';
});
});
});
manifest.addPartialVariant((variant) => {
variant.addPartialStream(ContentType.VIDEO, (stream) => {
stream.addDrmInfo('com.huawei.wiseplay', (drmInfo) => {
drmInfo.addCencInitData(initDataBase64);
drmInfo.keyIds.add(keyId);
drmInfo.encryptionScheme = 'cenc';
});
});
});
manifest.sequenceMode = sequenceMode;
manifest.type = shaka.media.ManifestParser.HLS;
});

fakeNetEngine.setResponseText('test:/master', master);

const actual = await parser.start('test:/master', playerInterface);
expect(actual).toEqual(manifest);
});

it('for PlayReady', async () => {
const initDataBase64 =
'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=';
Expand Down

0 comments on commit 7ef0f92

Please sign in to comment.