Home Reference Source

src/loader/key-loader.ts

  1. /*
  2. * Decrypt key Loader
  3. */
  4. import { ErrorTypes, ErrorDetails } from '../errors';
  5. import { logger } from '../utils/logger';
  6. import {
  7. LoaderStats,
  8. LoaderResponse,
  9. LoaderConfiguration,
  10. LoaderCallbacks,
  11. Loader,
  12. KeyLoaderContext,
  13. } from '../types/loader';
  14. import { LoadError } from './fragment-loader';
  15. import type { HlsConfig } from '../hls';
  16. import type { Fragment } from '../loader/fragment';
  17. import type { ComponentAPI } from '../types/component-api';
  18. import type { KeyLoadedData } from '../types/events';
  19.  
  20. export default class KeyLoader implements ComponentAPI {
  21. private readonly config: HlsConfig;
  22. public loader: Loader<KeyLoaderContext> | null = null;
  23. public decryptkey: Uint8Array | null = null;
  24. public decrypturl: string | null = null;
  25.  
  26. constructor(config: HlsConfig) {
  27. this.config = config;
  28. }
  29.  
  30. abort(): void {
  31. this.loader?.abort();
  32. }
  33.  
  34. destroy(): void {
  35. if (this.loader) {
  36. this.loader.destroy();
  37. this.loader = null;
  38. }
  39. }
  40.  
  41. load(frag: Fragment): Promise<KeyLoadedData | void> | never {
  42. const type = frag.type;
  43. const loader = this.loader;
  44. if (!frag.decryptdata) {
  45. throw new Error('Missing decryption data on fragment in onKeyLoading');
  46. }
  47.  
  48. // Load the key if the uri is different from previous one, or if the decrypt key has not yet been retrieved
  49. const uri = frag.decryptdata.uri;
  50. if (uri !== this.decrypturl || this.decryptkey === null) {
  51. const config = this.config;
  52. if (loader) {
  53. logger.warn(`abort previous key loader for type:${type}`);
  54. loader.abort();
  55. }
  56. if (!uri) {
  57. throw new Error('key uri is falsy');
  58. }
  59. const Loader = config.loader;
  60. const keyLoader =
  61. (frag.keyLoader =
  62. this.loader =
  63. new Loader(config) as Loader<KeyLoaderContext>);
  64. this.decrypturl = uri;
  65. this.decryptkey = null;
  66.  
  67. return new Promise((resolve, reject) => {
  68. const loaderContext: KeyLoaderContext = {
  69. url: uri,
  70. frag: frag,
  71. part: null,
  72. responseType: 'arraybuffer',
  73. };
  74.  
  75. // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times,
  76. // key-loader will trigger an error and rely on stream-controller to handle retry logic.
  77. // this will also align retry logic with fragment-loader
  78. const loaderConfig: LoaderConfiguration = {
  79. timeout: config.fragLoadingTimeOut,
  80. maxRetry: 0,
  81. retryDelay: config.fragLoadingRetryDelay,
  82. maxRetryDelay: config.fragLoadingMaxRetryTimeout,
  83. highWaterMark: 0,
  84. };
  85.  
  86. const loaderCallbacks: LoaderCallbacks<KeyLoaderContext> = {
  87. onSuccess: (
  88. response: LoaderResponse,
  89. stats: LoaderStats,
  90. context: KeyLoaderContext,
  91. networkDetails: any
  92. ) => {
  93. const frag = context.frag;
  94. if (!frag.decryptdata) {
  95. logger.error('after key load, decryptdata unset');
  96. return reject(
  97. new LoadError({
  98. type: ErrorTypes.NETWORK_ERROR,
  99. details: ErrorDetails.KEY_LOAD_ERROR,
  100. fatal: false,
  101. frag,
  102. networkDetails,
  103. })
  104. );
  105. }
  106. this.decryptkey = frag.decryptdata.key = new Uint8Array(
  107. response.data as ArrayBuffer
  108. );
  109.  
  110. // detach fragment key loader on load success
  111. frag.keyLoader = null;
  112. this.loader = null;
  113. resolve({ frag });
  114. },
  115.  
  116. onError: (
  117. error: { code: number; text: string },
  118. context: KeyLoaderContext,
  119. networkDetails: any
  120. ) => {
  121. this.resetLoader(context.frag, keyLoader);
  122. reject(
  123. new LoadError({
  124. type: ErrorTypes.NETWORK_ERROR,
  125. details: ErrorDetails.KEY_LOAD_ERROR,
  126. fatal: false,
  127. frag,
  128. networkDetails,
  129. })
  130. );
  131. },
  132.  
  133. onTimeout: (
  134. stats: LoaderStats,
  135. context: KeyLoaderContext,
  136. networkDetails: any
  137. ) => {
  138. this.resetLoader(context.frag, keyLoader);
  139. reject(
  140. new LoadError({
  141. type: ErrorTypes.NETWORK_ERROR,
  142. details: ErrorDetails.KEY_LOAD_TIMEOUT,
  143. fatal: false,
  144. frag,
  145. networkDetails,
  146. })
  147. );
  148. },
  149.  
  150. onAbort: (
  151. stats: LoaderStats,
  152. context: KeyLoaderContext,
  153. networkDetails: any
  154. ) => {
  155. this.resetLoader(context.frag, keyLoader);
  156. reject(
  157. new LoadError({
  158. type: ErrorTypes.NETWORK_ERROR,
  159. details: ErrorDetails.INTERNAL_ABORTED,
  160. fatal: false,
  161. frag,
  162. networkDetails,
  163. })
  164. );
  165. },
  166. };
  167.  
  168. keyLoader.load(loaderContext, loaderConfig, loaderCallbacks);
  169. });
  170. } else if (this.decryptkey) {
  171. // Return the key if it's already been loaded
  172. frag.decryptdata.key = this.decryptkey;
  173. return Promise.resolve({ frag });
  174. }
  175. return Promise.resolve();
  176. }
  177.  
  178. private resetLoader(frag: Fragment, loader: Loader<KeyLoaderContext>) {
  179. if (this.loader === loader) {
  180. this.loader = null;
  181. }
  182. loader.destroy();
  183. }
  184. }