���� JFIF �� � ( %"1"%)+...383,7(-.-
![]() Server : Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.20 System : Linux st2.domain.com 3.10.0-1127.10.1.el7.x86_64 #1 SMP Wed Jun 3 14:28:03 UTC 2020 x86_64 User : apache ( 48) PHP Version : 7.4.20 Disable Function : NONE Directory : /var/www/html/netphim/layout/default/node_modules/hls.js/src/loader/ |
import { ErrorTypes, ErrorDetails } from '../errors'; import { Fragment } from './fragment'; import { Loader, LoaderConfiguration, FragmentLoaderContext, } from '../types/loader'; import { getLoaderConfigWithoutReties } from '../utils/error-helper'; import type { HlsConfig } from '../config'; import type { BaseSegment, Part } from './fragment'; import type { ErrorData, FragLoadedData, PartsLoadedData, } from '../types/events'; const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb export default class FragmentLoader { private readonly config: HlsConfig; private loader: Loader<FragmentLoaderContext> | null = null; private partLoadTimeout: number = -1; constructor(config: HlsConfig) { this.config = config; } destroy() { if (this.loader) { this.loader.destroy(); this.loader = null; } } abort() { if (this.loader) { // Abort the loader for current fragment. Only one may load at any given time this.loader.abort(); } } load( frag: Fragment, onProgress?: FragmentLoadProgressCallback ): Promise<FragLoadedData> { const url = frag.url; if (!url) { return Promise.reject( new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, error: new Error( `Fragment does not have a ${url ? 'part list' : 'url'}` ), networkDetails: null, }) ); } this.abort(); const config = this.config; const FragmentILoader = config.fLoader; const DefaultILoader = config.loader; return new Promise((resolve, reject) => { if (this.loader) { this.loader.destroy(); } if (frag.gap) { if (frag.tagList.some((tags) => tags[0] === 'GAP')) { reject(createGapLoadError(frag)); return; } else { // Reset temporary treatment as GAP tag frag.gap = false; } } const loader = (this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : (new DefaultILoader(config) as Loader<FragmentLoaderContext>)); const loaderContext = createLoaderContext(frag); const loadPolicy = getLoaderConfigWithoutReties( config.fragLoadPolicy.default ); const loaderConfig: LoaderConfiguration = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0, highWaterMark: frag.sn === 'initSegment' ? Infinity : MIN_CHUNK_SIZE, }; // Assign frag stats to the loader's stats reference frag.stats = loader.stats; loader.load(loaderContext, loaderConfig, { onSuccess: (response, stats, context, networkDetails) => { this.resetLoader(frag, loader); let payload = response.data as ArrayBuffer; if (context.resetIV && frag.decryptdata) { frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16)); payload = payload.slice(16); } resolve({ frag, part: null, payload, networkDetails, }); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(frag, loader); reject( new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, response: { url, data: undefined, ...response }, error: new Error(`HTTP Error ${response.code} ${response.text}`), networkDetails, stats, }) ); }, onAbort: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject( new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.INTERNAL_ABORTED, fatal: false, frag, error: new Error('Aborted'), networkDetails, stats, }) ); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject( new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag, error: new Error(`Timeout after ${loaderConfig.timeout}ms`), networkDetails, stats, }) ); }, onProgress: (stats, context, data, networkDetails) => { if (onProgress) { onProgress({ frag, part: null, payload: data as ArrayBuffer, networkDetails, }); } }, }); }); } public loadPart( frag: Fragment, part: Part, onProgress: FragmentLoadProgressCallback ): Promise<FragLoadedData> { this.abort(); const config = this.config; const FragmentILoader = config.fLoader; const DefaultILoader = config.loader; return new Promise((resolve, reject) => { if (this.loader) { this.loader.destroy(); } if (frag.gap || part.gap) { reject(createGapLoadError(frag, part)); return; } const loader = (this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : (new DefaultILoader(config) as Loader<FragmentLoaderContext>)); const loaderContext = createLoaderContext(frag, part); // Should we define another load policy for parts? const loadPolicy = getLoaderConfigWithoutReties( config.fragLoadPolicy.default ); const loaderConfig: LoaderConfiguration = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0, highWaterMark: MIN_CHUNK_SIZE, }; // Assign part stats to the loader's stats reference part.stats = loader.stats; loader.load(loaderContext, loaderConfig, { onSuccess: (response, stats, context, networkDetails) => { this.resetLoader(frag, loader); this.updateStatsFromPart(frag, part); const partLoadedData: FragLoadedData = { frag, part, payload: response.data as ArrayBuffer, networkDetails, }; onProgress(partLoadedData); resolve(partLoadedData); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(frag, loader); reject( new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, part, response: { url: loaderContext.url, data: undefined, ...response, }, error: new Error(`HTTP Error ${response.code} ${response.text}`), networkDetails, stats, }) ); }, onAbort: (stats, context, networkDetails) => { frag.stats.aborted = part.stats.aborted; this.resetLoader(frag, loader); reject( new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.INTERNAL_ABORTED, fatal: false, frag, part, error: new Error('Aborted'), networkDetails, stats, }) ); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject( new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag, part, error: new Error(`Timeout after ${loaderConfig.timeout}ms`), networkDetails, stats, }) ); }, }); }); } private updateStatsFromPart(frag: Fragment, part: Part) { const fragStats = frag.stats; const partStats = part.stats; const partTotal = partStats.total; fragStats.loaded += partStats.loaded; if (partTotal) { const estTotalParts = Math.round(frag.duration / part.duration); const estLoadedParts = Math.min( Math.round(fragStats.loaded / partTotal), estTotalParts ); const estRemainingParts = estTotalParts - estLoadedParts; const estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts); fragStats.total = fragStats.loaded + estRemainingBytes; } else { fragStats.total = Math.max(fragStats.loaded, fragStats.total); } const fragLoading = fragStats.loading; const partLoading = partStats.loading; if (fragLoading.start) { // add to fragment loader latency fragLoading.first += partLoading.first - partLoading.start; } else { fragLoading.start = partLoading.start; fragLoading.first = partLoading.first; } fragLoading.end = partLoading.end; } private resetLoader(frag: Fragment, loader: Loader<FragmentLoaderContext>) { frag.loader = null; if (this.loader === loader) { self.clearTimeout(this.partLoadTimeout); this.loader = null; } loader.destroy(); } } function createLoaderContext( frag: Fragment, part: Part | null = null ): FragmentLoaderContext { const segment: BaseSegment = part || frag; const loaderContext: FragmentLoaderContext = { frag, part, responseType: 'arraybuffer', url: segment.url, headers: {}, rangeStart: 0, rangeEnd: 0, }; const start = segment.byteRangeStartOffset; const end = segment.byteRangeEndOffset; if (Number.isFinite(start) && Number.isFinite(end)) { let byteRangeStart = start; let byteRangeEnd = end; if (frag.sn === 'initSegment' && frag.decryptdata?.method === 'AES-128') { // MAP segment encrypted with method 'AES-128', when served with HTTP Range, // has the unencrypted size specified in the range. // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6 const fragmentLen = end - start; if (fragmentLen % 16) { byteRangeEnd = end + (16 - (fragmentLen % 16)); } if (start !== 0) { loaderContext.resetIV = true; byteRangeStart = start - 16; } } loaderContext.rangeStart = byteRangeStart; loaderContext.rangeEnd = byteRangeEnd; } return loaderContext; } function createGapLoadError(frag: Fragment, part?: Part): LoadError { const error = new Error(`GAP ${frag.gap ? 'tag' : 'attribute'} found`); const errorData: FragLoadFailResult = { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_GAP, fatal: false, frag, error, networkDetails: null, }; if (part) { errorData.part = part; } (part ? part : frag).stats.aborted = true; return new LoadError(errorData); } export class LoadError extends Error { public readonly data: FragLoadFailResult; constructor(data: FragLoadFailResult) { super(data.error.message); this.data = data; } } export interface FragLoadFailResult extends ErrorData { frag: Fragment; part?: Part; response?: { data: any; // error status code code: number; // error description text: string; url: string; }; networkDetails: any; } export type FragmentLoadProgressCallback = ( result: FragLoadedData | PartsLoadedData ) => void;