Skip to content

Commit 7ef0f92

Browse files
vlazhavelad
andauthored
feat: Add support for WisePlay DRM (#7854)
Some info: https://drmnow.pro/wiseplay/ https://developer.huawei.com/consumer/en/doc/Media-Guides/client-dev-0000001050040000 --------- Co-authored-by: Álvaro Velad Galván <[email protected]>
1 parent b153a9c commit 7ef0f92

10 files changed

+180
-31
lines changed

README.md

+22-21
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ HLS features supported:
134134
- MPEG-2 TS support
135135
- WebVTT and TTML
136136
- CEA-608/708 captions
137-
- Encrypted content with PlayReady and Widevine
137+
- Encrypted content with PlayReady, Widevine and WisePlay
138138
- Encrypted content with FairPlay (Safari on macOS and iOS 9+ only)
139139
- AES-128, AES-256 and AES-256-CTR support on browsers with Web Crypto API support
140140
- SAMPLE-AES and SAMPLE-AES-CTR (identity) support on browsers with ClearKey support
@@ -227,21 +227,22 @@ MSS features **not** supported:
227227

228228
## DRM support matrix
229229

230-
|Browser |Widevine |PlayReady|FairPlay |ClearKey⁶ |
231-
|:------------:|:--------:|:-------:|:-------:|:--------:|
232-
|Chrome¹ |**Y** | - | - |**Y** |
233-
|Firefox² |**Y** | - | - |**Y** |
234-
|Edge³ | - |**Y** | - | - |
235-
|Edge Chromium |**Y** |**Y** | - |**Y** |
236-
|Safari | - | - |**Y** | - |
237-
|Opera |**Y** | - | - |**Y** |
238-
|Chromecast |**Y** |**Y** | - |**Y** |
239-
|Tizen TV |**Y** |**Y** | - |**Y** |
240-
|WebOS⁷ |untested⁷ |untested⁷| - |untested⁷ |
241-
|Hisense⁷ |untested⁷ |untested⁷| - |untested⁷ |
242-
|Xbox One | - |**Y** | - | - |
243-
|Playstation 4⁷| - |untested⁷| - |untested⁷ |
244-
|Playstation 5⁷| - |untested⁷| - |untested⁷ |
230+
|Browser |Widevine |PlayReady|FairPlay |WisePlay |ClearKey⁶ |
231+
|:------------:|:--------:|:-------:|:-------:|:-------:|:--------:|
232+
|Chrome¹ |**Y** | - | - | - |**Y** |
233+
|Firefox² |**Y** | - | - | - |**Y** |
234+
|Edge³ | - |**Y** | - | - | - |
235+
|Edge Chromium |**Y** |**Y** | - | - |**Y** |
236+
|Safari | - | - |**Y** | - | - |
237+
|Opera |**Y** | - | - | - |**Y** |
238+
|Chromecast |**Y** |**Y** | - | - |**Y** |
239+
|Tizen TV |**Y** |**Y** | - | - |**Y** |
240+
|WebOS⁷ |untested⁷ |untested⁷| - | - |untested⁷ |
241+
|Hisense⁷ |untested⁷ |untested⁷| - | - |untested⁷ |
242+
|Xbox One | - |**Y** | - | - | - |
243+
|Playstation 4⁷| - |untested⁷| - | - |untested⁷ |
244+
|Playstation 5⁷| - |untested⁷| - | - |untested⁷ |
245+
|Huawei⁷ | - | - | - |untested⁷|untested⁷ |
245246

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

260-
|Manifest |Widevine |PlayReady|FairPlay |ClearKey |
261-
|:--------:|:--------:|:-------:|:-------:|:--------:|
262-
|DASH |**Y** |**Y** | - |**Y** |
263-
|HLS |**Y** |**Y** |**Y** ¹ | - |
264-
|MSS | - |**Y** | - | - |
261+
|Manifest |Widevine |PlayReady|FairPlay |WisePlay |ClearKey |
262+
|:--------:|:--------:|:-------:|:-------:|:-------:|:--------:|
263+
|DASH |**Y** |**Y** |**Y** |**Y** |**Y** |
264+
|HLS |**Y** |**Y** |**Y** ¹ |**Y** |**Y** |
265+
|MSS | - |**Y** | - | - | - |
265266

266267
NOTES:
267268
- ¹: By default, FairPlay is handled using Apple's native HLS player, when on

lib/drm/drm_engine.js

+1
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,7 @@ shaka.drm.DrmEngine = class {
18191819
'com.chromecast.playready',
18201820
'com.apple.fps.1_0',
18211821
'com.apple.fps',
1822+
'com.huawei.wiseplay',
18221823
];
18231824

18241825
if (!shaka.drm.DrmUtils.isBrowserSupported()) {

lib/hls/hls_parser.js

+43
Original file line numberDiff line numberDiff line change
@@ -4900,6 +4900,47 @@ shaka.hls.HlsParser = class {
49004900
return drmInfo;
49014901
}
49024902

4903+
/**
4904+
* @param {!shaka.hls.Tag} drmTag
4905+
* @return {?shaka.extern.DrmInfo}
4906+
* @private
4907+
*/
4908+
static wiseplayDrmParser_(drmTag) {
4909+
const method = drmTag.getRequiredAttrValue('METHOD');
4910+
const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR'];
4911+
if (!VALID_METHODS.includes(method)) {
4912+
shaka.log.error('WisePlay in HLS is only supported with [',
4913+
VALID_METHODS.join(', '), '], not', method);
4914+
return null;
4915+
}
4916+
4917+
let encryptionScheme = 'cenc';
4918+
if (method == 'SAMPLE-AES') {
4919+
encryptionScheme = 'cbcs';
4920+
}
4921+
4922+
const uri = drmTag.getRequiredAttrValue('URI');
4923+
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri.split('?')[0]);
4924+
4925+
// The data encoded in the URI is a PSSH box to be used as init data.
4926+
const pssh = shaka.util.BufferUtils.toUint8(parsedData.data);
4927+
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
4928+
'com.huawei.wiseplay', encryptionScheme, [
4929+
{initDataType: 'cenc', initData: pssh},
4930+
]);
4931+
4932+
const keyId = drmTag.getAttributeValue('KEYID');
4933+
if (keyId) {
4934+
const keyIdLowerCase = keyId.toLowerCase();
4935+
// This value should begin with '0x':
4936+
goog.asserts.assert(
4937+
keyIdLowerCase.startsWith('0x'), 'Incorrect KEYID format!');
4938+
// But the output should not contain the '0x':
4939+
drmInfo.keyIds = new Set([keyIdLowerCase.substr(2)]);
4940+
}
4941+
return drmInfo;
4942+
}
4943+
49034944
/**
49044945
* See: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-5.1
49054946
*
@@ -5197,6 +5238,8 @@ shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
51975238
shaka.hls.HlsParser.widevineDrmParser_,
51985239
'com.microsoft.playready':
51995240
shaka.hls.HlsParser.playreadyDrmParser_,
5241+
'urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c':
5242+
shaka.hls.HlsParser.wiseplayDrmParser_,
52005243
};
52015244

52025245

lib/offline/storage.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,7 @@ shaka.offline.Storage.defaultSystemIds_ = new Map()
19931993
.set('com.microsoft.playready.software',
19941994
'9a04f07998404286ab92e65be0885f95')
19951995
.set('com.microsoft.playready.hardware',
1996-
'9a04f07998404286ab92e65be0885f95');
1996+
'9a04f07998404286ab92e65be0885f95')
1997+
.set('com.huawei.wiseplay', '3d5e6d359b9a41e8b843dd3c6e72c42c');
19971998

19981999
shaka.Player.registerSupportPlugin('offline', shaka.offline.Storage.support);

lib/util/player_configuration.js

+2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ shaka.util.PlayerConfiguration = class {
149149
'com.microsoft.playready',
150150
'urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2':
151151
'com.apple.fps',
152+
'urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c':
153+
'com.huawei.wiseplay',
152154
},
153155
manifestPreprocessor:
154156
shaka.util.PlayerConfiguration.defaultManifestPreprocessor,

package-lock.json

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
"prepublishOnly": "python build/checkversion.py && python build/all.py --force"
107107
},
108108
"dependencies": {
109-
"eme-encryption-scheme-polyfill": "^2.1.6"
109+
"eme-encryption-scheme-polyfill": "^2.2.0"
110110
},
111111
"engines": {
112112
"node": ">=18"

project-words.txt

+1
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ Verimatrix
194194
Vidaa
195195
Vnova
196196
Widevine
197+
wiseplay
197198
Zenterio
198199

199200
# streaming

test/dash/dash_parser_content_protection_unit.js

+1
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ describe('DashParser ContentProtection', () => {
496496
buildDrmInfo('com.microsoft.playready', keyIds),
497497
buildDrmInfo('com.microsoft.playready', keyIds),
498498
buildDrmInfo('com.apple.fps', keyIds),
499+
buildDrmInfo('com.huawei.wiseplay', keyIds),
499500
], variantKeyIds);
500501
await testDashParser(source, expected, /* ignoreDrmInfo= */ true);
501502
});

