src/loader/key-loader.ts
- /*
- * Decrypt key Loader
- */
- import { Events } from '../events';
- import { ErrorTypes, ErrorDetails } from '../errors';
- import { logger } from '../utils/logger';
- import type Hls from '../hls';
- import { Fragment } from './fragment';
- import {
- LoaderStats,
- LoaderResponse,
- LoaderContext,
- LoaderConfiguration,
- LoaderCallbacks,
- Loader,
- FragmentLoaderContext,
- } from '../types/loader';
- import type { ComponentAPI } from '../types/component-api';
- import type { KeyLoadingData } from '../types/events';
-
- interface KeyLoaderContext extends LoaderContext {
- frag: Fragment;
- }
-
- export default class KeyLoader implements ComponentAPI {
- private hls: Hls;
- public loaders = {};
- public decryptkey: Uint8Array | null = null;
- public decrypturl: string | null = null;
-
- constructor(hls: Hls) {
- this.hls = hls;
-
- this._registerListeners();
- }
-
- private _registerListeners() {
- this.hls.on(Events.KEY_LOADING, this.onKeyLoading, this);
- }
-
- private _unregisterListeners() {
- this.hls.off(Events.KEY_LOADING, this.onKeyLoading);
- }
-
- destroy(): void {
- this._unregisterListeners();
- for (const loaderName in this.loaders) {
- const loader = this.loaders[loaderName];
- if (loader) {
- loader.destroy();
- }
- }
- this.loaders = {};
- }
-
- onKeyLoading(event: Events.KEY_LOADING, data: KeyLoadingData) {
- const { frag } = data;
- const type = frag.type;
- const loader = this.loaders[type];
- if (!frag.decryptdata) {
- logger.warn('Missing decryption data on fragment in onKeyLoading');
- return;
- }
-
- // Load the key if the uri is different from previous one, or if the decrypt key has not yet been retrieved
- const uri = frag.decryptdata.uri;
- if (uri !== this.decrypturl || this.decryptkey === null) {
- const config = this.hls.config;
- if (loader) {
- logger.warn(`abort previous key loader for type:${type}`);
- loader.abort();
- }
- if (!uri) {
- logger.warn('key uri is falsy');
- return;
- }
- const Loader = config.loader;
- const fragLoader = (frag.loader = this.loaders[type] = new Loader(
- config
- ) as Loader<FragmentLoaderContext>);
- this.decrypturl = uri;
- this.decryptkey = null;
-
- const loaderContext: KeyLoaderContext = {
- url: uri,
- frag: frag,
- responseType: 'arraybuffer',
- };
-
- // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times,
- // key-loader will trigger an error and rely on stream-controller to handle retry logic.
- // this will also align retry logic with fragment-loader
- const loaderConfig: LoaderConfiguration = {
- timeout: config.fragLoadingTimeOut,
- maxRetry: 0,
- retryDelay: config.fragLoadingRetryDelay,
- maxRetryDelay: config.fragLoadingMaxRetryTimeout,
- highWaterMark: 0,
- };
-
- const loaderCallbacks: LoaderCallbacks<KeyLoaderContext> = {
- onSuccess: this.loadsuccess.bind(this),
- onError: this.loaderror.bind(this),
- onTimeout: this.loadtimeout.bind(this),
- };
-
- fragLoader.load(loaderContext, loaderConfig, loaderCallbacks);
- } else if (this.decryptkey) {
- // Return the key if it's already been loaded
- frag.decryptdata.key = this.decryptkey;
- this.hls.trigger(Events.KEY_LOADED, { frag: frag });
- }
- }
-
- loadsuccess(
- response: LoaderResponse,
- stats: LoaderStats,
- context: KeyLoaderContext
- ) {
- const frag = context.frag;
- if (!frag.decryptdata) {
- logger.error('after key load, decryptdata unset');
- return;
- }
- this.decryptkey = frag.decryptdata.key = new Uint8Array(
- response.data as ArrayBuffer
- );
-
- // detach fragment loader on load success
- frag.loader = null;
- delete this.loaders[frag.type];
- this.hls.trigger(Events.KEY_LOADED, { frag: frag });
- }
-
- loaderror(response: LoaderResponse, context: KeyLoaderContext) {
- const frag = context.frag;
- const loader = frag.loader;
- if (loader) {
- loader.abort();
- }
-
- delete this.loaders[frag.type];
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.KEY_LOAD_ERROR,
- fatal: false,
- frag,
- response,
- });
- }
-
- loadtimeout(stats: LoaderStats, context: KeyLoaderContext) {
- const frag = context.frag;
- const loader = frag.loader;
- if (loader) {
- loader.abort();
- }
-
- delete this.loaders[frag.type];
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.KEY_LOAD_TIMEOUT,
- fatal: false,
- frag,
- });
- }
- }