-
Notifications
You must be signed in to change notification settings - Fork 7.5k
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
perf: Throttle ProgressControl ui updates, add document.hidden check to time-displays #6150
Changes from all commits
1912049
7c786c1
a31af57
6480e36
410fca0
a30c6c3
4c82849
1a517cb
7e5c4df
fbc12bd
20a307f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,20 @@ | |
* @file load-progress-bar.js | ||
*/ | ||
import Component from '../../component.js'; | ||
import {bind} from '../../utils/fn.js'; | ||
import * as Dom from '../../utils/dom.js'; | ||
import activeElement from '../../mixins/active-element.js'; | ||
|
||
// get the percent width of a time compared to the total end | ||
const percentify = function(time, end) { | ||
// no NaN | ||
let percent = (time / end) || 0; | ||
|
||
percent = (percent >= 1 ? 1 : percent) * 100; | ||
|
||
// round to two digits | ||
return percent.toFixed(2) + '%'; | ||
}; | ||
|
||
/** | ||
* Shows loading progress | ||
|
@@ -23,7 +36,16 @@ class LoadProgressBar extends Component { | |
constructor(player, options) { | ||
super(player, options); | ||
this.partEls_ = []; | ||
this.on(player, 'progress', this.update); | ||
this.update = bind(this, this.update); | ||
activeElement(this, { | ||
startUpdate: () => { | ||
this.on(player, 'progress', this.update); | ||
this.update(); | ||
}, | ||
stopUpdate: () => { | ||
this.off(player, 'progress', this.update); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
|
@@ -54,54 +76,55 @@ class LoadProgressBar extends Component { | |
* @listens Player#progress | ||
*/ | ||
update(event) { | ||
const liveTracker = this.player_.liveTracker; | ||
const buffered = this.player_.buffered(); | ||
const duration = (liveTracker && liveTracker.isLive()) ? liveTracker.seekableEnd() : this.player_.duration(); | ||
const bufferedEnd = this.player_.bufferedEnd(); | ||
const children = this.partEls_; | ||
const controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); | ||
|
||
// get the percent width of a time compared to the total end | ||
const percentify = function(time, end, rounded) { | ||
// no NaN | ||
let percent = (time / end) || 0; | ||
|
||
percent = (percent >= 1 ? 1 : percent) * 100; | ||
|
||
if (rounded) { | ||
percent = percent.toFixed(2); | ||
this.requestAnimationFrame(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hiding whitespace changes makes this a lot easier to diff. |
||
const liveTracker = this.player_.liveTracker; | ||
const buffered = this.player_.buffered(); | ||
const duration = (liveTracker && liveTracker.isLive()) ? liveTracker.seekableEnd() : this.player_.duration(); | ||
const bufferedEnd = this.player_.bufferedEnd(); | ||
const children = this.partEls_; | ||
const controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); | ||
const percent = percentify(bufferedEnd, duration); | ||
|
||
if (this.el_.style.width !== percent) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only update the style if it changed |
||
// update the width of the progress bar | ||
this.el_.style.width = percent; | ||
} | ||
|
||
return percent + '%'; | ||
}; | ||
// update the control-text | ||
if (controlTextPercentage.textContent !== percent) { | ||
Dom.textContent(controlTextPercentage, percent); | ||
} | ||
|
||
// update the width of the progress bar | ||
this.el_.style.width = percentify(bufferedEnd, duration); | ||
// add child elements to represent the individual buffered time ranges | ||
for (let i = 0; i < buffered.length; i++) { | ||
const start = buffered.start(i); | ||
const end = buffered.end(i); | ||
let part = children[i]; | ||
|
||
// update the control-text | ||
Dom.textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); | ||
if (!part) { | ||
part = this.el_.appendChild(Dom.createEl()); | ||
children[i] = part; | ||
} | ||
|
||
// add child elements to represent the individual buffered time ranges | ||
for (let i = 0; i < buffered.length; i++) { | ||
const start = buffered.start(i); | ||
const end = buffered.end(i); | ||
let part = children[i]; | ||
// set the percent based on the width of the progress bar (bufferedEnd) | ||
const leftPercent = percentify(start, bufferedEnd); | ||
const widthPercent = percentify(end - start, bufferedEnd); | ||
|
||
if (!part) { | ||
part = this.el_.appendChild(Dom.createEl()); | ||
children[i] = part; | ||
} | ||
if (part.style.left !== leftPercent) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only update when changed. |
||
part.style.left = leftPercent; | ||
} | ||
|
||
// set the percent based on the width of the progress bar (bufferedEnd) | ||
part.style.left = percentify(start, bufferedEnd); | ||
part.style.width = percentify(end - start, bufferedEnd); | ||
} | ||
if (part.style.width !== widthPercent) { | ||
part.style.widh = widthPercent; | ||
} | ||
} | ||
|
||
// remove unused buffered range elements | ||
for (let i = children.length; i > buffered.length; i--) { | ||
this.el_.removeChild(children[i - 1]); | ||
} | ||
children.length = buffered.length; | ||
// remove unused buffered range elements | ||
for (let i = children.length; i > buffered.length; i--) { | ||
this.el_.removeChild(children[i - 1]); | ||
} | ||
children.length = buffered.length; | ||
}); | ||
} | ||
|
||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
import Component from '../../component.js'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to do an update in |
||
import * as Dom from '../../utils/dom.js'; | ||
import {bind, throttle, UPDATE_REFRESH_INTERVAL} from '../../utils/fn.js'; | ||
import activeElement from '../../mixins/active-element.js'; | ||
|
||
import './seek-bar.js'; | ||
|
||
|
@@ -28,10 +29,32 @@ class ProgressControl extends Component { | |
super(player, options); | ||
this.handleMouseMove = throttle(bind(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL); | ||
this.throttledHandleMouseSeek = throttle(bind(this, this.handleMouseSeek), UPDATE_REFRESH_INTERVAL); | ||
this.throttledUpdate = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL); | ||
|
||
activeElement(this, { | ||
startUpdate: () => { | ||
this.updateInterval_ = this.setInterval(this.throttledUpdate, UPDATE_REFRESH_INTERVAL); | ||
this.throttledUpdate(); | ||
}, | ||
stopUpdate: () => { | ||
this.clearInterval(this.updateInterval_); | ||
this.updateInterval_ = null; | ||
} | ||
}); | ||
|
||
this.enable(); | ||
} | ||
|
||
update() { | ||
const seekBar = this.getChild('seekBar'); | ||
|
||
if (!seekBar) { | ||
return; | ||
} | ||
|
||
seekBar.update(); | ||
} | ||
|
||
/** | ||
* Create the `Component`'s DOM element | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,11 +5,10 @@ import Slider from '../../slider/slider.js'; | |
import Component from '../../component.js'; | ||
import {IS_IOS, IS_ANDROID} from '../../utils/browser.js'; | ||
import * as Dom from '../../utils/dom.js'; | ||
import * as Fn from '../../utils/fn.js'; | ||
import formatTime from '../../utils/format-time.js'; | ||
import {silencePromise} from '../../utils/promise'; | ||
import keycode from 'keycode'; | ||
import document from 'global/document'; | ||
import {bind, throttle, UPDATE_REFRESH_INTERVAL} from '../../utils/fn.js'; | ||
|
||
import './load-progress-bar.js'; | ||
import './play-progress-bar.js'; | ||
|
@@ -21,9 +20,6 @@ const STEP_SECONDS = 5; | |
// The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline. | ||
const PAGE_KEY_MULTIPLIER = 12; | ||
|
||
// The interval at which the bar should update as it progresses. | ||
const UPDATE_REFRESH_INTERVAL = 30; | ||
|
||
/** | ||
* Seek bar and container for the progress bars. Uses {@link PlayProgressBar} | ||
* as its `bar`. | ||
|
@@ -43,64 +39,7 @@ class SeekBar extends Slider { | |
*/ | ||
constructor(player, options) { | ||
super(player, options); | ||
this.setEventHandlers_(); | ||
} | ||
|
||
/** | ||
* Sets the event handlers | ||
* | ||
* @private | ||
*/ | ||
setEventHandlers_() { | ||
this.update = Fn.throttle(Fn.bind(this, this.update), UPDATE_REFRESH_INTERVAL); | ||
|
||
this.on(this.player_, 'timeupdate', this.update); | ||
this.on(this.player_, 'ended', this.handleEnded); | ||
this.on(this.player_, 'durationchange', this.update); | ||
if (this.player_.liveTracker) { | ||
this.on(this.player_.liveTracker, 'liveedgechange', this.update); | ||
} | ||
|
||
// when playing, let's ensure we smoothly update the play progress bar | ||
// via an interval | ||
this.updateInterval = null; | ||
|
||
this.on(this.player_, ['playing'], this.enableInterval_); | ||
|
||
this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); | ||
|
||
// we don't need to update the play progress if the document is hidden, | ||
// also, this causes the CPU to spike and eventually crash the page on IE11. | ||
if ('hidden' in document && 'visibilityState' in document) { | ||
this.on(document, 'visibilitychange', this.toggleVisibility_); | ||
} | ||
} | ||
|
||
toggleVisibility_(e) { | ||
if (document.hidden) { | ||
this.disableInterval_(e); | ||
} else { | ||
this.enableInterval_(); | ||
|
||
// we just switched back to the page and someone may be looking, so, update ASAP | ||
this.requestAnimationFrame(this.update); | ||
} | ||
} | ||
|
||
enableInterval_() { | ||
this.clearInterval(this.updateInterval); | ||
|
||
this.updateInterval = this.setInterval(() =>{ | ||
this.requestAnimationFrame(this.update); | ||
}, UPDATE_REFRESH_INTERVAL); | ||
} | ||
|
||
disableInterval_(e) { | ||
if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') { | ||
return; | ||
} | ||
|
||
this.clearInterval(this.updateInterval); | ||
this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL); | ||
} | ||
|
||
/** | ||
|
@@ -130,31 +69,33 @@ class SeekBar extends Slider { | |
* @private | ||
*/ | ||
update_(currentTime, percent) { | ||
const liveTracker = this.player_.liveTracker; | ||
let duration = this.player_.duration(); | ||
this.requestAnimationFrame(() => { | ||
const liveTracker = this.player_.liveTracker; | ||
let duration = this.player_.duration(); | ||
|
||
if (liveTracker && liveTracker.isLive()) { | ||
duration = this.player_.liveTracker.liveCurrentTime(); | ||
} | ||
if (liveTracker && liveTracker.isLive()) { | ||
duration = this.player_.liveTracker.liveCurrentTime(); | ||
} | ||
|
||
// machine readable value of progress bar (percentage complete) | ||
this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); | ||
|
||
// human readable value of progress bar (time complete) | ||
this.el_.setAttribute( | ||
'aria-valuetext', | ||
this.localize( | ||
'progress bar timing: currentTime={1} duration={2}', | ||
[formatTime(currentTime, duration), | ||
formatTime(duration, duration)], | ||
'{1} of {2}' | ||
) | ||
); | ||
|
||
// Update the `PlayProgressBar`. | ||
if (this.bar) { | ||
this.bar.update(Dom.getBoundingClientRect(this.el_), percent); | ||
} | ||
// machine readable value of progress bar (percentage complete) | ||
this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); | ||
|
||
// human readable value of progress bar (time complete) | ||
this.el_.setAttribute( | ||
'aria-valuetext', | ||
this.localize( | ||
'progress bar timing: currentTime={1} duration={2}', | ||
[formatTime(currentTime, duration), | ||
formatTime(duration, duration)], | ||
'{1} of {2}' | ||
) | ||
); | ||
|
||
// Update the `PlayProgressBar` when we are in mouse focus | ||
if (this.bar && (this.player_.ended() || (!this.parentComponent_ || this.parentComponent_.mouseFocus_))) { | ||
this.bar.update(Dom.getBoundingClientRect(this.el_), percent); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
|
@@ -169,15 +110,10 @@ class SeekBar extends Slider { | |
* The current percent at a number from 0-1 | ||
*/ | ||
update(event) { | ||
// if the offsetParent is null, then this element is hidden, in which case | ||
// we don't need to update it. | ||
if (this.el().offsetParent === null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not needed as this component is always visible for screen readers. |
||
return; | ||
} | ||
|
||
const percent = super.update(); | ||
const currentTime = !this.player_.ended() ? this.getCurrentTime_() : this.player_.duration(); | ||
|
||
this.update_(this.getCurrentTime_(), percent); | ||
this.update_(currentTime, percent); | ||
return percent; | ||
} | ||
|
||
|
@@ -196,19 +132,6 @@ class SeekBar extends Slider { | |
this.player_.currentTime(); | ||
} | ||
|
||
/** | ||
* We want the seek bar to be full on ended | ||
* no matter what the actual internal values are. so we force it. | ||
* | ||
* @param {EventTarget~Event} [event] | ||
* The `timeupdate` or `ended` event that caused this to run. | ||
* | ||
* @listens Player#ended | ||
*/ | ||
handleEnded(event) { | ||
this.update_(this.player_.duration(), 1); | ||
} | ||
|
||
/** | ||
* Get the percentage of media played so far. | ||
* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't a good reason for this to define this every time the update function is run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also css only takes into account the first two decimal places so we should just always round.