/* global gnca_settings */
import StickyVideo from './StickyVideo';
import InView from '../../utils/classes/InView';
import saveData from '../../utils/saveData';
import customEvent from '../../utils/customEvent';
import isVisible from '../../utils/dom/isVisible';
import Messenger from '../Messenger';
/**
* Video iframe embed functionality.
* This handles anything related to video embedding:
* Click to play, muted autoplay, sticky video.
* There is a separate module for the video player itself within the iframe.
*
* @class EmbedPlayer
* @prop {object} states - Set of classes that serve as flags for player states.
* @prop {object} classes - Set of css classes that can be used to select player features.
* @prop {element} $player - DOM reference to player node
* @prop {object} startListeners - Static array of listener for player click events
*/
class EmbedPlayer {
states = {
faded: 'is-faded-out',
animated: 'is-animated',
expanded: 'is-full-width',
};
classes = {
placeholder: 'c-video__placeholder',
embed: 'c-video__embed',
parentExpanded: 'c-posts__inner--expanded',
main: 'c-video',
post: 'c-post',
};
$player = null;
$container = null;
containerActiveCss = '';
static messageType = 'gnca_video';
static startListeners = [];
static instances = [];
/**
* @constructs
* @param {Element} $player - DOM reference to a video player embed
*/
constructor( $player ) {
this.$player = $player;
}
/**
* Add click handler to video player embed or initiate muted autoplay if indicated.
*
* @member init
*/
// hook up each instance of this widget
init() {
if ( ! this.$player.dataset.displayinlineInit ) {
// Determine if the embed player has an image placeholder.
const $placeholder = this.$player.querySelector( `.${this.classes.placeholder}` );
const hasPlaceholder = $placeholder && $placeholder.childNodes.length > 0;
if ( hasPlaceholder && isVisible( this.$player )
&& this.isAutoplayVideo()
&& EmbedPlayer.autoplayEnabled()
&& false === saveData() ) {
if ( 'true' === this.$player.dataset.autoplay ) {
// autoplay right away
this.start( true );
} else {
// autoplay when in view
this.startInView();
}
} else if ( hasPlaceholder ) {
const length = EmbedPlayer.startListeners.push( this.start.bind( this, false ) );
this.$player.addEventListener( 'click', EmbedPlayer.startListeners[length - 1]);
// We need to store this as a data attribute because there are some cases where
// another embed player instance is added to the same $player element, in which
// case we need to remove the old click handler to avoid weird issues like that
// found in GT-2152
this.$player.dataset.displayinlineListenerID = length - 1;
} else {
// No placeholder available for this embed player,
// immediately populate the video area with the video iframe.
this.start( this.isAutoplayVideo() );
}
// register embed player by player id.
EmbedPlayer.instances[this.$player.dataset.displayinlinePlayerId] = this;
}
}
/**
* Fade in video player and remove click handler.
*
* @member start
* @param {Boolean} mutedAutoplay - Determines if video should start automatically without sound
*/
start( mutedAutoplay = false, event = null ) {
if ( event ) {
const cssString = event.target && event.target.classList.length > 0 ? event.target.classList[0] : '';
// If user clicked Edit Video button (logged-in only)
// we want to trigger the default handler. Otherwise no.
if ( cssString.indexOf( 'c-editButton' ) < 0 ) {
event.preventDefault();
}
// If user clicked on an element that isn't part of the embed player, ignore
// e.g. sticky close button
if ( cssString.indexOf( this.classes.main ) < 0
&& cssString.indexOf( this.classes.post ) < 0 ) {
return;
}
}
if ( null !== this.$player.dataset.displayinlineListenerID ) {
this.$player.removeEventListener( 'click', EmbedPlayer.startListeners[this.$player.dataset.displayinlineListenerID]);
}
this.$player.dataset.displayinlineInit = 'true'; // eslint-disable-line no-param-reassign
// Append new video element to the DOM and return it
const $video = this.fadeInVideo( mutedAutoplay );
// If the video sits within a media container,
// reset video on video detach so the embed player can be re-initialized on click
if ( this.$player.dataset.displayinlineContainer ) {
this.$container = document.querySelector( `[data-container-id="${this.$player.dataset.displayinlineContainer}"]` );
if ( this.$container ) {
this.containerActiveCss = this.$container.classList.length > 0 ? this.$container.classList[0] : '';
this.containerActiveCss = `${this.containerActiveCss}--active`;
this.$container.classList.add( this.containerActiveCss );
}
const detachHandler = this.reset.bind( this, $video );
$video.addEventListener( customEvent.STICKY_DETACHED, detachHandler );
}
if ( 'true' === this.$player.dataset.displayinlineExpand ) {
this.expandVideoPlayer();
}
}
/**
* Add in view listener to trigger playback when player becomes in view.
*
* @member startInView
*/
startInView() {
// set up in view watcher for video player
const watcherName = 'embedPlayerWatcher';
let watcher = InView.getWatcher( watcherName );
if ( ! watcher ) {
watcher = new InView({
threshold: 0.5,
}, watcherName );
watcher.init();
}
this.$player.addEventListener( customEvent.IN_VIEW, ( event ) => {
if ( event.detail
&& watcherName === event.detail.caller.name ) {
if ( ! this.$player.dataset.displayinlineInit ) {
if ( event.detail.isInView ) {
this.start( true );
}
} else {
const $iframe = this.$player.querySelector( 'iframe' );
const iframeId = $iframe.getAttribute( 'id' );
if ( event.detail.isInView ) {
Messenger.sendMessage( iframeId, EmbedPlayer.messageType, 'resume' );
} else {
Messenger.sendMessage( iframeId, EmbedPlayer.messageType, 'pause' );
}
}
}
});
watcher.startWatching( this.$player );
}
/**
* Reset embed player to initial state and remove existing $video element
*
* @member reset
* @param {HTMLElement} $video - $video element to be removed
*/
reset( $video ) {
if ( null !== this.$player.dataset.displayinlineListenerID ) {
this.$player.addEventListener( 'click', EmbedPlayer.startListeners[this.$player.dataset.displayinlineListenerID]);
}
this.$player.dataset.displayinlineInit = null; // eslint-disable-line no-param-reassign
this.$player.removeChild( $video );
const $placeholder = this.$player.querySelector( `.${this.classes.placeholder}` );
$placeholder.classList.remove( this.states.faded );
$placeholder.classList.remove( this.states.animated );
if ( this.$container ) {
this.$container.classList.remove( this.containerActiveCss );
}
}
/**
* Fade out placeholder and fade in video player.
*
* @member fadeInVideo
* @param {Boolean} mutedAutoplay - Determines if video should start automatically without sound
*/
fadeInVideo( mutedAutoplay = false ) {
const $placeholder = this.$player.querySelector( `.${this.classes.placeholder}` );
$placeholder.classList.add( this.states.faded );
$placeholder.classList.add( this.states.animated );
const $video = this.getVideo( mutedAutoplay );
this.$player.appendChild( $video );
return $video;
}
/**
* Expand player to full width.
* Used when triggering a video within a story stream.
*
* @member expandVideoPlayer
*/
expandVideoPlayer() {
this.$player.classList.add( this.states.expanded );
this.$player.parentNode.classList.add( this.classes.parentExpanded );
}
/**
* Create video player iframe tag from settings.
*
* @member getVideo
* @param {Boolean} mutedAutoplay - Determines if video should start automatically without sound
* @return {Element} $video - Video player iframe object
*/
getVideo( mutedAutoplay = false ) {
let $src = this.$player.dataset.displayinline;
if ( true === mutedAutoplay ) {
$src = `${$src}&mute`;
}
if ( this.isAutoplayVideo() ) {
$src = `${$src}&embedAutoPlay`;
}
let $video = document.createElement( 'iframe' );
$video.setAttribute( 'id', `embedPlayer_${this.$player.dataset.displayinlineVideoId}` );
$video.dataset.displayinlineContent = '';
$video.classList.add( this.classes.embed );
$video.setAttribute( 'title', 'Sticky Video' );
$video.setAttribute( 'src', $src );
$video.setAttribute( 'scrolling', 'no' );
if ( this.stickyEnabled() ) {
$video = this.setupStickyVideo( $video );
}
return $video;
}
/**
* Initializes sticky video functionality
*
* @member setupStickyVideo
* @param {Element} $video - Video player iframe object
* @return {Element} = Video player iframe object with sticky video markup added
*/
setupStickyVideo( $video ) {
const iframeId = this.$player.dataset.displayinlinePlayerId;
$video.setAttribute( 'id', iframeId );
$video.setAttribute( 'allowfullscreen', true );
const sticky = new StickyVideo( $video, this );
return sticky.init();
}
/**
* Checks if this is a featured video
*
* @member isFeaturedVideo
* @param {Element} $player - DOM reference to a video player embed
* @return {Boolean} - True / False whether or not it's a featured video
*/
isFeaturedVideo() {
return ( 'true' === this.$player.dataset.displayinlineFeatured );
}
/**
* Checks if this is an autoplayed video
*
* @member isAutoplayVideo
* @param {Element} $player - DOM reference to a video player embed
* @return {Boolean} - True / False whether or not it's a featured video
*/
isAutoplayVideo() {
return ( 'true' === this.$player.dataset.autoplay
|| 'inview' === this.$player.dataset.autoplay
|| 'inviewonce' === this.$player.dataset.autoplay );
}
/**
* Checks to see if sticky video is enabled. First check if the sticky video setting is on.
* Then do a viewport width check if the video is set to be turned off on desktop.
*
* @member stickyEnabled()
* @return {Boolean} - True / False whether or not sticky video is enabled
*/
stickyEnabled() {
if ( this.$player.dataset.displayinlinePlayerId && this.$player.dataset.displayinlineSticky ) {
if ( this.$player.dataset.displayinlineStickyOnMobile ) {
const mobileBreakpoint = parseInt( this.$player.dataset.displayinlineStickyOnMobile, 10 );
return window.innerWidth < mobileBreakpoint;
}
return true;
}
return false;
}
/**
* Checks page settings to see if muted autoplay is enabled.
*
* @member autoplayEnabled
* @return {Boolean} - State of muted autoplay setting
*/
static autoplayEnabled() {
/* eslint-disable camelcase */
if ( 'undefined' !== typeof ( gnca_settings )
&& gnca_settings.video_settings
&& gnca_settings.video_settings.muted_autoplay ) {
return Boolean( gnca_settings.video_settings.muted_autoplay );
}
/* eslint-enable camelcase */
return false;
}
/**
* Return player instance by id
*
* @member getPlayer
* @return {string} id - id of the player
*/
static getPlayer( id ) {
return EmbedPlayer.instances[id];
}
}
export default EmbedPlayer;