mirror of
https://github.com/videojs/video.js.git
synced 2025-01-25 11:13:52 +02:00
perf: Various small performance improvements. (#4426)
These are a few performance improvements. * Use textContent instead of innerHTML for text content. * Construct TextTrackSettings content with HTML strings where possible because it's a bit faster than manual DOM construction. * Do minimal DOM updates to time controls. This reduces our timeupdate footprint from ~2-2.5ms to ~1ms!
This commit is contained in:
parent
7f7ea70cb7
commit
77ba3d13d9
@ -120,7 +120,7 @@ class ClickableComponent extends Component {
|
||||
const localizedText = this.localize(text);
|
||||
|
||||
this.controlText_ = text;
|
||||
this.controlTextEl_.innerHTML = localizedText;
|
||||
Dom.textContent(this.controlTextEl_, localizedText);
|
||||
if (!this.nonIconControl) {
|
||||
// Set title attribute if only an icon is shown
|
||||
el.setAttribute('title', localizedText);
|
||||
|
@ -1,8 +1,10 @@
|
||||
/**
|
||||
* @file current-time-display.js
|
||||
*/
|
||||
import document from 'global/document';
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import {bind, throttle} from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@ -23,8 +25,8 @@ class CurrentTimeDisplay extends Component {
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
|
||||
this.on(player, 'timeupdate', this.throttledUpdateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,18 +41,34 @@ class CurrentTimeDisplay extends Component {
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-current-time-display',
|
||||
// label the current time for screen reader users
|
||||
innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00'
|
||||
className: 'vjs-current-time-display'
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
});
|
||||
}, Dom.createEl('span', {
|
||||
className: 'vjs-control-text',
|
||||
textContent: this.localize('Current Time')
|
||||
}));
|
||||
|
||||
this.updateTextNode_();
|
||||
el.appendChild(this.contentEl_);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "current time" text node with new content using the
|
||||
* contents of the `formattedTime_` property.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateTextNode_() {
|
||||
if (this.textNode_) {
|
||||
this.contentEl_.removeChild(this.textNode_);
|
||||
}
|
||||
this.textNode_ = document.createTextNode(` ${this.formattedTime_ || '0:00'}`);
|
||||
this.contentEl_.appendChild(this.textNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current time display
|
||||
*
|
||||
@ -62,12 +80,11 @@ class CurrentTimeDisplay extends Component {
|
||||
updateContent(event) {
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
const localizedText = this.localize('Current Time');
|
||||
const formattedTime = formatTime(time, this.player_.duration());
|
||||
|
||||
if (formattedTime !== this.formattedTime_) {
|
||||
this.formattedTime_ = formattedTime;
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
||||
this.requestAnimationFrame(this.updateTextNode_);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
/**
|
||||
* @file duration-display.js
|
||||
*/
|
||||
import document from 'global/document';
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import {bind, throttle} from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@ -24,13 +26,17 @@ class DurationDisplay extends Component {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'durationchange', this.updateContent);
|
||||
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
|
||||
|
||||
// Also listen for timeupdate and loadedmetadata because removing those
|
||||
// listeners could have broken dependent applications/libraries. These
|
||||
// can likely be removed for 6.0.
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
this.on(player, 'loadedmetadata', this.updateContent);
|
||||
this.on(player, [
|
||||
'durationchange',
|
||||
|
||||
// Also listen for timeupdate and loadedmetadata because removing those
|
||||
// listeners could have broken dependent applications/libraries. These
|
||||
// can likely be removed for 7.0.
|
||||
'loadedmetadata',
|
||||
'timeupdate'
|
||||
], this.throttledUpdateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,18 +51,34 @@ class DurationDisplay extends Component {
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-duration-display',
|
||||
// label the duration time for screen reader users
|
||||
innerHTML: `<span class="vjs-control-text">${this.localize('Duration Time')}</span> 0:00`
|
||||
className: 'vjs-duration-display'
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
});
|
||||
}, Dom.createEl('span', {
|
||||
className: 'vjs-control-text',
|
||||
textContent: this.localize('Duration Time')
|
||||
}));
|
||||
|
||||
this.updateTextNode_();
|
||||
el.appendChild(this.contentEl_);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "current time" text node with new content using the
|
||||
* contents of the `formattedTime_` property.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateTextNode_() {
|
||||
if (this.textNode_) {
|
||||
this.contentEl_.removeChild(this.textNode_);
|
||||
}
|
||||
this.textNode_ = document.createTextNode(` ${this.formattedTime_ || '0:00'}`);
|
||||
this.contentEl_.appendChild(this.textNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update duration time display.
|
||||
*
|
||||
@ -73,14 +95,10 @@ class DurationDisplay extends Component {
|
||||
|
||||
if (duration && this.duration_ !== duration) {
|
||||
this.duration_ = duration;
|
||||
const localizedText = this.localize('Duration Time');
|
||||
const formattedTime = formatTime(duration);
|
||||
|
||||
// label the duration time for screen reader users
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
||||
this.formattedTime_ = formatTime(duration);
|
||||
this.requestAnimationFrame(this.updateTextNode_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('DurationDisplay', DurationDisplay);
|
||||
|
@ -1,8 +1,10 @@
|
||||
/**
|
||||
* @file remaining-time-display.js
|
||||
*/
|
||||
import document from 'global/document';
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import {bind, throttle} from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@ -23,9 +25,8 @@ class RemainingTimeDisplay extends Component {
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
this.on(player, 'durationchange', this.updateContent);
|
||||
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
|
||||
this.on(player, ['timeupdate', 'durationchange'], this.throttledUpdateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,18 +41,34 @@ class RemainingTimeDisplay extends Component {
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-remaining-time-display',
|
||||
// label the remaining time for screen reader users
|
||||
innerHTML: `<span class="vjs-control-text">${this.localize('Remaining Time')}</span> -0:00`
|
||||
className: 'vjs-remaining-time-display'
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
});
|
||||
}, Dom.createEl('span', {
|
||||
className: 'vjs-control-text',
|
||||
textContent: this.localize('Remaining Time')
|
||||
}));
|
||||
|
||||
this.updateTextNode_();
|
||||
el.appendChild(this.contentEl_);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "remaining time" text node with new content using the
|
||||
* contents of the `formattedTime_` property.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateTextNode_() {
|
||||
if (this.textNode_) {
|
||||
this.contentEl_.removeChild(this.textNode_);
|
||||
}
|
||||
this.textNode_ = document.createTextNode(` -${this.formattedTime_ || '0:00'}`);
|
||||
this.contentEl_.appendChild(this.textNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update remaining time display.
|
||||
*
|
||||
@ -63,20 +80,14 @@ class RemainingTimeDisplay extends Component {
|
||||
*/
|
||||
updateContent(event) {
|
||||
if (this.player_.duration()) {
|
||||
const localizedText = this.localize('Remaining Time');
|
||||
const formattedTime = formatTime(this.player_.remainingTime());
|
||||
|
||||
if (formattedTime !== this.formattedTime_) {
|
||||
this.formattedTime_ = formattedTime;
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> -${formattedTime}`;
|
||||
this.requestAnimationFrame(this.updateTextNode_);
|
||||
}
|
||||
}
|
||||
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
// var time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
// this.contentEl_.innerHTML = vjs.formatTime(time, this.player_.duration());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
|
||||
|
@ -368,24 +368,26 @@ class Html5 extends Tech {
|
||||
// playback has started, which triggers the live display erroneously.
|
||||
// Return NaN if playback has not started and trigger a durationupdate once
|
||||
// the duration can be reliably known.
|
||||
if (this.el_.duration === Infinity &&
|
||||
browser.IS_ANDROID && browser.IS_CHROME) {
|
||||
if (this.el_.currentTime === 0) {
|
||||
// Wait for the first `timeupdate` with currentTime > 0 - there may be
|
||||
// several with 0
|
||||
const checkProgress = () => {
|
||||
if (this.el_.currentTime > 0) {
|
||||
// Trigger durationchange for genuinely live video
|
||||
if (this.el_.duration === Infinity) {
|
||||
this.trigger('durationchange');
|
||||
}
|
||||
this.off('timeupdate', checkProgress);
|
||||
if (
|
||||
this.el_.duration === Infinity &&
|
||||
browser.IS_ANDROID &&
|
||||
browser.IS_CHROME &&
|
||||
this.el_.currentTime === 0
|
||||
) {
|
||||
// Wait for the first `timeupdate` with currentTime > 0 - there may be
|
||||
// several with 0
|
||||
const checkProgress = () => {
|
||||
if (this.el_.currentTime > 0) {
|
||||
// Trigger durationchange for genuinely live video
|
||||
if (this.el_.duration === Infinity) {
|
||||
this.trigger('durationchange');
|
||||
}
|
||||
};
|
||||
this.off('timeupdate', checkProgress);
|
||||
}
|
||||
};
|
||||
|
||||
this.on('timeupdate', checkProgress);
|
||||
return NaN;
|
||||
}
|
||||
this.on('timeupdate', checkProgress);
|
||||
return NaN;
|
||||
}
|
||||
return this.el_.duration || NaN;
|
||||
}
|
||||
|
@ -299,8 +299,9 @@ class TextTrackSettings extends ModalDialog {
|
||||
* @param {string} key
|
||||
* Configuration key to use during creation.
|
||||
*
|
||||
* @return {Element}
|
||||
* The DOM element that gets created.
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElSelect_(key, legendId = '', type = 'label') {
|
||||
@ -308,101 +309,94 @@ class TextTrackSettings extends ModalDialog {
|
||||
const id = config.id.replace('%s', this.id_);
|
||||
|
||||
return [
|
||||
createEl(type, {
|
||||
id,
|
||||
className: type === 'label' ? 'vjs-label' : '',
|
||||
textContent: this.localize(config.label)
|
||||
}, {
|
||||
}),
|
||||
createEl('select', {}, {
|
||||
'aria-labelledby': `${legendId} ${id}`
|
||||
}, config.options.map(o => {
|
||||
`<${type} id="${id}" class="${type === 'label' ? 'vjs-label' : ''}">`,
|
||||
this.localize(config.label),
|
||||
`</${type}>`,
|
||||
`<select aria-labelledby="${legendId} ${id}">`
|
||||
].
|
||||
concat(config.options.map(o => {
|
||||
const optionId = id + '-' + o[1];
|
||||
|
||||
return createEl('option', {
|
||||
id: optionId,
|
||||
textContent: this.localize(o[1]),
|
||||
value: o[0]
|
||||
}, {
|
||||
'aria-labelledby': `${legendId} ${id} ${optionId}`
|
||||
});
|
||||
}))
|
||||
];
|
||||
return [
|
||||
`<option id="${optionId}" value="${o[0]}" `,
|
||||
`aria-labelledby="${legendId} ${id} ${optionId}">`,
|
||||
this.localize(o[1]),
|
||||
'</option>'
|
||||
].join('');
|
||||
})).
|
||||
concat('</select>').join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create foreground color element for the component
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElFgColor_() {
|
||||
const legend = createEl('legend', {
|
||||
id: `captions-text-legend-${this.id_}`,
|
||||
textContent: this.localize('Text')
|
||||
});
|
||||
const legendId = `captions-text-legend-${this.id_}`;
|
||||
|
||||
const select = this.createElSelect_('color', legend.id);
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-text-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('textOpacity', legend.id));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-fg-color vjs-track-setting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
return [
|
||||
'<fieldset class="vjs-fg-color vjs-track-setting">',
|
||||
`<legend id="${legendId}">`,
|
||||
this.localize('Text'),
|
||||
'</legend>',
|
||||
this.createElSelect_('color', legendId),
|
||||
'<span class="vjs-text-opacity vjs-opacity">',
|
||||
this.createElSelect_('textOpacity', legendId),
|
||||
'</span>',
|
||||
'</fieldset>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create background color element for the component
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElBgColor_() {
|
||||
const legend = createEl('legend', {
|
||||
id: `captions-background-${this.id_}`,
|
||||
textContent: this.localize('Background')
|
||||
});
|
||||
const legendId = `captions-background-${this.id_}`;
|
||||
|
||||
const select = this.createElSelect_('backgroundColor', legend.id);
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-bg-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('backgroundOpacity', legend.id));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-bg-color vjs-track-setting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
return [
|
||||
'<fieldset class="vjs-bg-color vjs-track-setting">',
|
||||
`<legend id="${legendId}">`,
|
||||
this.localize('Background'),
|
||||
'</legend>',
|
||||
this.createElSelect_('backgroundColor', legendId),
|
||||
'<span class="vjs-bg-opacity vjs-opacity">',
|
||||
this.createElSelect_('backgroundOpacity', legendId),
|
||||
'</span>',
|
||||
'</fieldset>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create window color element for the component
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElWinColor_() {
|
||||
const legend = createEl('legend', {
|
||||
id: `captions-window-${this.id_}`,
|
||||
textContent: this.localize('Window')
|
||||
});
|
||||
const legendId = `captions-window-${this.id_}`;
|
||||
|
||||
const select = this.createElSelect_('windowColor', legend.id);
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-window-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('windowOpacity', legend.id));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-window-color vjs-track-setting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
return [
|
||||
'<fieldset class="vjs-window-color vjs-track-setting">',
|
||||
`<legend id="${legendId}">`,
|
||||
this.localize('Window'),
|
||||
'</legend>',
|
||||
this.createElSelect_('windowColor', legendId),
|
||||
'<span class="vjs-window-opacity vjs-opacity">',
|
||||
this.createElSelect_('windowOpacity', legendId),
|
||||
'</span>',
|
||||
'</fieldset>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -415,12 +409,13 @@ class TextTrackSettings extends ModalDialog {
|
||||
*/
|
||||
createElColors_() {
|
||||
return createEl('div', {
|
||||
className: 'vjs-track-settings-colors'
|
||||
}, undefined, [
|
||||
this.createElFgColor_(),
|
||||
this.createElBgColor_(),
|
||||
this.createElWinColor_()
|
||||
]);
|
||||
className: 'vjs-track-settings-colors',
|
||||
innerHTML: [
|
||||
this.createElFgColor_(),
|
||||
this.createElBgColor_(),
|
||||
this.createElWinColor_()
|
||||
].join('')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -432,21 +427,20 @@ class TextTrackSettings extends ModalDialog {
|
||||
* @private
|
||||
*/
|
||||
createElFont_() {
|
||||
const fontPercent = createEl('fieldset', {
|
||||
className: 'vjs-font-percent vjs-track-setting'
|
||||
}, undefined, this.createElSelect_('fontPercent', '', 'legend'));
|
||||
|
||||
const edgeStyle = createEl('fieldset', {
|
||||
className: 'vjs-edge-style vjs-track-setting'
|
||||
}, undefined, this.createElSelect_('edgeStyle', '', 'legend'));
|
||||
|
||||
const fontFamily = createEl('fieldset', {
|
||||
className: 'vjs-font-family vjs-track-setting'
|
||||
}, undefined, this.createElSelect_('fontFamily', '', 'legend'));
|
||||
|
||||
return createEl('div', {
|
||||
className: 'vjs-track-settings-font'
|
||||
}, undefined, [fontPercent, edgeStyle, fontFamily]);
|
||||
className: 'vjs-track-settings-font">',
|
||||
innerHTML: [
|
||||
'<fieldset class="vjs-font-percent vjs-track-setting">',
|
||||
this.createElSelect_('fontPercent', '', 'legend'),
|
||||
'</fieldset>',
|
||||
'<fieldset class="vjs-edge-style vjs-track-setting">',
|
||||
this.createElSelect_('edgeStyle', '', 'legend'),
|
||||
'</fieldset>',
|
||||
'<fieldset class="vjs-font-family vjs-track-setting">',
|
||||
this.createElSelect_('fontFamily', '', 'legend'),
|
||||
'</fieldset>'
|
||||
].join('')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,30 +453,17 @@ class TextTrackSettings extends ModalDialog {
|
||||
*/
|
||||
createElControls_() {
|
||||
const defaultsDescription = this.localize('restore all settings to the default values');
|
||||
const defaultsButton = createEl('button', {
|
||||
className: 'vjs-default-button',
|
||||
title: defaultsDescription,
|
||||
innerHTML: `${this.localize('Reset')}<span class='vjs-control-text'> ${defaultsDescription}</span>`
|
||||
});
|
||||
|
||||
const doneButton = createEl('button', {
|
||||
className: 'vjs-done-button',
|
||||
textContent: this.localize('Done')
|
||||
});
|
||||
|
||||
return createEl('div', {
|
||||
className: 'vjs-track-settings-controls'
|
||||
}, undefined, [defaultsButton, doneButton]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
*/
|
||||
createEl() {
|
||||
return super.createEl();
|
||||
className: 'vjs-track-settings-controls',
|
||||
innerHTML: [
|
||||
`<button class="vjs-default-button" title="${defaultsDescription}">`,
|
||||
this.localize('Reset'),
|
||||
`<span class="vjs-control-text"> ${defaultsDescription}</span>`,
|
||||
'</button>',
|
||||
`<button class="vjs-done-button">${this.localize('Done')}</button>`
|
||||
].join('')
|
||||
});
|
||||
}
|
||||
|
||||
content() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user