Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Lazy Loading Video Based on Connection Speed
class LazyVideoLoader {
constructor() {
this.videos = [].slice.call(document.querySelectorAll('.hero__bgvideo'));
// Abort when:
// - The browser does not support Promises.
// - There no videos.
// - If the user prefers reduced motion.
// - Device is mobile.
if (
typeof Promise === 'undefined' ||
!this.videos ||
window.matchMedia('(prefers-reduced-motion)').matches ||
window.innerWidth < 992
) {
return;
}
this.videos.forEach(this.loadVideo.bind(this));
}
loadVideo(video) {
this.setSource(video);
video.load();
this.checkLoadTime(video);
}
/**
* Find the children of the video that are <source> tags.
* Set the src attribute for each <source> based on the
* data-src attribute.
*
* @param {object} video The video element.
* @returns {void}
*/
setSource(video) {
const children = [].slice.call(video.children);
children.forEach((child) => {
if (
child.tagName === 'SOURCE' &&
typeof child.dataset.src !== 'undefined'
) {
child.setAttribute('src', child.dataset.src);
}
});
}
/**
* Checks if the video will be able to play through before
* a predetermined time has passed.
* @param {object} video The video element.
* @returns {void}
*/
checkLoadTime(video) {
// Create a promise that resolves when the
// video.canplaythrough event triggers.
const videoLoad = new Promise((resolve) => {
video.addEventListener('canplaythrough', () => {
resolve('can play');
});
});
// Create a promise that resolves after a
// predetermined time (2sec)
const videoTimeout = new Promise((resolve) => {
setTimeout(() => {
resolve('The video timed out.');
}, 2000);
});
// Race the promises to see which one resolves first.
Promise.race([videoLoad, videoTimeout]).then((data) => {
if (data === 'can play') {
video.play();
setTimeout(() => {
video.classList.add('video-loaded');
}, 500);
}
else {
this.cancelLoad(video);
}
});
}
/**
* Cancel the video loading by removing all
* <source> tags and then triggering video.load().
*
* @param {object} video The video element.
* @returns {void}
*/
cancelLoad(video) {
const children = [].slice.call(video.children);
children.forEach((child) => {
if (
child.tagName === 'SOURCE' &&
typeof child.dataset.src !== 'undefined'
) {
child.parentNode.removeChild(child);
}
});
// reload the video without <source> tags so it
// stops downloading.
video.load();
}
}
new LazyVideoLoader();
@ahmadalfy

This comment has been minimized.

Copy link

commented Feb 28, 2019

This is probably the only case I see a valid reason to use Promise.race. Hats off

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.