UNPKG

rx-player

Version:
369 lines (368 loc) 15.8 kB
"use strict"; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); var event_listeners_1 = require("../../../compat/event_listeners"); var on_height_width_change_1 = require("../../../compat/on_height_width_change"); var config_1 = require("../../../config"); var log_1 = require("../../../log"); var ranges_1 = require("../../../utils/ranges"); var task_canceller_1 = require("../../../utils/task_canceller"); var manual_time_ranges_1 = require("../manual_time_ranges"); var html_parsers_1 = require("./html_parsers"); var text_track_cues_store_1 = require("./text_track_cues_store"); var update_proportional_elements_1 = require("./update_proportional_elements"); /** * @param {Element} element * @param {Element} child */ function safelyRemoveChild(element, child) { try { element.removeChild(child); } catch (_error) { log_1.default.warn("text", "Can't remove text track: not in the element."); } } /** * @param {HTMLElement} element * @returns {Object|null} */ function getElementResolution(element) { var strRows = element.getAttribute("data-resolution-rows"); var strColumns = element.getAttribute("data-resolution-columns"); if (strRows === null || strColumns === null) { return null; } var rows = parseInt(strRows, 10); var columns = parseInt(strColumns, 10); if (rows === null || columns === null) { return null; } return { rows: rows, columns: columns }; } /** * TextDisplayer implementation which display buffered TextTracks in the given * HTML element. * @class HTMLTextDisplayer */ var HTMLTextDisplayer = /** @class */ (function () { /** * @param {HTMLMediaElement} videoElement * @param {HTMLElement} textTrackElement */ function HTMLTextDisplayer(videoElement, textTrackElement) { log_1.default.debug("text", "Creating HTMLTextDisplayer"); this._buffered = new manual_time_ranges_1.default(); this._videoElement = videoElement; this._textTrackElement = textTrackElement; this._sizeUpdateCanceller = new task_canceller_1.default(); this._subtitlesIntervalCanceller = new task_canceller_1.default(); this._buffer = new text_track_cues_store_1.default(); this._currentCues = []; this._isAutoRefreshing = false; } /** * Push text segment to the HTMLTextDisplayer. * @param {Object} infos * @returns {Object} */ HTMLTextDisplayer.prototype.pushTextData = function (infos) { var _a, _b; log_1.default.debug("text", "Appending new html text tracks"); var timestampOffset = infos.timestampOffset, appendWindow = infos.appendWindow, chunk = infos.chunk; if (chunk === null) { return (0, ranges_1.convertToRanges)(this._buffered); } var startTime = chunk.start, endTime = chunk.end, dataRaw = chunk.data, type = chunk.type, language = chunk.language, initTimescale = chunk.initTimescale; var appendWindowStart = (_a = appendWindow[0]) !== null && _a !== void 0 ? _a : 0; var appendWindowEnd = (_b = appendWindow[1]) !== null && _b !== void 0 ? _b : Infinity; var cues = (0, html_parsers_1.default)(type, dataRaw, { initTimescale: initTimescale, language: language }, timestampOffset); if (appendWindowStart !== 0 && appendWindowEnd !== Infinity) { // Removing before window start var i = 0; while (i < cues.length && cues[i].end <= appendWindowStart) { i++; } cues.splice(0, i); i = 0; while (i < cues.length && cues[i].start < appendWindowStart) { cues[i].start = appendWindowStart; i++; } // Removing after window end i = cues.length - 1; while (i >= 0 && cues[i].start >= appendWindowEnd) { i--; } // cues[i] is the last cue we want to keep, remove from i + 1 cues.splice(i + 1, cues.length); i = cues.length - 1; while (i >= 0 && cues[i].end > appendWindowEnd) { cues[i].end = appendWindowEnd; i--; } } var start; if (startTime !== undefined) { start = Math.max(appendWindowStart, startTime); } else { if (cues.length <= 0) { log_1.default.warn("text", "Current text tracks have no cues nor start time. Aborting"); return (0, ranges_1.convertToRanges)(this._buffered); } log_1.default.warn("text", "No start time given. Guessing from cues."); start = cues[0].start; } var end; if (endTime !== undefined) { end = Math.min(appendWindowEnd, endTime); } else { if (cues.length <= 0) { log_1.default.warn("text", "Current text tracks have no cues nor end time. Aborting"); return (0, ranges_1.convertToRanges)(this._buffered); } log_1.default.warn("text", "No end time given. Guessing from cues."); end = cues[cues.length - 1].end; } if (end <= start) { log_1.default.warn("text", "Invalid text track appended: ", "the start time is inferior or equal to the end time."); return (0, ranges_1.convertToRanges)(this._buffered); } this._buffer.insert(cues, start, end); this._buffered.insert(start, end); if (!this._isAutoRefreshing && !this._buffer.isEmpty()) { this.autoRefreshSubtitles(this._subtitlesIntervalCanceller.signal); } return (0, ranges_1.convertToRanges)(this._buffered); }; /** * Remove buffered data. * @param {number} start - start position, in seconds * @param {number} end - end position, in seconds * @returns {Object} */ HTMLTextDisplayer.prototype.removeBuffer = function (start, end) { log_1.default.debug("text", "Removing html text track data", { start: start, end: end }); this._buffer.remove(start, end); this._buffered.remove(start, end); if (this._isAutoRefreshing && this._buffer.isEmpty()) { this.refreshSubtitles(); this._isAutoRefreshing = false; this._subtitlesIntervalCanceller.cancel(); this._subtitlesIntervalCanceller = new task_canceller_1.default(); } return (0, ranges_1.convertToRanges)(this._buffered); }; /** * Returns the currently buffered data, in a TimeRanges object. * @returns {TimeRanges} */ HTMLTextDisplayer.prototype.getBufferedRanges = function () { return (0, ranges_1.convertToRanges)(this._buffered); }; HTMLTextDisplayer.prototype.reset = function () { log_1.default.debug("text", "Resetting HTMLTextDisplayer"); this.stop(); this._subtitlesIntervalCanceller = new task_canceller_1.default(); }; HTMLTextDisplayer.prototype.stop = function () { if (this._subtitlesIntervalCanceller.isUsed()) { return; } log_1.default.debug("text", "Stopping HTMLTextDisplayer"); this._disableCurrentCues(); this._buffer.remove(0, Infinity); this._buffered.remove(0, Infinity); this._isAutoRefreshing = false; this._subtitlesIntervalCanceller.cancel(); }; /** * Remove the current cue from being displayed. */ HTMLTextDisplayer.prototype._disableCurrentCues = function () { var e_1, _a; this._sizeUpdateCanceller.cancel(); if (this._currentCues.length > 0) { try { for (var _b = __values(this._currentCues), _c = _b.next(); !_c.done; _c = _b.next()) { var cue = _c.value; safelyRemoveChild(this._textTrackElement, cue.element); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } this._currentCues = []; } }; /** * Display a new Cue. If one was already present, it will be replaced. * @param {HTMLElement} elements */ HTMLTextDisplayer.prototype._displayCues = function (elements) { var e_2, _a, e_3, _b; var nothingChanged = this._currentCues.length === elements.length && this._currentCues.every(function (current, index) { return current.element === elements[index]; }); if (nothingChanged) { return; } // Remove and re-display everything // TODO More intelligent handling this._sizeUpdateCanceller.cancel(); try { for (var _c = __values(this._currentCues), _d = _c.next(); !_d.done; _d = _c.next()) { var cue = _d.value; safelyRemoveChild(this._textTrackElement, cue.element); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_2) throw e_2.error; } } this._currentCues = []; try { for (var elements_1 = __values(elements), elements_1_1 = elements_1.next(); !elements_1_1.done; elements_1_1 = elements_1.next()) { var element = elements_1_1.value; var resolution = getElementResolution(element); this._currentCues.push({ element: element, resolution: resolution }); this._textTrackElement.appendChild(element); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (elements_1_1 && !elements_1_1.done && (_b = elements_1.return)) _b.call(elements_1); } finally { if (e_3) throw e_3.error; } } var proportionalCues = this._currentCues.filter(function (cue) { return cue.resolution !== null; }); if (proportionalCues.length > 0) { this._sizeUpdateCanceller = new task_canceller_1.default(); this._sizeUpdateCanceller.linkToSignal(this._subtitlesIntervalCanceller.signal); var TEXT_TRACK_SIZE_CHECKS_INTERVAL = config_1.default.getCurrent().TEXT_TRACK_SIZE_CHECKS_INTERVAL; // update propertionally-sized elements periodically var heightWidthRef = (0, on_height_width_change_1.default)(this._textTrackElement, TEXT_TRACK_SIZE_CHECKS_INTERVAL, this._sizeUpdateCanceller.signal); heightWidthRef.onUpdate(function (_a) { var e_4, _b; var height = _a.height, width = _a.width; try { for (var proportionalCues_1 = __values(proportionalCues), proportionalCues_1_1 = proportionalCues_1.next(); !proportionalCues_1_1.done; proportionalCues_1_1 = proportionalCues_1.next()) { var cue = proportionalCues_1_1.value; var resolution = cue.resolution, element = cue.element; (0, update_proportional_elements_1.default)(height, width, resolution, element); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (proportionalCues_1_1 && !proportionalCues_1_1.done && (_b = proportionalCues_1.return)) _b.call(proportionalCues_1); } finally { if (e_4) throw e_4.error; } } }, { clearSignal: this._sizeUpdateCanceller.signal, emitCurrentValue: true, }); } }; /** * Auto-refresh the display of subtitles according to the media element's * position and events. * @param {Object} cancellationSignal */ HTMLTextDisplayer.prototype.autoRefreshSubtitles = function (cancellationSignal) { var _this = this; if (this._isAutoRefreshing || cancellationSignal.isCancelled()) { return; } var autoRefreshCanceller = null; var MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL = config_1.default.getCurrent().MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL; var stopAutoRefresh = function () { _this._isAutoRefreshing = false; if (autoRefreshCanceller !== null) { autoRefreshCanceller.cancel(); autoRefreshCanceller = null; } }; var startAutoRefresh = function () { stopAutoRefresh(); _this._isAutoRefreshing = true; autoRefreshCanceller = new task_canceller_1.default(); autoRefreshCanceller.linkToSignal(cancellationSignal); var intervalId = setInterval(function () { return _this.refreshSubtitles(); }, MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL); autoRefreshCanceller.signal.register(function () { clearInterval(intervalId); }); _this.refreshSubtitles(); }; (0, event_listeners_1.onSeeking)(this._videoElement, function () { stopAutoRefresh(); _this._disableCurrentCues(); }, cancellationSignal); (0, event_listeners_1.onSeeked)(this._videoElement, startAutoRefresh, cancellationSignal); (0, event_listeners_1.onEnded)(this._videoElement, startAutoRefresh, cancellationSignal); startAutoRefresh(); }; /** * Refresh current subtitles according to the current media element's * position. */ HTMLTextDisplayer.prototype.refreshSubtitles = function () { var MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL = config_1.default.getCurrent().MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL; var time; if (this._videoElement.paused || this._videoElement.playbackRate <= 0) { time = this._videoElement.currentTime; } else { // to spread the time error, we divide the regular chosen interval. time = Math.max(this._videoElement.currentTime + MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL / 1000 / 2, 0); } var cues = this._buffer.get(time); if (cues.length === 0) { this._disableCurrentCues(); } else { this._displayCues(cues); } }; return HTMLTextDisplayer; }()); exports.default = HTMLTextDisplayer; /* * The following ugly code is here to provide a compile-time check that an * `IHTMLTextTracksBufferSegmentData` (type of data pushed to a * `HTMLTextDisplayer`) 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); } }