diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index 4b1450b53c9..0c9acef2113 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -854,6 +854,7 @@ class Hls implements HlsEventEmitter { get firstLevel(): number; // Warning: (ae-setter-with-docs) The doc comment for the property "firstLevel" must appear on the getter, not the setter. set firstLevel(newLevel: number); + get forceStartLoad(): boolean; // (undocumented) static isSupported(): boolean; get latency(): number; diff --git a/demo/chart/timeline-chart.ts b/demo/chart/timeline-chart.ts index b98a7c7f3de..d954f258639 100644 --- a/demo/chart/timeline-chart.ts +++ b/demo/chart/timeline-chart.ts @@ -64,7 +64,7 @@ export class TimelineChart { options: Object.assign(getChartOptions(), chartJsOptions), plugins: [ { - afterRender: () => { + afterRender: (chart) => { this.imageDataBuffer = null; this.drawCurrentTime(); }, @@ -298,37 +298,41 @@ export class TimelineChart { details.fragments.forEach((fragment) => { // TODO: keep track of initial playlist start and duration so that we can show drift and pts offset // (Make that a feature of hls.js v1.0.0 fragments) - data.push( - Object.assign( - { - dataType: 'fragment', - }, - fragment - ) + const chartFragment = Object.assign( + { + dataType: 'fragment', + }, + fragment, + // Remove loader references for GC + { loader: null } ); + data.push(chartFragment); }); } if (details.partList) { details.partList.forEach((part) => { - data.push( - Object.assign( - { - dataType: 'part', - start: part.fragment.start + part.fragOffset, - }, - part - ) + const chartPart = Object.assign( + { + dataType: 'part', + start: part.fragment.start + part.fragOffset, + }, + part, + { + fragment: Object.assign({}, part.fragment, { loader: null }), + } ); + data.push(chartPart); }); if (details.fragmentHint) { - data.push( - Object.assign( - { - dataType: 'fragmentHint', - }, - details.fragmentHint - ) + const chartFragment = Object.assign( + { + dataType: 'fragmentHint', + }, + details.fragmentHint, + // Remove loader references for GC + { loader: null } ); + data.push(chartFragment); } } const start = getPlaylistStart(details); @@ -339,6 +343,7 @@ export class TimelineChart { if (this.hidden) { return; } + self.cancelAnimationFrame(this.rafDebounceRequestId); this.rafDebounceRequestId = self.requestAnimationFrame(() => this.update()); } @@ -394,6 +399,7 @@ export class TimelineChart { if (this.hidden) { return; } + self.cancelAnimationFrame(this.rafDebounceRequestId); this.rafDebounceRequestId = self.requestAnimationFrame(() => this.update()); } @@ -591,13 +597,15 @@ export class TimelineChart { return; } self.cancelAnimationFrame(this.rafDebounceRequestId); - this.rafDebounceRequestId = self.requestAnimationFrame(() => { - this.update(); - }); + this.rafDebounceRequestId = self.requestAnimationFrame(() => this.update()); } drawCurrentTime() { const chart = this.chart; + // @ts-ignore + if (chart?.panning) { + return; + } if (self.hls?.media && chart.data.datasets!.length) { const currentTime = self.hls.media.currentTime; const scale = this.chartScales[X_AXIS_SECONDS]; @@ -822,6 +830,13 @@ function getChartOptions() { x: null, y: null, }, + threshold: 100, + onPan: function ({ chart }) { + chart.panning = true; + }, + onPanComplete: function ({ chart }) { + chart.panning = false; + }, }, zoom: { enabled: true, diff --git a/demo/main.js b/demo/main.js index 5465940714c..1b69c2c1ffc 100644 --- a/demo/main.js +++ b/demo/main.js @@ -1629,16 +1629,6 @@ function addChartEventListeners(hls) { }, chart ); - hls.on( - Hls.Events.FRAG_LOADING, - () => { - // TODO: mutate level datasets - // Update loadLevel - chart.removeType('level'); - chart.updateLevels(hls.levels); - }, - chart - ); hls.on( Hls.Events.LEVEL_UPDATED, (eventName, { details }) => { diff --git a/src/controller/abr-controller.ts b/src/controller/abr-controller.ts index fa1f54a751e..07a2039da6a 100644 --- a/src/controller/abr-controller.ts +++ b/src/controller/abr-controller.ts @@ -64,6 +64,9 @@ class AbrController implements ComponentAPI { public destroy() { this.unregisterListeners(); this.clearTimer(); + // @ts-ignore + this.hls = this.onCheck = null; + this.fragCurrent = this.partCurrent = null; } protected onFragLoading(event: Events.FRAG_LOADING, data: FragLoadingData) { diff --git a/src/controller/audio-stream-controller.ts b/src/controller/audio-stream-controller.ts index 2d61a4e97fc..de2d338a523 100644 --- a/src/controller/audio-stream-controller.ts +++ b/src/controller/audio-stream-controller.ts @@ -58,13 +58,12 @@ class AudioStreamController constructor(hls: Hls, fragmentTracker: FragmentTracker) { super(hls, fragmentTracker, '[audio-stream-controller]'); - this.fragmentLoader = new FragmentLoader(hls.config); - this._registerListeners(); } protected onHandlerDestroying() { this._unregisterListeners(); + this.mainDetails = null; } private _registerListeners() { diff --git a/src/controller/audio-track-controller.ts b/src/controller/audio-track-controller.ts index ff6bb59b76b..79eee6dff30 100644 --- a/src/controller/audio-track-controller.ts +++ b/src/controller/audio-track-controller.ts @@ -48,6 +48,8 @@ class AudioTrackController extends BasePlaylistController { public destroy() { this.unregisterListeners(); + this.tracks.length = 0; + this.tracksInGroup.length = 0; super.destroy(); } diff --git a/src/controller/base-playlist-controller.ts b/src/controller/base-playlist-controller.ts index 5cf0baa2a9a..cf51367e47c 100644 --- a/src/controller/base-playlist-controller.ts +++ b/src/controller/base-playlist-controller.ts @@ -20,8 +20,8 @@ export default class BasePlaylistController implements NetworkComponentAPI { protected timer: number = -1; protected canLoad: boolean = false; protected retryCount: number = 0; - protected readonly log: (msg: any) => void; - protected readonly warn: (msg: any) => void; + protected log: (msg: any) => void; + protected warn: (msg: any) => void; constructor(hls: Hls, logPrefix: string) { this.log = logger.log.bind(logger, `${logPrefix}:`); @@ -31,6 +31,8 @@ export default class BasePlaylistController implements NetworkComponentAPI { public destroy(): void { this.clearTimer(); + // @ts-ignore + this.hls = this.log = this.warn = null; } protected onError(event: Events.ERROR, data: ErrorData): void { diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index a97d0d71c97..43bca8124ca 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -87,8 +87,8 @@ export default class BaseStreamController protected onvended: EventListener | null = null; private readonly logPrefix: string = ''; - protected readonly log: (msg: any) => void; - protected readonly warn: (msg: any) => void; + protected log: (msg: any) => void; + protected warn: (msg: any) => void; constructor(hls: Hls, fragmentTracker: FragmentTracker, logPrefix: string) { super(); @@ -96,6 +96,7 @@ export default class BaseStreamController this.log = logger.log.bind(logger, `${logPrefix}:`); this.warn = logger.warn.bind(logger, `${logPrefix}:`); this.hls = hls; + this.fragmentLoader = new FragmentLoader(hls.config); this.fragmentTracker = fragmentTracker; this.config = hls.config; this.decrypter = new Decrypter(hls as HlsEventEmitter, hls.config); @@ -112,11 +113,9 @@ export default class BaseStreamController public startLoad(startPosition: number): void {} public stopLoad() { + this.fragmentLoader.abort(); const frag = this.fragCurrent; if (frag) { - if (frag.loader) { - frag.loader.abort(); - } this.fragmentTracker.removeFragment(frag); } if (this.transmuxer) { @@ -263,6 +262,14 @@ export default class BaseStreamController protected onHandlerDestroyed() { this.state = State.STOPPED; this.hls.off(Events.KEY_LOADED, this.onKeyLoaded, this); + if (this.fragmentLoader) { + this.fragmentLoader.destroy(); + } + if (this.decrypter) { + this.decrypter.destroy(); + } + // @ts-ignore + this.hls = this.log = this.warn = this.decrypter = this.fragmentLoader = this.fragmentTracker = null; super.onHandlerDestroyed(); } diff --git a/src/controller/buffer-controller.ts b/src/controller/buffer-controller.ts index 3396ee2ed8b..533accb6b79 100644 --- a/src/controller/buffer-controller.ts +++ b/src/controller/buffer-controller.ts @@ -150,9 +150,9 @@ export default class BufferController implements ComponentAPI { } protected onMediaDetaching() { - logger.log('[buffer-controller]: media source detaching'); const { media, mediaSource, _objectUrl } = this; if (mediaSource) { + logger.log('[buffer-controller]: media source detaching'); if (mediaSource.readyState === 'open') { try { // endOfStream could trigger exception if any sourcebuffer is in updating state diff --git a/src/controller/cap-level-controller.ts b/src/controller/cap-level-controller.ts index e2bd66d0aa8..b0f55a01af0 100644 --- a/src/controller/cap-level-controller.ts +++ b/src/controller/cap-level-controller.ts @@ -18,7 +18,6 @@ import type Hls from '../hls'; class CapLevelController implements ComponentAPI { public autoLevelCapping: number; public firstLevel: number; - public levels: Array; public media: HTMLVideoElement | null; public restrictedLevels: Array; public timer: number | undefined; @@ -30,7 +29,6 @@ class CapLevelController implements ComponentAPI { constructor(hls: Hls) { this.hls = hls; this.autoLevelCapping = Number.POSITIVE_INFINITY; - this.levels = []; this.firstLevel = -1; this.media = null; this.restrictedLevels = []; @@ -47,10 +45,12 @@ class CapLevelController implements ComponentAPI { public destroy() { this.unregisterListener(); if (this.hls.config.capLevelToPlayerSize) { - this.media = null; - this.clientRect = null; this.stopCapping(); } + this.media = null; + this.clientRect = null; + // @ts-ignore + this.hls = this.streamController = null; } protected registerListeners() { @@ -58,7 +58,6 @@ class CapLevelController implements ComponentAPI { hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); } @@ -68,7 +67,6 @@ class CapLevelController implements ComponentAPI { hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); } @@ -101,7 +99,6 @@ class CapLevelController implements ComponentAPI { ) { const hls = this.hls; this.restrictedLevels = []; - this.levels = data.levels; this.firstLevel = data.firstLevel; if (hls.config.capLevelToPlayerSize && data.video) { // Start capping immediately if the manifest has signaled video codecs @@ -122,23 +119,16 @@ class CapLevelController implements ComponentAPI { } } - protected onLevelsUpdated( - event: Events.LEVELS_UPDATED, - data: LevelsUpdatedData - ) { - this.levels = data.levels; - } - protected onMediaDetaching() { this.stopCapping(); } detectPlayerSize() { if (this.media && this.mediaHeight > 0 && this.mediaWidth > 0) { - const levelsLength = this.levels ? this.levels.length : 0; - if (levelsLength) { + const levels = this.hls.levels; + if (levels.length) { const hls = this.hls; - hls.autoLevelCapping = this.getMaxLevel(levelsLength - 1); + hls.autoLevelCapping = this.getMaxLevel(levels.length - 1); if ( hls.autoLevelCapping > this.autoLevelCapping && this.streamController @@ -156,11 +146,12 @@ class CapLevelController implements ComponentAPI { * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled) */ getMaxLevel(capLevelIndex: number): number { - if (!this.levels) { + const levels = this.hls.levels; + if (!levels.length) { return -1; } - const validLevels = this.levels.filter( + const validLevels = levels.filter( (level, index) => CapLevelController.isLevelAllowed(index, this.restrictedLevels) && index <= capLevelIndex diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index a044e9f891b..70e17516dd6 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -120,6 +120,7 @@ class EMEController implements ComponentAPI { private _requestLicenseFailureCount: number = 0; private mediaKeysPromise: Promise | null = null; + private _onMediaEncrypted = this.onMediaEncrypted.bind(this); /** * @constructs @@ -141,6 +142,9 @@ class EMEController implements ComponentAPI { public destroy() { this._unregisterListeners(); + // @ts-ignore + this.hls = this._onMediaEncrypted = null; + this._requestMediaKeySystemAccess = null; } private _registerListeners() { @@ -318,7 +322,7 @@ class EMEController implements ComponentAPI { * @private * @param e {MediaEncryptedEvent} */ - private _onMediaEncrypted = (e: MediaEncryptedEvent) => { + private onMediaEncrypted(e: MediaEncryptedEvent) { logger.log(`Media is encrypted using "${e.initDataType}" init data type`); if (!this.mediaKeysPromise) { @@ -345,7 +349,7 @@ class EMEController implements ComponentAPI { this.mediaKeysPromise .then(finallySetKeyAndStartSession) .catch(finallySetKeyAndStartSession); - }; + } /** * @private diff --git a/src/controller/fragment-tracker.ts b/src/controller/fragment-tracker.ts index 097391012d5..3957cd6ae55 100644 --- a/src/controller/fragment-tracker.ts +++ b/src/controller/fragment-tracker.ts @@ -59,9 +59,9 @@ export class FragmentTracker implements ComponentAPI { } public destroy() { - this.fragments = Object.create(null); - this.timeRanges = Object.create(null); this._unregisterListeners(); + // @ts-ignore + this.fragments = this.timeRanges = null; } /** diff --git a/src/controller/gap-controller.ts b/src/controller/gap-controller.ts index a116bc1f76e..c5d9b14a62c 100644 --- a/src/controller/gap-controller.ts +++ b/src/controller/gap-controller.ts @@ -31,6 +31,11 @@ export default class GapController { this.hls = hls; } + public destroy() { + // @ts-ignore + this.hls = this.fragmentTracker = this.media = null; + } + /** * Checks if the playhead is stuck within a gap, and if so, attempts to free it. * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range). diff --git a/src/controller/latency-controller.ts b/src/controller/latency-controller.ts index a9f072088cf..8c0404f6be0 100644 --- a/src/controller/latency-controller.ts +++ b/src/controller/latency-controller.ts @@ -12,7 +12,7 @@ import type Hls from '../hls'; import type { HlsConfig } from '../config'; export default class LatencyController implements ComponentAPI { - private readonly hls: Hls; + private hls: Hls; private readonly config: HlsConfig; private media: HTMLMediaElement | null = null; private levelDetails: LevelDetails | null = null; @@ -117,6 +117,9 @@ export default class LatencyController implements ComponentAPI { public destroy(): void { this.unregisterListeners(); this.onMediaDetaching(); + this.levelDetails = null; + // @ts-ignore + this.hls = this.timeupdateHandler = null; } private registerListeners() { diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts index 78fe2009287..e8ba91e7686 100644 --- a/src/controller/level-controller.ts +++ b/src/controller/level-controller.ts @@ -59,9 +59,10 @@ export default class LevelController extends BasePlaylistController { } public destroy() { - super.destroy(); this._unregisterListeners(); this.manualLevelIndex = -1; + this._levels.length = 0; + super.destroy(); } public startLoad(): void { @@ -189,7 +190,10 @@ export default class LevelController extends BasePlaylistController { }; this.hls.trigger(Events.MANIFEST_PARSED, edata); - this.onParsedComplete(); + // Initiate loading after all controllers have received MANIFEST_PARSED + if (this.hls.config.autoStartLoad || this.hls.forceStartLoad) { + this.hls.startLoad(this.hls.config.startPosition); + } } else { this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index f45d31b1686..5c40be7fe38 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -58,9 +58,6 @@ export default class StreamController constructor(hls: Hls, fragmentTracker: FragmentTracker) { super(hls, fragmentTracker, '[stream-controller]'); - this.fragmentLoader = new FragmentLoader(hls.config); - this.state = State.STOPPED; - this._registerListeners(); } @@ -109,6 +106,7 @@ export default class StreamController protected onHandlerDestroying() { this._unregisterListeners(); + this.onMediaDetaching(); } public startLoad(startPosition: number): void { @@ -516,8 +514,13 @@ export default class StreamController media.removeEventListener('playing', this.onvplaying); media.removeEventListener('seeked', this.onvseeked); this.onvplaying = this.onvseeked = null; + this.videoBuffer = null; + } + this.fragPlaying = null; + if (this.gapController) { + this.gapController.destroy(); + this.gapController = null; } - super.onMediaDetaching(); } diff --git a/src/controller/subtitle-stream-controller.ts b/src/controller/subtitle-stream-controller.ts index 7152456c513..9072ad69cdb 100644 --- a/src/controller/subtitle-stream-controller.ts +++ b/src/controller/subtitle-stream-controller.ts @@ -46,7 +46,6 @@ export class SubtitleStreamController this.mediaBuffer = null; this.state = State.STOPPED; this.tracksBuffered = []; - this.fragmentLoader = new FragmentLoader(hls.config); this._registerListeners(); } diff --git a/src/controller/subtitle-track-controller.ts b/src/controller/subtitle-track-controller.ts index 6a397aeb157..1c32572f62f 100644 --- a/src/controller/subtitle-track-controller.ts +++ b/src/controller/subtitle-track-controller.ts @@ -35,6 +35,10 @@ class SubtitleTrackController extends BasePlaylistController { public destroy() { this.unregisterListeners(); + this.tracks.length = 0; + this.tracksInGroup.length = 0; + // @ts-ignore + this.trackChangeListener = null; super.destroy(); } diff --git a/src/controller/timeline-controller.ts b/src/controller/timeline-controller.ts index 3439e8da057..ea49a5d4fb2 100644 --- a/src/controller/timeline-controller.ts +++ b/src/controller/timeline-controller.ts @@ -58,8 +58,8 @@ export class TimelineController implements ComponentAPI { private unparsedVttFrags: Array = []; private captionsTracks: Record = {}; private nonNativeCaptionsTracks: Record = {}; - private readonly cea608Parser1!: Cea608Parser; - private readonly cea608Parser2!: Cea608Parser; + private cea608Parser1!: Cea608Parser; + private cea608Parser2!: Cea608Parser; private lastSn: number = -1; private prevCC: number = -1; private vttCCs: VTTCCs = newVTTCCs(); @@ -103,11 +103,6 @@ export class TimelineController implements ComponentAPI { this.cea608Parser2 = new Cea608Parser(3, channel3, channel4); } - this._registerListeners(); - } - - private _registerListeners(): void { - const { hls } = this; hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); @@ -122,7 +117,7 @@ export class TimelineController implements ComponentAPI { hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); } - private _unregisterListeners(): void { + public destroy(): void { const { hls } = this; hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); @@ -136,6 +131,8 @@ export class TimelineController implements ComponentAPI { hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); + // @ts-ignore + this.hls = this.config = this.cea608Parser1 = this.cea608Parser2 = null; } public addCues( @@ -278,10 +275,6 @@ export class TimelineController implements ComponentAPI { return media.addTextTrack(kind, label, lang); } - public destroy() { - this._unregisterListeners(); - } - private onMediaAttaching( event: Events.MEDIA_ATTACHING, data: MediaAttachingData diff --git a/src/crypt/decrypter.ts b/src/crypt/decrypter.ts index 3100d73975d..adacc2c3f74 100644 --- a/src/crypt/decrypter.ts +++ b/src/crypt/decrypter.ts @@ -48,6 +48,11 @@ export default class Decrypter { } } + destroy() { + // @ts-ignore + this.observer = null; + } + public isSync() { return this.config.enableSoftwareAES; } diff --git a/src/hls.ts b/src/hls.ts index d693884c24e..bcf54c25760 100644 --- a/src/hls.ts +++ b/src/hls.ts @@ -128,13 +128,6 @@ export default class Hls implements HlsEventEmitter { fragmentTracker )); - // Level Controller initiates loading after all controllers have received MANIFEST_PARSED - levelController.onParsedComplete = () => { - if (config.autoStartLoad || streamController.forceStartLoad) { - this.startLoad(config.startPosition); - } - }; - // Cap level controller uses streamController to flush the buffer capLevelController.setStreamController(streamController); // fpsController uses streamController to switch when frames are being dropped @@ -286,11 +279,19 @@ export default class Hls implements HlsEventEmitter { logger.log('destroy'); this.trigger(Events.DESTROYING, undefined); this.detachMedia(); - this.networkControllers.forEach((component) => component.destroy()); - this.coreComponents.forEach((component) => component.destroy()); - this.url = null; this.removeAllListeners(); this._autoLevelCapping = -1; + this.url = null; + if (this.networkControllers) { + this.networkControllers.forEach((component) => component.destroy()); + // @ts-ignore + this.networkControllers = null; + } + if (this.coreComponents) { + this.coreComponents.forEach((component) => component.destroy()); + // @ts-ignore + this.coreComponents = null; + } } /** @@ -387,7 +388,8 @@ export default class Hls implements HlsEventEmitter { * @type {Level[]} */ get levels(): Array { - return this.levelController.levels ? this.levelController.levels : []; + const levels = this.levelController.levels; + return levels ? levels : []; } /** @@ -786,6 +788,14 @@ export default class Hls implements HlsEventEmitter { get targetLatency(): number | null { return this.latencyController.targetLatency; } + + /** + * set to true when startLoad is called before MANIFEST_PARSED event + * @type {boolean} + */ + get forceStartLoad(): boolean { + return this.streamController.forceStartLoad; + } } export type { diff --git a/src/loader/fragment-loader.ts b/src/loader/fragment-loader.ts index 7cc6d638ea4..792dcba391d 100644 --- a/src/loader/fragment-loader.ts +++ b/src/loader/fragment-loader.ts @@ -20,6 +20,13 @@ export default class FragmentLoader { 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 @@ -53,6 +60,9 @@ export default class FragmentLoader { const DefaultILoader = config.loader; return new Promise((resolve, reject) => { + if (this.loader) { + this.loader.destroy(); + } const loader = (this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : (new DefaultILoader(config) as Loader)); @@ -139,6 +149,9 @@ export default class FragmentLoader { const DefaultILoader = config.loader; return new Promise((resolve, reject) => { + if (this.loader) { + this.loader.destroy(); + } const loader = (this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : (new DefaultILoader(config) as Loader)); @@ -246,6 +259,7 @@ export default class FragmentLoader { self.clearTimeout(this.partLoadTimeout); this.loader = null; } + loader.destroy(); } } diff --git a/src/utils/xhr-loader.ts b/src/utils/xhr-loader.ts index f742ea81b96..d6b1c26c8f0 100644 --- a/src/utils/xhr-loader.ts +++ b/src/utils/xhr-loader.ts @@ -35,12 +35,16 @@ class XhrLoader implements Loader { abortInternal(): void { const loader = this.loader; - if (loader && loader.readyState !== 4) { - this.stats.aborted = true; - loader.abort(); - } self.clearTimeout(this.requestTimeout); self.clearTimeout(this.retryTimeout); + if (loader) { + loader.onreadystatechange = null; + loader.onprogress = null; + if (loader.readyState !== 4) { + this.stats.aborted = true; + loader.abort(); + } + } } abort(): void { @@ -146,6 +150,8 @@ class XhrLoader implements Loader { } if (readyState === 4) { + xhr.onreadystatechange = null; + xhr.onprogress = null; const status = xhr.status; // http status between 200 to 299 are all successful if (status >= 200 && status < 300) { diff --git a/tests/unit/controller/cap-level-controller.js b/tests/unit/controller/cap-level-controller.js index d6267ae1110..6bc22f4acfc 100644 --- a/tests/unit/controller/cap-level-controller.js +++ b/tests/unit/controller/cap-level-controller.js @@ -176,7 +176,6 @@ describe('CapLevelController', function () { }); it('constructs with no restrictions', function () { - expect(capLevelController.levels).to.be.empty; expect(capLevelController.restrictedLevels).to.be.empty; expect(capLevelController.timer).to.not.exist; expect(capLevelController.autoLevelCapping).to.equal( @@ -212,7 +211,6 @@ describe('CapLevelController', function () { }; capLevelController.onManifestParsed(Events.MANIFEST_PARSED, data); - expect(capLevelController.levels).to.equal(data.levels); expect(capLevelController.firstLevel).to.equal(data.firstLevel); expect(capLevelController.restrictedLevels).to.be.empty; });