import customEvent from '../../utils/customEvent';
import InView from '../../utils/classes/InView';
/**
* SimpleVideo Module for playing video without ads, analytics, playback control
*
* @class SimpleVideo
* @prop {object} settings - configurations for SimpleVideo
* @prop {boolean} started - true if playback has started, false otherwise
* @prop {boolean} isInView - true if player is in view
* @prop {object} playler - video player object created via third party library (video.js)
* @prop {element} $poster - DOM reference of poster image element
* @prop {element} $container - DOM reference of container element where video is inserted
* @prop {string} posterSelector - css selector for poster image DOM element
* @prop {string} identifer - css class identifier a SimpleVideo element
* @prop {string} animateInCss - css class for animation of showing element
* @prop {string} animateOutCss - css class for animation of hiding element
* @prop {string} prefix - prefix of DOM element id of a SimpleVideo
* @prop {object} libraries - library URLs required for enabling video and the respective load state
* @prop {number} videoCount - number of SimpleVideo instances on the page
* @prop {boolean} ready - whether or not video object is ready to be instantiated
* @prop {number} numLibLoaded - number of scripts loaded
* @prop {string} API_READY - name of event fired when libraries are loaded
*/
class SimpleVideo {
settings = null;
started = false;
isInView = false;
player = false;
$poster = false;
$container = false;
identifier = 'c-simpleVideo';
posterSelector = '.vjs-poster';
animateInCss = 'animate-fadeIn';
animateOutCss = 'animate-fadeOut';
template = '';
watcher = false;
inViewListener = false;
static videoCount = 0;
static ready = false;
static numLibsToLoad = false;
static prefix = 'simpleVideo';
static libraries = {
'/assets/dist/vendor/videojs/video.min.js': false,
};
static API_READY = 'simpleVideoAPIReady';
/* global videojs */
/**
* Constructor
*/
constructor( settings ) {
this.settings = settings;
SimpleVideo.videoCount += 1;
}
/**
* Insert video template into the specified container element
* Load video scripts and initalize the video player
*
* @method init
*/
init() {
this.$container = document.querySelector( this.settings.selector );
if ( this.$container ) {
// Generate video template based on provided settings.
const width = this.settings.width || '100%';
const height = this.settings.height || '100%';
const autoPlay = this.settings.autoPlay || true;
this.template = `
<video-js id="${SimpleVideo.getVideoId()}" width="${width}" height="${height}" poster="${this.settings.poster}" autoplay="${autoPlay}" controls="false" playsinline>
<source src="${this.settings.source}" type="${this.settings.type}">
</video-js>
`;
// detect visibility of video container to play / pause video in place
this.watcher = InView.getWatcher( 'simpleVideoWatcher' );
if ( ! this.watcher ) {
this.watcher = new InView({ threshold: 1 }, 'simpleVideoWatcher' );
this.watcher.init();
}
this.inViewListener = this.handleVisibilityChange.bind( this );
this.$container.dataset.alwaysObserve = 'true';
this.$container.addEventListener( customEvent.IN_VIEW, this.inViewListener );
this.watcher.startWatching( this.$container );
// load video scripts if not yet done so, otherwise initialize the player right away
if ( ! SimpleVideo.ready ) {
const libs = Object.keys( SimpleVideo.libraries );
SimpleVideo.numLibsToLoad = libs.length;
libs.forEach( ( url ) => {
if ( ! SimpleVideo.libraries[url]) {
SimpleVideo.embedScript( url );
SimpleVideo.libraries[url] = true;
}
});
window.addEventListener( SimpleVideo.API_READY, () => {
if ( this.isInView ) {
this.initPlayer();
}
});
}
}
}
/**
* Initialize video player and set up event listener for animating in
* the player when ready.
*
* @method initPlayer
*/
initPlayer() {
// Bail if player already initialized
if ( this.player ) {
return;
}
// Insert video template into $container node
this.$container.innerHTML = this.template;
// Instantiate video player
this.player = videojs( SimpleVideo.getVideoId() );
this.player.muted( true );
this.player.addClass( this.identifier );
this.$poster = this.$container.querySelector( this.posterSelector );
this.player.one( 'playing', () => {
this.$poster.classList.add( this.animateOutCss );
this.started = true;
});
this.play();
}
/**
* Begin video playback
*
* @method play
* @param {string} url - optional. Video stream URL.
*/
play( url ) {
if ( url ) {
this.player.src( url );
}
this.show();
this.player.play();
}
/**
* Stop video playback
*
* @method stop
*/
stop() {
if ( ! this.player ) {
return;
}
this.player.dispose();
this.player = false;
this.$poster = false;
}
/**
* Remove video
*
* @method remove
*/
remove() {
this.$container.removeEventListener( customEvent.IN_VIEW, this.inViewListener );
this.watcher.stopWatching( this.$container );
this.stop();
this.started = false;
}
/**
* Animate in the video player
*
* @method show
*/
show() {
this.player.addClass( this.animateInCss );
}
/**
* Animate out the video player
*
* @method hide
*/
hide() {
this.player.removeClass( this.animateInCss );
if ( this.$poster ) {
this.$poster.classList.remove( this.animateOutCss );
}
}
/**
* Handle InView events
* stop playback when container element is invisible
* start playback when container element is visible
*
* @method handleVisibilityChange
* @param {object} event
*/
handleVisibilityChange( event ) {
this.isInView = event.detail.isInView;
// bail if JS not ready
if ( ! SimpleVideo.ready ) {
return;
}
if ( event.detail.isInView && ! this.player ) {
this.initPlayer();
} else if ( ! event.detail.isInView && this.player ) {
this.stop();
}
}
/**
* Embed JS script and listen for when the script is loaded
*
* @method embedScript
* @param {string} url - URL of js script
*/
static embedScript( url ) {
/* global gnca_settings */
let scriptUrl = url;
/* eslint-disable camelcase */
if ( 0 !== scriptUrl.indexOf( 'http' ) && gnca_settings ) {
scriptUrl = `${gnca_settings.js_base_path}${url}`;
}
/* eslint-enable */
const script = document.createElement( 'script' );
script.async = false;
script.type = 'text/javascript';
script.src = scriptUrl;
script.onload = () => {
SimpleVideo.onScriptLoaded();
};
const node = document.getElementsByTagName( 'script' )[0];
node.parentNode.insertBefore( script, node );
}
/**
* Handler for when a script is loaded
* Trigger API_READY event when all libraries have been loaded
*
* @method onScriptLoaded
*/
static onScriptLoaded() {
SimpleVideo.numLibsToLoad -= 1;
if ( 0 === SimpleVideo.numLibsToLoad ) {
SimpleVideo.ready = true;
customEvent.fire( window, SimpleVideo.API_READY );
}
}
/**
* Generate a unique id to identify a SimpleVideo element
*
* @method getVideoId
*/
static getVideoId() {
return `${SimpleVideo.prefix}-${SimpleVideo.videoCount}`;
}
}
export default SimpleVideo;