Home Reference Source

src/loader/key-loader.ts

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