rx-player
Version:
Canal+ HTML5 Video Player
203 lines (202 loc) • 7.71 kB
JavaScript
import addTextTrack from "../../../compat/add_text_track";
import removeCue from "../../../compat/remove_cue";
import log from "../../../log";
import { convertToRanges } from "../../../utils/ranges";
import ManualTimeRanges from "../manual_time_ranges";
import parseTextTrackToCues from "./native_parsers";
/**
* Implementation of an `ITextDisplayer` for "native" text tracks.
* "Native" text tracks rely on a `<track>` HTMLElement and its associated
* expected behavior to display subtitles synchronized to the video.
* @class NativeTextDisplayer
*/
export default class NativeTextDisplayer {
/**
* @param {HTMLMediaElement} videoElement
*/
constructor(videoElement) {
log.debug("NTD: Creating NativeTextDisplayer");
const { track, trackElement } = addTextTrack(videoElement);
this._buffered = new ManualTimeRanges();
this._videoElement = videoElement;
this._track = track;
this._trackElement = trackElement;
}
/**
* Push text segment to the NativeTextDisplayer.
* @param {Object} infos
* @returns {Object}
*/
pushTextData(infos) {
var _a, _b;
log.debug("NTD: Appending new native text tracks");
if (infos.chunk === null) {
return convertToRanges(this._buffered);
}
const { timestampOffset, appendWindow, chunk } = infos;
const { start: startTime, end: endTime, data: dataString, type, language } = chunk;
const appendWindowStart = (_a = appendWindow[0]) !== null && _a !== void 0 ? _a : 0;
const appendWindowEnd = (_b = appendWindow[1]) !== null && _b !== void 0 ? _b : Infinity;
const cues = parseTextTrackToCues(type, dataString, timestampOffset, language);
if (appendWindowStart !== 0 && appendWindowEnd !== Infinity) {
// Removing before window start
let i = 0;
while (i < cues.length && cues[i].endTime <= appendWindowStart) {
i++;
}
cues.splice(0, i);
i = 0;
while (i < cues.length && cues[i].startTime < appendWindowStart) {
cues[i].startTime = appendWindowStart;
i++;
}
// Removing after window end
i = cues.length - 1;
while (i >= 0 && cues[i].startTime >= appendWindowEnd) {
i--;
}
cues.splice(i, cues.length);
i = cues.length - 1;
while (i >= 0 && cues[i].endTime > appendWindowEnd) {
cues[i].endTime = appendWindowEnd;
i--;
}
}
let start;
if (startTime !== undefined) {
start = Math.max(appendWindowStart, startTime);
}
else {
if (cues.length <= 0) {
log.warn("NTD: Current text tracks have no cues nor start time. Aborting");
return convertToRanges(this._buffered);
}
log.warn("NTD: No start time given. Guessing from cues.");
start = cues[0].startTime;
}
let end;
if (endTime !== undefined) {
end = Math.min(appendWindowEnd, endTime);
}
else {
if (cues.length <= 0) {
log.warn("NTD: Current text tracks have no cues nor end time. Aborting");
return convertToRanges(this._buffered);
}
log.warn("NTD: No end time given. Guessing from cues.");
end = cues[cues.length - 1].endTime;
}
if (end <= start) {
log.warn("NTD: Invalid text track appended: ", "the start time is inferior or equal to the end time.");
return convertToRanges(this._buffered);
}
if (cues.length > 0) {
const firstCue = cues[0];
// NOTE(compat): cleanup all current cues if the newly added
// ones are in the past. this is supposed to fix an issue on
// IE/Edge.
// TODO Move to compat
const currentCues = this._track.cues;
if (currentCues !== null && currentCues.length > 0) {
if (firstCue.startTime < currentCues[currentCues.length - 1].startTime) {
this._removeData(firstCue.startTime, +Infinity);
}
}
for (const cue of cues) {
this._track.addCue(cue);
}
}
this._buffered.insert(start, end);
return convertToRanges(this._buffered);
}
/**
* Remove buffered data.
* @param {number} start - start position, in seconds
* @param {number} end - end position, in seconds
* @returns {Object}
*/
removeBuffer(start, end) {
this._removeData(start, end);
return convertToRanges(this._buffered);
}
/**
* Returns the currently buffered data, in a TimeRanges object.
* @returns {Array.<Object>}
*/
getBufferedRanges() {
return convertToRanges(this._buffered);
}
reset() {
log.debug("NTD: Aborting NativeTextDisplayer");
this._removeData(0, Infinity);
this._clearTrackElement();
}
stop() {
log.debug("NTD: Aborting NativeTextDisplayer");
this._removeData(0, Infinity);
const { _trackElement, _videoElement } = this;
if (_trackElement !== undefined && _videoElement.hasChildNodes()) {
try {
_videoElement.removeChild(_trackElement);
}
catch (_e) {
log.warn("NTD: Can't remove track element from the video");
}
}
this._track.mode = "disabled";
if (this._trackElement !== undefined) {
this._trackElement.innerHTML = "";
}
}
_removeData(start, end) {
log.debug("NTD: Removing native text track data", start, end);
const track = this._track;
const cues = track.cues;
if (cues !== null) {
for (let i = cues.length - 1; i >= 0; i--) {
const cue = cues[i];
const { startTime, endTime } = cue;
if (startTime >= start && startTime <= end && endTime <= end) {
removeCue(track, cue);
}
}
}
this._buffered.remove(start, end);
}
_clearTrackElement() {
const { _trackElement, _videoElement } = this;
if (_trackElement !== undefined && _videoElement.hasChildNodes()) {
try {
_videoElement.removeChild(_trackElement);
}
catch (_e) {
log.warn("NTD: Can't remove track element from the video");
}
}
// Ugly trick to work-around browser bugs by refreshing its mode
const oldMode = this._track.mode;
this._track.mode = "disabled";
this._track.mode = oldMode;
if (this._trackElement !== undefined) {
this._trackElement.innerHTML = "";
}
}
}
/*
* The following ugly code is here to provide a compile-time check that an
* `INativeTextTracksBufferSegmentData` (type of data pushed to a
* `NativeTextDisplayer`) can be derived from a `ITextTrackSegmentData`
* (text track data parsed from a segment).
*
* It doesn't correspond at all to real code that will be called. This is just
* a hack to tell TypeScript to perform that check.
*/
if (0 /* __ENVIRONMENT__.CURRENT_ENV */ === 1 /* __ENVIRONMENT__.DEV */) {
// @ts-expect-error: uncalled function just for type checking
function _checkType(input) {
function checkEqual(_arg) {
/* nothing */
}
checkEqual(input);
}
}