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 { ComponentAPI } 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 ComponentAPI {
  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. private _registerListeners() {
  38. this.hls.on(Events.KEY_LOADING, this.onKeyLoading, this);
  39. }
  40.  
  41. private _unregisterListeners() {
  42. this.hls.off(Events.KEY_LOADING, this.onKeyLoading);
  43. }
  44.  
  45. destroy(): void {
  46. this._unregisterListeners();
  47. for (const loaderName in this.loaders) {
  48. const loader = this.loaders[loaderName];
  49. if (loader) {
  50. loader.destroy();
  51. }
  52. }
  53. this.loaders = {};
  54. }
  55.  
  56. onKeyLoading(event: Events.KEY_LOADING, data: KeyLoadingData) {
  57. const { frag } = data;
  58. const type = frag.type;
  59. const loader = this.loaders[type];
  60. if (!frag.decryptdata) {
  61. logger.warn('Missing decryption data on fragment in onKeyLoading');
  62. return;
  63. }
  64.  
  65. // Load the key if the uri is different from previous one, or if the decrypt key has not yet been retrieved
  66. const uri = frag.decryptdata.uri;
  67. if (uri !== this.decrypturl || this.decryptkey === null) {
  68. const config = this.hls.config;
  69. if (loader) {
  70. logger.warn(`abort previous key loader for type:${type}`);
  71. loader.abort();
  72. }
  73. if (!uri) {
  74. logger.warn('key uri is falsy');
  75. return;
  76. }
  77. const Loader = config.loader;
  78. const fragLoader =
  79. (frag.loader =
  80. this.loaders[type] =
  81. new Loader(config) as Loader<FragmentLoaderContext>);
  82. this.decrypturl = uri;
  83. this.decryptkey = null;
  84.  
  85. const loaderContext: KeyLoaderContext = {
  86. url: uri,
  87. frag: frag,
  88. responseType: 'arraybuffer',
  89. };
  90.  
  91. // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times,
  92. // key-loader will trigger an error and rely on stream-controller to handle retry logic.
  93. // this will also align retry logic with fragment-loader
  94. const loaderConfig: LoaderConfiguration = {
  95. timeout: config.fragLoadingTimeOut,
  96. maxRetry: 0,
  97. retryDelay: config.fragLoadingRetryDelay,
  98. maxRetryDelay: config.fragLoadingMaxRetryTimeout,
  99. highWaterMark: 0,
  100. };
  101.  
  102. const loaderCallbacks: LoaderCallbacks<KeyLoaderContext> = {
  103. onSuccess: this.loadsuccess.bind(this),
  104. onError: this.loaderror.bind(this),
  105. onTimeout: this.loadtimeout.bind(this),
  106. };
  107.  
  108. fragLoader.load(loaderContext, loaderConfig, loaderCallbacks);
  109. } else if (this.decryptkey) {
  110. // Return the key if it's already been loaded
  111. frag.decryptdata.key = this.decryptkey;
  112. this.hls.trigger(Events.KEY_LOADED, { frag: frag });
  113. }
  114. }
  115.  
  116. loadsuccess(
  117. response: LoaderResponse,
  118. stats: LoaderStats,
  119. context: KeyLoaderContext
  120. ) {
  121. const frag = context.frag;
  122. if (!frag.decryptdata) {
  123. logger.error('after key load, decryptdata unset');
  124. return;
  125. }
  126. this.decryptkey = frag.decryptdata.key = new Uint8Array(
  127. response.data as ArrayBuffer
  128. );
  129.  
  130. // detach fragment loader on load success
  131. frag.loader = null;
  132. delete this.loaders[frag.type];
  133. this.hls.trigger(Events.KEY_LOADED, { frag: frag });
  134. }
  135.  
  136. loaderror(response: LoaderResponse, context: KeyLoaderContext) {
  137. const frag = context.frag;
  138. const loader = frag.loader;
  139. if (loader) {
  140. loader.abort();
  141. }
  142.  
  143. delete this.loaders[frag.type];
  144. this.hls.trigger(Events.ERROR, {
  145. type: ErrorTypes.NETWORK_ERROR,
  146. details: ErrorDetails.KEY_LOAD_ERROR,
  147. fatal: false,
  148. frag,
  149. response,
  150. });
  151. }
  152.  
  153. loadtimeout(stats: LoaderStats, context: KeyLoaderContext) {
  154. const frag = context.frag;
  155. const loader = frag.loader;
  156. if (loader) {
  157. loader.abort();
  158. }
  159.  
  160. delete this.loaders[frag.type];
  161. this.hls.trigger(Events.ERROR, {
  162. type: ErrorTypes.NETWORK_ERROR,
  163. details: ErrorDetails.KEY_LOAD_TIMEOUT,
  164. fatal: false,
  165. frag,
  166. });
  167. }
  168. }