mediakeys-helper.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import { optionalSelf } from './global';
  2. import { changeEndianness } from './keysystem-util';
  3. import { base64Decode } from './numeric-encoding-utils';
  4. import type { DRMSystemOptions, EMEControllerConfig } from '../config';
  5. /**
  6. * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess
  7. */
  8. export const enum KeySystems {
  9. CLEARKEY = 'org.w3.clearkey',
  10. FAIRPLAY = 'com.apple.fps',
  11. PLAYREADY = 'com.microsoft.playready',
  12. WIDEVINE = 'com.widevine.alpha',
  13. }
  14. // Playlist #EXT-X-KEY KEYFORMAT values
  15. export const enum KeySystemFormats {
  16. CLEARKEY = 'org.w3.clearkey',
  17. FAIRPLAY = 'com.apple.streamingkeydelivery',
  18. PLAYREADY = 'com.microsoft.playready',
  19. WIDEVINE = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
  20. }
  21. export function keySystemFormatToKeySystemDomain(
  22. format: KeySystemFormats,
  23. ): KeySystems | undefined {
  24. switch (format) {
  25. case KeySystemFormats.FAIRPLAY:
  26. return KeySystems.FAIRPLAY;
  27. case KeySystemFormats.PLAYREADY:
  28. return KeySystems.PLAYREADY;
  29. case KeySystemFormats.WIDEVINE:
  30. return KeySystems.WIDEVINE;
  31. case KeySystemFormats.CLEARKEY:
  32. return KeySystems.CLEARKEY;
  33. }
  34. }
  35. // System IDs for which we can extract a key ID from "encrypted" event PSSH
  36. export const enum KeySystemIds {
  37. CENC = '1077efecc0b24d02ace33c1e52e2fb4b',
  38. CLEARKEY = 'e2719d58a985b3c9781ab030af78d30e',
  39. FAIRPLAY = '94ce86fb07ff4f43adb893d2fa968ca2',
  40. PLAYREADY = '9a04f07998404286ab92e65be0885f95',
  41. WIDEVINE = 'edef8ba979d64acea3c827dcd51d21ed',
  42. }
  43. export function keySystemIdToKeySystemDomain(
  44. systemId: KeySystemIds,
  45. ): KeySystems | undefined {
  46. if (systemId === KeySystemIds.WIDEVINE) {
  47. return KeySystems.WIDEVINE;
  48. } else if (systemId === KeySystemIds.PLAYREADY) {
  49. return KeySystems.PLAYREADY;
  50. } else if (
  51. systemId === KeySystemIds.CENC ||
  52. systemId === KeySystemIds.CLEARKEY
  53. ) {
  54. return KeySystems.CLEARKEY;
  55. }
  56. }
  57. export function keySystemDomainToKeySystemFormat(
  58. keySystem: KeySystems,
  59. ): KeySystemFormats | undefined {
  60. switch (keySystem) {
  61. case KeySystems.FAIRPLAY:
  62. return KeySystemFormats.FAIRPLAY;
  63. case KeySystems.PLAYREADY:
  64. return KeySystemFormats.PLAYREADY;
  65. case KeySystems.WIDEVINE:
  66. return KeySystemFormats.WIDEVINE;
  67. case KeySystems.CLEARKEY:
  68. return KeySystemFormats.CLEARKEY;
  69. }
  70. }
  71. export function getKeySystemsForConfig(
  72. config: EMEControllerConfig,
  73. ): KeySystems[] {
  74. const { drmSystems, widevineLicenseUrl } = config;
  75. const keySystemsToAttempt: KeySystems[] = drmSystems
  76. ? [
  77. KeySystems.FAIRPLAY,
  78. KeySystems.WIDEVINE,
  79. KeySystems.PLAYREADY,
  80. KeySystems.CLEARKEY,
  81. ].filter((keySystem) => !!drmSystems[keySystem])
  82. : [];
  83. if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) {
  84. keySystemsToAttempt.push(KeySystems.WIDEVINE);
  85. }
  86. return keySystemsToAttempt;
  87. }
  88. export type MediaKeyFunc = (
  89. keySystem: KeySystems,
  90. supportedConfigurations: MediaKeySystemConfiguration[],
  91. ) => Promise<MediaKeySystemAccess>;
  92. export const requestMediaKeySystemAccess = (function (): MediaKeyFunc | null {
  93. if (optionalSelf?.navigator?.requestMediaKeySystemAccess) {
  94. return self.navigator.requestMediaKeySystemAccess.bind(self.navigator);
  95. } else {
  96. return null;
  97. }
  98. })();
  99. /**
  100. * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaKeySystemConfiguration
  101. */
  102. export function getSupportedMediaKeySystemConfigurations(
  103. keySystem: KeySystems,
  104. audioCodecs: string[],
  105. videoCodecs: string[],
  106. drmSystemOptions: DRMSystemOptions,
  107. ): MediaKeySystemConfiguration[] {
  108. let initDataTypes: string[];
  109. switch (keySystem) {
  110. case KeySystems.FAIRPLAY:
  111. initDataTypes = ['cenc', 'sinf'];
  112. break;
  113. case KeySystems.WIDEVINE:
  114. case KeySystems.PLAYREADY:
  115. initDataTypes = ['cenc'];
  116. break;
  117. case KeySystems.CLEARKEY:
  118. initDataTypes = ['cenc', 'keyids'];
  119. break;
  120. default:
  121. throw new Error(`Unknown key-system: ${keySystem}`);
  122. }
  123. return createMediaKeySystemConfigurations(
  124. initDataTypes,
  125. audioCodecs,
  126. videoCodecs,
  127. drmSystemOptions,
  128. );
  129. }
  130. function createMediaKeySystemConfigurations(
  131. initDataTypes: string[],
  132. audioCodecs: string[],
  133. videoCodecs: string[],
  134. drmSystemOptions: DRMSystemOptions,
  135. ): MediaKeySystemConfiguration[] {
  136. const baseConfig: MediaKeySystemConfiguration = {
  137. initDataTypes: initDataTypes,
  138. persistentState: drmSystemOptions.persistentState || 'optional',
  139. distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'optional',
  140. sessionTypes: drmSystemOptions.sessionTypes || [
  141. drmSystemOptions.sessionType || 'temporary',
  142. ],
  143. audioCapabilities: audioCodecs.map((codec) => ({
  144. contentType: `audio/mp4; codecs=${codec}`,
  145. robustness: drmSystemOptions.audioRobustness || '',
  146. encryptionScheme: drmSystemOptions.audioEncryptionScheme || null,
  147. })),
  148. videoCapabilities: videoCodecs.map((codec) => ({
  149. contentType: `video/mp4; codecs=${codec}`,
  150. robustness: drmSystemOptions.videoRobustness || '',
  151. encryptionScheme: drmSystemOptions.videoEncryptionScheme || null,
  152. })),
  153. };
  154. return [baseConfig];
  155. }
  156. export function isPersistentSessionType(
  157. drmSystemOptions: DRMSystemOptions,
  158. ): boolean {
  159. return (
  160. drmSystemOptions.sessionType === 'persistent-license' ||
  161. !!drmSystemOptions.sessionTypes?.some(
  162. (type) => type === 'persistent-license',
  163. )
  164. );
  165. }
  166. export function parsePlayReadyWRM(keyBytes: Uint8Array<ArrayBuffer>) {
  167. const keyBytesUtf16 = new Uint16Array(
  168. keyBytes.buffer,
  169. keyBytes.byteOffset,
  170. keyBytes.byteLength / 2,
  171. );
  172. const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
  173. // Parse Playready WRMHeader XML
  174. const xmlKeyBytes = keyByteStr.substring(
  175. keyByteStr.indexOf('<'),
  176. keyByteStr.length,
  177. );
  178. const parser = new DOMParser();
  179. const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
  180. const keyData = xmlDoc.getElementsByTagName('KID')[0];
  181. if (keyData) {
  182. const keyId = keyData.childNodes[0]
  183. ? keyData.childNodes[0].nodeValue
  184. : keyData.getAttribute('VALUE');
  185. if (keyId) {
  186. const keyIdArray = base64Decode(keyId).subarray(0, 16);
  187. // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
  188. // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
  189. changeEndianness(keyIdArray);
  190. return keyIdArray;
  191. }
  192. }
  193. return null;
  194. }