test/hls/hls_parser_unit.js

+99
Original file line numberDiff line numberDiff line change
@@ -3761,6 +3761,54 @@ describe('HlsParser', () => {
37613761
expect(newDrmInfoSpy).toHaveBeenCalled();
37623762
});
37633763

3764+
it('constructs DrmInfo for WisePlay', async () => {
3765+
const master = [
3766+
'#EXTM3U\n',
3767+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
3768+
'RESOLUTION=960x540,FRAME-RATE=60\n',
3769+
'video\n',
3770+
].join('');
3771+
3772+
const initDataBase64 =
3773+
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';
3774+
3775+
const keyId = 'abc123';
3776+
3777+
const media = [
3778+
'#EXTM3U\n',
3779+
'#EXT-X-TARGETDURATION:6\n',
3780+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
3781+
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
3782+
'KEYID=0X' + keyId + ',',
3783+
'KEYFORMAT="urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c",',
3784+
'URI="data:text/plain;base64,',
3785+
initDataBase64, '",\n',
3786+
'#EXT-X-MAP:URI="init.mp4"\n',
3787+
'#EXTINF:5,\n',
3788+
'#EXT-X-BYTERANGE:121090@616\n',
3789+
'main.mp4',
3790+
].join('');
3791+
3792+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3793+
manifest.anyTimeline();
3794+
manifest.addPartialVariant((variant) => {
3795+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
3796+
stream.encrypted = true;
3797+
stream.addDrmInfo('com.huawei.wiseplay', (drmInfo) => {
3798+
drmInfo.addCencInitData(initDataBase64);
3799+
drmInfo.keyIds.add(keyId);
3800+
drmInfo.encryptionScheme = 'cenc';
3801+
});
3802+
});
3803+
});
3804+
manifest.sequenceMode = sequenceMode;
3805+
manifest.type = shaka.media.ManifestParser.HLS;
3806+
});
3807+
3808+
await testHlsParser(master, media, manifest);
3809+
expect(newDrmInfoSpy).toHaveBeenCalled();
3810+
});
3811+
37643812
it('constructs DrmInfo for PlayReady', async () => {
37653813
const master = [
37663814
'#EXTM3U\n',
@@ -4039,6 +4087,57 @@ describe('HlsParser', () => {
40394087
expect(actual).toEqual(manifest);
40404088
});
40414089

4090+
it('for WisePlay', async () => {
4091+
const initDataBase64 =
4092+
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';
4093+
4094+
const keyId = 'abc123';
4095+
4096+
const master = [
4097+
'#EXTM3U\n',
4098+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
4099+
'RESOLUTION=960x540,FRAME-RATE=30\n',
4100+
'video\n',
4101+
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
4102+
'RESOLUTION=960x540,FRAME-RATE=60\n',
4103+
'video2\n',
4104+
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
4105+
'KEYID=0X' + keyId + ',',
4106+
'KEYFORMAT="urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c",',
4107+
'URI="data:text/plain;base64,',
4108+
initDataBase64, '",\n',
4109+
].join('');
4110+
4111+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
4112+
manifest.anyTimeline();
4113+
manifest.addPartialVariant((variant) => {
4114+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
4115+
stream.addDrmInfo('com.huawei.wiseplay', (drmInfo) => {
4116+
drmInfo.addCencInitData(initDataBase64);
4117+
drmInfo.keyIds.add(keyId);
4118+
drmInfo.encryptionScheme = 'cenc';
4119+
});
4120+
});
4121+
});
4122+
manifest.addPartialVariant((variant) => {
4123+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
4124+
stream.addDrmInfo('com.huawei.wiseplay', (drmInfo) => {
4125+
drmInfo.addCencInitData(initDataBase64);
4126+
drmInfo.keyIds.add(keyId);
4127+
drmInfo.encryptionScheme = 'cenc';
4128+
});
4129+
});
4130+
});
4131+
manifest.sequenceMode = sequenceMode;
4132+
manifest.type = shaka.media.ManifestParser.HLS;
4133+
});
4134+
4135+
fakeNetEngine.setResponseText('test:/master', master);
4136+
4137+
const actual = await parser.start('test:/master', playerInterface);
4138+
expect(actual).toEqual(manifest);
4139+
});
4140+
40424141
it('for PlayReady', async () => {
40434142
const initDataBase64 =
40444143
'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=';

0 commit comments

Comments
 (0)