Skip to content

Commit

Permalink
Merge pull request #3628 from video-dev/task/cleanup-hls-destroy
Browse files Browse the repository at this point in the history
Cleanup hls.destroy()
  • Loading branch information
robwalch committed Mar 17, 2021
2 parents f44bb96 + c353e56 commit b0fa62d
Show file tree
Hide file tree
Showing 24 changed files with 164 additions and 106 deletions.
1 change: 1 addition & 0 deletions api-extractor/report/hls.js.api.md
Expand Up @@ -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;
Expand Down
67 changes: 41 additions & 26 deletions demo/chart/timeline-chart.ts
Expand Up @@ -64,7 +64,7 @@ export class TimelineChart {
options: Object.assign(getChartOptions(), chartJsOptions),
plugins: [
{
afterRender: () => {
afterRender: (chart) => {
this.imageDataBuffer = null;
this.drawCurrentTime();
},
Expand Down Expand Up @@ -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);
Expand All @@ -339,6 +343,7 @@ export class TimelineChart {
if (this.hidden) {
return;
}
self.cancelAnimationFrame(this.rafDebounceRequestId);
this.rafDebounceRequestId = self.requestAnimationFrame(() => this.update());
}

Expand Down Expand Up @@ -394,6 +399,7 @@ export class TimelineChart {
if (this.hidden) {
return;
}
self.cancelAnimationFrame(this.rafDebounceRequestId);
this.rafDebounceRequestId = self.requestAnimationFrame(() => this.update());
}

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 0 additions & 10 deletions demo/main.js
Expand Up @@ -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 }) => {
Expand Down
3 changes: 3 additions & 0 deletions src/controller/abr-controller.ts
Expand Up @@ -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) {
Expand Down
3 changes: 1 addition & 2 deletions src/controller/audio-stream-controller.ts
Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions src/controller/audio-track-controller.ts
Expand Up @@ -48,6 +48,8 @@ class AudioTrackController extends BasePlaylistController {

public destroy() {
this.unregisterListeners();
this.tracks.length = 0;
this.tracksInGroup.length = 0;
super.destroy();
}

Expand Down
6 changes: 4 additions & 2 deletions src/controller/base-playlist-controller.ts
Expand Up @@ -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}:`);
Expand All @@ -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 {
Expand Down
17 changes: 12 additions & 5 deletions src/controller/base-stream-controller.ts
Expand Up @@ -87,15 +87,16 @@ 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();
this.logPrefix = logPrefix;
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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
}

Expand Down
2 changes: 1 addition & 1 deletion src/controller/buffer-controller.ts
Expand Up @@ -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
Expand Down
29 changes: 10 additions & 19 deletions src/controller/cap-level-controller.ts
Expand Up @@ -18,7 +18,6 @@ import type Hls from '../hls';
class CapLevelController implements ComponentAPI {
public autoLevelCapping: number;
public firstLevel: number;
public levels: Array<Level>;
public media: HTMLVideoElement | null;
public restrictedLevels: Array<number>;
public timer: number | undefined;
Expand All @@ -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 = [];
Expand All @@ -47,18 +45,19 @@ 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() {
const { hls } = this;
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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/controller/eme-controller.ts
Expand Up @@ -120,6 +120,7 @@ class EMEController implements ComponentAPI {
private _requestLicenseFailureCount: number = 0;

private mediaKeysPromise: Promise<MediaKeys> | null = null;
private _onMediaEncrypted = this.onMediaEncrypted.bind(this);

/**
* @constructs
Expand All @@ -141,6 +142,9 @@ class EMEController implements ComponentAPI {

public destroy() {
this._unregisterListeners();
// @ts-ignore
this.hls = this._onMediaEncrypted = null;
this._requestMediaKeySystemAccess = null;
}

private _registerListeners() {
Expand Down Expand Up @@ -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) {
Expand All @@ -345,7 +349,7 @@ class EMEController implements ComponentAPI {
this.mediaKeysPromise
.then(finallySetKeyAndStartSession)
.catch(finallySetKeyAndStartSession);
};
}

/**
* @private
Expand Down

0 comments on commit b0fa62d

Please sign in to comment.