Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VTT cues are never cleaned up. Fix memory leak in live. #2309

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/controller/buffer-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ class BufferController extends EventHandler {
// time will lead to playback freezing)
// credits for level target duration - https://github.com/videojs/http-streaming/blob/3132933b6aa99ddefab29c10447624efd6fd6e52/src/segment-loader.js#L91
this.removeBufferRange(bufferType, sb, 0, targetBackBufferPosition);
this.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, { bufferEnd: targetBackBufferPosition });
JordanPawlett marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down
26 changes: 24 additions & 2 deletions src/controller/id3-track-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import Event from '../events';
import EventHandler from '../event-handler';
import ID3 from '../demux/id3';
import { logger } from '../utils/logger';
import { sendAddTrackEvent, clearCurrentCues } from '../utils/texttrack-utils';
import { sendAddTrackEvent, clearCurrentCues, getClosestCue } from '../utils/texttrack-utils';

class ID3TrackController extends EventHandler {
constructor (hls) {
super(hls,
Event.MEDIA_ATTACHED,
Event.MEDIA_DETACHING,
Event.FRAG_PARSING_METADATA);
Event.FRAG_PARSING_METADATA,
Event.LIVE_BACK_BUFFER_REACHED
);
this.id3Track = undefined;
this.media = undefined;
}
Expand Down Expand Up @@ -91,6 +93,26 @@ class ID3TrackController extends EventHandler {
}
}
}

JordanPawlett marked this conversation as resolved.
Show resolved Hide resolved
onLiveBackBufferReached ({ bufferEnd }) {
if (!this.id3Track || !this.id3Track.cues || !this.id3Track.cues.length) {
return;
}
const foundCue = getClosestCue(this.id3Track.cues, bufferEnd);
if (!foundCue) {
return;
}

let removeCues = true;
while (removeCues) {
const cue = this.id3Track.cues[0];
if (!this.id3Track.cues.length || cue.id === foundCue.id) {
removeCues = false;
return;
}
this.id3Track.removeCue(cue);
}
}
}

export default ID3TrackController;
4 changes: 3 additions & 1 deletion src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ const HlsEvents = {
// fired when a decrypt key loading is completed - data: { frag : fragment object, payload : key payload, stats : { trequest, tfirst, tload, length } }
KEY_LOADED: 'hlsKeyLoaded',
// fired upon stream controller state transitions - data: { previousState, nextState }
STREAM_STATE_TRANSITION: 'hlsStreamStateTransition'
STREAM_STATE_TRANSITION: 'hlsStreamStateTransition',
// fired when the live back buffer is reached defined by the liveBackBufferLength config option - data : { bufferEnd: number }
LIVE_BACK_BUFFER_REACHED: 'hlsLiveBackBufferReached'
};

export default HlsEvents;
39 changes: 39 additions & 0 deletions src/utils/texttrack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,42 @@ export function clearCurrentCues (track: TextTrack) {
}
}
}

/**
* Given a list of Cues, finds the closest cue matching the given time.
* Modified verison of binary search O(log(n)).
*
* @export
* @param {(TextTrackCueList | TextTrackCue[])} cues - List of cues.
* @param {number} time - Target time, to find closest cue to.
* @returns {TextTrackCue}
*/
export function getClosestCue (cues: TextTrackCueList | TextTrackCue[], time: number): TextTrackCue {
// If the offset is less than the first element, the first element is the closest.
if (time < cues[0].endTime) {
return cues[0];
}
// If the offset is greater than the last cue, the last is the closest.
if (time > cues[cues.length - 1].endTime) {
return cues[cues.length - 1];
}

let left = 0;
let right = cues.length - 1;

while (left <= right) {
const mid = Math.floor((right + left) / 2);

if (time < cues[mid].endTime) {
right = mid - 1;
} else if (time > cues[mid].endTime) {
left = mid + 1;
} else {
// If it's not lower or higher, it must be equal.
return cues[mid];
}
}
// At this point, left and right have swapped.
// No direct match was found, left or right element must be the closest. Check which one has the smallest diff.
return (cues[left].endTime - time) < (time - cues[right].endTime) ? cues[left] : cues[right];
}