@@ -7,7 +7,7 @@ import { Events } from '../events';
77import { ErrorTypes , ErrorDetails } from '../errors' ;
88
99import { logger } from '../utils/logger' ;
10- import { DRMSystemOptions , EMEControllerConfig } from '../config' ;
10+ import { DRMSystemOptions , EMEControllerConfig , KeyidValue } from '../config' ;
1111import { KeySystems , MediaKeyFunc } from '../utils/mediakeys-helper' ;
1212import Hls from '../hls' ;
1313import { ComponentAPI } from '../types/component-api' ;
@@ -56,6 +56,36 @@ const createWidevineMediaKeySystemConfigurations = function (
5656 ] ;
5757} ;
5858
59+ const createClearkeyMediaKeySystemConfigurations = function (
60+ audioCodecs : string [ ] ,
61+ videoCodecs : string [ ] ,
62+ drmSystemOptions : DRMSystemOptions
63+ ) : MediaKeySystemConfiguration [ ] { /* jshint ignore:line */
64+ const baseConfig : MediaKeySystemConfiguration = {
65+ initDataTypes : [ 'keyids' , 'mp4' ] ,
66+ // label: "",
67+ // persistentState: "not-allowed", // or "required" ?
68+ // distinctiveIdentifier: "not-allowed", // or "required" ?
69+ // sessionTypes: ['temporary'],
70+ audioCapabilities : [ ] , // { contentType: 'audio/mp4; codecs="mp4a.40.2"' }
71+ videoCapabilities : [ ] // { contentType: 'video/mp4; codecs="avc1.42E01E"' }
72+ } ;
73+
74+ audioCodecs . forEach ( ( codec ) => {
75+ logger . log ( codec ) ;
76+ } ) ;
77+
78+ videoCodecs . forEach ( ( codec ) => {
79+ // logger.log(codec);
80+ baseConfig . videoCapabilities ! . push ( {
81+ contentType : `video/mp4; codecs="${ codec } "`
82+ } ) ;
83+ } ) ;
84+ return [
85+ baseConfig
86+ ] ;
87+ } ;
88+
5989/**
6090 * The idea here is to handle key-system (and their respective platforms) specific configuration differences
6191 * in order to work with the local requestMediaKeySystemAccess method.
@@ -75,8 +105,10 @@ const getSupportedMediaKeySystemConfigurations = function (
75105 drmSystemOptions : DRMSystemOptions
76106) : MediaKeySystemConfiguration [ ] {
77107 switch ( keySystem ) {
78- case KeySystems . WIDEVINE :
79- return createWidevineMediaKeySystemConfigurations ( audioCodecs , videoCodecs , drmSystemOptions ) ;
108+ case KeySystems . WIDEVINE :
109+ return createWidevineMediaKeySystemConfigurations ( audioCodecs , videoCodecs , drmSystemOptions ) ;
110+ case KeySystems . CLEARKEY :
111+ return createClearkeyMediaKeySystemConfigurations ( audioCodecs , videoCodecs , drmSystemOptions ) ;
80112 default :
81113 throw new Error ( `Unknown key-system: ${ keySystem } ` ) ;
82114 }
@@ -102,6 +134,8 @@ class EMEController implements ComponentAPI {
102134 private _widevineLicenseUrl ?: string ;
103135 private _licenseXhrSetup ?: ( xhr : XMLHttpRequest , url : string ) => void ;
104136 private _emeEnabled : boolean ;
137+ private _clearkeyServerUrl ?: string ;
138+ private _clearkeyPair : KeyidValue ;
105139 private _requestMediaKeySystemAccess : MediaKeyFunc | null ;
106140 private _drmSystemOptions : DRMSystemOptions ;
107141
@@ -124,6 +158,8 @@ class EMEController implements ComponentAPI {
124158 this . _widevineLicenseUrl = this . _config . widevineLicenseUrl ;
125159 this . _licenseXhrSetup = this . _config . licenseXhrSetup ;
126160 this . _emeEnabled = this . _config . emeEnabled ;
161+ this . _clearkeyServerUrl = this . _config . clearkeyServerUrl ;
162+ this . _clearkeyPair = this . _config . clearkeyPair ;
127163 this . _requestMediaKeySystemAccess = this . _config . requestMediaKeySystemAccessFunc ;
128164 this . _drmSystemOptions = this . _config . drmSystemOptions ;
129165
@@ -248,6 +284,89 @@ class EMEController implements ComponentAPI {
248284 } ) ;
249285 }
250286
287+ private _handleMessage ( keySession : MediaKeySession , message : ArrayBuffer ) {
288+ // If you had a license server, you would make an asynchronous XMLHttpRequest
289+ // with event.message as the body. The response from the server, as a
290+ // Uint8Array, would then be passed to session.update().
291+ // Instead, we will generate the license synchronously on the client, using
292+ // the hard-coded KEY.
293+ if ( this . _clearkeyPair == null ) {
294+ logger . error ( 'Failed to load the keys' ) ;
295+ }
296+
297+ let license = this . _generateLicense ( message ) ;
298+
299+ keySession . update ( license ) . catch (
300+ function ( error ) {
301+ logger . error ( 'Failed to update the session' , error ) ;
302+ }
303+ ) ;
304+ logger . log ( `Received license data (length: ${ license ? license . byteLength : license } ), updating key-session` ) ;
305+ }
306+
307+ private _generateLicense ( message ) {
308+ // Parse the clearkey license request.
309+ let request = JSON . parse ( new TextDecoder ( ) . decode ( message ) ) ;
310+ type responseFormat = {
311+ kty ?: string ,
312+ alg ?: string ,
313+ kid ?: string ,
314+ k ?: string
315+ }
316+
317+ let keyarray : responseFormat [ ] = [ ] ;
318+ for ( let id of request . kids ) {
319+ let decodedBase64 = this . base64ToHex ( id ) ;
320+ // logger.log(`decodedBase64: ${decodedBase64}`);
321+ if ( ! this . _clearkeyPair . hasOwnProperty ( decodedBase64 ) ) {
322+ logger . error ( 'No pair key, please use lower case' ) ;
323+ }
324+ keyarray . push (
325+ {
326+ kty : 'oct' ,
327+ alg : 'A128KW' ,
328+ kid : id ,
329+ k : this . hexToBase64 ( this . _clearkeyPair [ decodedBase64 ] )
330+ // k: "aeqoAqZ2Ovl56NGUD7iDkg"
331+ }
332+ ) ;
333+ }
334+
335+ logger . log ( JSON . stringify ( {
336+ keys : keyarray ,
337+ type : 'temporary'
338+ } ) ) ;
339+
340+ return new TextEncoder ( ) . encode ( JSON . stringify ( {
341+ keys : keyarray ,
342+ type : 'temporary'
343+ } ) ) ;
344+ }
345+
346+ private hexToBase64 ( hexstring ) {
347+ var encodedBase64 = btoa ( hexstring . match ( / \w { 2 } / g) . map ( function ( a ) {
348+ return String . fromCharCode ( parseInt ( a , 16 ) ) ;
349+ } ) . join ( '' ) ) ;
350+
351+ var start = 0 ;
352+ var end = encodedBase64 . length ;
353+ while ( end > start && encodedBase64 [ end - 1 ] === '=' ) {
354+ -- end ;
355+ }
356+ return ( start > 0 || end < encodedBase64 . length ) ? encodedBase64 . substring ( start , end ) : encodedBase64 ;
357+ }
358+
359+ private base64ToHex ( str ) {
360+ const raw = atob ( str ) ;
361+ logger . log ( raw ) ;
362+ let result = '' ;
363+ for ( let i = 0 ; i < raw . length ; i ++ ) {
364+ const hex = raw . charCodeAt ( i ) . toString ( 16 ) ;
365+ result += ( hex . length === 2 ? hex : '0' + hex ) ;
366+ }
367+ return result . toLowerCase ( ) ;
368+ }
369+
251370 /**
252371 * @private
253372 * @param {* } keySession
@@ -268,10 +387,14 @@ class EMEController implements ComponentAPI {
268387 private _onKeySessionMessage ( keySession : MediaKeySession , message : ArrayBuffer ) {
269388 logger . log ( 'Got EME message event, creating license request' ) ;
270389
271- this . _requestLicense ( message , ( data : ArrayBuffer ) => {
272- logger . log ( `Received license data (length: ${ data ? data . byteLength : data } ), updating key-session` ) ;
273- keySession . update ( data ) ;
274- } ) ;
390+ if ( this . _clearkeyPair && this . _clearkeyServerUrl === void 0 ) {
391+ this . _handleMessage ( keySession , message ) ;
392+ } else {
393+ this . _requestLicense ( message , ( data : ArrayBuffer ) => {
394+ logger . log ( `Received license data (length: ${ data ? data . byteLength : data } ), updating key-session` ) ;
395+ keySession . update ( data ) ;
396+ } ) ;
397+ }
275398 }
276399
277400 /**
@@ -590,7 +713,11 @@ class EMEController implements ComponentAPI {
590713 ( videoCodec : string | undefined ) : videoCodec is string => ! ! videoCodec
591714 ) ;
592715
593- this . _attemptKeySystemAccess ( KeySystems . WIDEVINE , audioCodecs , videoCodecs ) ;
716+ if ( this . _clearkeyPair || this . _clearkeyServerUrl ) {
717+ this . _attemptKeySystemAccess ( KeySystems . CLEARKEY , audioCodecs , videoCodecs ) ;
718+ } else {
719+ this . _attemptKeySystemAccess ( KeySystems . WIDEVINE , audioCodecs , videoCodecs ) ;
720+ }
594721 }
595722}
596723
0 commit comments